FILE: TuneAccess.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Ades, February 25, 1986 4:18:08 pm PST
Swinehart, May 9, 1986 6:20:46 pm PDT
The information in jukeboxes can be manipulated in two ways: (a) by real time access from a lark, using the Voicestream interface (b) using the routines in this interface to give the client at a workstation access to the data stored as tunes in the jukebox. The only way provided to write new chirps (tune segments) is via the Voicestream interface. However the workstation client can (a) read all the information in a jukebox (b) write the "user" portion of each tune header, plus the ambient noise values in chirps.
The user portion of the tune header is intended for use by higher level data structures, e.g. to store reference counts. Once a tune has been stored, using a dictation service, the tune is regarded as immutable and the voice is manipulated using "voice rope" structures which refer to portions of chirps.
The tunes in a jukebox do not have a foolproof locking mechanism associated with them: it is up to the user to open a tune before using it, with write access if any of the routines to be called perform write operations, and then to close it after use. The relevant Open and Close routines can be found in Jukebox: an example of the correct use of Open and Close can be found in the routine "PrintTune" in file " TuneCommandsImpl".
The user of these routines will need to use some routines from Jukebox: their definitions are listed as comments here for convenience. OpenJukebox and CloseJukebox are probably only of occasional use to the user since normally there is only one jukebox and it has been opened by the bluejay server as part of its initialisation. The variable "instances" is more of interest here.
All of the TuneAccess routines are SAFE or TRUSTED [nearly all are TRUSTED], but the Jukebox routines are not - all calls from CEDAR programs will need to be TRUSTED
All routines in this package may signal various manifestations of Jukebox.Error
DIRECTORY
BasicTime USING [GMT],
File USING [wordsPerPage],
Jukebox USING [Handle, Tune, RunArrayRange, RunComponent, EnergyRange, LengthRange, bytesPerChirp, singlePktLength, RunArray, bytesPerMS];
TuneAccess: CEDAR DEFINITIONS =
BEGIN
userHeaderLength: CARDINAL = File.wordsPerPage*2; -- in bytes
Byte: TYPE = [0..256);
ByteBlock: TYPE = REF ByteSequence;
ByteSequence: TYPE = RECORD [
length: NAT, 
s: PACKED SEQUENCE max: NAT OF Byte ];
EnergyBlock: TYPE = REF EnergySequence;
EnergySequence: TYPE = RECORD [
length: NAT,
s: SEQUENCE max: NAT OF Jukebox.EnergyRange ];
Jukebox.EnergyRange is [0..2^14)
EncryptedSilence: TYPE = REF EncryptedSilenceArray;
EncryptedSilenceArray: TYPE = ARRAY [0..Jukebox.bytesPerMS) OF Byte;
Jukebox.OpenJukebox: PROC [name: Rope.ROPE] RETURNS [Jukebox.Handle];
This just opens a jukebox and loads state into memory so that the jukebox can be used.
Jukebox.CloseJukebox: PROC [self: Handle] RETURNS [Jukebox.Handle];
Does the obvious thing: moves everything of interest in the jukebox back to disk. Returns NIL.
Jukebox.instances: LIST OF Jukebox.Handle;
A vital global variable, used by several clients; a list of all currently open jukeboxes
NextTuneNumber: PROC [jukebox: Jukebox.Handle, currentTuneID: INT ← -1] RETURNS [tuneID: INT];
Searches the tune number space of a jukebox monotonically for the next existing tune: default currentTuneID produces lowest numbered tune. Result of -1 indicates no next tune. The jukebox must be open. Since a delete on a tune may be proceeding in parallel, callers should be prepared for a subsequent OpenTune to fail with Jukebox.Error[BadTune] (= no such tune).
Jukebox.OpenTune: PROC [self: Handle, tuneId: INT, write: BOOL] RETURNS [Tune];
Opens a tune for reading or writing. The tune must exist. Procedure may signal a Jukebox.Error
Jukebox.CloseTune: PROC [self: Handle, tune: Tune];
Closes a tune and updates disk information.
Jukebox.TuneSize: PROC [tune: Jukebox.Tune] RETURNS [INT];
Procedure to find the length of a tune in chirps
ReadTuneHeader: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, nBytes: CARDINAL ← userHeaderLength, block: ByteBlock ← NIL] RETURNS [ByteBlock];
reads nBytes from the user header of a tune, starting at the beginning of the header. If nBytes>userHeaderLength then reads userHeaderLength. The tune must be open. No interpretation of the header is assumed by this code, hence a plain sequence of bytes is returned.
If the block supplied is too small for the data requested, or NIL, another block is allocated for the returnBlock
WriteTuneHeader: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, block: ByteBlock, nBytes: CARDINAL ← userHeaderLength];
similar to the above: the portion of the header beyond the bytes written is unaffected and if the number of bytes in the sequence is less than nBytes, the number written will be block.length
ReadRunArray: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, runArray: REF Jukebox.RunArray ← NIL] RETURNS [REF Jukebox.RunArray];
as ReadTuneheader but only applies to the complete array. If chirpNumber lies beyond the end of the tune, Jukebox.EOF will be signalled. If the chirp was not allocated (i.e. consists only of silence) then Jukebox.MissingChirp will be signalled.
InterpretRunArrayElement: PROC [runArray: REF Jukebox.RunArray, currArrayIndex: Jukebox.RunArrayRange] RETURNS [length: Jukebox.LengthRange, energy: Jukebox.EnergyRange, skipNextArrayElement: BOOLEAN, silence: BOOLEAN];
the runArray describes how the period for which each chirp lasts is composed: the runArray elements are each a variant record whose structure is described in the Jukebox interface. This routine interprets how many sampling periods an element represents and the energy level during that period. If the silence flag is set then there are no samples in the chirp corresponding to this period. Some periods of sound are represented by two run elements: the flag skipNextArrayElement indicates that the specified element is the first of a pair [and that the information from the second one has been interpreted]
GetEnergyProfile: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, energyBlock: EnergyBlock ← NIL, divisions: INT ← Jukebox.bytesPerChirp/Jukebox.singlePktLength, signalMissingChirp: BOOLEANFALSE] RETURNS [EnergyBlock];
if you only want the energy profile from the chirp, use this procedure instead of the previous two: it will divide the chirp into divisions parts [ignoring remainder] and compute average energy for each one: normally sensible to specify a number of divisions which divides exactly into Jukebox.bytesPerChirp: [default devisions are 20ms long] and more than Jukebox.bytesPerChirp divisions will be errored (as will fewer than 1). Exception Jukebox.EOF may be raised as for GetEnergyProfile: MissingChirp is handled internally (returning profile of all silences) unless signalMissingChirp = TRUE. Allocation of a new EnergyBlock is on the same terms as for ReadTuneHeader
ReadChirpSamples: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, nBytes: CARDINAL ← Jukebox.bytesPerChirp, block: ByteBlock ← NIL] RETURNS [ByteBlock];
as ReadTuneHeader: as the samples are DES-encrypted 8 bit mu-law thingies, they are simply returned as a ByteBlock. Exceptions may be raised as for ReadRunArray. The jukebox does not hold the keys by which these samples may be decrypted: decryption is the responsibility of the caller. Note also that the jukebox only holds samples for non-silent periods in a tune and that these samples are stored without gaps representing the silences. To format the chirp into a continuous record of Jukebox.bytesPerChirp samples, use the next routine
ReadFormattedSamples: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, block: ByteBlock ← NIL, encryptedSilence: EncryptedSilence ← NIL, signalMissingChirp: BOOLEANFALSE] RETURNS [ByteBlock];
routine to read a chirp and return a block of Jukebox.bytesPerChirp samples corresponding exactly and contiguously to the sound represented by that chirp. The samples are still DES encrypted. If block is NIL or less than Jukebox.bytesPerChirp bytes long a new ByteBlock will be allocated. Parts of the chirp may be silent, implying that there are no samples stored for those parts. If the caller gives an 8 byte represention of DES-encrypted silence this will be placed in those parts; otherwise they will be filled with all zeros. Signals as for ReadChirpSamples, except that if signalMissingChirp is FALSE the routine will catch Jukebox.MissingChirp and return an all-silent block
by the nature of the DES encryption silence/sound bursts must occur in multiples of 8 bytes: if not then this code will ERROR
GetCreateDate: PROC [tune: Jukebox.Tune] RETURNS [createDate: BasicTime.GMT] = TRUSTED INLINE {RETURN [tune.createDate]};
GetWriteDate: PROC [tune: Jukebox.Tune] RETURNS [appendDate: BasicTime.GMT] = TRUSTED INLINE {RETURN [tune.appendDate]};
GetReadDate: PROC [tune: Jukebox.Tune] RETURNS [playDate: BasicTime.GMT] = TRUSTED INLINE {RETURN [tune.playDate]};
ReadAmbientLevel: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT] RETURNS [Jukebox.EnergyRange];
each chirp contains an ambient noise value, which the voicestream playback mechanism uses as a criterion for what is silence and what is worth playing back
will signal Jukebox.MissingChirp if applicable
WriteAmbientLevel: PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, ambientLevel: Jukebox.EnergyRange, fromChirp: INT ← 0, toChirp: INT ← -1];
if toChirp<fromChirp then only toChirp is written; except that toChirp=-1 means write through to the end of the chirp
catches Jukebox.MissingChirp, writing to all specified chirps that are not missing
END.