<> <> <> <> <> <<>> <> <<>> DIRECTORY BluejaySmarts USING [ConvDesc, GetConversation, DistributeKey, IntervalReq, IntervalReqBody, SetInterval], BluejayUtils USING [DescribeTune, DescribeInterval], GVNames USING [IsMemberUpArrow], IV, Jukebox USING [CloseTune, CreateTune, EOF, Error, IntervalSpec, IntervalSpecBody, IntervalSpecs, MissingChirp, Tune, TuneID], RecordingServiceRegister USING [database, jukebox], RefID USING [ID], Rope USING [Cat, Equal, Find, ROPE], RPC USING [GetCaller], Thrush USING [ConversationID, Credentials, EncryptionKey, NB, none, nullKey, SHHH], VoiceStream USING [AddPiece, FlushPieces], VoiceUtils USING [Problem], VoiceRopeDB USING [AddInterest, DropInterest, Error, Handle, Header, Interest, InterestInfo, InterestForRef, NextTuneOnList, ReadVoiceRope, SimpleTuneList, TuneID, TuneList, TuneListInterval, UnpackHeader, UnpackInterest, VoiceRopeInfo, WriteVoiceRope], VoiceRopeServer; VoiceRopeServerImpl: CEDAR PROGRAM -- Should this be a monitor? IMPORTS BluejaySmarts, BluejayUtils, GVNames, Jukebox, RecordingServiceRegister, Rope, RPC, VoiceStream, VoiceUtils, VoiceRopeDB EXPORTS VoiceRopeServer ~ BEGIN OPEN VoiceRopeServer; ROPE: TYPE ~ Rope.ROPE; <> beep: VoiceRope _ NIL; -- beep to prompt recording playBeep: BOOLEAN _ FALSE; Record: PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, credentials: Thrush.Credentials, serviceID: RefID.ID, intID: CARD _ 0, queueIt: BOOL _ TRUE] RETURNS [nb: Thrush.NB _ $success, voiceRope: VoiceRope] = { <> <<{ $recording, $started, intID } when recording begins.>> <<{ $recording, $finished, intID } when the recording of this interval has finished.>> <<{ $recording, $abandoned, intID } when all recording and playback actions ending with this interval has been flushed.>> ENABLE { VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; GOTO Fail; }; Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => { VoiceUtils.Problem["Error detected in Bluejay", $Bluejay]; nb _ $voiceStreamFailure; GOTO Fail; }; }; key: Thrush.EncryptionKey; tune: Jukebox.Tune; tuneID: Jukebox.TuneID; intervalSpec: Jukebox.IntervalSpec _ NEW[Jukebox.IntervalSpecBody]; cDesc: BluejaySmarts.ConvDesc; ir: BluejaySmarts.IntervalReq; vrinfo: VoiceRopeDB.VoiceRopeInfo; <> [nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID]; IF nb # $success THEN RETURN; <> IF beep = NIL THEN { [voiceRope: beep] _ GetByInterest[shhh: shhh, class: "SysNoises", refID: "BeepTune"]; IF beep = NIL THEN VoiceUtils.Problem["Can't find beep tune to prompt recording", $VoiceRope]; }; <> TRUSTED { tune _ Jukebox.CreateTune[RecordingServiceRegister.jukebox, -1]; tuneID _ intervalSpec.tuneID _ tune.tuneId; Jukebox.CloseTune[RecordingServiceRegister.jukebox, tune]; }; <> <> IF playBeep AND beep#NIL THEN [] _ Play[shhh: shhh, voiceRope: beep, credentials: credentials, serviceID: serviceID, queueIt: queueIt] ELSE IF NOT queueIt THEN TRUSTED { VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously }; ir _ NEW[BluejaySmarts.IntervalReqBody _ [ iSpec: intervalSpec^, direction: $record, cDesc: cDesc, requestingParty: credentials.partyID, intID: intID, firstInterval: TRUE, lastInterval: TRUE]]; [nb] _ BluejaySmarts.SetInterval[ir]; IF nb # $success THEN RETURN; intervalSpec.length _ ir.iSpec.length; IF cDesc.keyTable # NIL THEN key _ cDesc.keyTable[1]; TRUSTED { VoiceStream.AddPiece[cDesc.info.stream, intervalSpec, $record, ir]; }; <> <> vrinfo.struct _ VoiceRopeDB.SimpleTuneList[tune: tuneID, start: 0, length: -1, key: key]; vrinfo.creator _ RPC.GetCaller[shhh]; vrinfo _ VoiceRopeDB.WriteVoiceRope[handle: RecordingServiceRegister.database, vr: vrinfo]; voiceRope _ NEW[VoiceRopeInterval _ [ropeID: vrinfo.vrID, start: 0, length: vrinfo.length]]; EXITS Fail => RETURN[nb, NIL]; }; Play: PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, voiceRope: VoiceRope, credentials: Thrush.Credentials, serviceID: RefID.ID, intID: CARD _ 0, queueIt: BOOL _ TRUE] RETURNS [nb: Thrush.NB _ $success] = { <> <<{ $recording/$playback, $started, intID } as the interval begins playing/recording.>> <<{ $recording/$playback, $scheduled, intID } as the interval is accepted for recording/playback, if there is already a list of pieces in the queue ahead of this request.>> <<{ $recording/$playback, $finished, intID } when the interval has finished.>> <<{ $recording/$playback, $abandoned, intID } when all recording and playback actions ending with this interval has been flushed.>> ENABLE { VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; GOTO Fail; }; Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => { VoiceUtils.Problem["Error detected in Bluejay", $Bluejay]; nb _ $voiceStreamFailure; GOTO Fail; }; }; key: Thrush.EncryptionKey; cDesc: BluejaySmarts.ConvDesc; ir: BluejaySmarts.IntervalReq; info: VoiceRopeDB.VoiceRopeInfo; intervalSpec: Jukebox.IntervalSpec; playQueue, last: Jukebox.IntervalSpecs; [nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID]; IF nb # $success THEN RETURN; IF NOT queueIt THEN TRUSTED { VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously }; info _ ReadVoiceRope[voiceRope]; IF info.struct=NIL THEN RETURN[nb: $noSuchVoiceRope]; <> IF info.playAccess # NIL THEN { caller: ROPE _ RPC.GetCaller[shhh]; IF NOT CheckPermission[caller, info.playAccess] THEN RETURN[nb: $noPermission]; }; <> UNTIL info.struct=NIL DO intervalSpec _ NEW[Jukebox.IntervalSpecBody]; [intervalSpec.tuneID, intervalSpec.start, intervalSpec.length, key, info.struct] _ VoiceRopeDB.NextTuneOnList[info.struct]; IF last = NIL THEN last _ playQueue _ LIST[intervalSpec] ELSE { last.rest _ LIST[intervalSpec]; last _ last.rest; }; [nb, intervalSpec.keyIndex] _ BluejaySmarts.DistributeKey[cDesc: cDesc, key: key, wait: info.struct=NIL]; SELECT nb FROM $success, $newKeys => NULL; ENDCASE => GOTO Fail; ENDLOOP; <> FOR q: Jukebox.IntervalSpecs _ playQueue, q.rest WHILE q#NIL DO ir _ NEW[BluejaySmarts.IntervalReqBody _ [ iSpec: q.first^, direction: $play, cDesc: cDesc, requestingParty: credentials.partyID, intID: intID, firstInterval: q=playQueue, lastInterval: q.rest=NIL]]; [nb] _ BluejaySmarts.SetInterval[ir]; IF nb # $success THEN RETURN; q.first.length _ ir.iSpec.length; TRUSTED { VoiceStream.AddPiece[cDesc.info.stream, q.first, $play, ir]; }; ENDLOOP; EXITS Fail => RETURN[nb]; }; Stop: PUBLIC PROC[shhh: Thrush.SHHH _ Thrush.none, credentials: Thrush.Credentials, serviceID: RefID.ID] RETURNS [nb: Thrush.NB _ $success] = { <> <> <<{ $recording, $abandoned, intID } as indicated above.>> ENABLE { Jukebox.Error, Jukebox.MissingChirp, Jukebox.EOF => { VoiceUtils.Problem["Error detected in Bluejay", $Bluejay]; nb _ $voiceStreamFailure; GOTO Fail; }; }; cDesc: BluejaySmarts.ConvDesc; [nb, cDesc] _ BluejaySmarts.GetConversation[smartsID: serviceID, conv: credentials.convID]; IF nb # $success THEN RETURN; TRUSTED { VoiceStream.FlushPieces[cDesc.info.stream]; -- Will report, synchronously }; EXITS Fail => RETURN[nb]; }; <> <> Retain: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, class: InterestClass, refID: Rope.ROPE, other: Rope.ROPE _ NIL] RETURNS [nb: Thrush.NB _ $success] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; info: VoiceRopeDB.InterestInfo; info.vrID _ vr.ropeID; info.class _ class; info. refID _ refID; info.data _ other; [] _ VoiceRopeDB.AddInterest[RecordingServiceRegister.database, info]; }; Forget: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, class: InterestClass, refID: Rope.ROPE] RETURNS [nb: Thrush.NB _ $success] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; info: VoiceRopeDB.InterestInfo; info.vrID _ vr.ropeID; info.class _ class; info. refID _ refID; VoiceRopeDB.DropInterest[RecordingServiceRegister.database, info]; }; GetByInterest: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, class: InterestClass, refID: Rope.ROPE] RETURNS [nb: Thrush.NB _ $success, voiceRope: VoiceRope] ~ { <> <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; interest: VoiceRopeDB.Interest; info: VoiceRopeDB.InterestInfo; interest _ VoiceRopeDB.InterestForRef[RecordingServiceRegister.database, class, refID]; IF interest = NIL THEN RETURN[nb: $noSuchVoiceRope, voiceRope: NIL]; info _ VoiceRopeDB.UnpackInterest[interest]; voiceRope _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: -1]]; }; <> Cat: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr1, vr2, vr3, vr4, vr5: VoiceRope _ NIL] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; piece, info: VoiceRopeDB.VoiceRopeInfo; caller: ROPE _ RPC.GetCaller[shhh]; info.struct _ NIL; info.length _ 0; IF vr1 # NIL THEN { piece _ ReadVoiceRope[vr: vr1, unpackHeader: TRUE]; IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ piece.struct; info.length _ piece.length; }; IF vr2 # NIL THEN { piece _ ReadVoiceRope[vr: vr2, unpackHeader: TRUE]; IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ CatEntries[info.struct, piece.struct]; info.length _ info.length + piece.length; }; IF vr3 # NIL THEN { piece _ ReadVoiceRope[vr: vr3, unpackHeader: TRUE]; IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ CatEntries[info.struct, piece.struct]; info.length _ info.length + piece.length; }; IF vr4 # NIL THEN { piece _ ReadVoiceRope[vr: vr4, unpackHeader: TRUE]; IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ CatEntries[info.struct, piece.struct]; info.length _ info.length + piece.length; }; IF vr5 # NIL THEN { piece _ ReadVoiceRope[vr: vr5, unpackHeader: TRUE]; IF piece.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF piece.editAccess#NIL AND NOT CheckPermission[caller, piece.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ CatEntries[info.struct, piece.struct]; info.length _ info.length + piece.length; }; IF info.struct = NIL THEN RETURN[new: NIL]; -- nb=$success info.creator _ caller; info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info]; new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]]; }; <<>> Substr: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT _ 0, len: INT _ LAST[INT]] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; info: VoiceRopeDB.VoiceRopeInfo; caller: ROPE _ RPC.GetCaller[shhh]; info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE]; IF info.struct=NIL THEN RETURN[nb: $noSuchVoiceRope, new: NIL]; IF info.editAccess#NIL AND NOT CheckPermission[caller, info.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; <> start _ start - (start MOD 8); -- round down to multiple of 8 len _ IF len<0 OR len=LAST[INT] THEN -1 ELSE len - (len MOD 8); info.struct _ VoiceRopeDB.TuneListInterval[info.struct, start, len]; IF len # -1 AND start+len < info.length THEN info.length _ len ELSE info.length _ info.length - start; info.creator _ caller; info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info]; new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]]; }; <<>> Replace: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT _ 0, len: INT _ LAST[INT], with: VoiceRope _ NIL] RETURNS [nb: Thrush.NB _ $success, new: VoiceRope] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; baseInfo, withInfo, info: VoiceRopeDB.VoiceRopeInfo; caller: ROPE _ RPC.GetCaller[shhh]; baseInfo _ ReadVoiceRope[vr: vr, unpackHeader: TRUE]; withInfo _ ReadVoiceRope[vr: with, unpackHeader: TRUE]; IF (baseInfo.struct=NIL) OR ((with#NIL) AND (withInfo.struct=NIL)) THEN RETURN [nb: $noSuchVoiceRope, new: NIL]; IF baseInfo.editAccess#NIL AND NOT CheckPermission[caller, baseInfo.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; IF with#NIL AND withInfo.editAccess#NIL AND NOT CheckPermission[caller, withInfo.editAccess] THEN RETURN[nb: $noPermission, new: NIL]; info.struct _ NIL; info.length _ 0; IF start # 0 THEN { <> start _ start - (start MOD 8); -- round down to multiple of 8 info.struct _ VoiceRopeDB.TuneListInterval[CopyEntry[baseInfo.struct], 0, start]; info.length _ start; }; info.struct _ CatEntries[info.struct, withInfo.struct]; info.length _ info.length + withInfo.length; IF len # -1 AND start+len < baseInfo.length THEN { <> len _ len - (len MOD 8); -- round down to multiple of 8 info.struct _ CatEntries[info.struct, VoiceRopeDB.TuneListInterval[baseInfo.struct, start+len, LAST[INT]]]; info.length _ info.length + baseInfo.length - (start+len); }; info.creator _ caller; info _ VoiceRopeDB.WriteVoiceRope[RecordingServiceRegister.database, info]; new _ NEW[VoiceRopeInterval _ [ropeID: info.vrID, start: 0, length: info.length]]; }; <<>> Length: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope] RETURNS [nb: Thrush.NB _ $success, len: INT] ~ { <> ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; info: VoiceRopeDB.VoiceRopeInfo; vr.start _ 0; vr.length _ -1; info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE]; IF info.struct = NIL THEN nb _ $noSuchVoiceRope; len _ info.length; }; <<>> <> DescribeRope: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, minSilence: INT _ -1] RETURNS [nb: Thrush.NB _ $success, length: INT _ 0, noise: IntervalSpecs _ NIL] ~ { ENABLE VoiceRopeDB.Error => { VoiceUtils.Problem[explanation, $VoiceRope]; nb _ ec; CONTINUE; }; struct: VoiceRopeDB.TuneList; tuneSpec: Jukebox.IntervalSpec _ NEW[Jukebox.IntervalSpecBody]; tuneNoises: Jukebox.IntervalSpecs; tuneExists: BOOLEAN; noiseEnd: IntervalSpecs _ NIL; -- last item on current list of noise intervals samples: INT _ 0; struct _ ReadVoiceRope[vr].info.struct; IF struct = NIL THEN nb _ $noSuchVoiceRope; UNTIL struct=NIL DO -- find noises in each tune segment [tune: tuneSpec.tuneID, start: tuneSpec.start, length: tuneSpec.length, rest: struct] _ VoiceRopeDB.NextTuneOnList[struct]; [tuneExists, tuneNoises] _ BluejayUtils.DescribeInterval[intervalSpec: tuneSpec, minSilence: minSilence]; IF NOT tuneExists THEN { VoiceUtils.Problem[Rope.Cat["Nonexistent tune in voice rope: ", vr.ropeID], $VoiceRope]; RETURN[nb: $badVoiceRope, length: -1, noise: NIL]; }; UNTIL tuneNoises = NIL DO IF noiseEnd = NIL THEN { noise _ LIST[[start: samples + tuneNoises.first.start - tuneSpec.start, length: tuneNoises.first.length]]; noiseEnd _ noise; } ELSE { noiseEnd.rest _ LIST[[start: samples + tuneNoises.first.start - tuneSpec.start, length: tuneNoises.first.length]]; noiseEnd _ noiseEnd.rest; }; tuneNoises _ tuneNoises.rest; ENDLOOP; samples _ samples + tuneSpec.length; ENDLOOP; }; <<>> GetEnergies: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, samplesPerSegment: [1..8000] _ 160] RETURNS [nb: Thrush.NB, energies: EnergySequence] ~ { <> RETURN[$notYetImplemented, NIL]; }; <<>> FetchBlock: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, start: INT, len: INT _ 8000, decrypt: BOOLEAN _ TRUE] RETURNS [nb: Thrush.NB, block: VoiceBlock] ~ { <> RETURN[$notYetImplemented, NIL]; }; StoreBlock: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, block: VoiceBlock, key: Thrush.EncryptionKey _ Thrush.nullKey] RETURNS [nb: Thrush.NB, voiceRope: VoiceRope] ~ { <> RETURN[$notYetImplemented, NIL]; }; <> SetPermissions: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope, playAccess: Users, editAccess: Users] RETURNS [nb: Thrush.NB] ~ { <> <> info: VoiceRopeDB.VoiceRopeInfo; caller: ROPE _ RPC.GetCaller[shhh]; IF NOT Rope.Equal[caller, info.creator, FALSE] THEN RETURN[nb: $noPermission]; vr.start _ 0; vr.length _ -1; info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE]; IF info.struct = NIL THEN RETURN[nb: $noSuchVoiceRope]; info.playAccess _ playAccess; info.editAccess _ editAccess; info _ VoiceRopeDB.WriteVoiceRope[handle: RecordingServiceRegister.database, vr: info, replace: TRUE]; RETURN[nb: $success]; }; GetPermissions: PUBLIC PROC [shhh: Thrush.SHHH _ Thrush.none, vr: VoiceRope] RETURNS [nb: Thrush.NB, playAccess: Users, editAccess: Users] ~ { <> info: VoiceRopeDB.VoiceRopeInfo; info _ ReadVoiceRope[vr: vr, unpackHeader: TRUE]; IF info.struct = NIL THEN RETURN[nb: $noSuchVoiceRope, playAccess: NIL, editAccess: NIL]; RETURN[nb: $success, playAccess: info.playAccess, editAccess: info.editAccess]; }; CheckPermission: PROC [name: ROPE, acl: Users] RETURNS [OK: BOOLEAN] ~ { FOR l: Users _ acl, l.rest WHILE l#NIL DO IF Rope.Equal[l.first, name, FALSE] THEN RETURN[OK: TRUE]; ENDLOOP; FOR l: Users _ acl, l.rest WHILE l#NIL DO IF Rope.Find[l.first, "^"]#-1 AND GVNames.IsMemberUpArrow[l.first, name]=yes THEN RETURN[OK: TRUE]; ENDLOOP; RETURN[OK: FALSE]; }; <> ReadVoiceRope: PROC [vr: VoiceRope, unpackHeader: BOOLEAN _ FALSE] RETURNS [info: VoiceRopeDB.VoiceRopeInfo] ~ { header: VoiceRopeDB.Header; tune: VoiceRopeDB.TuneID; start, length: INT; key: Thrush.EncryptionKey; rest: VoiceRopeDB.TuneList; tuneExists: BOOLEAN; wantSubinterval: BOOLEAN; IF vr = NIL THEN {info.length _ 0; RETURN}; <> [header, info.struct] _ VoiceRopeDB.ReadVoiceRope[RecordingServiceRegister.database, vr.ropeID]; IF header = NIL THEN RETURN; <> wantSubinterval _ NOT (vr.start = 0 AND vr.length < 0); IF unpackHeader OR wantSubinterval THEN { info _ VoiceRopeDB.UnpackHeader[header]; IF info.length < 0 THEN { -- must determine the actual tune length [tune, start, length, key, rest] _ VoiceRopeDB.NextTuneOnList[info.struct]; <> IF NOT (rest=NIL AND length<0) THEN { VoiceUtils.Problem[Rope.Cat["Negative length field for voice rope: ", vr.ropeID], $VoiceRope]; info.struct _ NIL; RETURN; }; [tuneExists, info.length] _ BluejayUtils.DescribeTune[tune]; IF NOT tuneExists THEN { VoiceUtils.Problem[Rope.Cat["Nonexistent tune in voice rope: ", vr.ropeID], $VoiceRope]; info.struct _ NIL; RETURN; }; <> info.length _ info.length * 8000 - start; info.struct _ VoiceRopeDB.SimpleTuneList[tune, start, info.length, key]; }; wantSubinterval _ wantSubinterval AND NOT (vr.start = 0 AND vr.length >= info.length); }; <> IF wantSubinterval THEN { <> vr.start _ vr.start - (vr.start MOD 8); -- round down to multiple of 8 vr.length _ IF vr.length<0 THEN -1 ELSE vr.length - (vr.length MOD 8); info.struct _ VoiceRopeDB.TuneListInterval[info.struct, vr.start, vr.length]; info.length _ info.length - vr.start; IF vr.length > 0 THEN info.length _ MIN[info.length, vr.length]; }; }; CatEntries: PROC [e1, e2: VoiceRopeDB.TuneList] RETURNS [VoiceRopeDB.TuneList] ~ { <> ptr: VoiceRopeDB.TuneList _ e1; IF ptr = NIL THEN RETURN[e2]; UNTIL ptr.rest = NIL DO ptr _ ptr.rest; ENDLOOP; ptr.rest _ e2; RETURN[e1]; }; CopyEntry: PROC [entry: VoiceRopeDB.TuneList] RETURNS [VoiceRopeDB.TuneList] ~ { <> new, end: VoiceRopeDB.TuneList; IF entry = NIL THEN RETURN[NIL]; new _ LIST[entry.first]; end _ new; FOR e: VoiceRopeDB.TuneList _ entry.rest, e.rest WHILE e # NIL DO end.rest _ LIST[e.first]; end _ end.rest; ENDLOOP; RETURN[new]; }; END. <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>>