File: OldTunesImpl.mesa
command to archive a tune from an old style jukebox, converting it into the new standard archive format, so that it can be restored into a new style jukebox
see routine BuildChirpRecord for documentation of differences in tunes
Ades, March 6, 1986 3:25:19 pm PST
Swinehart, February 24, 1986 1:08:21 pm PST
DIRECTORY
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
Jukebox USING [Handle, Tune, bytesPerChirp, RunArray, FindJukebox, OpenJukebox, CloseJukebox, OpenTune, CloseTune, LengthRange, EnergyRange, RunArrayRange, RunComponent, MissingChirp, Error, singlePktLength],
TuneAccess USING [ByteBlock, userHeaderLength, ReadTuneHeader, ByteSequence, ReadRunArray, ReadChirpSamples, GetCreateDate, GetWriteDate, GetReadDate],
Rope USING [ROPE, Fetch, Length],
IO USING [STREAM, UnsafeBlock, UnsafePutBlock, PutChar, Close],
FS USING [StreamOpen, Error],
Convert USING [IntFromRope],
TuneArchive USING [TuneInformation, ChirpContents, TuneInformationHeader, ChirpContentsRecord, chirpContentsBytes, tuneInformationBytes, PrintArchiveInfo],
BasicTime USING [Now, TimeNotKnown];
OldTunesImpl: CEDAR PROGRAM
IMPORTS Commander, CommandTool, Jukebox, TuneAccess, Rope, IO, FS, Convert, BasicTime, TuneArchive =
BEGIN
ArchiveTune: PROC [jukeboxName: Rope.ROPE, tuneID: INT, toFile: Rope.ROPE] RETURNS [tuneInformation: TuneArchive.TuneInformation, internalJukeboxName: Rope.ROPE] = TRUSTED {
stream: IO.STREAMNIL;
jukebox: Jukebox.Handle ← NIL;
tune: Jukebox.Tune ← NIL;
weOpened: BOOLEANFALSE; -- all declared here for catch phrase use
{
ENABLE UNWIND =>
{ IF stream # NIL THEN IO.Close[stream];
IF tune # NIL THEN Jukebox.CloseTune[jukebox, tune];
IF jukebox # NIL AND weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox];
};
these variables and the RETURN parameters are used to retrieve data from the jukebox: internalJukeboxName is the name for the jukebox that is actually stored in the jukebox when open, as opposed to the parameter passed to Jukebox.OpenJukebox
tuneInformation ← NEW[TuneArchive.TuneInformationHeader];
{ -- just so as to be able to do an assignment before all the declarations
byteBlock: TuneAccess.ByteBlock ← NEW[TuneAccess.ByteSequence[MAX [TuneAccess.userHeaderLength, Jukebox.bytesPerChirp]]];
chirpContents: TuneArchive.ChirpContents ← NEW[TuneArchive.ChirpContentsRecord];
runArray: REF Jukebox.RunArray ← NEW[Jukebox.RunArray];
these are the arguments corresponding to the above for IO.UnsafePutBlock: in cases where it is not given above (thus defaulting to 0), before a call to IO.UnsafePutBlock the block's count field should be set appropriately (in bytes)
unsafeOfTuneInformation: IO.UnsafeBlock ← [LOOPHOLE[tuneInformation], 0, TuneArchive.tuneInformationBytes];
byteBlockPointer: LONG POINTERLOOPHOLE[byteBlock];
unsafeOfByteBlock: IO.UnsafeBlock ← [byteBlockPointer + SIZE[TuneAccess.ByteSequence[0]]]; -- yetch
unsafeOfChirpContents: IO.UnsafeBlock ← [LOOPHOLE[chirpContents], 0, TuneArchive.chirpContentsBytes];
unsafeOfRunArray: IO.UnsafeBlock ← [LOOPHOLE[runArray]];
jukebox ← Jukebox.FindJukebox[jukeboxName];
IF jukebox = NIL THEN
{ jukebox ← Jukebox.OpenJukebox[jukeboxName];
weOpened ← TRUE
};
stream ← FS.StreamOpen[toFile, $create];
tune ← Jukebox.OpenTune[self: jukebox, tuneId: tuneID, write: FALSE];
internalJukeboxName ← BuildTuneInformation[jukebox, tune, tuneInformation];
IO.UnsafePutBlock[stream, unsafeOfTuneInformation];
FOR i: INT IN [0..tuneInformation.jukeboxNameLength) DO IO.PutChar[stream, internalJukeboxName.Fetch[i]] ENDLOOP;
byteBlock ← TuneAccess.ReadTuneHeader[jukebox, tune, TuneAccess.userHeaderLength, byteBlock];
unsafeOfByteBlock.count ← TuneAccess.userHeaderLength;
IO.UnsafePutBlock[stream, unsafeOfByteBlock];
FOR j: INT IN [0..tuneInformation.sizeInChirps) DO
BuildChirpRecord[jukebox, tune, j, chirpContents, runArray, byteBlock];
IO.UnsafePutBlock[stream, unsafeOfChirpContents];
unsafeOfRunArray.count ← chirpContents.runArrayLength*2;
IO.UnsafePutBlock[stream, unsafeOfRunArray];
unsafeOfByteBlock.count ← chirpContents.sampleBytes;
IO.UnsafePutBlock[stream, unsafeOfByteBlock]
ENDLOOP;
IO.Close[stream];
Jukebox.CloseTune[jukebox, tune];
IF weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox]
}}};
BuildTuneInformation: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, info: TuneArchive.TuneInformation] RETURNS [internalJukeboxName: Rope.ROPE] = {
build up the tuneInformation, mainly by reading the tune and return the jukebox name as a string
info.jukeboxNameLength ← jukebox.jukeboxName.Length;
TRUSTED {info.tuneNumber ← tune.tuneId};
info.createDate ← TuneAccess.GetCreateDate[tune];
info.writeDate ← TuneAccess.GetWriteDate[tune];
info.readDate ← TuneAccess.GetReadDate[tune];
info.archiveDate ← BasicTime.Now[];
TRUSTED {info.sizeInChirps ← tune.size};
RETURN [jukebox.jukeboxName]
};

