FILE: Jukebox.mesa
Last Edited by: Swinehart, September 25, 1984 8:41:22 am PDT
Last Edited by: Stewart, December 30, 1983 11:33 am
Ades, February 20, 1986 4:22:19 pm PST
DIRECTORY
BasicTime USING [GMT],
FS USING [nullOpenFile, OpenFile],
IO USING [STREAM],
Rope USING [ROPE],
VM USING [Interval, nullInterval];
Jukebox: DEFINITIONS =
BEGIN
Tune: TYPE = LONG POINTER TO TuneDescriptor;
pagesPerChirp: CARDINAL = 16;
bytesPerChirp: CARDINAL = 8000;
bytesPerMS: CARDINAL = 8;
PageNumber: TYPE = INT;
ErrorCode: TYPE = {JukeboxExists, JukeboxOpen, NoJukebox, NoTunesAvailable, BadTune, JukeboxFormat, TooManyTunes, TuneLocked, Bug, TuneTooLarge, NoFreeChirps, BadTunePointer, TunesOpen, BadChirpPointer, NoDiskSpace};
Error: ERROR[reason: ErrorCode, rope: Rope.ROPE];
MissingChirp: ERROR;
EOF: ERROR;
WindowOrigin: TYPE = RECORD [file: FS.OpenFile, base: PageNumber];
instances: LIST OF Handle;
A vital global variable, used by several clients; a list of all currently open jukeboxes
routines to act on entire jukeboxes:
CreateJukebox: PROC [name: Rope.ROPE, nPages: INT, nTunes: INT];
This procedure creates a new jukebox by the given name (which must not exist already), containing the given number of pages, and having space for nTune tune descriptors. The jukebox is NOT opened.
DeleteJukebox: PROC [name: Rope.ROPE];
Delete a jukebox, it must not be open: the check for jukebox already open is the only difference from simply deleting the file
FindJukebox: PROC [name: Rope.ROPE] RETURNS [Handle];
This returns the jukebox if it is already open, otherwise returns NIL.
OpenJukebox: PROC [name: Rope.ROPE] RETURNS [Handle];
This just opens a juke box and loads state into memory so that the jukebox can be used.
CloseJukebox: PROC [self: Handle] RETURNS [Handle];
Does the obvious thing: moves everything of interest in the jukebox back to disk. Returns NIL.
Info: PROC [self: Handle] RETURNS [name: Rope.ROPE, nPages, nTunes: INT, tuneHWM: INT];
This just returns information about the open jukebox. If the jukebox is not open, then name is NIL. Otherwise, name is the name of the open jukebox, nPages is the number of pages available to store chirps, nTunes is the total number of tunes it can store and tuneHWM is the high water mark of tune allocation in the jukebox.

