-- IntMusic.mesa
-- edited by Woods, November 13, 1980 1:54 PM
-- edited by Brotz, December 31, 1980 10:20 AM
-- edited by Barth, December 15, 1980 3:46 PM

DIRECTORY
inD: FROM "InteractorDefs",
Inline,
ProcessDefs,
SegmentDefs,
TimeDefs;

IntMusic: PROGRAM
IMPORTS Inline, ProcessDefs, SegmentDefs, TimeDefs
EXPORTS inD =

BEGIN

machineType: SegmentDefs.MachineType = SegmentDefs.memConfig.AltoType;


GetLongTime: PROCEDURE RETURNS [time: LONG CARDINAL] = INLINE
BEGIN
AltoITimeFormat: TYPE = MACHINE DEPENDENT RECORD
[lowBits: [0 .. 1023], trailingZeros: [0 .. 63], highBits: CARDINAL];
AltoIITimeFormat: TYPE = MACHINE DEPENDENT RECORD
[crud1: [0 .. 15], lowBits: [0 .. 1023], crud2: [0 .. 3], highBits: CARDINAL];
time ← TimeDefs.ReadClock[];
SELECT machineType FROM
AltoI => NULL;
AltoII, AltoIIXM =>
LOOPHOLE[time, AltoITimeFormat].lowBits
← LOOPHOLE[time, AltoIITimeFormat].lowBits;
ENDCASE => time ← 0;
LOOPHOLE[time, AltoITimeFormat].trailingZeros ← 0;
END; -- of GetLongTime --

PlayNote: PROCEDURE [cps, ticks: CARDINAL] =
BEGIN -- play given note for given time
out: POINTER TO CARDINAL = LOOPHOLE[177016B];
nanosecs, startTime, rtinc: LONG CARDINAL;
time, outState: CARDINAL;
-- cannot read out↑ directly, so keep private state of output port.

IF cps = 0 THEN
BEGIN
time ← inD.realTimeClock↑;
UNTIL inD.realTimeClock↑ - time >= ticks DO out↑ ← 0 ENDLOOP;
END
ELSE BEGIN
ProcessDefs.DisableInterrupts[];
nanosecs ← (1000000000+LONG[cps/2])/LONG[cps];
out↑ ← outState ← 0;
rtinc ← (nanosecs*LONG[32]) / LONG[38080];
time ← 2*Inline.LowHalf[(LONG[ticks]*38993920)/nanosecs];
startTime ← GetLongTime[];
THROUGH [0..time) DO
out↑ ← outState ← IF outState=0 THEN 177777B ELSE 0;
ProcessDefs.EnableInterrupts[];
ProcessDefs.DisableInterrupts[];
UNTIL GetLongTime[] - startTime >= rtinc DO ENDLOOP;
startTime ← startTime + rtinc;
ENDLOOP;
ProcessDefs.EnableInterrupts[];
END;
END; -- of PlayNote --

-- PlayTune is the main procedure. It interprets its string 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 (five ticks, to be precise), but upper-case letters cause tones that are "held" the
-- full time while lower-case notes last only three ticks followed by a brief rest. C is the
-- bottom of the octave; B is higher than 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 (the fourth ">" is ignored), and notes near the top of the
-- highest octave may not be struck accurately. Finally, use "%" to get an eighth-rest.

PlayTune: PUBLIC PROCEDURE [s: STRING] =
-- Tweaks speaker output to play tune encoded in s.
BEGIN
secs: CARDINAL = 15;
-- maximum number of seconds of music we can handle
note: CARDINAL ← 0;
-- number of notes represented by s
cps, octaves, hold: ARRAY [1 .. 8*secs] OF CARDINAL;
scale: TYPE = CHARACTER [’A .. ’G];
twelfths: ARRAY scale OF CARDINAL = [12, 14, 3, 5, 7, 8, 10];
card: CARDINAL;
freq: LONG CARDINAL;
octave: CARDINAL ← 8;
root: LONG CARDINAL = 10595;
-- 12th root of 2, times 10000
freqA: LONG CARDINAL = 1760;
-- highest octave we’ll bother with

SELECT machineType FROM
AltoI, AltoII, AltoIIXM => NULL;
ENDCASE => RETURN;

FOR card IN [0 .. s.length) DO
SELECT s[card] FROM
IN [’A..’G] => BEGIN
cps[note ← MIN [note+1, 8*secs]] ← twelfths[s[card]];
octaves[note] ← octave;
hold[note] ← 5;
END;
IN [’a..’g] => BEGIN
cps[note ← MIN [note+1, 8*secs]] ← twelfths[s[card]+(’A-’a)];
octaves[note] ← octave;
hold[note] ← 3;
END;
’# => IF note>0 THEN cps[note] ← cps[note]+1;
-- sharps are one twelfth-octave higher
’< => octave ← MIN [octave*2, 256];
’> => octave ← MAX [octave/2, 1];
’% => BEGIN
cps[note ← MIN [note+1, 8*secs]] ← 0;
hold[note] ← octaves[note] ← 5;
END;
ENDCASE;
ENDLOOP;
FOR card IN [1..note] DO
freq ← SELECT cps[card] FROM
0 => LONG[0],
<12 => freqA * LONG[100],
ENDCASE => freqA * LONG[200];
THROUGH [0..cps[card] MOD 12) DO freq ← freq*root/LONG[10000] ENDLOOP;
cps[card] ← Inline.LowHalf[(freq/LONG[octaves[card]]+LONG[50])/LONG[100]];
IF card>1 AND cps[card]=cps[card-1] AND (hold[card-1] MOD 5)=0 THEN
BEGIN
hold[card] ← hold[card]+hold[card-1];
-- combine notes for smoother play
hold[card-1] ← 0;
END;
ENDLOOP;
FOR card IN [1..note] DO
IF hold[card]#0 THEN PlayNote [cps[card], hold[card]];
IF hold[card] MOD 5#0 THEN PlayNote [0, 5-(hold[card] MOD 5)];
ENDLOOP;
END; -- of PlayTune --


END. -- of IntMusic --