<> <> << >> DIRECTORY Jukebox USING [Handle, WindowOrigin, Error, Tune, Info, FindClientSpace, magicTuneHeader, RunArrayRange, RunComponent, bytesPerChirp, pagesPerChirp, FindChirp, singlePktLength, MissingChirp, LengthRange, EnergyRange, RunData, EOF, RunArray, bytesPerMS], VM USING [Allocate, Interval, AddressForPageNumber, Free], File USING [wordsPerPage], FS USING [Read, Write], PrincOps USING [ByteBltBlock], PrincOpsUtils USING [ByteBlt], TuneAccess; TuneAccessImpl: CEDAR PROGRAM IMPORTS Jukebox, VM, FS, PrincOpsUtils EXPORTS TuneAccess SHARES Jukebox = BEGIN OPEN TuneAccess; NextTuneNumber: PUBLIC PROC [jukebox: Jukebox.Handle, currentTuneID: INT _ -1] RETURNS [tuneID: INT] = TRUSTED { <> <<>> headerWindow: Jukebox.WindowOrigin; space: VM.Interval _ VM.Allocate[count: 1]; tuneFirstDiskHeaderPage: Jukebox.Tune _ LOOPHOLE[VM.AddressForPageNumber[page: space.page]];-- name is a reminder only to use those parts which reside on disk and no more than the first page thereof tuneFound: BOOLEAN _ FALSE; tuneHWM: INT; IF currentTuneID < -1 THEN currentTuneID _ -1; IF jukebox.hdr = NIL THEN ERROR Jukebox.Error[reason: NoJukebox, rope: "Jukebox not open"]; [tuneHWM: tuneHWM] _ Jukebox.Info[jukebox]; DO currentTuneID _ currentTuneID + 1; IF currentTuneID > tuneHWM THEN EXIT; headerWindow.file _ jukebox.window.file; headerWindow.base _ jukebox.firstDesPageNumber + 2*currentTuneID; FS.Read[file: headerWindow.file, from: headerWindow.base, nPages: space.count, to: VM.AddressForPageNumber[space.page]]; tuneFound _ tuneFirstDiskHeaderPage.state = inUse; IF tuneFound THEN EXIT ENDLOOP; VM.Free[space]; RETURN [IF tuneFound THEN currentTuneID ELSE -1] }; ReadTuneHeader: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, nBytes: CARDINAL _ userHeaderLength, block: ByteBlock _ NIL] RETURNS [ByteBlock] = TRUSTED { headerFileWindow: Jukebox.WindowOrigin; headerVMWindow: VM.Interval; byteBltBlockOfVMWindow: PrincOps.ByteBltBlock; pointerToSequence: LONG POINTER; byteBltBlockOfSequence: PrincOps.ByteBltBlock; bytesBlted: INT; IF nBytes>userHeaderLength THEN nBytes _ userHeaderLength; IF jukebox.hdr = NIL THEN ERROR Jukebox.Error[reason: NoJukebox, rope: "Jukebox not open"]; IF tune.magic # Jukebox.magicTuneHeader THEN ERROR Jukebox.Error[BadTunePointer, "Corrupt tune or tune pointer"]; <<>> <> IF block = NIL OR block.max> WriteTuneHeader: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, block: ByteBlock, nBytes: CARDINAL _ userHeaderLength] = TRUSTED { headerFileWindow: Jukebox.WindowOrigin; headerVMWindow: VM.Interval; byteBltBlockOfVMWindow: PrincOps.ByteBltBlock; pointerToSequence: LONG POINTER; byteBltBlockOfSequence: PrincOps.ByteBltBlock; bytesBlted: INT; nBytes _ MIN[nBytes, userHeaderLength, block.length]; IF jukebox.hdr = NIL THEN ERROR Jukebox.Error[reason: NoJukebox, rope: "Jukebox not open"]; IF tune.magic # Jukebox.magicTuneHeader THEN ERROR Jukebox.Error[BadTunePointer, "Corrupt tune or tune pointer"]; <<>> headerFileWindow _ Jukebox.FindClientSpace[jukebox, tune.tuneId]; headerVMWindow _ VM.Allocate[count: 1]; byteBltBlockOfVMWindow _ [LOOPHOLE[VM.AddressForPageNumber[headerVMWindow.page]], 0, nBytes]; pointerToSequence _ LOOPHOLE[block]; -- this rather grotty code points us beyond the pointerToSequence _ pointerToSequence + SIZE[ByteSequence[0]]; -- sequence header, to where byteBltBlockOfSequence _ [pointerToSequence, 0, nBytes]; -- the elements should be FS.Read[file: headerFileWindow.file, from: headerFileWindow.base, nPages: 1, to: VM.AddressForPageNumber[headerVMWindow.page]]; bytesBlted _ PrincOpsUtils.ByteBlt[from: byteBltBlockOfSequence, to: byteBltBlockOfVMWindow]; IF bytesBlted # nBytes THEN {VM.Free[headerVMWindow]; ERROR} ELSE { FS.Write[file: headerFileWindow.file, to: headerFileWindow.base, nPages: 1, from: VM.AddressForPageNumber[headerVMWindow.page]]; VM.Free[headerVMWindow] } }; <<>> ReadRunArray: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, runArray: REF Jukebox.RunArray _ NIL] RETURNS [REF Jukebox.RunArray] = TRUSTED { runArrayFileWindow: Jukebox.WindowOrigin; runArrayVMWindow: VM.Interval; byteBltBlockOfVMWindow: PrincOps.ByteBltBlock; byteBltBlockOfResult: PrincOps.ByteBltBlock; bytesBlted: INT; <> pageOffsetInChirp: INT = Jukebox.bytesPerChirp/(File.wordsPerPage*2); byteOffsetInPage: INT = Jukebox.bytesPerChirp MOD (File.wordsPerPage*2); runDataPages: INT = Jukebox.pagesPerChirp - pageOffsetInChirp; runArrayBytes: INT = (LAST[Jukebox.RunArrayRange] - FIRST[Jukebox.RunArrayRange] + 1)*2; <> IF runArray = NIL THEN runArray _ NEW[ARRAY Jukebox.RunArrayRange OF Jukebox.RunComponent]; <<>> <> runArrayFileWindow _ Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirpNumber, signalMissingChirp: TRUE, signalEOF: TRUE]; runArrayFileWindow.base _ runArrayFileWindow.base + pageOffsetInChirp; runArrayVMWindow _ VM.Allocate[count: runDataPages]; byteBltBlockOfVMWindow _ [LOOPHOLE[VM.AddressForPageNumber[runArrayVMWindow.page]], byteOffsetInPage, byteOffsetInPage + runArrayBytes]; byteBltBlockOfResult _ [LOOPHOLE[runArray], 0, runArrayBytes]; FS.Read[file: runArrayFileWindow.file, from: runArrayFileWindow.base, nPages: runDataPages, to: VM.AddressForPageNumber[runArrayVMWindow.page]]; bytesBlted _ PrincOpsUtils.ByteBlt[from: byteBltBlockOfVMWindow, to: byteBltBlockOfResult]; VM.Free[runArrayVMWindow]; IF bytesBlted # runArrayBytes THEN ERROR; RETURN [runArray]; }; InterpretRunArrayElement: PUBLIC PROC [runArray: REF Jukebox.RunArray, currArrayIndex: Jukebox.RunArrayRange] RETURNS [length: Jukebox.LengthRange, energy: Jukebox.EnergyRange, skipNextArrayElement: BOOLEAN _ FALSE, silence: BOOLEAN _ FALSE] = { WITH currEl: runArray[currArrayIndex] SELECT FROM silence => { length _ currEl.length; energy _ 0; silence _ TRUE }; singlePkt => { length _ Jukebox.singlePktLength; energy _ currEl.energy }; soundEnergy => { energy _ currEl.energy; length _ NARROW[runArray[currArrayIndex+1], Jukebox.RunComponent[soundLength]].length; skipNextArrayElement _ TRUE }; ENDCASE => ERROR; }; GetEnergyProfile: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, energyBlock: EnergyBlock _ NIL, divisions: INT _ Jukebox.bytesPerChirp/Jukebox.singlePktLength, signalMissingChirp: BOOLEAN _ FALSE] RETURNS [EnergyBlock] = { ENABLE Jukebox.MissingChirp => { IF signalMissingChirp THEN REJECT ELSE { FOR i: NAT IN [0..energyBlock.length) DO energyBlock[i] _ 0 ENDLOOP; GOTO ReturnEnergyBlock } }; runArray: REF ARRAY Jukebox.RunArrayRange OF Jukebox.RunComponent; endOfLastDivision: Jukebox.LengthRange; divisionSize: INT; currDivision: INT _ 0; endOfCurrDivision: Jukebox.LengthRange; endOfCurrRunElement: Jukebox.LengthRange _ 0; energyOfCurrRunElement: Jukebox.EnergyRange; currRunArrayIndex: Jukebox.RunArrayRange _ 0; samplesAccountedFor: Jukebox.LengthRange _ 0; samplesBeingAccounted: Jukebox.LengthRange; energyAccumulator: INT _ 0; NextRunElement: PROC RETURNS [energyOfCurrRunElement: Jukebox.EnergyRange] = INLINE { length: Jukebox.LengthRange; dualElementRepresentation: BOOLEAN; [length: length, energy: energyOfCurrRunElement, skipNextArrayElement: dualElementRepresentation] _ InterpretRunArrayElement[runArray, currRunArrayIndex]; currRunArrayIndex _ currRunArrayIndex + (IF dualElementRepresentation THEN 2 ELSE 1); endOfCurrRunElement _ endOfCurrRunElement + length }; IF divisions < 1 OR divisions>Jukebox.bytesPerChirp THEN ERROR; divisionSize _ Jukebox.bytesPerChirp/divisions; endOfLastDivision _ divisionSize*divisions; endOfCurrDivision _ divisionSize; IF energyBlock = NIL OR energyBlock.max= endOfCurrRunElement THEN energyOfCurrRunElement _ NextRunElement[]; samplesBeingAccounted _ MIN[endOfCurrRunElement, endOfCurrDivision] - samplesAccountedFor; energyAccumulator _ energyAccumulator + INT[samplesBeingAccounted]*INT[energyOfCurrRunElement]; samplesAccountedFor _ samplesAccountedFor + samplesBeingAccounted ENDLOOP; energyBlock[currDivision] _ energyAccumulator/INT[divisionSize]; energyAccumulator _ 0; currDivision _ currDivision + 1; IF endOfCurrDivision = Jukebox.bytesPerChirp THEN EXIT ELSE endOfCurrDivision _ endOfCurrDivision + divisionSize ENDLOOP; RETURN[energyBlock]; EXITS -- for handling MissingChirp error ReturnEnergyBlock => RETURN[energyBlock] }; ReadChirpSamples: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, nBytes: CARDINAL _ Jukebox.bytesPerChirp, block: ByteBlock _ NIL] RETURNS [ByteBlock] = TRUSTED { sampleFileWindow: Jukebox.WindowOrigin; sampleVMWindow: VM.Interval; byteBltBlockOfVMWindow: PrincOps.ByteBltBlock; pointerToSequence: LONG POINTER; byteBltBlockOfSequence: PrincOps.ByteBltBlock; bytesBlted: INT; filePagesToRead: INT; <> IF nBytes>Jukebox.bytesPerChirp THEN nBytes _ Jukebox.bytesPerChirp; filePagesToRead _ (nBytes+(File.wordsPerPage*2)-1)/(File.wordsPerPage*2); IF block = NIL OR block.max> sampleFileWindow _ Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirpNumber, signalMissingChirp: TRUE, signalEOF: TRUE]; sampleVMWindow _ VM.Allocate[count: filePagesToRead]; byteBltBlockOfVMWindow _ [LOOPHOLE[VM.AddressForPageNumber[sampleVMWindow.page]], 0, nBytes]; pointerToSequence _ LOOPHOLE[block]; -- this rather grotty code points us beyond the pointerToSequence _ pointerToSequence + SIZE[ByteSequence[0]]; -- sequence header, to where byteBltBlockOfSequence _ [pointerToSequence, 0, nBytes]; -- the elements should be FS.Read[file: sampleFileWindow.file, from: sampleFileWindow.base, nPages: filePagesToRead, to: VM.AddressForPageNumber[sampleVMWindow.page]]; bytesBlted _ PrincOpsUtils.ByteBlt[from: byteBltBlockOfVMWindow, to: byteBltBlockOfSequence]; VM.Free[sampleVMWindow]; IF bytesBlted # nBytes THEN ERROR ELSE block.length _ nBytes; RETURN [block] }; <> ChirpFormat: TYPE = REF ChirpFormatRecord; ChirpFormatRecord: TYPE = RECORD [ elementsPlus1: Jukebox.RunArrayRange, <> array: ARRAY Jukebox.RunArrayRange OF RECORD [ length: Jukebox.LengthRange, -- length of this lump storedBase: Jukebox.LengthRange, -- where in the chirp it is stored trueBase: Jukebox.LengthRange, -- the correct position in time that it represents silence: BOOLEAN ] ]; ReadFormattedSamples: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT, block: ByteBlock _ NIL, encryptedSilence: EncryptedSilence _ NIL, signalMissingChirp: BOOLEAN _ FALSE] RETURNS [ByteBlock] = { ENABLE Jukebox.MissingChirp => { IF signalMissingChirp THEN REJECT ELSE { sampleNumber, silenceNumber: INT _ 0; WHILE sampleNumber < Jukebox.bytesPerChirp DO block[sampleNumber] _ encryptedSilence[silenceNumber]; sampleNumber _ sampleNumber + 1; silenceNumber _ (silenceNumber + 1) MOD Jukebox.bytesPerMS ENDLOOP; GOTO Quit } }; runArray: REF Jukebox.RunArray; chirpFormat: ChirpFormat _ NEW[ChirpFormatRecord]; samplesAccounted: Jukebox.LengthRange _ 0; newLength: Jukebox.LengthRange; skipNextArrayElement, currentlySilence: BOOLEAN; currArrayIndex: Jukebox.RunArrayRange _ 0; validSamples: Jukebox.LengthRange; <> <<>> <> IF block = NIL OR block.max> runArray _ ReadRunArray[jukebox, tune, chirpNumber, NIL]; chirpFormat.elementsPlus1 _ 0; chirpFormat.array[0].length _ 0; -- set up the first element as sound, length 0 chirpFormat.array[0].storedBase _ 0; chirpFormat.array[0].trueBase _ 0; chirpFormat.array[0].silence _ FALSE; WHILE samplesAccounted < Jukebox.bytesPerChirp DO [length: newLength, skipNextArrayElement: skipNextArrayElement, silence: currentlySilence] _ InterpretRunArrayElement[runArray, currArrayIndex]; samplesAccounted _ samplesAccounted + newLength; currArrayIndex _ currArrayIndex + (IF skipNextArrayElement THEN 2 ELSE 1); IF chirpFormat.array[chirpFormat.elementsPlus1].silence # currentlySilence THEN { chirpFormat.elementsPlus1 _ chirpFormat.elementsPlus1 + 1; chirpFormat.array[chirpFormat.elementsPlus1].length _ 0; chirpFormat.array[chirpFormat.elementsPlus1].storedBase _ chirpFormat.array[chirpFormat.elementsPlus1-1].storedBase + (IF currentlySilence THEN chirpFormat.array[chirpFormat.elementsPlus1-1].length ELSE 0); -- think about it!! chirpFormat.array[chirpFormat.elementsPlus1].trueBase _ chirpFormat.array[chirpFormat.elementsPlus1-1].trueBase + chirpFormat.array[chirpFormat.elementsPlus1-1].length; chirpFormat.array[chirpFormat.elementsPlus1].silence _ currentlySilence }; chirpFormat.array[chirpFormat.elementsPlus1].length _ chirpFormat.array[chirpFormat.elementsPlus1].length + newLength ENDLOOP; <> <<>> validSamples _ chirpFormat.array[chirpFormat.elementsPlus1].storedBase + (IF ~chirpFormat.array[chirpFormat.elementsPlus1].silence THEN chirpFormat.array[chirpFormat.elementsPlus1].length ELSE 0); block _ ReadChirpSamples[jukebox, tune, chirpNumber, validSamples, block]; <<>> FOR el: Jukebox.RunArrayRange DECREASING IN [0..chirpFormat.elementsPlus1] DO IF chirpFormat.array[el].silence THEN -- okay to do this loop forwards as it doesn't involve any copies { currSample: Jukebox.LengthRange _ chirpFormat.array[el].trueBase; lastSamplePlus1: Jukebox.LengthRange _ currSample + chirpFormat.array[el].length; silenceNumber: INT _ 0; WHILE currSample < lastSamplePlus1 DO block[currSample] _ encryptedSilence[silenceNumber]; currSample _ currSample + 1; silenceNumber _ (silenceNumber + 1) MOD Jukebox.bytesPerMS ENDLOOP; IF silenceNumber # 0 THEN ERROR; -- silence/sound runs must be multiples of 8!!!! } ELSE { IF (chirpFormat.array[el].length MOD 8) # 0 THEN ERROR; IF ~(chirpFormat.array[el].trueBase = chirpFormat.array[el].storedBase OR chirpFormat.array[el].length = 0) THEN WimpFakeReverseByteBlt[block: block, from: chirpFormat.array[el].storedBase, to: chirpFormat.array[el].trueBase, nBytes: chirpFormat.array[el].length] } ENDLOOP; block.length _ Jukebox.bytesPerChirp; RETURN[block] EXITS Quit => { block.length _ Jukebox.bytesPerChirp; RETURN[block] } }; <<>> WimpFakeReverseByteBlt: PROC [block: ByteBlock, from: Jukebox.LengthRange, to: Jukebox.LengthRange, nBytes: Jukebox.LengthRange] = INLINE { destCounter: INT _ to + nBytes - 1; FOR sourceCounter: INT DECREASING IN [from..from+nBytes) DO block[destCounter] _ block[sourceCounter]; destCounter _ destCounter - 1 ENDLOOP }; <<>> ReadAmbientLevel: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, chirpNumber: INT] RETURNS [ambientLevel: Jukebox.EnergyRange] = TRUSTED { runDataFileWindow: Jukebox.WindowOrigin; runDataVMWindow: VM.Interval; runData: Jukebox.RunData; <> <> pageOffsetInChirp: INT = Jukebox.bytesPerChirp/(File.wordsPerPage*2); byteOffsetInPage: INT = Jukebox.bytesPerChirp MOD (File.wordsPerPage*2); runDataPages: INT = Jukebox.pagesPerChirp - pageOffsetInChirp; <> runDataFileWindow _ Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirpNumber, signalMissingChirp: TRUE, signalEOF: TRUE]; runDataFileWindow.base _ runDataFileWindow.base + pageOffsetInChirp; runDataVMWindow _ VM.Allocate[count: 1]; runData _ VM.AddressForPageNumber[runDataVMWindow.page] + (byteOffsetInPage/2); <> FS.Read[file: runDataFileWindow.file, from: runDataFileWindow.base, nPages: runDataPages, to: VM.AddressForPageNumber[runDataVMWindow.page]]; ambientLevel _ runData.ambientLevel; VM.Free[runDataVMWindow] }; <<>> WriteAmbientLevel: PUBLIC PROC [jukebox: Jukebox.Handle, tune: Jukebox.Tune, ambientLevel: Jukebox.EnergyRange, fromChirp: INT, toChirp: INT] = TRUSTED { <> runDataFileWindow: Jukebox.WindowOrigin; runDataVMWindow: VM.Interval; runData: Jukebox.RunData; pageOffsetInChirp: INT = Jukebox.bytesPerChirp/(File.wordsPerPage*2); byteOffsetInPage: INT = Jukebox.bytesPerChirp MOD (File.wordsPerPage*2); runDataPages: INT = Jukebox.pagesPerChirp - pageOffsetInChirp; IF fromChirp < 0 THEN ERROR; IF toChirp = -1 THEN toChirp _ tune.size - 1; IF toChirp < fromChirp THEN toChirp _ fromChirp; IF toChirp >= tune.size THEN ERROR Jukebox.EOF[]; <> runDataVMWindow _ VM.Allocate[count: 1]; runData _ VM.AddressForPageNumber[runDataVMWindow.page] + (byteOffsetInPage/2); <<>> FOR chirp: INT IN [fromChirp..toChirp] DO ENABLE Jukebox.MissingChirp => LOOP; runDataFileWindow _ Jukebox.FindChirp[self: jukebox, tune: tune, chirp: chirp, signalMissingChirp: TRUE, signalEOF: TRUE]; runDataFileWindow.base _ runDataFileWindow.base + pageOffsetInChirp; FS.Read[file: runDataFileWindow.file, from: runDataFileWindow.base, nPages: runDataPages, to: VM.AddressForPageNumber[runDataVMWindow.page]]; runData.ambientLevel _ ambientLevel; FS.Write[file: runDataFileWindow.file, to: runDataFileWindow.base, nPages: runDataPages, from: VM.AddressForPageNumber[runDataVMWindow.page]] ENDLOOP; VM.Free[runDataVMWindow] }; <<>> <<>> END. <<>> << >>