FILE: TuneArchiveImpl.mesa
Ades, February 20, 1986 4:02:25 pm PST
DIRECTORY
Jukebox USING [Handle, Tune, bytesPerChirp, RunArray, FindJukebox, OpenJukebox, CloseJukebox, OpenTune, LengthRange, EnergyRange, RunArrayRange, RunComponent, MissingChirp, pagesPerChirp, ArchiveCloseTune, CreateTune, RunData, WindowOrigin, FindChirp],
TuneAccess USING [ByteBlock, userHeaderLength, ReadTuneHeader, ByteSequence, ReadAmbientLevel, ReadRunArray, ReadChirpSamples, GetCreateDate, GetWriteDate, GetReadDate, WriteTuneHeader, InterpretRunArrayElement],
Rope USING [ROPE, Fetch, Length, Concat, FromChar],
IO USING [STREAM, UnsafeBlock, UnsafePutBlock, UnsafeGetBlock, PutChar, PutF, int, rope, Close, GetChar],
FS USING [StreamOpen, FileInfo, Write],
Convert USING [RopeFromTime],
BasicTime USING [Now],
FileNames USING [ConvertToSlashFormat],
VM USING [Interval, Allocate, Free, AddressForPageNumber],
TuneArchive;
TuneArchiveImpl:
CEDAR
PROGRAM
IMPORTS Jukebox, TuneAccess, Rope,
IO,
FS, Convert, BasicTime, FileNames,
VM
EXPORTS TuneArchive =
BEGIN OPEN TuneArchive;
ArchiveTune:
PUBLIC
PROC [jukeboxName: Rope.
ROPE, tuneID:
INT, toFile: Rope.
ROPE]
RETURNS [tuneInformation:TuneInformation, internalJukeboxName: Rope.
ROPE] =
TRUSTED {
stream: IO.STREAM ← NIL;
jukebox: Jukebox.Handle ← NIL;
tune: Jukebox.Tune ← NIL;
weOpened: BOOLEAN ← FALSE; -- all declared here for catch phrase use
{
ENABLE
UNWIND =>
{
IF stream #
NIL
THEN
IO.Close[stream];
IF tune # NIL THEN Jukebox.ArchiveCloseTune[jukebox, tune];
we use ArchiveCloseTune here so that the read/write dates will not be altered by the archive
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[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: ChirpContents ← NEW[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, tuneInformationBytes];
byteBlockPointer: LONG POINTER ← LOOPHOLE[byteBlock];
unsafeOfByteBlock: IO.UnsafeBlock ← [byteBlockPointer + SIZE[TuneAccess.ByteSequence[0]]]; -- yetch
unsafeOfChirpContents: IO.UnsafeBlock ← [LOOPHOLE[chirpContents], 0, 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.ArchiveCloseTune[jukebox, tune]; -- comments as previous use of ArchiveCloseTune
IF weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox]
BuildTuneInformation:
PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, info: 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]
};
BuildChirpRecord:
PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber:
INT, chirpContents: 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;
skipNextArrayElement: BOOLEAN;
silence: BOOLEAN;
runArray ← TuneAccess.ReadRunArray[jukebox, tune, chirpNumber, runArray];
WHILE accountedSamples<Jukebox.bytesPerChirp
DO
[thisLength, , skipNextArrayElement, silence] ← TuneAccess.InterpretRunArrayElement[runArray, usefulArrayLength];
IF ~silence THEN usefulSamples ← usefulSamples + thisLength;
accountedSamples ← accountedSamples + thisLength;
usefulArrayLength ← usefulArrayLength + (IF skipNextArrayElement THEN 2 ELSE 1)
chirpContents.runArrayLength ← usefulArrayLength;
chirpContents.sampleBytes ← usefulSamples;
chirpContents.ambientLevel ← TuneAccess.ReadAmbientLevel[jukebox, tune, chirpNumber];
byteBlock ← TuneAccess.ReadChirpSamples[jukebox, tune, chirpNumber, usefulSamples, byteBlock]
};
ReadArchiveHeader:
PUBLIC
PROC [readableFileStream:
IO.
STREAM]
RETURNS [tuneInformation: TuneArchive.TuneInformation, archivedJukeboxName: Rope.
ROPE ←
NIL] = {
unsafeOfTuneInformation: IO.UnsafeBlock;
unsafeBytesRead: INT;
tuneInformation ← NEW[TuneInformationHeader];
unsafeOfTuneInformation ← [LOOPHOLE[tuneInformation], 0, tuneInformationBytes];
TRUSTED {unsafeBytesRead ← readableFileStream.UnsafeGetBlock[unsafeOfTuneInformation]};
IF unsafeBytesRead # tuneInformationBytes THEN ERROR;
archivedJukeboxName ← NIL;
FOR i: INT IN [1..tuneInformation.jukeboxNameLength] DO archivedJukeboxName ← archivedJukeboxName.Concat[Rope.FromChar[readableFileStream.GetChar]] ENDLOOP
PrintArchiveInfo:
PUBLIC
PROC [out:
IO.
STREAM, archiveFileName: Rope.
ROPE, tuneInformation: TuneArchive.TuneInformation, archivedJukeboxName: Rope.
ROPE] = {
length: INT;
fullFName, attachedTo: Rope.ROPE;
[bytes: length, fullFName: fullFName, attachedTo: attachedTo] ← FS.FileInfo[name: archiveFileName];
fullFName ← FileNames.ConvertToSlashFormat[fullFName];
IF attachedTo # NIL THEN attachedTo ← FileNames.ConvertToSlashFormat[attachedTo];
out.PutF["File \"%g\":\n", IO.rope[fullFName]];
IF attachedTo # NIL THEN out.PutF["(attached to \"%g\")\n", IO.rope[attachedTo]];
out.PutF[" archive of tune number %d from jukebox \"%g\"\n", IO.int[tuneInformation.tuneNumber], IO.rope[archivedJukeboxName]];
out.PutF[" made on %g", IO.rope[Convert.RopeFromTime[tuneInformation.archiveDate]]];
IF length = -1
THEN out.PutF["\n***FS Error whilst trying to determine length of archive file\n\n"]
ELSE out.PutF["; length %d bytes\n\n", IO.int[length]];
out.PutF[" Tune length %d chirps\n", IO.int[tuneInformation.sizeInChirps]];
out.PutF[" Created: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.createDate]]];
out.PutF[" Last written: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.writeDate]]];
out.PutF[" Last read: %g\n", IO.rope[Convert.RopeFromTime [tuneInformation.readDate]]]
RestoreTune:
PUBLIC
PROC [fromFile: Rope.
ROPE, jukeboxName: Rope.
ROPE ←
NIL, tuneID:
INT ← -1]
RETURNS [tuneIDused:
INT, restoredJukeboxName: Rope.
ROPE] =
TRUSTED {
fileStream: IO.STREAM ← NIL;
jukebox: Jukebox.Handle ← NIL;
tune: Jukebox.Tune ← NIL;
weOpened: BOOLEAN ← FALSE; -- all declared here for catch phrase use
chirpVMWindow: VM.Interval ← VM.Allocate[Jukebox.pagesPerChirp];
{
ENABLE
UNWIND =>
{
IF fileStream #
NIL
THEN
IO.Close[fileStream];
IF tune # NIL THEN Jukebox.ArchiveCloseTune[jukebox, tune];
IF jukebox # NIL AND weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox];
VM.Free[chirpVMWindow]
};
tuneInformation: TuneArchive.TuneInformation;
archivedJukeboxName: Rope.ROPE;
byteBlock: TuneAccess.ByteBlock ← NEW[TuneAccess.ByteSequence[TuneAccess.userHeaderLength]];
byteBlockPointer: LONG POINTER ← LOOPHOLE[byteBlock];
unsafeOfByteBlock: IO.UnsafeBlock ← [byteBlockPointer + SIZE[TuneAccess.ByteSequence[0]], 0, TuneAccess.userHeaderLength]; -- yetch
unsafeBytesRead: INT;
fileStream ← FS.StreamOpen[fromFile];
[tuneInformation, archivedJukeboxName] ← ReadArchiveHeader[readableFileStream: fileStream];
IF jukeboxName = NIL THEN jukeboxName ← archivedJukeboxName;
IF tuneID = -1 THEN tuneID ← tuneInformation.tuneNumber;
IF tuneID < -1 THEN tuneID ← -1; -- means "create a tune from unallocated ID space"
jukebox ← Jukebox.FindJukebox[jukeboxName];
IF jukebox = NIL THEN
{ jukebox ← Jukebox.OpenJukebox[jukeboxName];
weOpened ← TRUE
};
tune ← Jukebox.CreateTune[self: jukebox, tuneId: tuneID];
RestoreTuneInformation[tune, tuneInformation];
now have enough info to set return parameters
tuneIDused ← tune.tuneId;
restoredJukeboxName ← jukebox.jukeboxName;
unsafeBytesRead ← fileStream.UnsafeGetBlock[unsafeOfByteBlock];
IF unsafeBytesRead # TuneAccess.userHeaderLength THEN ERROR;
TuneAccess.WriteTuneHeader[jukebox, tune, byteBlock];
FOR j: INT IN [0..tuneInformation.sizeInChirps) DO
RestoreChirp[jukebox, tune, j, fileStream, chirpVMWindow] ENDLOOP;
Jukebox.ArchiveCloseTune[jukebox, tune]; -- special one; enables the restored timestamp information to persist
IF weOpened THEN jukebox ← Jukebox.CloseJukebox[jukebox];
IO.Close[fileStream];
VM.Free[chirpVMWindow]
}};
RestoreTuneInformation:
PROC [tune: Jukebox.Tune, tuneInformation: TuneInformation] =
TRUSTED {
the only information that need to be copied back into the [non-user portion of the] tune header are the three dates: the rest of the TuneInformation is either discarded, overriden by the parameters to RestoreTune or written into the tune explicitly by various parts of RestoreTune
tune.createDate ← tuneInformation.createDate;
tune.appendDate ← tuneInformation.writeDate;
tune.playDate ← tuneInformation.readDate
there isn't even the need to write this information through, since this will be done by Jukebox.ArchiveCloseTune
};
RestoreChirp:
PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber:
INT, fileStream:
IO.
STREAM, chirpVMWindow:
VM.Interval] =
TRUSTED {
chirpContents: ChirpContents ← NEW[ChirpContentsRecord];
unsafeOfChirpContents: IO.UnsafeBlock ← [LOOPHOLE[chirpContents], 0, chirpContentsBytes];
unsafeBytesRead: INT ← fileStream.UnsafeGetBlock[unsafeOfChirpContents];
chirpVMWindowpointer: LONG POINTER ← LOOPHOLE[VM.AddressForPageNumber[chirpVMWindow.page]];
unsafeOfSamples: IO.UnsafeBlock ← [chirpVMWindowpointer, 0, chirpContents.sampleBytes];
unsafeOfRunData: IO.UnsafeBlock ← [chirpVMWindowpointer + (Jukebox.bytesPerChirp/2), 0, chirpContents.runArrayLength*2];
runData: Jukebox.RunData ← LOOPHOLE[unsafeOfRunData.base];
N.B. the preceding lines, as well as being horrible, assume knowledge of the exact structure of a chirp. They are also not very efficient as they could be done once only outside RestoreChirp, but this is for the sake of abstraction
IF unsafeBytesRead # chirpContentsBytes THEN ERROR;
unsafeBytesRead ← fileStream.UnsafeGetBlock[unsafeOfRunData];
IF unsafeBytesRead # chirpContents.runArrayLength*2 THEN ERROR;
unsafeBytesRead ← fileStream.UnsafeGetBlock[unsafeOfSamples];
IF unsafeBytesRead # chirpContents.sampleBytes THEN ERROR;
runData.ambientLevel ← chirpContents.ambientLevel;
IF chirpContents^ = [0, 1, 0] AND runData.runArray[0] = [silence[Jukebox.bytesPerChirp]] THEN RETURN; -- this will have the effect of leaving a 'missing chirp'
otherwise, if there is useful data then allocate another chirp in the tune and then write into it
{
chirpWindow: Jukebox.WindowOrigin ← Jukebox.FindChirp[self: jukebox, tune: tune,
chirp: chirpNumber, signalMissingChirp: FALSE, signalEOF: FALSE];
FS.Write[file: chirpWindow.file, to: chirpWindow.base, nPages: Jukebox.pagesPerChirp, from: VM.AddressForPageNumber[chirpVMWindow.page]]
}
};
END.