File: TuneCompareImpl.mesa
commands to operate on tunes within a jukebox, plus one to list the tunes in a given jukebox(es)
Ades, March 6, 1986 3:20:39 pm PST
DIRECTORY
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
Convert USING [IntFromRope],
IO USING [int, PutF, STREAM],
Jukebox USING [CloseJukebox, ArchiveCloseTune, Error, FindJukebox, Handle, OpenJukebox, OpenTune, Tune, TuneSize, EnergyRange, RunArray, LengthRange, RunArrayRange, bytesPerChirp, MissingChirp],
Rope USING [ ROPE],
TuneAccess USING [ReadRunArray, InterpretRunArrayElement, ReadChirpSamples, ByteBlock, ReadTuneHeader, userHeaderLength, ReadAmbientLevel];
TuneCompareImpl: CEDAR PROGRAM
IMPORTS Commander, CommandTool, Convert, IO, Jukebox, TuneAccess =
BEGIN
CompareTunes: Commander.CommandProc = {
tune1: Jukebox.Tune ← NIL;
jukebox1: Jukebox.Handle ← NIL;
weOpened1: BOOLEANFALSE;
tune2: Jukebox.Tune ← NIL;
jukebox2: Jukebox.Handle ← NIL;
weOpened2: BOOLEANFALSE;
{
ENABLE
Jukebox.Error => {msg ← rope; GOTO QuitInError};
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO QuitInError}];
IF argv.argc # 5 THEN
RETURN [$Failure, "Usage: CompareTunes jukebox1 tuneID1 jukebox2 tuneID2\n"];
TRUSTED
{ jukebox1 ← Jukebox.FindJukebox[argv[1]];
IF jukebox1 = NIL THEN {
weOpened1 ← TRUE;
jukebox1 ← Jukebox.OpenJukebox[argv[1]];
};
jukebox2 ← Jukebox.FindJukebox[argv[3]];
IF jukebox2 = NIL THEN {
weOpened2 ← TRUE;
jukebox2 ← Jukebox.OpenJukebox[argv[3]];
};
tune1 ← Jukebox.OpenTune[self: jukebox1, tuneId: Convert.IntFromRope[argv[2]], write: FALSE];
tune2 ← Jukebox.OpenTune[self: jukebox2, tuneId: Convert.IntFromRope[argv[4]], write: FALSE]
};
TRUSTED {
IF Jukebox.TuneSize[tune1] # Jukebox.TuneSize[tune2]
THEN
{ cmd.out.PutF["Tunes are of different chirp lengths!\n"];
GOTO Quit
}
};
{ header1: TuneAccess.ByteBlock ← TuneAccess.ReadTuneHeader[jukebox1, tune1, TuneAccess.userHeaderLength, NIL];
header2: TuneAccess.ByteBlock ← TuneAccess.ReadTuneHeader[jukebox2, tune2, TuneAccess.userHeaderLength, NIL];
FOR i: INT IN [0..TuneAccess.userHeaderLength) DO
IF header1[i] # header2[i] THEN GOTO mismatch
REPEAT
mismatch => {
cmd.out.PutF["User data headers differ at byte %d\n", IO.int[i]];
GOTO failure }
ENDLOOP;
cmd.out.PutF["User data headers match\n"]
EXITS
failure => NULL
};
{ ambientDiscrepancy: BOOLEANFALSE;
TRUSTED {
FOR chirpNumber: INT IN [0..Jukebox.TuneSize[tune1]) DO
ENABLE
Jukebox.MissingChirp => LOOP;
IF TuneAccess.ReadAmbientLevel[jukebox1, tune1, chirpNumber] # TuneAccess.ReadAmbientLevel[jukebox2, tune2, chirpNumber]
THEN
IF ambientDiscrepancy
THEN cmd.out.PutF[", %d", IO.int[chirpNumber]]
ELSE
{ ambientDiscrepancy ← TRUE;
cmd.out.PutF["Ambient levels differ in chirps %d", IO.int[chirpNumber]]
}
ENDLOOP};
cmd.out.PutF[IF ambientDiscrepancy THEN "\n" ELSE "Ambient levels match throughout\n"]
};
TRUSTED {
FOR chirpNumber: INT IN [0..Jukebox.TuneSize[tune1]) DO
runArray1, runArray2: REF Jukebox.RunArray;
timeAccounted, samplesAccounted: Jukebox.LengthRange ← 0;
currRunElement: Jukebox.RunArrayRange ← 0;
thisLength: Jukebox.LengthRange;
skip, silence: BOOLEAN;
cmd.out.PutF["Chirp %d", IO.int[chirpNumber]];
runArray1 ← TuneAccess.ReadRunArray[jukebox1, tune1, chirpNumber, NIL ! Jukebox.MissingChirp => { runArray2 ← TuneAccess.ReadRunArray[jukebox2, tune2, chirpNumber, NIL ! Jukebox.MissingChirp => { cmd.out.PutF[" okay (missing)\n"]; LOOP }]; cmd.out.PutF[":\n chirp missing in first tune\n"]; LOOP } ];
runArray2 ← TuneAccess.ReadRunArray[jukebox1, tune1, chirpNumber, NIL ! Jukebox.MissingChirp => { cmd.out.PutF[":\n chirp missing in second tune\n"]; LOOP }];
WHILE timeAccounted<Jukebox.bytesPerChirp DO
IF runArray1[currRunElement] # runArray2[currRunElement] THEN EXIT;
[thisLength, , skip, silence] ← TuneAccess.InterpretRunArrayElement[runArray1, currRunElement];
IF skip THEN
{ currRunElement ← currRunElement + 1;
IF runArray1[currRunElement] # runArray2[currRunElement] THEN EXIT
};
currRunElement ← currRunElement + 1;
timeAccounted ← timeAccounted + thisLength;
IF ~silence THEN samplesAccounted ← samplesAccounted + thisLength
ENDLOOP;
SELECT timeAccounted FROM
< Jukebox.bytesPerChirp =>
cmd.out.PutF[":\n Run arrays differ at element %d\n", IO.int[currRunElement]];
> Jukebox.bytesPerChirp =>
cmd.out.PutF[":\n FORMAT ERROR: Run arrays do not describe exactly %d samples\n", IO.int[Jukebox.bytesPerChirp]];
= Jukebox.bytesPerChirp =>
{ samples1: TuneAccess.ByteBlock ← TuneAccess.ReadChirpSamples[jukebox1, tune1, chirpNumber,
samplesAccounted];
samples2: TuneAccess.ByteBlock ← TuneAccess.ReadChirpSamples[jukebox2, tune2, chirpNumber,
samplesAccounted];
FOR i: INT IN [0..samplesAccounted) DO
IF samples1[i] # samples2[i] THEN GOTO mismatch
REPEAT
mismatch => {
cmd.out.PutF[":\n Samples differ at element %d\n", IO.int[i]];
GOTO failure }
ENDLOOP;
cmd.out.PutF[" okay (%d samples and %d run elements)\n", IO.int[samplesAccounted], IO.int[currRunElement]]
EXITS
failure => NULL
}
ENDCASE
ENDLOOP};
TRUSTED
{ Jukebox.ArchiveCloseTune[jukebox1, tune1];
Jukebox.ArchiveCloseTune[jukebox2, tune2];
we use ArchiveCloseTune so that the read/write dates will not be altered by this command
IF weOpened1 THEN jukebox1 ← Jukebox.CloseJukebox[jukebox1];
IF weOpened2 THEN jukebox2 ← Jukebox.CloseJukebox[jukebox2]
}
EXITS
Quit =>
TRUSTED {
IF tune1 # NIL THEN Jukebox.ArchiveCloseTune[jukebox1, tune1];
IF tune2 # NIL THEN Jukebox.ArchiveCloseTune[jukebox2, tune2];
IF jukebox1 # NIL AND weOpened1 THEN jukebox1 ← Jukebox.CloseJukebox[jukebox1];
IF jukebox2 # NIL AND weOpened2 THEN jukebox2 ← Jukebox.CloseJukebox[jukebox2]
};
QuitInError => TRUSTED
{ IF tune1 # NIL THEN Jukebox.ArchiveCloseTune[jukebox1, tune1];
IF tune2 # NIL THEN Jukebox.ArchiveCloseTune[jukebox2, tune2];
IF jukebox1 # NIL AND weOpened1 THEN jukebox1 ← Jukebox.CloseJukebox[jukebox1];
IF jukebox2 # NIL AND weOpened2 THEN jukebox2 ← Jukebox.CloseJukebox[jukebox2];
RETURN [$Failure, msg]
}
}};

Commander.Register[key: "CompareTunes", proc: CompareTunes, doc: "CompareTunes jukebox1 tuneID1 jukebox2 tuneID2"];
END.