<> <> <> DIRECTORY Basics USING [UnsafeBlock, LongDiv, LongMult, LowHalf], FS USING [Error], PlayOps USING [PlayTuneProc, BeepProc], Process USING [MsecToTicks, Ticks, TicksToMsec], Rope USING [ROPE, ToRefText], RopeFile USING [Create], BasicTime USING [MicrosecondsToPulses, PulsesToMicroseconds] ; PlayUtils: PROGRAM IMPORTS Basics, FS, Process, Rope, RopeFile, BasicTime EXPORTS PlayOps = { <<-------------------------------------------------------------->> <> <<-------------------------------------------------------------->> Note: TYPE = MACHINE DEPENDENT RECORD [ SELECT OVERLAID * FROM initial => [ twelfths, octave, duration: CARDINAL, notify: BOOL_FALSE, pause: BOOL_FALSE, x:X_0], intermediate => [ cps: CARDINAL, usecs: Microseconds, ni: BOOL_FALSE, pi: BOOL_FALSE, y:X_0], final => [ freq, pulses: CARDINAL, ticks: Process.Ticks, nf: BOOL_FALSE, pf: BOOL_FALSE, z:X_0], ENDCASE ]; X: TYPE = [0..37777B]; NoteArray: TYPE = REF NoteArrayObject; NoteArrayObject: TYPE = RECORD [SEQUENCE size: CARDINAL OF Note]; Microseconds: TYPE = LONG CARDINAL; <<-------------------------------------------------------------->> <> <<-------------------------------------------------------------->> initFreq: CARDINAL = LAST[CARDINAL]; lastFreq: CARDINAL _ initFreq; notifyNext: BOOL_FALSE; pauseNext: BOOL_FALSE; <> <> <" is encountered, all subsequent notes are an octave higher; a "<" lowers all subsequent notes by an octave. Going up more than 3 octaves is not permitted (additional ">"s are ignored), and notes near the top of the highest octave may not be struck accurately.>> <> <> <> <> <" to give the note duration and/or "," for the lower-case implicit rest, where and are strings of digits representing milliseconds. The values will be constrained to the usual limits (e.g., will be forced between 16 and 1024 ms). Subsequent "*", "_", etc., have their usual effects.>> <> <> <> <> PlayString: PUBLIC PlayOps.PlayTuneProc = { ENABLE ABORTED => CONTINUE; rT: REF TEXT; IF music # NIL THEN { IF file THEN music _ RopeFile.Create[name: music, raw: FALSE ! FS.Error => CONTINUE]; rT _ Rope.ToRefText[base: music]; PlayBlock[musicBlock: [LOOPHOLE[@rT.text+SIZE[INTEGER]], 0, rT.length], random: random, beepProc: beepProc, chunkSize: chunkSize]; }; }; PlayBlock: PUBLIC PROCEDURE [ musicBlock: Basics.UnsafeBlock, random: BOOLEAN _ FALSE, beepProc: PlayOps.BeepProc, chunkSize: CARDINAL _ 75, quietFinish: BOOLEAN _ TRUE --wh: Window.Handle _ NIL--] = { initialOctave: CARDINAL = 8; initialDuration: CARDINAL = 128; -- approximately an eighth-note, as default initialBreak: CARDINAL = 48; -- lower-case note gap, 3/64 by default music: LONG POINTER TO PACKED ARRAY [0..0) OF CHARACTER _ musicBlock.base; note: CARDINAL _ 0; -- number of tones so far noteArray: NoteArray _ NEW[NoteArrayObject[chunkSize]]; scale: TYPE = CHARACTER ['A..'G]; twelfths: ARRAY scale OF CARDINAL = [12, 14, 3, 5, 7, 8, 10]; numberPtr: LONG POINTER TO CARDINAL _ NIL; -- where digits go, if anywhere number, numberMin, numberMax, card: CARDINAL _ 0; octave: CARDINAL _ initialOctave; duration: CARDINAL _ initialDuration; break: CARDINAL _ initialBreak; triplet: BOOLEAN _ FALSE; -- gets set to TRUE between parentheses freqA: LONG CARDINAL = 1760; -- highest octave we'll bother with slideStart: CARDINAL _ 0; -- index of first note of slide, if one is in progress slide: CARDINAL _ 0; -- length of current slide, if any, in 64th-notes root: LONG CARDINAL = 10595; -- 12th root of 2, times 10000 root96: ARRAY [0..8) OF LONG CARDINAL = -- 2^(n/96), times 10000, for slides [10000, 10072, 10145, 10219, 10293, 10368, 10443, 10518]; limits: ARRAY {min,max} OF ARRAY BOOLEAN OF RECORD [duration, break, octave: CARDINAL] = [ <> [[duration: 8, break: 2, octave: 1], -- normal [duration: 64, break: 24, octave: 2]], -- random <> [[duration: 1024, break: 384, octave: 256], -- normal [duration: 256, break: 96, octave: 16]] -- random ]; <> <> Frequency: PROCEDURE [key: Note, tweak: CARDINAL _ 0 -- 96ths -- ] RETURNS [CARDINAL] = { freq: LONG CARDINAL; twelfths: CARDINAL _ key.twelfths; octave: CARDINAL _ key.octave*32; IF octave = 0 OR twelfths = 0 THEN RETURN[twelfths]; <> freq _ freqA*LONG[32]; IF tweak >= 8 THEN twelfths _ twelfths + (tweak/8); THROUGH [0..twelfths/12) DO octave _ octave/2 ENDLOOP; THROUGH [0..twelfths MOD 12) DO freq _ (freq*root+LONG[5000])/LONG[10000] ENDLOOP; IF tweak # 0 THEN freq _ (freq*root96[tweak MOD 8]+LONG[5000])/LONG[10000]; RETURN[Basics.LongDiv[freq + LONG[octave/2], octave]]; }; ShipOneChunk: PROCEDURE [last: CARDINAL] RETURNS [CARDINAL] = { c: CARDINAL; maxNoteDuration: Microseconds = 1024000; FOR c IN [1..last] DO noteArray[c].cps _ Frequency[noteArray[c]]; noteArray[c].usecs _ Basics.LongMult[noteArray[c].duration, 1000]; IF c > 1 AND noteArray[c].cps = noteArray[c - 1].cps THEN { <> <> IF (noteArray[c].usecs _ noteArray[c].usecs + noteArray[c-1].usecs) > maxNoteDuration THEN { noteArray[c].usecs _ noteArray[c].usecs - maxNoteDuration; noteArray[c - 1].usecs _ maxNoteDuration; } ELSE noteArray[c - 1].usecs _ 0; }; ENDLOOP; PlayNotes [last, noteArray--, wh--]; RETURN[0]; }; DonePlaying: PROCEDURE = {beepProc[beepFreq: 0, beepTime: 0]; lastFreq _ initFreq}; Beep: PROCEDURE [note: Note] = { IF note.freq # lastFreq THEN beepProc[beepFreq: (lastFreq _ note.freq), beepTime: Process.TicksToMsec[note.ticks]+BasicTime.PulsesToMicroseconds[note.pulses]/1000, notify: note.notify, pause: note.pause]; }; PlayNotes: PROCEDURE [n: CARDINAL, notes: NoteArray--, wh: Window.Handle--] = { c: CARDINAL; tix: Process.Ticks; FOR c IN [1..n] DO -- convert usecs to ticks and pulses tix _ Process.MsecToTicks[Basics.LongDiv[notes[c].usecs, 1000]]; WHILE Basics.LongMult[Process.TicksToMsec[tix], 1000] > notes[c].usecs DO tix _ tix - 1; ENDLOOP; notes[c].pulses _ Basics.LowHalf [BasicTime.MicrosecondsToPulses [notes[c].usecs]]; notes[c].ticks _ tix; ENDLOOP; FOR c IN [1..n] DO -- play this batch of notes IF notes[c].pulses # 0 THEN { <> Beep [notes[c]]; }; ENDLOOP; }; NextNote: PROCEDURE [need: CARDINAL] = INLINE { IF slideStart # 0 AND note > slideStart THEN RETURN; -- looking for end of slide IF note >= chunkSize - need THEN note _ ShipOneChunk[note]; note _ note + 1; }; AddToSlide: PROCEDURE [incr: CARDINAL] = { slide _ MIN[chunkSize, slide + incr/limits[min][FALSE].duration]; IF slideStart - 1 + slide > chunkSize THEN { -- slide won't fit, empty the buffer [] _ ShipOneChunk[slideStart - 1]; noteArray[1] _ noteArray[slideStart]; noteArray[2] _ noteArray[slideStart + 1]; note _ note - (slideStart - 1); slideStart _ 1; }; }; SetNotify: PROC = { IF notifyNext THEN noteArray[note].notify _ TRUE; IF pauseNext THEN noteArray[note].pause _ TRUE; notifyNext _ FALSE; pauseNext _ FALSE; }; { ENABLE UNWIND => CONTINUE; IF music # NIL THEN <> FOR card _ musicBlock.startIndex, card_card+1 UNTIL card=musicBlock.count DO IF numberPtr # NIL AND music[card] NOT IN ['0..'9] THEN { numberPtr^ _ MIN[MAX[number, numberMin], numberMax]; numberPtr _ NIL}; SELECT music[card] FROM IN ['A..'G] => { NextNote[1]; IF slideStart # 0 THEN AddToSlide[noteArray[note].duration]; noteArray[note] _ [ initial[ twelfths[music[card]], octave, IF triplet THEN (duration*2 + 1)/3 ELSE duration]]; SetNotify[]; }; IN ['a..'g] => { <<-- this is where the "slop" mentioned for brackets comes in>> NextNote[2]; IF slideStart # 0 THEN AddToSlide[noteArray[note].duration]; noteArray[note] _ [ initial[ twelfths[music[card] + ('A - 'a)], octave, MAX[(IF triplet THEN (duration*2 + 1)/3 ELSE duration), break] - break]]; SetNotify[]; IF slideStart = 0 THEN noteArray[note _ note + 1] _ [initial[0, 0, break]] ELSE noteArray[note].duration _ (IF triplet THEN (duration*2 + 1)/3 ELSE duration); }; '# => IF note > 0 THEN { -- sharps are one twelfth-octave higher IF noteArray[note].octave = 0 THEN -- octave 0 is really the "break" between notes noteArray[note - 1].twelfths _ noteArray[note - 1].twelfths + 1 ELSE noteArray[note].twelfths _ noteArray[note].twelfths + 1; }; '+ => IF note > 0 THEN { -- warning: you can exceed 2147 msecs using ***C++ IF noteArray[note].octave = 0 THEN -- octave 0 is the "break" between notes noteArray[note - 1].duration _ noteArray[note - 1].duration*3/2 + noteArray[note].duration/2 ELSE noteArray[note].duration _ noteArray[note].duration*3/2; }; '- => IF note > 0 THEN { IF noteArray[note].octave # 0 THEN -- octave 0 is the "break" between notes noteArray[note].duration _ noteArray[note].duration/2 ELSE IF noteArray[note - 1].duration > noteArray[note].duration THEN noteArray[note - 1].duration _ noteArray[note - 1].duration/2 - noteArray[note].duration/2 ELSE noteArray[note - 1].duration _ 0; }; '< => octave _ MIN[octave*2, limits[max][random].octave]; '> => octave _ MAX[octave/2, limits[min][random].octave]; '/ => duration _ MAX[duration/2, limits[min][random].duration]; '* => duration _ MIN[duration*2, limits[max][random].duration]; '_ => break _ MAX[break/2, limits[min][random].break]; '^ => break _ MIN[break*2, limits[max][random].break]; '(, ') => triplet _ (music[card] = '(); '% => { NextNote[1]; IF slideStart # 0 THEN AddToSlide[noteArray[note].duration]; noteArray[note] _ [ initial[ 0, octave, IF triplet THEN (duration*2 + 1)/3 ELSE duration]]; SetNotify[]; }; '{ => IF slideStart = 0 THEN { NextNote[3]; -- need room for starting and ending notes, plus slop (see above) slideStart _ note; slide _ noteArray[note].duration _ noteArray[note + 1].duration _ 0; note _ note - 1; -- haven't actually found starting note yet }; '} => IF slideStart # 0 THEN { IF note # slideStart + 1 OR noteArray[note].twelfths = 0 OR noteArray[note - 1].twelfths = 0 <> OR slide + noteArray[note - 1].duration + noteArray[note].duration < 2 THEN <> note _ slideStart - 1 -- ignore slide completely ELSE { diff: INTEGER; -- number of 96ths of an octave between the two ends temp, delta: CARDINAL; low: Note; AddToSlide[ noteArray[note - 1].duration + noteArray[note].duration]; diff _ (INTEGER[noteArray[note].twelfths] - INTEGER[noteArray[note - 1].twelfths])*8; temp _ noteArray[note].octave; DO SELECT noteArray[note - 1].octave FROM < temp => {temp _ temp/2; diff _ diff - 96}; > temp => {temp _ temp*2; diff _ diff + 96}; ENDCASE => EXIT; ENDLOOP; low _ noteArray[IF diff < 0 THEN note ELSE note - 1]; FOR temp IN [0..slide) DO delta _ Basics.LongDiv[ Basics.LongMult[ IF diff < 0 THEN (slide - 1 - temp) ELSE temp, ABS[diff]] + LONG[(slide - 1)/2], slide - 1]; noteArray[slideStart + temp] _ [ initial[Frequency[low, delta], 0, limits[min][FALSE].duration]]; SetNotify[]; ENDLOOP; note _ slideStart + slide - 1; }; slide _ slideStart _ 0; }; '; => { note _ ShipOneChunk[note]; slide _ slideStart _ 0; }; '. => IF NOT random THEN { note _ ShipOneChunk[note]; octave _ initialOctave; duration _ initialDuration; break _ initialBreak; triplet _ FALSE; slide _ slideStart _ 0; NextNote[1]; noteArray[note] _ [initial[0, octave, 1000]]; SetNotify[]; note _ ShipOneChunk[note]; }; '[ => notifyNext _ TRUE; '] => pauseNext _ TRUE; IN ['0..'9] => number _ number*10 + music[card] - '0; '@ => IF NOT random THEN { numberPtr _ @duration; numberMin _ limits[min][FALSE].duration; numberMax _ limits[max][FALSE].duration; number _ 0; }; ', => IF NOT random THEN { numberPtr _ @break; numberMin _ limits[min][FALSE].break; numberMax _ limits[max][FALSE].break; number _ 0; }; ENDCASE; ENDLOOP; IF note > 0 THEN [] _ ShipOneChunk[note]; IF quietFinish THEN DonePlaying []; noteArray _ NIL; }; }; }. <> <= bug , PlayUtils, PlayUtils>> <<>> <<>> <> <> <<>> <<>>