routine BuildChirpRecord distinguishes the old format from the new: the run array of an old tune was just an array [0..Jukebox.RunArrayRange] of 16 bit integers: even ones represent silence and odd ones sound. No energy or ambient values were stored, so we will convert them to max energy and min ambient noise.
we need the following type to make things clear:
OldRunArray: TYPE = REF ARRAY Jukebox.RunArrayRange OF CARDINAL;
This record is the same length and in the same position within the chirp as a new style RunArray so that we'll pick it up using TuneAccess.ReadRunArray and then LOOPHOLE it   
BuildChirpRecord: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, chirpContents: TuneArchive.ChirpContents, runArray: REF Jukebox.RunArray, byteBlock: TuneAccess.ByteBlock] = {
build up chirpContents, also fill runArray and byteBlock appropriately
ENABLE Jukebox.MissingChirp =>
{ chirpContents.ambientLevel ← 0;
chirpContents.runArrayLength ← 1;
chirpContents.sampleBytes ← 0;
TRUSTED {runArray[0] ← [silence[Jukebox.bytesPerChirp]]};
GOTO Return
};
usefulArrayLength: Jukebox.RunArrayRange ← 0;
usefulSamples: Jukebox.LengthRange ← 0;
accountedSamples: Jukebox.LengthRange ← 0;
thisLength: Jukebox.LengthRange;
newElementsUsed: Jukebox.LengthRange;
silence: BOOLEAN;
oldRunArray: OldRunArray;
loopHoledOldRunArray: REF Jukebox.RunArray;
oldRunArrayPosition: Jukebox.RunArrayRange ← 0;
loopHoledOldRunArray ← TuneAccess.ReadRunArray[jukebox, tune, chirpNumber, NIL];
TRUSTED {oldRunArray ← LOOPHOLE[loopHoledOldRunArray]};
WHILE accountedSamples<Jukebox.bytesPerChirp DO
[thisLength, newElementsUsed, silence] ← InterpretOldRunArrayElement[oldRunArray, runArray, usefulArrayLength, oldRunArrayPosition];
IF ~silence THEN usefulSamples ← usefulSamples + thisLength;
accountedSamples ← accountedSamples + thisLength;
usefulArrayLength ← usefulArrayLength + newElementsUsed;
oldRunArrayPosition ← oldRunArrayPosition + 1
ENDLOOP;
IF accountedSamples # Jukebox.bytesPerChirp THEN
{ elementAdded: BOOLEAN;
samplesDiscarded: BOOLEAN;
[elementAdded: elementAdded, samplesDiscarded: samplesDiscarded] ← FrigLastElement[runArray, usefulArrayLength, accountedSamples - Jukebox.bytesPerChirp];
IF elementAdded THEN usefulArrayLength ← usefulArrayLength + 1;
IF samplesDiscarded THEN usefulSamples ← usefulSamples - (accountedSamples - Jukebox.bytesPerChirp)
};
chirpContents.runArrayLength ← usefulArrayLength;
chirpContents.sampleBytes ← usefulSamples;
chirpContents.ambientLevel ← FIRST[Jukebox.EnergyRange];
byteBlock ← TuneAccess.ReadChirpSamples[jukebox, tune, chirpNumber, usefulSamples, byteBlock]
EXITS
Return => RETURN
};
InterpretOldRunArrayElement: PROC [oldRunArray: OldRunArray, newRunArray: REF Jukebox.RunArray, usefulArrayLength: Jukebox.RunArrayRange, oldRunArrayPosition: Jukebox.RunArrayRange] RETURNS [thisLength: Jukebox.LengthRange, newElementsUsed: Jukebox.LengthRange, silence: BOOLEAN] = {
we need to be careful as zero length silences and sounds may occur in this record
IF (oldRunArrayPosition MOD 2) = 0
THEN -- silence
{ IF usefulArrayLength # 0 AND newRunArray[usefulArrayLength-1].elementType = silence
THEN -- last thing added to newRunArray was silence, so just add more silence
{ -- this works okay for case of run length = 0
TRUSTED
{
newRunArray[usefulArrayLength-1] ← [silence[NARROW[newRunArray[usefulArrayLength-1], Jukebox.RunComponent[silence]].length + oldRunArray[oldRunArrayPosition]]]
}; -- got that ??
thisLength ← oldRunArray[oldRunArrayPosition];
newElementsUsed ← 0;
silence ← TRUE
}
ELSE -- last thing in run wasn't silence, or this is beginning of run
{ IF oldRunArray[oldRunArrayPosition] = 0
THEN -- special case for run length = 0: just no change !!
{ thisLength ← 0;
newElementsUsed ← 0;
silence ← TRUE
}
ELSE
{
TRUSTED {newRunArray[usefulArrayLength] ← [silence[oldRunArray[oldRunArrayPosition]]]};
thisLength ← oldRunArray[oldRunArrayPosition];
newElementsUsed ← 1;
silence ← TRUE
}
}
}
ELSE -- sound
{ samplesToDispose: Jukebox.LengthRange ← oldRunArray[oldRunArrayPosition];
silence ← FALSE;
thisLength ← samplesToDispose;
newElementsUsed ← 0;
WHILE samplesToDispose>0 DO
IF samplesToDispose >= Jukebox.singlePktLength
THEN
{
TRUSTED
{
newRunArray[usefulArrayLength+newElementsUsed] ← [singlePkt[LAST[Jukebox.EnergyRange]]]
};
samplesToDispose ← samplesToDispose - Jukebox.singlePktLength;
newElementsUsed ← newElementsUsed + 1
}
ELSE
{
TRUSTED
{
newRunArray[usefulArrayLength+newElementsUsed] ← [soundEnergy[LAST[Jukebox.EnergyRange]]];
newRunArray[usefulArrayLength+newElementsUsed+1] ← [soundLength[samplesToDispose]]
};
samplesToDispose ← 0;
newElementsUsed ← newElementsUsed + 2
}
ENDLOOP
}
};
FrigLastElement: PROC [runArray: REF Jukebox.RunArray, usefulArrayLength: Jukebox.EnergyRange, samplesToLose: Jukebox.LengthRange] RETURNS [samplesDiscarded: BOOLEANTRUE, elementAdded: BOOLEANFALSE] = TRUSTED {
this gets executed if the last run element brought the total over Jukebox.bytesPerChirp, so we have to discard some
WITH curr: runArray[usefulArrayLength] SELECT FROM
silence =>
{ curr.length ← curr.length - (samplesToLose);
samplesDiscarded ← FALSE
};
soundLength =>
curr.length ← curr.length - (samplesToLose);
singlePkt => -- requires that we make a new element
{ runArray[usefulArrayLength] ← [soundEnergy[LAST[Jukebox.EnergyRange]]];
runArray[usefulArrayLength+1] ← [soundLength[Jukebox.singlePktLength - samplesToLose]];
elementAdded ← TRUE
};
ENDCASE => ERROR
};
ArchiveOldTune: Commander.CommandProc = {
ENABLE
{FS.Error => IF error.group = bug THEN REJECT ELSE {msg ← error.explanation; GOTO Quit};
Jukebox.Error => {msg ← rope; GOTO Quit};
BasicTime.TimeNotKnown => {msg ← "Unable to determine current time: aborting archive"; GOTO Quit}
};
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO Quit}];
tuneInformation: TuneArchive.TuneInformation;
internalJukeboxName: Rope.ROPE;
IF argv.argc # 4 THEN
RETURN [$Failure, "Usage: ArchiveTune jukeboxName tuneNumber fileName"];
[tuneInformation, internalJukeboxName] ← ArchiveTune[jukeboxName: argv[1], tuneID: Convert.IntFromRope[argv[2]], toFile: argv[3]];
TuneArchive.PrintArchiveInfo[out: cmd.out, archiveFileName: argv[3], tuneInformation: tuneInformation, archivedJukeboxName: internalJukeboxName]
EXITS
Quit => RETURN [$Failure, msg]
};

Commander.Register[key: "ArchiveOldTune", proc: ArchiveOldTune, doc: "ArchiveOldTune jukeboxName tuneNumber fileName - archive and convert a tune from an old-style [= Pre-Ades] jukebox as an FS file"];
END.