<> <> <> <<>> 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: BOOL _ FALSE; 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|*_allOpenJukeboxes [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: BOOLEAN _ FALSE; 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]}; <> 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: BOOL _ FALSE; 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; <> 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]; <> <<>> 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"]]; <> 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] <> }; 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 <> <> { runArray: REF Jukebox.RunArray; runArrayFileWindow: Jukebox.WindowOrigin; runArrayVMWindow: VM.Interval; pointerToVMWindow: LONG POINTER; <<>> <> pageOffsetInChirp: INT = Jukebox.bytesPerChirp/(File.wordsPerPage*2); wordOffsetInPage: INT = (Jukebox.bytesPerChirp MOD (File.wordsPerPage*2))/2; runDataPages: INT = Jukebox.pagesPerChirp - pageOffsetInChirp; <<>> <> 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]]; <> 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]; <> 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: BOOLEAN _ FALSE; 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.STREAM _ NIL; {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|*_allOpenJukeboxes [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.