routines to act on single tunes: note that tune access is not AT ALL watertight and is open to abuse by careless clients: before using a tune it is the client's duty to ensure that HE opened it (as opposed to it being open for some other reason) and that it is open for writing if that is the access required. Clients should also do a close corresponding to their open!
CreateTune: PROC [self: Handle, tuneId: INT ← -1] RETURNS [Tune];
Creates a tune and opens it for writing. If tuneId is negative, then we allocate a new tuneId of our choosing. Otherwise, the given tuneId is used, and if the tune already exists then it is nulled.
DeleteTune: PROC [self: Handle, tuneId: INT];
Recycles the chirp storage for the tune and marks the tune as no longer in use (this means that a later call to CreateTune[] may allocate this tune.
OpenTune: PROC [self: Handle, tuneId: INT, write: BOOL] RETURNS [Tune];
Opens a tune for reading or writing. The tune must exist.
CloseTune: PROC [self: Handle, tune: Tune];
Closes a tune and updates disk information.
TuneSize: PROC [tune: Tune] RETURNS [INT] = INLINE { RETURN[tune.size]; };
Returns size of tune in chirps.
FindChirp: PROC [self: Handle, tune: Tune, chirp: INT, signalMissingChirp: BOOLFALSE, signalEOF: BOOLFALSE] RETURNS [WindowOrigin];
Sets a window to correspond to the location of a chirp in a tune. If the chirp falls past the end of the tune and signalEOF is TRUE, then EOF is signalled. If the chirp hasn't been allocated in the file, then a new chirp is allocated (if signalMissingChirp is FALSE) or a MissingChirp is raised (if signalMissingChirp is TRUE).
FindClientSpace: PROC [self: Handle, tuneId: INT] RETURNS [WindowOrigin];
Returns information that can be used to read or write the client space for a given tune.
FirstNoisyIntervalIn: PROC [
self: Handle, tune: Tune, start: INT, length: INT, minSilence: INT𡤁]
RETURNS [intStart, intLength, nextNoise: INT];
Identify the interval within the interval specified by [start,length] that contains a talkspurt (range of non-silence) with embedded silent intervals at most minSilence ms. long. Finding none, set intStart to start and intLength to 0. If additional noisy intervals exist within or beyond the specified interval, indicate with nextNoise the beginning of the next one (for convenience in continuing the scan.)
Scavenge: PROC [self: Handle, stream: IO.STREAM, makeFixes: BOOL] RETURNS[nFree: INT, recovered: INT];
nFree => Free blocks after collection
recovered => Previously unknown blocks added to
free list
This procedure examines the entire jukebox (which must be open) to find chirps that are used in more than one place, for example in two different tunes or in one tune and on the free list. If makeFixes is TRUE then the inconsistencies are resolved by removing one of the pointers. Furthermore, if a bogus format is discovered in a tune header and makeFixes is TRUE, then the whole tune is deleted.
All the tunes in the jukebox must be closed for this routine to succeed.
ArchiveCloseTune: PROC [self: Handle, tune: Tune];
A special version of CloseTune, for use by the archive package: it closes the tune but does not update the date information, so that a tune can be restored from archive and bear its correct timestamp information [i.e. as if the file had been there all along]. Also used by commands such as listtunes, which print information about tunes and which are not supposed to affect the states of tunes they examine
Private Definitions:
Handle: TYPE = REF JukeboxObject;
JukeboxObject: TYPE = MONITORED RECORD [
window: Jukebox.WindowOrigin ← [FS.nullOpenFile, 0],
hdr: LONG POINTER TO JukeboxHeader ← NIL,
hdrSpace: VM.Interval ← VM.nullInterval,
currentMapPageNumber: INT ← 0,
maxMapPageNumber: INT ← 0,
free: LONG POINTER TO FreeList ← NIL,
freeSpace: VM.Interval ← VM.nullInterval,
currentFreeChirpNumber: INT ← 0,
firstDesPageNumber: INT ← 0,
openTuneHeaderList: Tune ← NIL,
jukeboxName: Rope.ROPENIL
];
The structure of a jukebox file is as follows:
1) The first page contains the jukebox header, including chirp allocation and other miscellaneous information.
2) The second and following pages contain a bitmap indicating the availability of tune headers.
3) After the tune header bit map come many pages of tune headers, two pages for each possible tune. The first page of each tune header is for Bluejay use only, and the second page is for client use only (Bluejay provides ops to read it and write it). Tune headers are allocated statically, and are similar in format to Unix i-nodes (three levels of block directory).
4) All pages after the tune header are used for storage of the tunes, and for indirect tune chirp lists, and for free list blocks. The pages of a tune are grouped into "chirps". Each chirp contains several pages, in order to reduce disk access time. Each chirp contains one second of conversation in 16 pages. Chirps are page aligned, with the 192 bytes per chirp following the speech samples used as a run list for interpreting the sequence of the samples, as defined below.
The following are "magic" numbers placed at the beginning of all blocks to aid in detecting misuse of jukeboxes.
magicJukeboxHeader: PRIVATE INTEGER = 14513;
magicTuneBitMap: PRIVATE INTEGER = 14722;
magicTuneHeader: PRIVATE INTEGER = 28617;
magicChirpList: PRIVATE INTEGER = 7111;
magicDeepList: PRIVATE INTEGER = -4613;
magicFreeList: PRIVATE INTEGER = -15322;
magicChirp: PRIVATE INTEGER = 6215;
JukeboxHeader: PRIVATE TYPE = RECORD [
This record is read and written directly from the first page of jukebox files.
It contains overall descriptive information about the jukebox.
magic: INTEGER, -- Magic number for consistency checks.
nTunes: INT, -- Number of tune headers allocated.
nFreeChirps: INT, -- Number of free chirps.
nChirps: INT, -- Total no. of chirps in jukebox.
freeListHead: PageNumber, -- Pointer to chirp at head of free list.
firstChirp: PageNumber, -- Pointer to first chirp above tune headers.
tuneHWM: INT-- highest number tune that has ever been allocated in jukebox
];
bitMapSize: PRIVATE INTEGER = 4;
TuneBitMap: PRIVATE TYPE = RECORD [
This record is read and written directly from the tune header bitmap pages.
One bit is contained for each tune header. One means header is available.
magic: INTEGER, -- Magic number for consistency checks.
bits: ARRAY [0..bitMapSize) OF CARDINAL
];
nHdrChirps: PRIVATE INTEGER = 10; -- Number of zeroth-level chirps in tune header.
tuneState: PUBLIC TYPE = {inUse, available};
RunType: TYPE = { end, noisy, silent };
TuneDescriptor: PUBLIC TYPE = RECORD [
Defines the structure and usage of a voice recording. This is read and written directly from the tune header pages on disk. The first definitions are kept on disk, as well as being used in main memory when the tune is "open".
magic: INTEGER, -- Magic number for consistency checks.
state: tuneState,
createDate: BasicTime.GMT,
appendDate: BasicTime.GMT,
playDate: BasicTime.GMT,
size: INT, -- Number of chirps in tune.
chirps: ARRAY [0..nHdrChirps) OF PageNumber,
Pointers to first 50 chirps of tune.
indirectChirp: PageNumber,-- Pointer to indirect chirp (each page contains
a ChirpList of chirp pointers).
deepChirp: PageNumber, -- Pointer to doubly-indirect chirp (each page
contains ChirpList of indirect chirps).
The following entries are used only in the main-memory version when the tune is open.
tuneId: INT, -- Name of the tune.
openCount: INTEGER, -- Number of times this tune is open.
next: Tune, -- All open tunes are linked together.
space: VM.Interval, -- Space used to write descriptor to disk.
writable: BOOL, -- TRUE means only one access at a time.
kill: BOOL, -- Deallocate tune as soon as it's not in use.
indirectSpace: VM.Interval,-- A space and pointer are cached (potentially)
indirectCache: LONG POINTER TO ChirpList,
indirect: INT,
deepSpace: VM.Interval, -- for one single indirect block and the double
deepCache: LONG POINTER TO ChirpList, -- indirect block.
Remainder is for NextNoisyIntervalIn -- space could maybe be shared with above.
runSpace: VM.Interval, -- for one block containing chirp run table
runChirp: INT,  -- chirp # currently occupying runSpace
runData: RunData,  -- NIL when run table stuff not initialized.
runIndex: RunArrayRange,  -- current index into current run table.
samplesInChirp: INT,  -- position corresponding to index
samplesToChirp: INT,  -- position in tune corresponding to beginning of chirp
runSize: INT, -- length of current run (0-length intervening runs don't clear it)
runType: RunType -- noisy, silent, end
];
chirpListSize: PRIVATE INTEGER = 64;
ChirpList: PRIVATE TYPE = RECORD [
These records are read and written directly from the chirp list blocks on disk.
magic: INTEGER, -- Magic number for consistency checks.
chirps: ARRAY [0..chirpListSize) OF PageNumber,
The following values are only used when ChirpLists are in main memory.
dirty: BOOL, -- This is always FALSE on disk.
page: PageNumber -- Page from which list was read.
];
freeListSize: PRIVATE INTEGER = 16;
FreeList: PRIVATE TYPE = RECORD [
These records are read and written directly from the free list blocks on disk.
The first free pointer in the block refers to the block itself.
magic: INTEGER, -- Magic number for consistency checks.
nChirps: INTEGER, -- Number of valid chirps in the array.
next: PageNumber, -- Next chirp in free list chain.
chirps: ARRAY [0..freeListSize) OF PageNumber
];
RunArrayRange: TYPE = [0..95);
RunElementType: TYPE = {silence,singlePkt,soundEnergy,soundLength};
maxEnergy: INTEGER = 16383;
EnergyRange: TYPE = [0..maxEnergy];
LengthRange: TYPE = [0..bytesPerChirp];
alas LengthRange must be able to take value 0, for initialisation of chirps
RunArray: TYPE = ARRAY RunArrayRange OF RunComponent;

RunDataObject: TYPE = RECORD [
This record represents the extra space in each chirp, after the end of recorded voice, used to
represent the structure of the voice.
The bytesperchirp's worth of space holds all the samples received from a lark during the chirp's duration: they are packed without leaving spaces for silences. On playback samples are picked out from this block leaving timing gaps according to the specification of the runArray.
           
The ambientLevel is a guide to the energy level of background noise when the recording was made
    
runArray: RunArray,
ambientLevel: EnergyRange
];
RunComponent: TYPE = MACHINE DEPENDENT RECORD [
Each component in the runArray may either represent a period of silence, in which case the length is given in samples, or a series of actual samples, in which case it is represented by TWO RunComponents: the first gives an energy value for the block of samples and the second the length the block in samples. A special case of a series of samples is a singlePkt containing exactly 160 samples: it simply carries an energy value.
variantTag (0: 0..15): SELECT elementType (0: 0..1): RunElementType FROM
silence => [length (0: 2..15): LengthRange],
singlePkt => [energy (0: 2..15): EnergyRange],
soundEnergy => [energy (0: 2..15): EnergyRange],
soundLength => [length (0: 2..15): LengthRange]
ENDCASE
];
SilentRun: TYPE = silence RunComponent;
singlePktLength: CARDINAL = 20*bytesPerMS;
RunData: TYPE = LONG POINTER TO RunDataObject;
hangoverPackets: INT = 20;
the number of silent packets which must occur after a talkspurt before it is considered not worth transmitting them. This number is used, in conjunction with a fixed ambient noise value, within the lark in order to suppress what it thinks is silence. If we decide to use a different ambience value then we need to know the hangover length. The Lark's idea of ambient noise, DIVIDED BY FOUR BECAUSE WE USE ONLY 14 BITS C.F. THE LARK'S 16 TO REPRESENT IT, is
larkAmbientNoiseThreshold: EnergyRange = 144;
END.
December 26, 1983 7:35 pm, Stewart, Cedar 5
Ades, January 27, 1986 2:36:10 pm PST
changed RunData: now used to record energies of packets as well as a run length profile for sound/silence