<> <> <> <> <> <<>> DIRECTORY Beam USING [Draw, Drawn, SetSyncs], Chord USING [Adjust, Draw], Event, MusicDefs, Note USING [Delta, Draw, InVoice], Score, Sheet, Utility USING [DrawBox, DrawChar, DrawLine, SetColor, SetCP]; EventImpl: CEDAR PROGRAM IMPORTS Beam, Chord, Event, Note, Score, Sheet, Utility EXPORTS Event = BEGIN OPEN MusicDefs; GetScoreIndex: PUBLIC PROC[score: ScorePTR, event: EventPTR] RETURNS[index: CARDINAL _ 0] = { binary: CARDINAL _ 8192; IF event = NIL THEN RETURN[score.length]; FOR i: CARDINAL IN [0..13) DO binary _ binary/2; IF index + binary >= score.length THEN LOOP; IF score[index+binary].time <= event.time THEN index _ index + binary; IF score[index].time # event.time THEN LOOP; IF score[index] = event THEN RETURN[index]; <> FOR j: CARDINAL IN (index..score.length) WHILE score[j].time = event.time DO IF score[j] = event THEN RETURN[j]; ENDLOOP; FOR j: CARDINAL DECREASING IN [0..index) WHILE score[j].time = event.time DO IF score[j] = event THEN RETURN[j]; ENDLOOP; EXIT; ENDLOOP; <> FOR i: CARDINAL IN [0..score.length) DO IF score[i] = event THEN RETURN[i]; ENDLOOP; RETURN[score.length]; -- not in score }; GetStaff: PUBLIC PROC[staves: StavesPTR, staff: CARDINAL] RETURNS[Staff] ~ { RETURN[staves.staff[staff]]; }; Invisible: PUBLIC PROC[score: ScorePTR, index: CARDINAL, leftEdge: Time] RETURNS[BOOL] = { event: EventPTR ~ score[index]; WITH event SELECT FROM ev: StavesPTR => IF ev.staves # clef THEN RETURN[FALSE]; ev: KeySignaturePTR => NULL; ENDCASE => RETURN[FALSE]; RETURN[event.time-leftEdge <= 15]; }; <<>> <<******************************************************************>> <> <<******************************************************************>> SetStave: PUBLIC PROC[score: ScorePTR, oldS: StavesPTR, newS: StavesPTR] = { n, o: BOOL; pitch, height: INTEGER; IF newS = NIL THEN RETURN; height _ newS.height; IF newS.staves # style THEN { pitch _ newS.staff[newS.value].pitch; newS^ _ oldS^; newS.height _ height; FOR j: CARDINAL IN [0..newS.length) DO IF newS.staff[j].y # newS.staff[newS.value].y THEN LOOP; newS.staff[j].pitch _ pitch; ENDLOOP; RETURN}; <> newS^ _ score.style[newS.value]^; newS.height _ height; <> IF oldS = NIL THEN RETURN; IF oldS.length # newS.length THEN RETURN; FOR i: CARDINAL IN [1..newS.length) DO n _ newS.staff[i].y = newS.staff[i-1].y; o _ oldS.staff[i].y = oldS.staff[i-1].y; IF n # o THEN RETURN; ENDLOOP; FOR i: CARDINAL IN [0..newS.length) DO newS.staff[i].pitch _ oldS.staff[i].pitch; ENDLOOP; }; GetOctava: PUBLIC PROC[score: ScorePTR, octava: StavesPTR] RETURNS[StavesPTR] = { OPEN Event; -- USING [GetScoreIndex, GetStaff]; IF octava#NIL THEN SELECT octava.staves FROM octava1 => FOR i: NAT IN (GetScoreIndex[score, octava]..score.length) DO WITH score[i] SELECT FROM staves: StavesPTR => IF staves.staves=octava2 THEN { IF octava.value = staves.value THEN RETURN[staves]; IF GetStaff[octava, octava.value].y=GetStaff[staves, staves.value].y THEN RETURN[staves]; }; ENDCASE; ENDLOOP; octava2 => FOR i: NAT DECREASING IN[0..GetScoreIndex[score, octava]) DO WITH score[i] SELECT FROM staves: StavesPTR => IF staves.staves=octava1 THEN { IF octava.value = staves.value THEN RETURN[staves]; IF GetStaff[octava, octava.value].y = GetStaff[staves, staves.value].y THEN RETURN[staves]; }; ENDCASE; ENDLOOP; ENDCASE; RETURN[NIL]; }; <<>> Draw: PUBLIC PROC[score: ScorePTR, event: EventPTR] = { Utility.SetColor[score.sheet.context, black]; WITH event SELECT FROM sync: SyncPTR => DrawSync[score, sync]; staves: StavesPTR => SELECT staves.staves FROM style => DrawMeasure[score, measure, event.time, 10]; clef => Sheet.DrawClef[ score.sheet, staves.staff[staves.value].pitch, staves.value, staves.time]; octava1 => { octava2: StavesPTR ~ Event.GetOctava[score, staves]; IF octava2 = NIL THEN ERROR; Sheet.DrawOctava[score.sheet, staves.staff[staves.value].pitch, staves.value, staves.height, staves.time, octava2.time]; }; octava2 => IF Event.GetOctava[score, Event.GetOctava[score, staves]]#staves THEN DrawMeasure[score, doubleMeasure, staves.time, 20]; -- ELSE already drawn ENDCASE; event: KeySignaturePTR => Sheet.DrawKey[ score.sheet, event.key, Score.GetKey[score, event.time-1], event.time]; event: TimeSignaturePTR => Score.DrawTimeSignature[score, event.ts, event.time]; event: MetronomePTR => Score.DrawMetronome[score, event.metronome, event.time]; event: MeasurePTR => DrawMeasure[score, event.measure, event.time, 0]; ENDCASE; }; DrawMeasure: PROC[score: ScorePTR, type: MeasureType, time: Time, dy: INTEGER] = { OPEN Utility; staves: StavesPTR; x1, y1, top, delta: INTEGER; IF time = 0 THEN RETURN; IF score.sheet.printing THEN dy _ 0; Utility.SetColor[score.sheet.context, black]; staves _ Sheet.FindStaves[score.sheet, time]; [x1, y1] _ Sheet.Map[score.sheet, time, , staves.length-1]; y1 _ y1 - dy; top _ -staves.staff[staves.length-1].y+2*dy; SELECT type FROM measure => DrawLine[score.sheet.context, x1, y1, x1, y1+top]; doubleMeasure => {DrawLine[score.sheet.context, x1, y1, x1, y1+top]; IF score.sheet.printing OR score.sheet.scale = 1 THEN delta _ 2 ELSE delta _ 3; DrawLine[score.sheet.context, x1-delta, y1, x1-delta, y1+top]}; repeat1 => { DrawBox[score.sheet.context, [x1, y1, x1+3, y1+top]]; DrawLine[score.sheet.context, x1+5, y1, x1+5, y1+top]; }; repeat2, endMeasure => { DrawBox[score.sheet.context, [x1+1, y1, x1-2, y1+top]]; DrawLine[score.sheet.context, x1-5, y1, x1-5, y1+top]; }; ENDCASE; IF type # repeat1 AND type # repeat2 THEN RETURN; IF type = repeat1 THEN delta _ 6 ELSE delta _ -13; FOR i: NAT IN[0..staves.length) DO [x1, y1] _ Sheet.Map[score.sheet, time, , i]; SetCP[score.sheet.context, x1+delta, y1+16+4]; DrawChar[score.sheet.context, '.]; SetCP[score.sheet.context, x1+delta, y1+16-4]; DrawChar[score.sheet.context, '.]; ENDLOOP; }; <<>> <<****************************************************************************>> <> <> <> <> <<****************************************************************************>> AddNote: PUBLIC PROC[score: ScorePTR, sync: SyncPTR, note: NotePTR] = { c: ChordPTR _ note.chord; IF note.sync = sync THEN RETURN; -- already in the sync FOR i: CARDINAL IN [0..(IF c = NIL THEN 1 ELSE c.length)) DO <> IF c # NIL THEN note _ c.note[i]; RemoveNote[score, note.sync, note, FALSE]; note.sync _ sync; sync.note[sync.length] _ note; sync.length _ sync.length + 1; IF note.beam # NIL THEN Beam.SetSyncs[note.beam]; ENDLOOP; }; RemoveNote: PUBLIC PROC[score: ScorePTR, sync: SyncPTR, note: NotePTR, free: BOOL] = { <> IF note = NIL OR sync = NIL THEN RETURN; FOR i: CARDINAL IN [0..sync.length) DO IF sync.note[i] # note THEN LOOP; sync.length _ sync.length - 1; sync.note[i] _ sync.note[sync.length]; sync.note[sync.length] _ NIL; ENDLOOP; IF free AND sync.length = 0 THEN Event.Free[score, sync]; }; NewSync: PUBLIC PROC[score: ScorePTR, length: CARDINAL] RETURNS[sync: SyncPTR] = { sync _ NEW[EventRec.sync[length]]; <> <> <> <> <> <> <> <> }; Free: PUBLIC PROC[score: ScorePTR, event: EventPTR] = { SetBackPointers[score, event, NIL]; <> }; SetBackPointers: PROC[score: ScorePTR, event, new: EventPTR] = { <> <> index: CARDINAL _ Event.GetScoreIndex[score, event]; IF index # score.length THEN score[index] _ new; IF index # score.length AND new = NIL THEN { score.length _ score.length - 1; FOR i: CARDINAL IN [0..score.length) DO score[i] _ score[i+1]; ENDLOOP; score[score.length] _ NIL; }; SELECT event.type FROM sync => { sync: SyncPTR _ Event.Sync[event]; newSync: SyncPTR _ Event.Sync[new]; FOR i: CARDINAL IN [0..sync.length) DO sync[i].sync _ newSync; ENDLOOP}; measure => NULL; timeSignature => { IF score.cache.ts1 = event THEN score.cache.ts1 _ Event.TimeSignature[new]; IF score.cache.ts2 = event THEN score.cache.ts2 _ Event.TimeSignature[new]}; keySignature => { IF score.cache.key1 = event THEN score.cache.key1 _ Event.KeySignature[new]; IF score.cache.key2 = event THEN score.cache.key2 _ Event.KeySignature[new]}; metronome => { IF score.cache.met1 = event THEN score.cache.met1 _ Event.Metronome[new]; IF score.cache.met2 = event THEN score.cache.met2 _ Event.Metronome[new]}; staves => Sheet.Reset[score]; ENDCASE => ERROR; }; AddTimes: PUBLIC PROC[event: EventPTR, time, toc: Time] ~ { -- add times to notes event.time _ event.time + time; WITH event SELECT FROM sync: SyncPTR => FOR i: CARDINAL IN [0..sync.length) DO sync.note[i].toc _ sync.note[i].toc + toc; ENDLOOP; ENDCASE; }; Grace: PUBLIC PROC[sync: SyncPTR] RETURNS[BOOLEAN] ~ { FOR j: CARDINAL IN[0..sync.length) DO IF ~sync.note[j].grace THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE] }; InVoice: PUBLIC PROC[sync: SyncPTR, voice: CARDINAL] RETURNS[BOOLEAN] ~ { FOR j: CARDINAL IN [0..sync.length) DO IF sync.note[j].voice = voice THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; DrawSync: PROC[score: ScorePTR, s: SyncPTR] = { b: BeamPTR; c: ChordPTR; sync: ARRAY [0..16) OF NotePTR; x, y: INTEGER; min, vMin: INTEGER _ 1000; max, vMax: INTEGER _ -1000; FOR i: CARDINAL IN [0..s.length) DO sync[i] _ s[i]; ENDLOOP; FOR i: CARDINAL IN [0..s.length) DO IF s[i] = NIL THEN EXIT; IF sync[i] = NIL THEN LOOP; c _ s[i].chord; b _ s[i].beam; IF b # NIL AND b.beam # NIL THEN b _ b.beam; IF c # NIL THEN FOR j: CARDINAL IN [0..c.length) DO IF c.note[j] = NIL THEN EXIT; FOR k: CARDINAL IN (i..s.length) DO IF sync[k] = c.note[j] THEN sync[k] _ NIL; ENDLOOP; ENDLOOP; IF b # NIL AND NOT Beam.Drawn[score, b] THEN { [] _ Beam.Draw[score, b]; LOOP; }; IF c # NIL AND b = NIL THEN { Chord.Draw[score, c]; LOOP; }; IF b = NIL THEN Note.Draw[score, s[i]]; ENDLOOP; IF NOT score.sheet.sync THEN RETURN; <> FOR i: CARDINAL IN [0..s.length) DO IF s[i] = NIL THEN EXIT; [x, y] _ Sheet.MapNote[score.sheet, s[i]]; min _ MIN[min, y]; max _ MAX[max, y]; IF ~Note.InVoice[s[i], score.sheet.voice] THEN LOOP; vMin _ MIN[vMin, y]; vMax _ MAX[vMax, y]; ENDLOOP; Utility.SetColor[score.sheet.context, light]; IF min # vMin OR max # vMax THEN Utility.DrawLine[score.sheet.context, x+4, min-6, x+4, max+6]; Utility.SetColor[score.sheet.context, black]; IF vMin # vMax AND vMin # 1000 THEN Utility.DrawLine[score.sheet.context, x+4, vMin-6, x+4, vMax+6]; }; Adjust: PUBLIC PROC[score: ScorePTR, s: SyncPTR] = { n: NotePTR; c: ChordPTR; sync: ARRAY [0..16) OF NotePTR; delta: INTEGER; pad: REF ScratchPad ~ NEW[ScratchPad]; top: NAT _ s.length-1; bottom: NAT _ 0; length: NAT _ 0; IF s = NIL THEN RETURN; FOR i: NAT IN [0..s.length) DO sync[i] _ s[i]; ENDLOOP; <> FOR i: NAT IN [0..s.length) DO IF (n _ s[i]) = NIL THEN EXIT; n.delta _ n.accDelta _ 0; IF n.rest THEN LOOP; c _ n.chord; pad[length].n _ n; pad[length].c _ c; pad[length].y _ Sheet.MapNote[score.sheet, n].y; pad[length].acc _ Score.GetAccidental[score, n]; pad[length].stemUp _ IF c = NIL THEN n.stemUp ELSE c.stemUp; pad[length].stem _ IF pad[length].stemUp THEN pad[length].y+32 ELSE pad[length].y-32; length _ length+1; ENDLOOP; SortPad[FALSE, pad]; FOR i: NAT IN [0..length) DO IF i # 0 AND pad[i].y = pad[i-1].y AND pad[i].acc = pad[i-1].acc THEN pad[i].acc _ inKey; IF pad[i].acc # inKey THEN top _ MIN[top, i]; IF pad[i].acc # inKey THEN bottom _ MAX[bottom, i]; ENDLOOP; <> FOR i: NAT IN [0..length) DO IF sync[i] = NIL THEN LOOP; c _ sync[i].chord; IF c # NIL THEN Chord.Adjust[score.sheet, c] ELSE LOOP; <> FOR j: NAT IN [0..c.length) DO FOR k: NAT IN [0..s.length) DO IF sync[k] = c.note[j] THEN {sync[k] _ NIL; EXIT}; ENDLOOP; ENDLOOP; ENDLOOP; <> FOR i: NAT IN [1..length) DO FOR j: NAT DECREASING IN [0..i) DO IF pad[j].c = pad[i].c AND pad[i].c # NIL THEN LOOP; <> IF Overlap[score, i, j, pad, length] THEN LOOP; delta _ Note.Delta[score.sheet, pad[j].n]; <> IF pad[j].stemUp AND NOT pad[i].stemUp THEN { IF pad[j].y-pad[i].y >= 8 THEN LOOP; MoveRightTo[pad[i], delta]; MoveLeft[pad[i], 8]; <> <> <> EXIT; }; <> IF pad[j].y-pad[i].y < 8 THEN MoveRightTo[pad[i], delta+(IF pad[j].n.value = whole THEN 10 ELSE 8)]; IF pad[i].n.value = whole OR pad[i].n.value = unknown THEN LOOP; IF pad[j].stem < pad[i].y THEN MoveRightTo[pad[i], delta+2]; IF pad[i].stem > pad[j].y THEN MoveRightTo[pad[i], delta+2]; ENDLOOP; ENDLOOP; IF top > bottom THEN RETURN; <> <> <<1) all of the accidentals appear to the left of all of the notes>> <<2) accidentals for notes offset in a chord have the same offset>> <<3) there must be at least three lines vertically between accidentals>> <<4) the highest and lowest accidentals should have the same horizontal position>> <<5) lower accidentals should go to the left of higher accidentals>> FOR i: NAT IN [0..length) DO pad[i].x _ -10; ENDLOOP; FOR i: NAT IN [1..length) DO IF pad[i-1].y-pad[i].y >= 8 THEN LOOP; IF pad[i-1].acc = inKey OR pad[i].acc = inKey THEN LOOP; IF Note.Delta[score.sheet, pad[i-1].n] > Note.Delta[score.sheet, pad[i].n] THEN pad[i-1].push _ i ELSE pad[i].push _ i-1; ENDLOOP; <> <> PlaceAccidental[score, pad, top, length]; FOR i: NAT IN [0..length) DO IF i = top THEN LOOP; IF pad[i].push # bottom THEN LOOP; PlaceAccidental[score, pad, i, length]; ENDLOOP; PlaceAccidental[score, pad, bottom, length]; FOR i: NAT IN [0..length) DO IF i = top OR i = bottom THEN LOOP; IF pad[i].push = bottom THEN LOOP; IF pad[i].push = padLength THEN LOOP; PlaceAccidental[score, pad, i, length]; ENDLOOP; FOR i: NAT IN [0..length) DO IF i = top OR i = bottom THEN LOOP; IF pad[i].push # padLength THEN LOOP; PlaceAccidental[score, pad, i, length]; ENDLOOP; <> FOR i: NAT IN [0..length) DO IF pad[i].acc = inKey THEN LOOP; pad[i].n.accDelta _ -pad[i].n.delta - 8*pad[i].x; ENDLOOP; }; Overlap: PROC[score: ScorePTR, a, b: NAT, pad: REF ScratchPad, length: NAT] RETURNS[BOOL] = { found: BOOL; top, bottom: NAT _ length; PartOf: PROC[i, a: NAT] RETURNS[BOOL] = INLINE { RETURN[a = i OR (pad[i].c = pad[a].c AND pad[a].c # NIL)]}; IF pad[a].c = NIL AND pad[b].c = NIL THEN { -- just single notes IF pad[a].y # pad[b].y THEN RETURN[FALSE]; IF pad[a].n.pitch # pad[b].n.pitch THEN RETURN[FALSE]; IF pad[a].stemUp = pad[b].stemUp THEN RETURN[FALSE]; IF pad[a].n.value = whole OR pad[b].n.value = whole THEN RETURN[FALSE]; RETURN[TRUE]}; <> <> FOR i: NAT IN [0..length) DO IF ~PartOf[i, a] THEN LOOP; IF top = length THEN top _ i; bottom _ i; ENDLOOP; IF pad[a].stemUp THEN top _ 0 ELSE bottom _ length-1; <> FOR i: NAT IN [top..bottom] DO IF ~PartOf[i, b] THEN LOOP; found _ FALSE; FOR j: NAT IN [top..bottom] DO IF ~PartOf[j, a] THEN LOOP; IF pad[i].n.pitch # pad[j].n.pitch OR pad[i].y # pad[j].y THEN LOOP; IF Note.Delta[score.sheet, pad[i].n] # Note.Delta[score.sheet, pad[j].n] THEN LOOP; found _ TRUE; EXIT; ENDLOOP; IF ~found THEN RETURN[FALSE]; ENDLOOP; top _ length; <> FOR i: NAT IN [0..length) DO IF ~PartOf[i, b] THEN LOOP; IF top = length THEN top _ i; bottom _ i; ENDLOOP; IF pad[b].stemUp THEN top _ 0 ELSE bottom _ length-1; <> FOR i: NAT IN [top..bottom] DO IF ~PartOf[i, a] THEN LOOP; found _ FALSE; FOR j: NAT IN [top..bottom] DO IF ~PartOf[j, b] THEN LOOP; IF pad[i].n.pitch # pad[j].n.pitch OR pad[i].y # pad[j].y THEN LOOP; IF Note.Delta[score.sheet, pad[i].n] # Note.Delta[score.sheet, pad[j].n] THEN LOOP; found _ TRUE; EXIT; ENDLOOP; IF ~found THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; PlaceAccidental: PROC[score: ScorePTR, pad: REF ScratchPad, i, length: NAT] = { blocked: BOOL; d, h: INTEGER; IF i = padLength THEN RETURN; IF pad[i].acc = inKey THEN RETURN; FOR j: NAT IN [0..length) DO IF pad[j].push = i THEN pad[i].x _ MAX[pad[j].x, pad[i].x]; ENDLOOP; FOR d IN [MAX[pad[i].x, 0]..6) DO blocked _ FALSE; FOR j: NAT IN [0..length) DO SELECT (IF pad[j].y > pad[i].y THEN pad[j].acc ELSE pad[i].acc) FROM flat, doubleFlat, doubleSharp => h _ 8; ENDCASE => h _ 16; SELECT (IF pad[j].y > pad[i].y THEN pad[i].acc ELSE pad[j].acc) FROM doubleSharp => NULL; ENDCASE => h _ h+8; IF pad[j].y-pad[i].y >= h THEN LOOP; IF pad[i].y-pad[j].y >= 24 THEN EXIT; IF pad[i].y-pad[j].y >= h THEN LOOP; IF d = 0 AND Note.Delta[score.sheet, pad[j].n] < 0 THEN {blocked _ TRUE; EXIT}; IF j = i THEN LOOP; IF j = pad[i].push THEN LOOP; IF pad[j].acc # inKey AND pad[j].x = d THEN {blocked _ TRUE; EXIT}; ENDLOOP; IF blocked THEN LOOP; MoveAccTo[pad, i, d]; RETURN; ENDLOOP; ERROR; }; MoveRightTo: PROC[p: Scratch, x: INTEGER] = { IF p.c = NIL THEN p.n.delta _ MAX[p.n.delta, x] ELSE p.c.delta _ MAX[p.c.delta, x - p.n.delta]; }; MoveLeft: PROC[p: Scratch, delta: INTEGER] = { IF p.c = NIL THEN p.n.delta _ MIN[p.n.delta, -delta] ELSE p.c.delta _ MIN[p.c.delta, -delta - p.n.delta]; }; MoveAccTo: PROC[p: REF ScratchPad, i: NAT, delta: INTEGER] = { p[i].x _ MAX[p[i].x, delta]; IF p[i].push # padLength AND p[p[i].push].x > -1 THEN MoveAccTo[p, p[i].push, p[i].x+1]; }; SortPad: PROC[ascending: BOOL, pad: REF ScratchPad] = { temp: Scratch; FOR i: NAT IN [0..padLength) DO IF pad[i].n = NIL THEN EXIT; FOR j: NAT IN (i..padLength) DO IF pad[j].n = NIL THEN EXIT; IF NOT ascending AND pad[i].y < pad[j].y OR ascending AND pad[i].y > pad[j].y THEN { temp _ pad[i]; pad[i] _ pad[j]; pad[j] _ temp; }; ENDLOOP; ENDLOOP; }; padLength: NAT = 16; ScratchPad: TYPE = ARRAY [0..padLength) OF Scratch; Scratch: TYPE = RECORD[ x, y, stem: INTEGER _ 0, push: NAT _ padLength, acc: Accidental, stemUp: BOOL, c: ChordPTR, n: NotePTR _ NIL ]; END.