DIRECTORY ImageDefs USING [AddCleanupProcedure, CleanupItem, CleanupProcedure, CleanupMask], InlineDefs USING [BITSHIFT, HighHalf, LowHalf], IODefs USING [WriteLine, WriteNumber, WriteString], Mopcodes USING [zMISC], MusicDefs, MusicHardwareDefs, Note USING [Duration, GetBackTie], Piece USING [AddSync, NearestSync], ProcessDefs USING [CV, DisableInterrupts, EnableInterrupts, GetPriority, InterruptLevel, Priority, SetPriority, SetTimeout], Real USING [Fix], Sync USING [AddNote], SystemDefs USING [AllocateSegment], Score USING [AddToPitch, GetKey, GetMetrenome], Screen USING [DisplayMessage, InvertListen, InvertPlay], Utility USING [NewNote, NewSync], Voice USING [ClearState, SetState, State, StatePTR]; MusicProcess: CEDAR MONITOR IMPORTS ImageDefs, InlineDefs, IODefs, MusicDefs, Note, Piece, ProcessDefs, Real, Sync, SystemDefs, Score, Screen, Utility, Voice EXPORTS MusicDefs, Score = BEGIN OPEN MusicDefs, MusicHardwareDefs; listen: CONDITION; -- microcode signals whenever a buffer is ready for listenProcess play: CONDITION; -- microcode signals whenever it is through with a buffer listenProcess, playProcess: PROCESS; scoreIN: PiecePTR _ NIL; scoreOUT: PiecePTR _ NIL; playPhysical: BOOL _ FALSE; offset: LONG CARDINAL; -- since we can't set musicIO.time, we use an offset to start in the middle of a piece. meter: INTEGER; -- speed of play (halftime, normal, doubletime). NOT metrenome! cleanup: ImageDefs.CleanupItem; InitializeSynthesizer: PUBLIC PROC = { originalLevel: ProcessDefs.Priority _ ProcessDefs.GetPriority[]; cleanup _ [NIL, ImageDefs.CleanupMask[Finish] +ImageDefs.CleanupMask[Save] +ImageDefs.CleanupMask[Restore] +ImageDefs.CleanupMask[Checkpoint] +ImageDefs.CleanupMask[Restart], CleanupMicrocode]; ImageDefs.AddCleanupProcedure[@cleanup]; InitMicrocode[]; ProcessDefs.SetTimeout[@play, 1]; ProcessDefs.SetPriority[2]; listenProcess _ FORK ProcessBuffer; playProcess _ FORK ProcessScore; ProcessDefs.SetPriority[originalLevel]; }; StartListening: PUBLIC PROC[s: PiecePTR]= { IF playing THEN RETURN; meter _ 128; sync _ NIL; offset _0; IF debugInput THEN IODefs.WriteLine["offset reset--input"]; counter _ 0; scoreIN _ s; setOffset _ TRUE; partial _ ALL[[pressed, 0, 0]]; keyboardIN _ ALL[[0,[up, up, up, up, up, up]]]; StartMicrocode[listening]; }; StopListening: PUBLIC ENTRY PROC = { StopMicrocode[listening]; NOTIFY listen; }; StartPlaying: PUBLIC ENTRY PROC[s: PiecePTR, first: CARDINAL, physical: BOOL, displayCursor: PROC[time: LONG CARDINAL]]= { IF listening AND (scoreIN[0]#NIL OR ~Empty[INbuffer]) THEN RETURN; IF ~physical THEN StopMicrocode[listening]; pendingArray _ ALL[[NIL, NIL, 0, up, FALSE, 0]]; pending _ NIL; scoreOUT _ s; in _ 0; -- in and out are used by the cursor displayer out _ 1; start _ first; setOffset _ TRUE; SELECT TRUE FROM BlueBug[] => meter _ 256; RedBug[] => meter _ 64; ENDCASE => meter _ 128; playPhysical _ physical; DisplayCursor _ displayCursor; StartMicrocode[playing]; IF ~microcode THEN RealStopMicrocode[]; NOTIFY play; --you fool! you forgot to loadmb music.mb! }; StopPlaying: PUBLIC PROC = { StopMicrocode[playing]; IF listening AND scoreIN[0]=NIL AND Empty[INbuffer] THEN StopListening[]; }; Empty: PROC[b: EventBufferPTR] RETURNS[BOOL] = { IF b=NIL THEN RETURN[TRUE]; FOR i: CARDINAL IN [0..b.length) DO IF b.data[i].htoc<100 THEN LOOP; -- transient garbage RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; DisplayCursor: PROC[time: LONG CARDINAL]; setOffset: BOOL _ FALSE; -- so we know when to determine the offset counter: CARDINAL; -- number of times the microcode clock has wrapped around ProcessBuffer: ENTRY PROC = { c, channel: CARDINAL _ 0; compare: ChannelWord; event: MusicEvent; DO --wait until a buffer is ready and then process it WHILE ~listening AND INbuffer.length=0 DO WAIT listen; ENDLOOP; WHILE INbuffer = musicIO.headOfInputList DO WAIT listen; ENDLOOP; IF ~microcode THEN RealStopMicrocode[]; IF debugInput THEN WriteBuffer[INbuffer]; FOR c IN [0..INbuffer.length) DO INbuffer.data[c].channelWord.channel _ --! hack to work around bug in the microcode SELECT INbuffer.data[c].channelWord.channel FROM 12 => 0, 15 => 15, ENDCASE => INbuffer.data[c].channelWord.channel + 1; event _ INbuffer.data[c]; channel _ event.channelWord.channel; IF channel = 15 THEN counter _ counter + 1; IF channel > 12 THEN LOOP; IF counter=0 AND event.htoc < 100 THEN LOOP; --transient garbage compare _ keyboardIN[channel]; ExtractTransitions[compare, event.channelWord, event.htoc]; keyboardIN[channel] _ event.channelWord; ENDLOOP; IF ~listening THEN FOR i: CARDINAL IN [0..12] DO IF keyboardIN[i].key = [up, up, up, up, up, up] THEN LOOP; compare _ keyboardIN[i]; event.channelWord _ [i,[up, up, up, up, up, up]]; event.htoc _ MAX[event.htoc, InlineDefs.LowHalf[musicIO.time]]; ExtractTransitions[compare, event.channelWord, event.htoc]; keyboardIN[channel] _ event.channelWord; ENDLOOP; GetNextInputBuffer[]; ENDLOOP; }; ExtractTransitions: INTERNAL PROC[x, y: ChannelWord, time: CARDINAL]= { OPEN InlineDefs; delta, pitch: INTEGER; channel: CARDINAL=y.channel; transition: RECORD[a, b, c: KeyState]; e: MusicEvent _ [LOOPHOLE[time], counter]; toc: LONG CARDINAL _ LOOPHOLE[e]; toc _ Scale[toc, meter, 128]+offset; IF setOffset THEN {setOffset _ FALSE; offset _ 500-toc; toc _ 500; IF debugInput THEN {WriteLong[offset]; IODefs.WriteLine[" (offset)"]}}; FOR i: CARDINAL IN [0..6) DO IF x.key[i]=y.key[i] THEN LOOP; pitch _ Pitch[channel, i]; delta _ InlineDefs.LowHalf[(toc-partial[pitch].toc)]; IF delta<0 THEN delta _ 32000; transition _ [partial[pitch].last, x.key[i], y.key[i]]; SELECT transition FROM [pressed, up, pressed] => partial[pitch].toc_ toc; [up, pressed, up] => partial[pitch].toc_ 0; -- ignore transient bounce [up, pressed, down] => NULL; --partial[pitch].loudness_ delta; [down, pressed, down] => NULL; [pressed, down, pressed] => partial[pitch].duration_ delta; [pressed, down, up] => {partial[pitch].duration_ delta; AddNote[pitch]}; [down, pressed, up] => AddNote[pitch]; ENDCASE => Error[]; partial[pitch].last _ x.key[i]; ENDLOOP; }; AddNote: INTERNAL PROC[pitch: CARDINAL] = { note: NotePTR; IF partial[pitch].toc=0 THEN RETURN; IF partial[pitch].duration=0 THEN Error[]; note _ Utility.NewNote[]; note^ _ []; --defaults to the values listed in MusicDefs note.pitch _ pitch; note.toc _ partial[pitch].toc; note.duration _ partial[pitch].duration; SELECT TRUE FROM note.pitch IN [79..92] => { note.stemUp _ FALSE; note.staff _ 0; }; note.pitch IN [68..78] => { note.stemUp_ TRUE; note.staff _ 0; }; note.pitch IN [55..67] => { note.stemUp _ FALSE; note.staff _ 1; }; note.pitch IN [44..54] => { note.stemUp_ TRUE; note.staff _ 1; }; note.pitch IN [34..43] => { note.stemUp _ FALSE; note.staff _ 2; }; note.pitch IN [22..33] => { note.stemUp_ TRUE; note.staff _ 2; }; note.pitch IN [12..21] => { note.stemUp _ FALSE; note.staff _ 3; }; note.pitch IN [5..11] => { note.stemUp_ TRUE; note.staff _ 3; }; ENDCASE; IF sync=NIL OR ABS[note.toc - sync.time*MusicDefs.TF] > 200 THEN sync _ scoreIN[Piece.NearestSync[scoreIN, note.toc/MusicDefs.TF, TRUE]]; IF sync=NIL OR ABS[note.toc - sync.time*MusicDefs.TF] > 200 THEN { sync _ Utility.NewSync[]; sync.type _ notes; sync.time _ note.toc/MusicDefs.TF; Piece.AddSync[scoreIN, sync]; }; Sync.AddNote[sync, note]; partial[pitch].toc _ 0; partial[pitch].duration _ 0; }; sync: SyncPTR _ NIL; Pitch: PROC[channel, note: CARDINAL] RETURNS[CARDINAL]= INLINE { RETURN[channel*6 + note+12]; }; partial: ARRAY [0..100) OF RECORD[last: KeyState, toc: LONG CARDINAL, duration: CARDINAL--, loudness: CARDINAL--]; keyboardIN: ARRAY [0..12] OF ChannelWord; start: CARDINAL _ 0; ProcessScore: PROC = { endOfTime: LONG CARDINAL _ 0; endOfTime _ endOfTime-1; -- wraps around to largest long cardinal trill _InlineDefs.LowHalf[Scale[512, 128, 70]]; trillDur _(3*trill)/4; grace _InlineDefs.LowHalf[Scale[300, 128, 70]]; graceDur _(3*grace)/2; DO -- forever WHILE NOT playing DO WaitOnPlay[]; ENDLOOP; musicIO.time _ 0; OUTbuffer.data[0] _ reStart; OUTbuffer.length _ 1; lastTOC _ 0; InitKeyboardOUT[]; IF debugOutput THEN IODefs.WriteLine["processing score"]; IF playPhysical THEN TranslatePhysical[] ELSE TranslateLogical[]; IF debugOutput THEN IODefs.WriteLine["pending array"]; FlushPending[endOfTime]; -- flushes everything GetNextOutputBuffer[]; -- has side effect of writing current buffer WHILE playing DO -- wait for microcode to finish IF musicIO.headOfOutputList=NIL THEN StopPlaying[] ELSE WaitOnPlay[]; ENDLOOP; ENDLOOP; }; TranslatePhysical: PROC = { n: NotePTR; sync: SyncPTR; time, maxTime, lastFlush, duration: LONG CARDINAL _ 0; FOR i: CARDINAL IN [start..maxScoreLength) DO IF NOT playing THEN EXIT; IF (sync _ scoreOUT[i])=NIL THEN EXIT; FOR j: CARDINAL IN [0..syncLength) DO IF (n _ sync.event[j])=NIL THEN EXIT; IF n.rest THEN LOOP; IF n.tie#NIL AND n.tie.pitch=n.pitch THEN LOOP; -- skip the second note of a tied pair IF voice AND n.voice#selectedVoice THEN LOOP; IF n.toc=0 THEN time _ n.sync.time*TF ELSE time _ n.toc; IF n.duration=0 THEN duration _ Duration[n, 128] ELSE duration _ n.duration; maxTime _ MAX[maxTime, time]; ProcessNote[MAX[time, lastFlush], duration, n.pitch, loudness] ENDLOOP; IF maxTime>1000 THEN lastFlush _ maxTime-1000; FlushPending[lastFlush]; ENDLOOP; }; TranslateLogical: PROC = { i, j: CARDINAL; n: NotePTR _ NIL; vs: Voice.State; sync: SyncPTR _ NIL; now, delta, toc, max: Time _ 0; lastDuration, lastNow, lastToc: Time _ 0; pitch, metrenome, count: INTEGER _ 0; isLogicalSync, wasLogicalSync, nonGraceSync: BOOL _ TRUE; metrenome _ Score.GetMetrenome[scoreOUT[start].time]; index _ [0, 0]; Voice.ClearState[@vs]; FOR i: CARDINAL IN [0..maxVoice] DO vs[i].sum _ 10*trill; ENDLOOP; FOR i IN [start..maxScoreLength) DO IF NOT playing THEN EXIT; IF (sync _ score[i])=NIL THEN LOOP; IF sync.type=repeat1 THEN PushRepeat[i, stack1]; -- encountered a left-paren repeat IF sync.type=repeat2 THEN { -- encountered a right-paren repeat IF TopRepeat[stack2]=i THEN -- we've repeated once, don't repeat again. { [] _ PopRepeat[stack2]; LOOP; }; IF NoRepeat[stack1] THEN LOOP; PushRepeat[i, stack2]; -- save repeat so we won't repeat twice i _ PopRepeat[stack1]; -- go to beginning of the repeat }; IF sync.type=metrenome THEN metrenome _ sync.value; IF sync.type#notes THEN LOOP; isLogicalSync _ FALSE; toc _ 0; nonGraceSync _ FALSE; FOR j IN [0..syncLength) DO IF (n _ sync.event[j])=NIL THEN EXIT; toc _ MAX[toc, sync.event[j].toc]; IF ~n.grace THEN nonGraceSync _ TRUE; IF sync.event[j].value#unknown THEN isLogicalSync _ TRUE; ENDLOOP; max _ Voice.SetState[@vs, sync, metrenome]; IF wasLogicalSync AND isLogicalSync THEN IF max > now THEN now _ max ELSE now _ lastNow+lastDuration; IF wasLogicalSync AND ~isLogicalSync THEN { IF lastToc#0 THEN delta _ lastNow-lastToc ELSE delta_ (lastNow+lastDuration)-toc; now_ toc+delta}; IF ~wasLogicalSync AND ~isLogicalSync THEN now _ toc+delta; IF ~wasLogicalSync AND isLogicalSync THEN IF toc#0 AND toc+delta>now AND toc+delta { -- n.tie is tied back to a trill time: LONG CARDINAL _ toc; duration _ Note.Duration[n, metrenome]; ProcessNote[time, trillDur, n.pitch, loudness]; ProcessNote[time+trill, trillDur, Score.AddToPitch[key, n.pitch, 1], loudness]; time _ time+2*trill; duration _ duration-2*trill; WHILE duration>=2*trill DO ProcessNote[time, trillDur, n.pitch, loudness]; ProcessNote[time+trill, trillDur, Score.AddToPitch[key, n.pitch, 1], loudness]; time _ time+2*trill; duration _ duration-2*trill; ENDLOOP; }; n.embellish=mordent1 => { ProcessNote[toc-2*trill, trillDur, n.pitch, loudness]; ProcessNote[toc-trill, trillDur, Score.AddToPitch[key, n.pitch, 1], loudness]; ProcessNote[toc, duration, n.pitch, loudness]; }; n.embellish=mordent2 => { ProcessNote[toc-2*trill, trillDur, n.pitch, loudness]; ProcessNote[toc-trill, trillDur, Score.AddToPitch[key, n.pitch,-1], loudness]; ProcessNote[toc, duration, n.pitch, loudness]; }; n.grace => ProcessNote[toc, graceDur, n.pitch, loudness]; ENDCASE => ProcessNote[toc, duration, n.pitch, loudness]; }; Trilled: PROC[n: NotePTR] RETURNS[BOOL] = { WHILE n.tie#NIL DO IF n.tie.pitch#n.pitch THEN RETURN[FALSE]; IF (n _ n.tie).embellish=trill THEN RETURN[TRUE]; IF n.embellish#none THEN RETURN[FALSE]; ENDLOOP; RETURN[FALSE]; }; Duration: PROC[n: NotePTR, metrenome: INTEGER] RETURNS[INTEGER] = { l: REAL _ 1; tie: NotePTR; d: Time _ Note.Duration[n, metrenome]; IF n.grace THEN d _ grace; WHILE n.tied DO IF n.embellish=trill THEN EXIT; tie _ Note.GetBackTie[n]; IF n.pitch#tie.pitch THEN EXIT; d _ d + Note.Duration[tie, metrenome]; n _ tie; ENDLOOP; RETURN[InlineDefs.LowHalf[MIN[7*(d/8), 32000]]]; }; Scale: PROC[time, top, bottom: LONG CARDINAL] RETURNS[LONG CARDINAL] = { l: REAL _ 1; RETURN[Real.Fix[l*top*time/bottom]]; }; loudness: INTEGER _ 90; PushRepeat: PROC[i, s: CARDINAL] = { stack[s][index[s]] _ i; index[s] _ index[s] + 1; }; PopRepeat: PROC[s: CARDINAL] RETURNS[CARDINAL] = { IF NoRepeat[s] THEN Error; index[s] _ index[s] - 1; RETURN[stack[s][index[s]]]; }; TopRepeat: PROC[s: CARDINAL] RETURNS[CARDINAL] = { RETURN[IF NoRepeat[s] THEN 10000 ELSE stack[s][index[s]-1]]; }; NoRepeat: PROC[s: CARDINAL] RETURNS[BOOL] = { RETURN[index[s]=0] }; stack: ARRAY [1..2] OF ARRAY [0..10] OF CARDINAL; index: ARRAY [1..2] OF CARDINAL _ [0, 0]; stack1: CARDINAL = 1; stack2: CARDINAL = 2; WaitOnPlay: ENTRY PROC = { time: Time; sync: SyncPTR; WAIT play; WHILE dataStructureInFlux DO WAIT play; ENDLOOP; IF musicIO.headOfOutputList=NIL THEN RETURN; time _ Scale[musicIO.time, meter, 128]+offset; IF playPhysical THEN DisplayCursor[time] ELSE IF (sync _ GetSync[time])#NIL THEN DisplayCursor[sync.time]; }; Associate: PROC[time: LONG CARDINAL, sync: CARDINAL] = { IF list[in] = [time, sync] THEN RETURN; in _ (in+1) MOD listLength; list[in] _ [time, sync]; }; GetSync: PROC[time: LONG CARDINAL] RETURNS[SyncPTR] = { IF out = (in+1) MOD listLength THEN RETURN[NIL]; WHILE list[(out+1) MOD listLength].time87 DO pitch _ pitch-12; ENDLOOP; -- ditto IF pitch>72 THEN loudness _ (2*loudness)/3; -- adjust for dynamics of keyboard IF setOffset THEN {offset _ (IF time>500 THEN time-500 ELSE 0); setOffset _ FALSE; IF debugOutput THEN {WriteLong[offset]; IODefs.WriteLine[" (offset)"]}}; time _ Scale[(time-offset), 128, meter]; -- adjust for tempo (not metrenome!) duration _ Scale[duration, 128, meter]; duration _ MAX[duration, 2*loudness]; time _ MAX[time, lastTOC]; FOR temp _ pending, temp.next WHILE temp#NIL DO IF temp.pitch#pitch THEN LOOP; IF temp.toc