DIRECTORY
FS USING [Read],
Jukebox USING [ bytesPerChirp, FindChirp, Handle, MissingChirp, pagesPerChirp, RunType, Tune, WindowOrigin, singlePktLength, RunComponent, EnergyRange, hangoverPackets],
VM USING [AddressForPageNumber, Allocate, WordsForPages];
FirstNoisyIntervalIn:
PUBLIC PROC [
self: Handle, tune: Tune, start: INT, length: INT, minSilence: INT]
RETURNS [intStart: INT, intLength: INT𡤀, nextNoise: INT←-1] = {
variables referenced in the following nested procedures
ambientLevel: Jukebox.EnergyRange;
samplesSinceLastNonSilence: INT ← 0; -- any value will do, since we are not interested until the precedingSilence state, when we will reset it and rerun SetRunType anyhow
SetPosBefore:
ENTRY PROC[self: Handle, tune: Tune, sample:
INT]
RETURNS [intStart, intEnd: INT, runType: RunType] = {
ENABLE UNWIND => NULL;
Place "cursor" at the beginning of chirp containing sample.
IF tune.runData#
NIL AND sample=tune.samplesToChirp+tune.samplesInChirp
THEN {
intStart ← sample;
intEnd ← intStart + NextElementLength[tune];
runType ← tune.runType;
ambientLevel ← tune.runData.ambientLevel
}
ELSE
[intStart, intEnd, runType] ← SetPosToChirp[self, tune, sample/Jukebox.bytesPerChirp];
};
SetPosToChirp:
INTERNAL PROC[self: Handle, tune: Tune, chirp:
INT]
RETURNS [intStart, intEnd: INT, runType: RunType] = {
Place "cursor" within chirp containing sample, somewhere before run containing sample.
samplesStart: INT;
IF tune.runData=
NIL THEN {
tune.runSpace ← VM.Allocate[count: 1];
tune.runData ←
VM.AddressForPageNumber[tune.runSpace.page] +
((Jukebox.bytesPerChirp/2) - VM.WordsForPages[Jukebox.pagesPerChirp-1]);
tune.runChirp ← -1;
};
samplesStart ← MIN[chirp, tune.size]*Jukebox.bytesPerChirp;
IF chirp >= tune.size
THEN {
tune.samplesToChirp ← tune.samplesInChirp ← -1;
RETURN[samplesStart, LAST[INT], end];
}; -- end of tune indication!
IF chirp#tune.runChirp
THEN {
missing: BOOL←FALSE;
chirpWindow: Jukebox.WindowOrigin;
tune.runChirp ← chirp;
chirpWindow ← Jukebox.FindChirp[self: self, tune: tune, chirp: chirp, signalMissingChirp:
TRUE, signalEOF:
TRUE!Jukebox.MissingChirp => {
missing←TRUE; CONTINUE; }];
IF ~missing THEN
{
FS.Read[file: chirpWindow.file, from: chirpWindow.base + ( Jukebox.pagesPerChirp - 1), nPages: 1, to: VM.AddressForPageNumber[tune.runSpace.page]];
read just the last page of each chirp
ambientLevel ← tune.runData.ambientLevel
}
ELSE -- empty chirp
tune.runData.runArray[0] ← [silence[Jukebox.bytesPerChirp]]
no need to set ambientLevel here, since it is only looked at when there are samples
};
tune.runIndex ← 0;
tune.samplesInChirp ← 0;
tune.samplesToChirp ← samplesStart;
SetRunType[tune];
RETURN[samplesStart, samplesStart + NextElementLength[tune], tune.runType];
};
NextRun:
ENTRY PROC[self: Handle, tune: Tune]
RETURNS [intStart, intEnd: INT, runType: RunType] = {
Current "pointer" always denotes a valid entry.
Move to next valid entry, or indicate end.
ENABLE UNWIND => NULL;
tune.samplesInChirp ← tune.samplesInChirp + NextElementLength[tune];
IF tune.samplesInChirp=Jukebox.bytesPerChirp
THEN {
[intStart, intEnd, runType] ← SetPosToChirp[self, tune, tune.runChirp+1];
RETURN;
};
tune.runIndex ← tune.runIndex+(IF tune.runData.runArray[tune.runIndex].elementType = soundEnergy THEN 2 ELSE 1);
SetRunType[tune];
runType ← tune.runType;
intStart ← tune.samplesToChirp+tune.samplesInChirp;
intEnd ← intStart + NextElementLength[tune];
};
NextElementLength:
PROC[tune: Tune]
RETURNS [length: NAT ← 0]= {
SELECT tune.runData.runArray[tune.runIndex].elementType FROM
silence => length ← NARROW[tune.runData.runArray[tune.runIndex], Jukebox.RunComponent[silence]].length;
singlePkt => length ← singlePktLength;
soundEnergy => length ← NARROW[tune.runData.runArray[tune.runIndex+1], Jukebox.RunComponent[soundLength]].length;
soundLength => ERROR;
ENDCASE
};
SetRunType: PROC [tune: Tune] = {
procedure uses the ambient level, current energy level and time since last over-threshold sound to determine if we should regard next element as sound or silence: it is not called in end-of-tune cases
belowThreshold: BOOLEAN ← TRUE;
samplesOmitted: BOOLEAN ← FALSE;
WITH curr: tune.runData.runArray[tune.runIndex]
SELECT FROM
silence => samplesOmitted ← TRUE;
singlePkt => belowThreshold ← (curr.energy <= ambientLevel);
soundEnergy => belowThreshold ← (curr.energy <= ambientLevel);
ENDCASE => ERROR;
IF ~belowThreshold THEN samplesSinceLastNonSilence ← 0;
tune.runType ← IF (~samplesOmitted) AND samplesSinceLastNonSilence <= Jukebox.hangoverPackets*Jukebox.singlePktLength THEN noisy ELSE silent;
IF belowThreshold THEN samplesSinceLastNonSilence ← samplesSinceLastNonSilence + NextElementLength[tune]
start of FirstNoisyIntervalIn routine body
searchState: {beforeStart, precedingSilence, inNoise, lessThanMinSilence, followingSilence} ← beforeStart;
runStart, runEnd: INT;
runType: RunType;
searchEnd: INT = start+length;
intStart←start;
IF start<0 OR length<0 THEN RETURN;
better to change the callers' parameter types, except lots of thing might need rewriting?
all 'nosuch found' returns have inStart=start and intLength=0
[runStart, runEnd, runType] ← SetPosBefore[self, tune, start];
IF runType = end THEN RETURN;
DO -- this is simply a state machine
SELECT searchState FROM
beforeStart =>
IF runEnd<start THEN [runStart, runEnd, runType] ← NextRun[self, tune] ELSE
{ searchState ← precedingSilence;
samplesSinceLastNonSilence ← Jukebox.hangoverPackets*Jukebox.singlePktLength;
resetting this and then calling SetRunType ensures that we don't pick up a short burst of below-ambience samples at the beginning of the NoisyInterval
SetRunType[tune];
runType ← tune.runType
};
precedingSilence =>
SELECT runType FROM
silent =>
IF runEnd >= searchEnd THEN {intStart←start; searchState ← followingSilence} ELSE
[runStart, runEnd, runType] ← NextRun[self, tune];
noisy =>
{intStart ← IF runStart > start THEN runStart ELSE start;
searchState ← inNoise};
ENDCASE => ERROR;
inNoise =>
SELECT runType FROM
noisy =>
IF searchEnd < runEnd
THEN { intLength ← searchEnd - intStart;
nextNoise ← searchEnd;
RETURN
}
ELSE [runStart, runEnd, runType] ← NextRun[self, tune];
end =>
{ intLength ← runStart - intStart; RETURN };
silent =>
{ intLength ← runStart - intStart; searchState ← lessThanMinSilence };
ENDCASE => ERROR;
lessThanMinSilence =>
SELECT runType FROM
silent =>
IF runEnd - (intStart + intLength) >= minSilence THEN searchState ← followingSilence
ELSE [runStart, runEnd, runType] ← NextRun[self, tune];
noisy =>
searchState ← inNoise;
ENDCASE => ERROR;
followingSilence =>
SELECT runType FROM
silent =>
[runStart, runEnd, runType] ← NextRun[self, tune];
noisy =>
{nextNoise ← runStart; RETURN};
ENDCASE => ERROR;
ENDCASE => ERROR;
ENDLOOP