DIRECTORY FS USING [Error], IO, Real USING [ Round ], TuneParse USING [ Db, Hertz, Milliseconds, Tone, ToneList, ToneSpec, ToneSpecRec ], Rope ; TuneParseImpl: CEDAR PROGRAM IMPORTS Real, FS, IO, Rope EXPORTS TuneParse = { Db: TYPE ~ TuneParse.Db; -- These are only used in interfaces; they're all INTs. Hertz: TYPE ~ TuneParse.Hertz; Milliseconds: TYPE ~ TuneParse.Milliseconds; Note: TYPE = REF NoteRec; NoteRec: TYPE = RECORD [ twelfths, octave, duration, breakTime: INTฌ0, initialized: BOOLฌFALSE ]; initialOctave: INT = 8; initialDuration: INT = 128; -- approximately an eighth-note, as default initialBreak: INT = 48; -- lower-case note gap, 3/64 by default scale: TYPE = CHARACTER ['A..'G]; twelfths: ARRAY scale OF INT = [12, 14, 3, 5, 7, 8, 10]; root: REAL = 1.0595; -- 12th root of 2 root96: ARRAY [0..8) OF REAL = -- 2ญ(n/96), for slides [1.0000, 1.0072, 1.0145, 1.0219, 1.0293, 1.0368, 1.0443, 1.0518]; freqA: INT = 1760; -- highest octave we'll bother with ParseTune: PUBLIC PROC [tune: Rope.ROPE, volume: Db] RETURNS[ tones: TuneParse.ToneSpec] ~ { tune2: Rope.ROPE; pos: INT = Rope.Find[tune, "&"]; IF pos>=0 THEN { tune2 ฌ Rope.Substr[tune, pos+1]; tune ฌ Rope.Substr[tune, 0, pos]; }; tones ฌ ParseStr[tune, volume]; IF tune2.Length[]#0 THEN { tones2: TuneParse.ToneSpec = ParseStr[tune2, 0]; tones ฌ MergeToneSpecs[tones, tones2]; }; }; ParseStr: PROC [tune: Rope.ROPE, volume: INT] RETURNS[tones: TuneParse.ToneSpec ฌ NIL]= { mS: IO.STREAM; note: Note; octave: INT ฌ initialOctave; duration: INT ฌ initialDuration; break: INT ฌ initialBreak; triplet: BOOLEAN ฌ FALSE; -- gets set to TRUE between parentheses lastTone: TuneParse.ToneList ฌ NIL; tone: TuneParse.Tone ฌ [f1: 0, f2: 0, on: 0, off: 0]; inProgress: BOOLฌFALSE; repeatIndefinitely: BOOLฌTRUE; Frequency: PROCEDURE [key: Note, tweak: INT ฌ 0 -- 96ths -- ] RETURNS [freq: INT] = { freqR: REALฌ0.0; twelfths: INT ฌ key.twelfths; octave: INT ฌ key.octave*32; IF octave = 0 OR twelfths = 0 THEN RETURN[twelfths]; freqR ฌ freqA*32; IF tweak >= 8 THEN twelfths ฌ twelfths + (tweak/8); THROUGH [0..twelfths/12) DO octave ฌ octave/2 ENDLOOP; THROUGH [0..twelfths MOD 12) DO freqR ฌ freqR*root ENDLOOP; IF tweak # 0 THEN freqR ฌ freqR*root96[tweak MOD 8]; freqR ฌ (freqR+octave/2)/octave; freq ฌ Real.Round[freqR]; }; AddTone: PROC [freq: INT, duration: INT] = { IF (freq#0 AND (freq#tone.f1 OR tone.off#0)) OR ~inProgress OR duration=0 THEN { IF inProgress AND tone.on#0 THEN { -- in progress is previous note or leading rest. toneEnt: TuneParse.ToneList; toneEnt ฌ LIST[tone]; IF lastTone=NIL THEN tones.tonesฌlastToneฌtoneEnt ELSE { lastTone.rest ฌ toneEnt; lastToneฌtoneEnt }; -- Append tone.off ฌ 0; }; tone.f1 ฌ freq; tone.on ฌ duration; inProgress ฌ TRUE; } ELSE IF freq=0 THEN tone.off ฌ tone.off + duration ELSE tone.on ฌ tone.on + duration; }; DonePlaying: PROCEDURE = { AddTone[0, 0]; }; NextNote: PROCEDURE [] = { IF NOT note.initialized THEN RETURN; AddTone[Frequency[note], note.duration]; IF note.breakTime#0 THEN AddTone[0, note.breakTime]; noteญ ฌ []; }; { ENABLE UNWIND => CONTINUE; mS ฌ IO.RIS[tune]; IF mS=NIL THEN RETURN; -- Should complain! tones ฌ NEW[TuneParse.ToneSpecRec ฌ []]; tones.asRope ฌ tune; note ฌ NEW[NoteRecฌ[]]; WHILE NOT mS.EndOf[] DO char: CHAR ฌ mS.GetChar[]; SELECT char FROM IN ['A..'G] => { NextNote[]; note.twelfths ฌ twelfths[char]; note.octave ฌ octave; note.duration ฌ IF triplet THEN (duration*2 + 1)/3 ELSE duration; note.initialized ฌ TRUE; }; IN ['a..'g] => { NextNote[]; note.twelfths ฌ twelfths[char + ('A - 'a)]; note.octave ฌ octave; note.duration ฌ MAX[(IF triplet THEN (duration*2 + 1)/3 ELSE duration), break] - break; note.breakTime ฌ break; note.initialized ฌ TRUE; }; '% => { NextNote[]; note.duration ฌ IF triplet THEN (duration*2 + 1)/3 ELSE duration; note.initialized ฌ TRUE; }; '# => IF note.initialized THEN note.twelfths ฌ note.twelfths + 1; --sharps 1/12 higher '+ => IF note.initialized THEN note.duration ฌ ((note.duration+note.breakTime)*3/2) - note.breakTime; '- => IF note.initialized THEN note.duration ฌ MAX[((note.duration+note.breakTime)/2), note.breakTime] - note.breakTime; '< => octave ฌ octave*2; '> => octave ฌ octave/2; '/ => duration ฌ MAX[duration/2, 8]; '* => duration ฌ duration*2; '_ => break ฌ MAX[break/2, 2]; '^ => break ฌ break*2; '(, ') => triplet ฌ ( char = '( ); ': => repeatIndefinitely ฌ NOT repeatIndefinitely; '@ => duration ฌ mS.GetInt[!IO.Error, FS.Error => CONTINUE]; -- Should complain! '! => volume ฌ -mS.GetInt[!IO.Error, FS.Error => CONTINUE]; -- Should complain! ', => break ฌ mS.GetInt[!IO.Error, FS.Error => CONTINUE]; -- Should complain! ENDCASE; ENDLOOP; NextNote[]; DonePlaying[]; tones.volume ฌ volume; tones.repeatIndefinitely ฌ repeatIndefinitely; }; }; MergeToneSpecs: PUBLIC PROC[t1, t2: TuneParse.ToneSpec, t2Divisor: INTฌ1, t2Delay: INTฌ0, volumeIncrement: INTฌ0] RETURNS[ts: TuneParse.ToneSpec] = { tone: TuneParse.Tone ฌ [0,0,0,0, 0]; l1, l2: TuneParse.ToneList; lastTone: TuneParse.ToneList ฌ NIL; volume: INT ฌ volumeIncrement; SendTone: PROC = { IF tone.repetitions#0 THEN { newLast: TuneParse.ToneList ฌ LIST[tone]; IF lastTone=NIL THEN ts.tones ฌ newLast ELSE lastTone.rest ฌ newLast; lastTone ฌ newLast; tone ฌ [0,0,0,0, 0]; }; }; d, d1, d2: TuneParse.Milliseconds ฌ 0; IF t1=NIL THEN RETURN[t2]; -- aberrant call ts ฌ NEW[TuneParse.ToneSpecRec ฌ [ repeatIndefinitely: t1.repeatIndefinitely, volume: t1.volume+volumeIncrement]]; IF t2Delay#0 THEN l2 ฌ LIST[[0, 0, 0, t2Delay, 0]]; l1 ฌ t1.tones; IF t2#NIL THEN IF l2=NIL THEN l2 ฌ t2.tones ELSE l2.rest ฌ t2.tones; DO f1, f2: TuneParse.Milliseconds; [f1, d1, l1] ฌ Duration[l1, d, 1]; [f2, d2, l2] ฌ Duration[l2, d, t2Divisor]; d ฌ MIN[d1, d2]; IF d=0 THEN d ฌ MAX[d1, d2]; IF d=0 THEN EXIT; IF f1#0 OR f2#0 THEN { SendTone[]; tone.f1 ฌ f1; tone.f2 ฌ f2; tone.on ฌ d; } ELSE tone.off ฌ tone.off + d; tone.repetitions ฌ 1; ENDLOOP; SendTone[]; }; Duration: PROC[oldT: TuneParse.ToneList, reduction: INT, divisor: INT] RETURNS [f: TuneParse.Hertz, duration: TuneParse.Milliseconds, t: TuneParse.ToneList] = { newVal: INT; t ฌ oldT; IF t=NIL THEN RETURN[0, 0, NIL]; IF reduction#0 THEN { newVal ฌ LOOPHOLE[t.first.on, INT]-reduction; IF newVal>=0 THEN { IF newVal=0 AND t.first.off=0 THEN t ฌ t.rest ELSE { t ฌIF t.first.repetitions=0 THEN t ELSE CONS[[t.first.f1, 0, 0, t.first.off, 0], t.rest]; t.first.on ฌ newVal; }; } ELSE { IF newVal#(-reduction) THEN ERROR; newVal ฌ LOOPHOLE[t.first.off, INT]-reduction; IF newVal>0 THEN { IF t.first.repetitions=0 THEN t.first.off ฌ newVal ELSE t ฌ CONS[[0, 0, 0, newVal, 0], t.rest]; } ELSE IF newVal=0 THEN t ฌ t.rest ELSE ERROR; }; }; IF t=NIL THEN RETURN[0, 0, NIL]; IF t.first.on#0 THEN RETURN[t.first.f1/divisor, t.first.on, t] ELSE IF t.first.off#0 THEN RETURN[0, t.first.off, t] ELSE ERROR; }; }.  TuneParseImpl.mesa Derived from LarkPlayImpl Copyright ำ 1984, 1992 by Xerox Corporation. All rights reserved. Last Edited by: Pier, May 25, 1984 2:26:24 pm PDT Polle Zellweger (PTZ) October 30, 1987 6:27:17 pm PST Last Edited by: Swinehart, September 29, 1992 4:59 pm PDT -------------------------------------------------------------- TYPEs and Constants -------------------------------------------------------------- ParseTune is the main procedure. It interprets its block as follows: A letter from "A" through "G" specifies a note. If the letter is followed by "#" then the corresponding sharp-note is played (meaningful only for C, D, F, G, and A). All notes are eighth-notes, but upper-case letters cause tones that are "held" the full time while lower-case notes last for 3/64 seconds less and followed by a 3/64 rest. C is the bottom of the octave; B is above C. When ">" 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. A "/" in the string halves the note durations, down to a minimum of 64th-notes; a "*" doubles the durations up to a maximum of full-notes. A lower-case 1/16th-note would actually be a 64th-note followed by a 3/64 rest, which may or may not be what you want; a lower-case 32nd-note vanishes completely! To halve and double the amount of implicit rest "stolen" from lower-case notes, use "_" and "^", respectively. Use "%" to get an explicit rest (as distinct from the implicit ones after lower-case notes). The rest is the same length as a note, i.e., initially an eighth-rest, and changed via "/" and "*". A "+" causes the preceding note to be held for an extra 50%. Thus a quarter-note followed by a "+" becomes a 3/8-note, etc. A "-" causes the preceding note's duration to be halved. This is effectively a shorthand for bracketing the note with "/" and "*". A left parenthesis causes subsequent notes and explicit rests to be at 2/3 normal duration, until a right parenthesis is reached. Three notes enclosed in parentheses yield a "triplet". If you think you know exactly what tempo you want, use "@;" 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. "!; sets the volume to n. 0 is loudest, and some integer > 0 is softest; experiment. This overrides the default specified in PlayString, but may not take effect until several notes after it appears, unless specified first. A colon (":") in the string toggles the "repeatIndefinitely" specification, originally TRUE. Thus, one can arrange for a single-repetition of the tune. Everything following an ampersand ("&") represents a second tune to be played in parallel with the first. This is not compatible with "duet-mode"; it can be used to make customized standard ring-tones and DTMF tones, though. NB: Not implemented any more. Two or more notes and/or rests enclosed in braces, as "{C%%%G#}", yield a "slide" from the first note to the last. The duration of the slide equals the total duration of the notes and rests; observe that upper- and lower-case notes have the same effect. The slide consists of 64th-notes at equally-spaced (logarithmic) frequencies. Warning: This can eat up a lot of array space! NB: Not used for anything any more. The argument "chunkSize" is the number of tones (including rests, explicit and implicit) that are played as a unit; if you exceed this limit, the first chunk of music gets played and there is a slight pause while the next "chunkSize" notes get parsed. If you know where you want this pause to occur, you can force it by putting a semi-colon at that point in the string. NB: Soon to disappear: explicit requests via "[" and "]" to break up the tune into playable pieces, and to request notification that a previous piece has almost finished. This function will instead compute it. if octave is 0, believe whatever's already there; might be precomputed slide Build a list of tones. A tone, end marker, or leading rest Non-leading, non-trailing rest, or tied note. -- this is where the "slop" mentioned for brackets comes in First, take care of reductions. Polle Zellweger (PTZ) October 30, 1987 6:24:08 pm PST Catch IO.Error for GetInt; because we now have users (via FinchValues) who may construct tunes with bad syntax. Still need to report these errors to the user. changes to: PlayStr Dan Swinehart (DCS) September 11, 1992 1:18:32 pm PDT Major revision and rename for use with Phoenix (Suns) in Unix. Eliminate the list of lists nonsense. ส k•NewlineDelimiter –"cedarcode" style™™J™Jšœ ฯeœ6™BJšœ1™1Icode™5J™9—K˜šฯk ˜ Kšœžœ ˜Kšžœ˜Kšœžœ ˜K˜SKšœ˜K˜—K˜šœž ˜Kšžœžœžœ˜Kšžœ˜K˜Jšœ>™>Jšœ™Jšœ>™>K˜Kšœžœฯc7˜PKšœžœ˜Kšœžœ˜,K˜Kšœžœžœ ˜šœ žœžœ˜Kšœ'žœ˜-Kšœ žœž˜šœ˜K˜——Kšœžœ˜Kšœžœ Ÿ+˜HKšœžœŸ'˜@Kšœžœž œ ˜!Kšœ žœžœžœ˜8Kšœžœ Ÿ˜'š œžœžœžœŸœŸ˜7K˜A—Kšœžœ Ÿ#˜7K˜J™EJšœำ™ำJšœบ™บJšœž™žJšœม™มJšœ™Jšœน™นJšœ้™้Jšœใ™ใJšžœVžœ=™˜J™แJšœ›™›Jšœ–™–Jšœา™าK˜šฯn œžœžœ žœ ˜4Kšžœ ˜'Kšœ žœ˜Kšœžœ˜ šžœžœ˜K˜!K˜!K˜—K˜šžœžœ˜K˜0K˜&K˜—K˜K˜—š  œžœ žœ žœžœžœ˜YKšœžœžœ˜Kšœ ˜ Kšœžœ˜Kšœ žœ˜ Kšœžœ˜Kšœ žœžœŸ'˜BK˜Kšœžœ˜#K˜5Kšœ žœžœ˜Kšœžœžœ˜J™š  œž œžœŸ œ˜=Kšžœžœ˜Kšœžœ˜Kšœ žœ˜Kšœžœ˜K˜Kšžœ žœžœžœ ˜4JšœL™LK˜Kšžœ žœ!˜3Kšžœžœžœ˜6Kšžœžœžœžœ˜;šžœ ž˜Kšœžœ˜"—K˜ K˜Kšœ˜K˜—š œžœžœ žœ˜,K™š žœ žœžœžœ žœ žœ˜PJšœ#™#šžœ žœ žœŸ0˜SK˜Kšœ žœ˜Kšžœ žœžœ˜1Kšžœ0Ÿ ˜=K˜ K˜—K˜K˜Kšœ žœ˜K˜—Kšžœžœžœ˜2šžœ˜"J™-—K˜K˜—Kš  œž œ˜,K˜š œž œ˜Kšžœžœžœžœ˜$K˜(Kšžœžœ˜4K˜ Kšœ˜K˜—Kšœžœžœžœ˜Kšœžœžœ˜Kš žœžœžœžœŸ˜*Kšœžœ˜(K˜Kšœžœ ˜šžœžœ ž˜Kšœžœ˜šžœž˜˜šžœ˜K˜ K˜K˜Kšœžœ žœžœ ˜AKšœžœ˜Kšœ˜K˜—šžœ˜JšŸ<™Kšžœžœžœžœ˜4Kšžœžœ˜ K˜—K˜Kšœ˜K˜—™5Kšœqฯtœ.ก™กKšœ ฯr™—™5K™e—K™—…—ฌ8!