File: TunecommandsImpl.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:54:32 pm PST
DIRECTORY
Commander USING [CommandProc, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
Convert USING [IntFromRope, RopeFromTime],
IO USING [int, card, PutF, PutChar, rope, STREAM, Close, EndOfStream],
File USING [wordsPerPage],
FS USING [Error, StreamOpen, Read, Write],
BasicTime USING [TimeNotKnown],
Jukebox USING [CloseJukebox, CloseTune, Error, FindJukebox, Handle, Info, OpenJukebox, OpenTune, Tune, TuneSize, instances, EnergyRange, RunArray, RunArrayRange, MissingChirp, bytesPerChirp, singlePktLength, hangoverPackets, ArchiveCloseTune, WindowOrigin, RunComponent, pagesPerChirp, FindChirp],
Rope USING [Equal, ROPE],
TuneAccess USING [NextTuneNumber, WriteAmbientLevel, GetCreateDate, GetWriteDate, GetReadDate, ReadAmbientLevel, ReadRunArray, InterpretRunArrayElement],
TuneArchive USING [TuneInformation, ArchiveTune, PrintArchiveInfo, ReadArchiveHeader, RestoreTune],
VM USING [Interval, Allocate, Free, AddressForPageNumber];
TuneCommandsImpl: CEDAR PROGRAM
IMPORTS Commander, CommandTool, Convert, IO, FS, BasicTime, Jukebox, Rope, TuneAccess, TuneArchive, VM =
BEGIN
ListTunes: Commander.CommandProc = {
weOpened: BOOLFALSE;
jukebox: Jukebox.Handle ← NIL;
{
ENABLE Jukebox.Error => {
msg ← rope;
GOTO Quit
};
highest: INT←-1;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd ! CommandTool.Failed => { msg ← errorMsg; GOTO Quit}];
IF argv.argc > 3 THEN RETURN[$Failure, "Usage: ListTunes [jukeboxname|*𡤊llOpenJukeboxes [upToTuneNumber]]"];
IF argv.argc = 3 THEN highest ← Convert.IntFromRope[argv[2]];
IF argv.argc = 1 OR argv[1].Equal["*"] THEN {
FOR list: LIST OF Jukebox.Handle ← Jukebox.instances, list.rest WHILE list # NIL DO
PrintData[list.first, cmd.out, highest];
ENDLOOP;
IF Jukebox.instances = NIL THEN cmd.out.PutF["No jukeboxes currently open\n"];
RETURN
};
TRUSTED
{ jukebox ← Jukebox.FindJukebox[argv[1]];
IF jukebox = NIL THEN {
jukebox ← Jukebox.OpenJukebox[argv[1]];
weOpened ← TRUE;
}
};
PrintData[jukebox, cmd.out, highest];
IF weOpened THEN TRUSTED {jukebox ← Jukebox.CloseJukebox[jukebox]};
EXITS
Quit =>
{ IF weOpened AND jukebox # NIL THEN TRUSTED {jukebox ← Jukebox.CloseJukebox[jukebox]};
RETURN[$Failure, msg]
}
}};
PrintData: PROC [jukebox: Jukebox.Handle, out: IO.STREAM, highest: INT←-1] = {
name: Rope.ROPE;
nPages: INT;
nTunes: INT;
tuneHWM: INT;
totalChirpsUsed: INT ← 0;
totalTunes: INT ← 0;
tune: Jukebox.Tune;
tuneSize: INT;
currentTuneID: INT ← -1;
searchAllOfBox: BOOLEANFALSE;
TRUSTED {[name: name, nPages: nPages, nTunes: nTunes, tuneHWM: tuneHWM] ← Jukebox.Info[jukebox]};
out.PutF["Jukebox %g:\n Capacity %d chirps and %d tunes\n Tune usage High Water Mark %d\n", IO.rope[name], IO.int[nPages/Jukebox.pagesPerChirp], IO.int[nTunes], IO.int[tuneHWM]];
IF highest=-1 THEN {highest ← nTunes; searchAllOfBox ← TRUE};
DO
ENABLE ABORTED => EXIT;
currentTuneID ← TuneAccess.NextTuneNumber[jukebox, currentTuneID];
IF currentTuneID = -1 OR currentTuneID > highest THEN EXIT;
TRUSTED {tune ← Jukebox.OpenTune[self: jukebox, tuneId: currentTuneID, write: FALSE ! Jukebox.Error => {
IF reason # BadTune THEN REJECT
ELSE LOOP;
}]};
totalTunes ← totalTunes + 1;
TRUSTED {tuneSize ← Jukebox.TuneSize[tune]};
totalChirpsUsed ← totalChirpsUsed + tuneSize;
out.PutF[" Tune %6d, %6d chirps\n", IO.int[currentTuneID], IO.int[tuneSize]];
TRUSTED {Jukebox.ArchiveCloseTune[jukebox, tune]};
we use ArchiveCloseTune so that the read/write dates will not be altered by this command
ENDLOOP;
IF searchAllOfBox
THEN out.PutF[" Total usage: %d chirps in %d tunes\n", IO.int[totalChirpsUsed], IO.int[totalTunes]]
ELSE out.PutF["Usage up to tune %d: %d chirps in %d tunes\n", IO.int[highest], IO.int[totalChirpsUsed], IO.int[totalTunes]]
};
PrintTune: Commander.CommandProc = {
weOpened: BOOLFALSE;
jukebox: Jukebox.Handle ← NIL;
tune: Jukebox.Tune ← NIL;
fixIt: BOOL ← cmd.procData.clientData = $FixTune;
{{
ENABLE Jukebox.Error => {
msg ← rope;
GOTO Quit;
};
argv: CommandTool.ArgumentVector;
tuneID: INT;
tuneSize: INT;
runArray: REF Jukebox.RunArray;
runLength: INT;
totalSamples: INT;
fixThisChirp: BOOL;
currElement: Jukebox.RunArrayRange;
silence: BOOLEAN;
skipNextElement: BOOLEAN;
runEnergy: Jukebox.EnergyRange;
packetsSinceNonSilence: INT ← Jukebox.hangoverPackets;
ambientLevel: Jukebox.EnergyRange;
lastElement, thisElement: {silent, singlePkt, otherSound};
currTypeLength: INT;
this procedure merely makes the output of this command easier to scan: silent periods are bold, hangover periods are italicised and sound periods are in plain script.
SetLooks: PROC [packetsInSample: INT, sampleEnergy: Jukebox.EnergyRange, ambientLevel: Jukebox.EnergyRange, setLooksOnlyOnChange: BOOLEAN] = {
IF sampleEnergy>ambientLevel
THEN
{IF packetsSinceNonSilence > 0 OR ~setLooksOnlyOnChange THEN
{packetsSinceNonSilence ← 0; cmd.out.PutF["%L", IO.rope[" "]]}}
ELSE
IF packetsSinceNonSilence = 0 OR ~setLooksOnlyOnChange
THEN
{packetsSinceNonSilence ← packetsSinceNonSilence + packetsInSample;
cmd.out.PutF["%L", IO.rope[IF packetsSinceNonSilence>Jukebox.hangoverPackets THEN "bI" ELSE "Bi"]]
}
ELSE
{ notAlreadyBold: BOOLEAN ← packetsSinceNonSilence<=Jukebox.hangoverPackets;
packetsSinceNonSilence ← packetsSinceNonSilence + packetsInSample;
IF notAlreadyBold AND packetsSinceNonSilence>Jukebox.hangoverPackets THEN cmd.out.PutF["%L", IO.rope["bI"]]
}
};
argv ← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO Quit}];
IF argv.argc # 3 THEN {
msg ← IF fixIt THEN "Usage: FixTune jukeboxName|# tuneNumber" ELSE "Usage: PrintTune jukeboxName|# tuneNumber";
GOTO Quit;
};
IF argv[1].Equal["#"] THEN {
IF Jukebox.instances=NIL THEN { msg ← "No jukebox open"; GOTO Quit};
jukebox ← Jukebox.instances.first;
}
ELSE TRUSTED {jukebox ← Jukebox.FindJukebox[argv[1]]};
IF jukebox = NIL THEN TRUSTED {
jukebox ← Jukebox.OpenJukebox[argv[1]];
weOpened ← TRUE
};
tuneID ← Convert.IntFromRope[argv[2]];
TRUSTED
{ tune ← Jukebox.OpenTune[self: jukebox, tuneId: tuneID, write: fixIt];
tuneSize ← Jukebox.TuneSize[tune];
};
cmd.out.PutF["Tune %d from jukebox \"%g\": %d chirps\n", IO.int[tuneID], IO.rope[jukebox.jukeboxName], IO.int[tuneSize]];
cmd.out.PutF["Created: %g\n", IO.rope[Convert.RopeFromTime[TuneAccess.GetCreateDate[tune]]]];
cmd.out.PutF["Last written: %g\n", IO.rope[Convert.RopeFromTime[TuneAccess.GetWriteDate[tune]]]];
cmd.out.PutF["Last read: %g\n", IO.rope[Convert.RopeFromTime[TuneAccess.GetReadDate[tune]]]];
FOR chirp: INT IN [0..tuneSize) DO
ENABLE {
Jukebox.MissingChirp => {
SetLooks[Jukebox.bytesPerChirp/Jukebox.singlePktLength, 0, 1, TRUE]; -- these numbers assert "this is silence for one chirp"
cmd.out.PutF["\n******Chirp %d missing\n", IO.int[chirp]];
CONTINUE
};
};
ambientLevel ← TuneAccess.ReadAmbientLevel[jukebox, tune, chirp];
cmd.out.PutF["\n******Chirp %d: ambient noise %d", IO.int[chirp], IO.int[ambientLevel]];
runArray ← TuneAccess.ReadRunArray[jukebox, tune, chirp];
currElement ← 0;
totalSamples ← 0;
fixThisChirp ← FALSE;
currTypeLength ← 0; -- therefore no need to preset lastElement
FOR ri: NAT IN Jukebox.RunArrayRange DO
[length: runLength, energy: runEnergy, skipNextArrayElement: skipNextElement, silence: silence] ← TuneAccess.InterpretRunArrayElement[runArray, currElement];
this procedure is designed to mask how a chirp is constituted. We want to print out how it is constituted: however to save code and make this whole command clearer, we will use this routine and then re-interpret its results from our knowledge of the runArray
lastElement ← thisElement;
thisElement ← IF silence THEN silent ELSE (IF skipNextElement THEN otherSound ELSE singlePkt);
IF lastElement # thisElement THEN currTypeLength ← 0;
SELECT thisElement FROM
silent =>
{ cmd.out.PutF["%L", IO.rope["bI"]];
this is silence regardless of hangover time, so reset looks
IF currTypeLength = 0 THEN cmd.out.PutF["\nSilence periods - lengths:"];
IF currTypeLength MOD 8 = 0 THEN cmd.out.PutF["\n "];
cmd.out.PutF["%8d", IO.card[runLength]];
SetLooks[runLength/Jukebox.singlePktLength, 0, 1, FALSE]
because we manually reset the looks above, assert setLooksOnlyOnChange = FALSE here
};
singlePkt =>
{ SetLooks[1, runEnergy, ambientLevel, TRUE];
IF currTypeLength = 0 THEN cmd.out.PutF["\n20ms packets - energies:"];
IF currTypeLength MOD 8 = 0 THEN cmd.out.PutF["\n "];
cmd.out.PutF["%8d", IO.card[runEnergy]];
};
otherSound =>
{ 
SetLooks[(runLength+Jukebox.singlePktLength/2)/Jukebox.singlePktLength, runEnergy, ambientLevel, TRUE];
IF currTypeLength = 0 THEN cmd.out.PutF["\nNon-standard length packets:"];
cmd.out.PutF["\n Length %6d, energy %7d", IO.card[runLength], IO.card[runEnergy]];
}
ENDCASE;
totalSamples ← totalSamples + runLength;
IF totalSamples = Jukebox.bytesPerChirp THEN EXIT;
IF totalSamples > Jukebox.bytesPerChirp THEN GOTO badChirp;
currTypeLength ← currTypeLength + 1;
currElement ← currElement + (IF skipNextElement THEN 2 ELSE 1)
REPEAT
badChirp =>
{ fixThisChirp ← TRUE;
cmd.out.PutF[IF fixIt THEN "\nBad chirp format/length; fixing . . . " ELSE
"\n****** Bad chirp format - use FixTune?"]
};
ENDLOOP;
cmd.out.PutChar['\n];
IF fixIt AND fixThisChirp THEN TRUSTED
although we have used TuneAccess to avoid low level code above, it does not support writing run arrays, so we need to pollute the following with unsafe constructs
we have the number of the offending elemens in currElement, so we will read in the whole array again, correct it and wrtie it back. This is not very efficient, but makes the code a lot tidier than trying to use the run array as we have it above [i.e. contains all the unpleasantness in one area]
{ runArray: REF Jukebox.RunArray;
runArrayFileWindow: Jukebox.WindowOrigin;
runArrayVMWindow: VM.Interval;
pointerToVMWindow: LONG POINTER;
these constants make it clearer whence to pick up the run array data
pageOffsetInChirp: INT = Jukebox.bytesPerChirp/(File.wordsPerPage*2);
wordOffsetInPage: INT = (Jukebox.bytesPerChirp MOD (File.wordsPerPage*2))/2;
runDataPages: INT = Jukebox.pagesPerChirp - pageOffsetInChirp;
shouldn't get errors on this call, since we've already accessed this chirp
runArrayFileWindow ← Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirp, signalMissingChirp: TRUE, signalEOF: TRUE];
runArrayFileWindow.base ← runArrayFileWindow.base + pageOffsetInChirp;
runArrayVMWindow ← VM.Allocate[count: runDataPages];
pointerToVMWindow ← LOOPHOLE[VM.AddressForPageNumber[runArrayVMWindow.page]];
runArray ← LOOPHOLE[pointerToVMWindow + wordOffsetInPage];
FS.Read[file: runArrayFileWindow.file, from: runArrayFileWindow.base, nPages: runDataPages, to: VM.AddressForPageNumber[runArrayVMWindow.page]];
now correct the run array, using the variables we set up above
SELECT runArray[currElement].elementType FROM
silence => runArray[currElement]
← [silence[runLength - (totalSamples - Jukebox.bytesPerChirp)]];
soundEnergy => runArray[currElement+1] ← [soundLength[runLength -
(totalSamples - Jukebox.bytesPerChirp)]];
singlePkt => -- since this is last valid entry in chirp, okay to trample 'nextElement'
{
runArray[currElement] ← [soundEnergy[NARROW[runArray[currElement], Jukebox.RunComponent[singlePkt]].energy]];
runArray[currElement+1] ← [soundLength[Jukebox.singlePktLength - (totalSamples - Jukebox.bytesPerChirp)]]
};
soundLength => -- just replace with silence: this is a really unexpected error (unlikely we'd get here without an error in InterpretRunArrayElement)
runArray[currElement] ← [silence[Jukebox.bytesPerChirp - totalSamples]];
ENDCASE;
FS.Write[file: runArrayFileWindow.file, from: VM.AddressForPageNumber[runArrayVMWindow.page], nPages: runDataPages, to: runArrayFileWindow.base];
VM.Free[runArrayVMWindow]
}
ENDLOOP;
SetLooks[1,1,0, FALSE]; -- this simply sets the type face of the window back to plain before exiting
TRUSTED
{ IF fixIt THEN Jukebox.CloseTune[jukebox, tune] ELSE Jukebox.ArchiveCloseTune[jukebox, tune];
we use ArchiveCloseTune when just priniting the tune, so that the read/write dates will not be altered by this command
IF weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox]
};
};
EXITS
Quit => TRUSTED
{IF tune # NIL THEN { IF fixIt THEN Jukebox.CloseTune[jukebox, tune] ELSE Jukebox.ArchiveCloseTune[jukebox, tune]};
IF weOpened AND jukebox # NIL THEN jukebox ← Jukebox.CloseJukebox[jukebox];
RETURN[$Failure, msg];
};
};
};
SetAmbient: Commander.CommandProc = {
weOpened: BOOLEANFALSE;
jukebox: Jukebox.Handle;
tune: Jukebox.Tune;
tuneSize: INT;
fromChirp: INT ← 0; -- values passed to WriteAmbientLevel by default
toChirp: INT ← -1;
{
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO Quit}];
tuneID: INT;
ambientLevel: Jukebox.EnergyRange;
IF argv.argc # 4 AND argv.argc # 6 THEN
{ msg ← "Usage: SetAmbient jukeboxName|# tuneNumber energyValue [fromChirpNumber toChirpNumber]";
RETURN
};
IF argv.argc = 6 THEN
{ fromChirp ← Convert.IntFromRope[argv[4]];
toChirp ← Convert.IntFromRope[argv[5]]
};
IF argv[1].Equal["#"]
THEN
{ IF Jukebox.instances=NIL THEN { cmd.out.PutF["No jukebox open\n"]; RETURN};
jukebox ← Jukebox.instances.first;
}
ELSE
{ TRUSTED {jukebox ← Jukebox.FindJukebox[argv[1]]};
IF jukebox = NIL THEN TRUSTED
{ jukebox ← Jukebox.OpenJukebox[argv[1] ! Jukebox.Error => {msg ← rope; GOTO Quit}];
weOpened ← TRUE
}
};
tuneID ← Convert.IntFromRope[argv[2]];
ambientLevel ← Convert.IntFromRope[argv[3]];
TRUSTED {tune ← Jukebox.OpenTune[self: jukebox, tuneId: tuneID, write: TRUE ! Jukebox.Error => {msg ← rope; GOTO Quit}]};
TRUSTED {tuneSize ← Jukebox.TuneSize[tune]};
IF tuneSize > 0
THEN TuneAccess.WriteAmbientLevel[jukebox: jukebox, tune: tune, ambientLevel: ambientLevel, fromChirp: fromChirp, toChirp: toChirp]
ELSE cmd.out.PutF["Tune contains no chirps, so that its ambience is a trifle academic\n"];
TRUSTED {Jukebox.CloseTune[jukebox, tune]};
IF weOpened THEN TRUSTED {jukebox ← Jukebox.CloseJukebox[jukebox]}
EXITS
Quit =>
{ IF weOpened THEN TRUSTED {jukebox ← Jukebox.CloseJukebox[jukebox]};
RETURN[$Failure, msg]
}
}
};
ArchiveTune: 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
{ cmd.out.PutF["Usage: ArchiveTune jukeboxName tuneNumber fileName\n"];
RETURN
};
[tuneInformation, internalJukeboxName] ← TuneArchive.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]
};
TuneArchiveInfo: Commander.CommandProc = {
stream: IO.STREAMNIL;
{ENABLE
{FS.Error => IF error.group = bug THEN REJECT ELSE {msg ← error.explanation; GOTO Quit};
IO.EndOfStream => {msg ← "Attempt to read past end of file - not an archive file?";
GOTO Quit}};
tuneInformation: TuneArchive.TuneInformation;
archivedJukeboxName: Rope.ROPE;
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO Quit}];
IF argv.argc # 2 THEN
{ cmd.out.PutF["Usage: TuneArchiveInfo archiveFileName\n"];
RETURN
};
stream ← FS.StreamOpen[argv[1]];
[tuneInformation, archivedJukeboxName] ← TuneArchive.ReadArchiveHeader[readableFileStream: stream];
TuneArchive.PrintArchiveInfo[out: cmd.out, archiveFileName: argv[1], tuneInformation: tuneInformation, archivedJukeboxName: archivedJukeboxName];
IO.Close[stream]
EXITS
Quit =>
{ IF stream # NIL THEN IO.Close[stream];
RETURN[$Failure, msg]
}
}};
RestoreTune: Commander.CommandProc = {
ENABLE
{FS.Error => IF error.group = bug THEN REJECT ELSE {msg ← error.explanation; GOTO Quit};
IO.EndOfStream => {msg ← "Attempt to read past end of file - not an archive file?"; GOTO Quit};
Jukebox.Error => {msg ← rope; GOTO Quit}
};
argv: CommandTool.ArgumentVector
← CommandTool.Parse[cmd ! CommandTool.Failed => {msg ← errorMsg; GOTO Quit}];
tuneIDused: INT;
restoredJukeboxName: Rope.ROPE;
IF argv.argc > 4 OR argv.argc < 2 THEN
{ cmd.out.PutF["Usage: RestoreTune fileName [tuneNumber←numberOriginallyArchived [jukeboxName[jukeboxOriginallyArchived]]\n"];
RETURN
};
[tuneIDused, restoredJukeboxName] ← TuneArchive.RestoreTune[fromFile: argv[1], jukeboxName: IF argv.argc = 4 THEN argv[3] ELSE NIL, tuneID: IF argv.argc # 2 THEN Convert.IntFromRope[argv[2]] ELSE -1];
cmd.out.PutF["Restored as tune %d in jukebox %g\n", IO.int[tuneIDused], IO.rope[restoredJukeboxName]]
EXITS
Quit => RETURN[$Failure, msg]
};

Commander.Register[key: "ListTunes", proc: ListTunes, doc: "ListTunes [jukeboxName|*𡤊llOpenJukeboxes [upToTuneNumber]] - print information about tunes in a Jukebox: * means allOpenJukeboxes"];
Commander.Register[key: "PrintTune", proc: PrintTune, doc: "PrintTune jukeboxName|# tuneNumber - print tune information: # represents the first currently open jukebox"];
Commander.Register[key: "FixTune", proc: PrintTune, doc: "FixTune jukeboxName|# tuneNumber - print and fix up a tune: # represents the first currently open jukebox", clientData: $FixTune];
Commander.Register[key: "SetAmbient", proc: SetAmbient, doc: "SetAmbient
jukeboxname|# tuneNumber energyValue [fromChirpNumber toChirpNumber] - set ambient energy level in tune: # signifies first open jukebox; if given, fromChirpNumber and toChirpNumber are passed exactly as given to TuneAccess.WriteAmbientLevel, q.v."];
Commander.Register[key: "ArchiveTune", proc: ArchiveTune, doc: "ArchiveTune jukeboxName tuneNumber fileName - archive a tune from a jukebox as an FS file"];
Commander.Register[key: "TuneArchiveInfo", proc: TuneArchiveInfo, doc: "TuneArchiveInfo archiveFileName - list details of the tune archived into a file and of the file"];
Commander.Register[key: "RestoreTune", proc: RestoreTune, doc: "RestoreTune fileName [tuneNumber←numberOriginallyArchived [jukeboxName[jukeboxOriginallyArchived]] - restore a tune archive file back into a jukebox"];
END.