SoundListImpl.mesa:
sound lists are descriptions of the silence/sound profiles of a ropeInterval and form part of the data structure behind a voice viewer
Ades, September 23, 1986 10:10:49 am PDT
DIRECTORY
Rope USING [ROPE, Concat, FromChar],
VoiceRope USING [IntervalSpecs],
VoiceMarkers USING [DisplayCharMarks],
VoiceViewers USING [VoiceViewerInfo, Sound, SoundList, SoundInterval, soundRopeCharDivisions, soundRopeResolution, soundRopeCharLength],
SoundList;
SoundListImpl: CEDAR PROGRAM IMPORTS Rope, VoiceMarkers EXPORTS SoundList = BEGIN
these first few routines are the various append functions required by the public routines following them
AppendSound: PROC [list: VoiceViewers.SoundList, entry: VoiceViewers.Sound] RETURNS [VoiceViewers.SoundList] = {
oneElementList: VoiceViewers.SoundList ← CONS[entry, NIL];
hangOffPoint: VoiceViewers.SoundList ← list;
IF list = NIL THEN RETURN [oneElementList];
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList;
RETURN [list]
};
AppendSoundToSoundInterval: PROC [soundInterval: VoiceViewers.SoundInterval, entry: VoiceViewers.Sound] = {
oneElementList: VoiceViewers.SoundList ← CONS[entry, NIL];
hangOffPoint: VoiceViewers.SoundList ← soundInterval.soundList;
IF hangOffPoint = NIL THEN {soundInterval.soundList ← oneElementList; RETURN};
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
hangOffPoint.rest ← oneElementList
};
AppendSoundListToSoundList: PROC [head, tail: VoiceViewers.SoundList] = {
head is assumed non-nil
hangOffPoint: VoiceViewers.SoundList ← head;
IF tail = NIL THEN RETURN;
WHILE hangOffPoint.rest # NIL DO hangOffPoint ← hangOffPoint.rest ENDLOOP;
IF hangOffPoint.first.sound = 0 OR tail.first.silence = 0 THEN -- these two can be amalgamated
{ hangOffPoint.first.sound ← hangOffPoint.first.sound + tail.first.sound;
hangOffPoint.first.silence ← hangOffPoint.first.silence + tail.first.silence;
tail ← tail.rest
};
hangOffPoint.rest ← tail
};
-------- public procedures ---------
SoundListFromIntervalSpecs: PUBLIC PROC [intervalSpecs: VoiceRope.IntervalSpecs, lengthOfRopeInterval: INT] RETURNS [soundList: VoiceViewers.SoundList ← NIL] = {
lastSoundEnd: INT ← 0;
FOR l: VoiceRope.IntervalSpecs ← intervalSpecs, l.rest WHILE l # NIL DO
soundList ← AppendSound[soundList, [l.first.start - lastSoundEnd, l.first.length]];
lastSoundEnd ← l.first.start + l.first.length
ENDLOOP;
IF lengthOfRopeInterval > lastSoundEnd THEN soundList ← AppendSound[soundList, [lengthOfRopeInterval-lastSoundEnd, 0]]
};
ExtractSoundList: PUBLIC PROC [voiceViewerInfo: VoiceViewers.VoiceViewerInfo, soundInterval: VoiceViewers.SoundInterval] = {
voiceViewerInfo contains the sound list for a complete rope. Use the interval specification from soundInterval.ropeInterval to create a new sound list for that interval, placing it in soundInterval.soundList
soughtStart: INT ← soundInterval.ropeInterval.start;
soughtEnd: INT ← soughtStart + soundInterval.ropeInterval.length;
currStart, currEnd: INT ← 0;
thisSilence, thisSound: INT;
FOR l: VoiceViewers.SoundList ← voiceViewerInfo.soundList, l.rest WHILE l # NIL AND soughtEnd > currEnd DO
currStart ← currEnd;
thisSilence ← l.first.silence;
thisSound ← l.first.sound;
currEnd ← currStart + thisSilence + thisSound;
IF soughtStart < currEnd THEN
{ IF soughtStart > currStart
THEN
{ thisSound ← thisSound - MAX [soughtStart-currStart-thisSilence, 0];
thisSilence ← thisSilence - MIN [soughtStart-currStart, thisSilence]
};
IF soughtEnd < currEnd
THEN
{ thisSilence ← thisSilence - MAX [currEnd-soughtEnd-thisSound, 0];
thisSound ← thisSound - MIN [ currEnd-soughtEnd, thisSound]
};
AppendSoundToSoundInterval[soundInterval, [thisSilence, thisSound]]
}
ENDLOOP
};
ReplaceSoundList: PUBLIC PROC [voiceViewerInfo: VoiceViewers.VoiceViewerInfo, cutStart: INT, cutLength: INT, replacement: VoiceViewers.SoundList] = {
voiceViewerInfo contains the sound list for a complete rope. Cut out the specified portion and at the cutting point insert the replacement soundlist.
tail, workingPtr: VoiceViewers.SoundList;
endCurrSound, startCurrSound: INT ← 0;
IF cutStart = 0
THEN
{ tail ← voiceViewerInfo.soundList;
voiceViewerInfo.soundList ← NIL
}
ELSE
{ FOR workingPtr ← voiceViewerInfo.soundList, workingPtr.rest DO
no tests for shooting off the end in this routine: if we get an error then our caller has gone badly wrong and there is no obvious way to recover
endCurrSound ← endCurrSound + workingPtr.first.silence + workingPtr.first.sound;
IF endCurrSound >= cutStart THEN EXIT
ENDLOOP;
tail ← workingPtr.rest;
workingPtr.rest ← NIL;
IF endCurrSound > cutStart THEN -- have to 'move sound from workingPtr to tail'
{ silenceToMove: INTMAX [endCurrSound-cutStart-workingPtr.first.sound, 0];
soundToMove: INTMIN [endCurrSound-cutStart, workingPtr.first.sound];
tail ← CONS[[silenceToMove, soundToMove], tail];
workingPtr.first.silence ← workingPtr.first.silence - silenceToMove;
workingPtr.first.sound ← workingPtr.first.sound - soundToMove
}
};
having got the head of the old rope in voiceViewerInfo.soundList, now delete the unwanted sound from the tail
endCurrSound ← 0; DO
startCurrSound ← endCurrSound;
IF startCurrSound = cutLength THEN EXIT;
endCurrSound ← endCurrSound + tail.first.silence + tail.first.sound;
IF endCurrSound > cutLength THEN EXIT;
tail ← tail.rest;
ENDLOOP;
IF startCurrSound < cutLength THEN -- delete some of the sound in the head of the list
{ silenceToSave: INTMAX [endCurrSound-cutLength-tail.first.sound, 0];
soundToSave: INTMIN [endCurrSound-cutLength, tail.first.sound];
tail.first.silence ← silenceToSave;
tail.first.sound ← soundToSave
};
we can now join up our three lists:
IF replacement = NIL THEN replacement ← tail ELSE AppendSoundListToSoundList[replacement, tail];
IF voiceViewerInfo.soundList = NIL THEN voiceViewerInfo.soundList ← replacement ELSE AppendSoundListToSoundList[voiceViewerInfo.soundList, replacement]
};
SoundChars: PUBLIC PROC [viewerInfo: VoiceViewers.VoiceViewerInfo, skipChars: INT ← 0] RETURNS [soundRope: Rope.ROPENIL, remnant: INT] = {
produce the graphical representation of the SoundList within a VoiceViewerInfo record. The caller already has the first skipChars of the representation in his hand, so omit these from the returned rope. If the sound list does not exactly fill a whole number of characters, return as remnant the number of samples left over. Any marker characters indicated in the VoiceViewerInfo are inserted
soundList: VoiceViewers.SoundList ← viewerInfo.soundList;
partiallyFilled: BOOLEANFALSE;
partsFilled: [0..VoiceViewers.soundRopeCharDivisions] ← 0;
soundMajority: ARRAY [0..VoiceViewers.soundRopeCharDivisions) OF BOOLEAN;
samplesAccounted, soundComponents: [0..VoiceViewers.soundRopeResolution] ← 0;
skipping: BOOLEAN ← skipChars>0;
skipSamples: INT ← skipChars*VoiceViewers.soundRopeCharLength;
AddChars: PROC [length: INT, sound: BOOLEAN] = {
AddToPartChar: PROC [availableSamples: INT, sound: BOOLEAN] RETURNS [usedSamples: [0..VoiceViewers.soundRopeResolution]] = {
partiallyFilled ← TRUE;
usedSamples ← MIN[availableSamples, VoiceViewers.soundRopeResolution-samplesAccounted];
samplesAccounted ← samplesAccounted + usedSamples;
IF sound THEN soundComponents ← soundComponents + usedSamples;
IF samplesAccounted = VoiceViewers.soundRopeResolution
THEN
{ soundMajority[partsFilled] ← soundComponents >= VoiceViewers.soundRopeResolution/2;
samplesAccounted ← 0;
soundComponents ← 0;
partsFilled ← partsFilled + 1;
IF partsFilled = VoiceViewers.soundRopeCharDivisions
THEN
{ binaryOfChar: INT ← 0;
partsFilled ← 0;
partiallyFilled ← FALSE;
FOR i: INT IN [0..VoiceViewers.soundRopeCharDivisions) DO
binaryOfChar ← binaryOfChar*2 + (IF soundMajority[i] THEN 1 ELSE 0)
ENDLOOP;
soundRope ← soundRope.Concat[Rope.FromChar[IF binaryOfChar # 0 THEN ('A + binaryOfChar) ELSE '!]] -- 'A is the base type of the sound font [i.e. all segments clear] but the 'all clear' character is actually ! so as to make 'word selections' work in normal tioga terms
}
}
};
WHILE partiallyFilled AND length>0 DO
taken: INT ← AddToPartChar[length, sound];
length ← length - taken
ENDLOOP;
WHILE length >= VoiceViewers.soundRopeCharLength DO
soundRope ← soundRope.Concat[Rope.FromChar[IF sound THEN ('A + 15) ELSE '!]];
15 represents 'all bits are sound' - i.e. 2**soundRopeCharDivisions-1
length ← length - VoiceViewers.soundRopeCharLength
ENDLOOP;
WHILE length>0 DO
taken: INT ← AddToPartChar[length, sound];
length ← length - taken
ENDLOOP
};
FOR l: VoiceViewers.SoundList ← soundList, l.rest WHILE l # NIL DO
silence: INT ← l.first.silence;
sound: INT ← l.first.sound;
IF skipping
THEN
{ IF silence >= skipSamples
THEN
{ silence ← silence - skipSamples;
skipping ← FALSE;
AddChars[length: silence, sound: FALSE];
AddChars[length: sound, sound: TRUE]
}
ELSE
{ skipSamples ← skipSamples - silence;
IF sound >= skipSamples
THEN
{ sound ← sound - skipSamples;
skipping ← FALSE;
AddChars[length: sound, sound: TRUE]
}
ELSE skipSamples ← skipSamples - sound;
}
}
ELSE
{ AddChars[length: silence, sound: FALSE];
AddChars[length: sound, sound: TRUE]
}
ENDLOOP;
remnant ← IF partiallyFilled THEN partsFilled*VoiceViewers.soundRopeResolution+samplesAccounted ELSE 0;
soundRope ← VoiceMarkers.DisplayCharMarks[soundRope, viewerInfo.charMarkList, skipChars]
};
LastSilenceInSoundList: PUBLIC PROC [soundList: VoiceViewers.SoundList, lengthGreaterThan: INT ← 0] RETURNS [startsAt: INT, lasts: INT ← -1] = {
all INTs are values in samples, as for the rest of this interface. lasts=-1 indicates that there are no silence intervals in the list of lengthGreaterThan that specified
l: VoiceViewers.SoundList ← soundList;
currSilenceStart: INT ← 0;
WHILE l # NIL DO
IF l.first.silence > lengthGreaterThan THEN
{ startsAt ← currSilenceStart;
lasts ← l.first.silence
};
currSilenceStart ← currSilenceStart + l.first.silence + l.first.sound;
l ← l.rest
ENDLOOP
};
END.