-- Author: John Maxwell -- Last Edited by: Maxwell, November 22, 1983 1:00 pm DIRECTORY Beam USING [Height, InVoice], Event USING [GetOctava, Octava, Staves, Sync], Inline USING [LongCOPY], MusicDefs, Note USING [Delta, FindChord, Free, InVoice], Piece USING [CleanUpEvents], Real USING [FixI], Score USING [GetKey, ShowPitch], Selection USING [selection], Sheet USING [FindLine, FindSection, Height, NearestTime, New, NextLine], Utility USING [FreeSegment, NewSegment]; PieceImplA: PROGRAM IMPORTS Beam, Event, Inline, MusicDefs, Note, Piece, Real, Score, Selection, Sheet, Utility EXPORTS MusicDefs, Piece = BEGIN OPEN MusicDefs; dataStructureInFlux: PUBLIC BOOLEAN ← FALSE; -- **************************************************************************** -- data abstractions for a piece -- CONSTRAINT: score.event[i-1].time <= score.event[i].time <= score.event[i+1].time -- CONSTRAINT: score.event[i] # NIL for all i such that 0 <= i < score.length -- CONSTRAINT: score.event[score.length] = NIL -- CONSTRAINT: score.event[i] # score.event[j] for all i # j < score.length -- **************************************************************************** Overflow: PUBLIC SIGNAL[old, new: ScorePTR] = CODE; AddEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] RETURNS[new: ScorePTR] = BEGIN -- any procedure that inserts syncs should process syncs in decreasing order IF event = NIL THEN RETURN[score]; IF score.length+1 = score.max THEN score ← New[score.max+100, TRUE, score]; -- insert sync dataStructureInFlux ← TRUE; FOR i: CARDINAL DECREASING IN [0..score.length) DO IF score.event[i] = event THEN ERROR; IF score.event[i].time < event.time THEN {score.event[i+1] ← event; event ← NIL; EXIT}; IF score.event[i].time = event.time AND ~LessThan[event, score.event[i]] THEN {score.event[i+1] ← event; event ← NIL; EXIT}; score.event[i+1] ← score.event[i]; ENDLOOP; IF event # NIL THEN score.event[0] ← event; score.length ← score.length + 1; dataStructureInFlux ← FALSE; RETURN[score]; END; LessThan: PROCEDURE[a, b: EventPTR] RETURNS[BOOLEAN] = INLINE BEGIN SELECT TRUE FROM a.type = staves AND Event.Staves[a].staves = style => RETURN[TRUE]; b.type = staves AND Event.Staves[b].staves = style => RETURN[FALSE]; a.type = keySignature => RETURN[TRUE]; b.type = keySignature => RETURN[FALSE]; a.type = measure => RETURN[TRUE]; b.type = measure => RETURN[FALSE]; a.type = metrenome => RETURN[TRUE]; b.type = metrenome => RETURN[FALSE]; a.type = timeSignature => RETURN[TRUE]; b.type = timeSignature => RETURN[FALSE]; a.type = staves => RETURN[TRUE]; b.type = staves => RETURN[FALSE]; ENDCASE => RETURN[TRUE]; END; RemoveEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR, free: BOOLEAN] = BEGIN length: CARDINAL ← 0; octava: EventPTR ← NIL; IF Event.Octava[event] AND free THEN octava ← Event.GetOctava[score, Event.Staves[event]]; FOR i: CARDINAL IN [0..score.length) DO IF score.event[i] = event THEN LOOP; IF i # length THEN score.event[length] ← score.event[i]; length ← length+1; ENDLOOP; IF event.type = sync THEN { sync: SyncPTR ← Event.Sync[event]; FOR i: CARDINAL IN [0..sync.length) DO sync.note[i].sync ← NIL; ENDLOOP}; score.length ← length; score.event[score.length] ← NIL; IF free THEN zone.FREE[@event]; IF free AND octava # NIL THEN { SetDirty[score, octava.time, octava.time]; RemoveEvent[score, octava, FALSE]; zone.FREE[@octava]}; END; nullScore: ScorePTR ← zone.NEW[ScoreRec[0]]; New: PUBLIC PROCEDURE[length: CARDINAL, auxiliary: BOOLEAN, old: ScorePTR] RETURNS[score: ScorePTR] = BEGIN oldSize: CARDINAL; nullSize: CARDINAL ← SIZE[ScoreRec[0]]; score ← Utility.NewSegment[SIZE[ScoreRec[length]], length, nullSize-1]; IF old = NIL THEN old ← nullScore; -- gets the default values for us oldSize ← SIZE[ScoreRec[old.length]]; Inline.LongCOPY[from: old, nwords: nullSize-1, to: score]; -- skip 'max' Inline.LongCOPY[from: old+nullSize, nwords: oldSize-nullSize, to: score+nullSize]; IF score.max # length THEN ERROR; -- check that it worked. IF old # nullScore THEN { SetBackPointers[old, score]; Utility.FreeSegment[old]}; IF auxiliary THEN { IF score.cache = NIL THEN score.cache ← zone.NEW[CacheRec]; IF score.sheet = NIL THEN score.sheet ← Sheet.New[100]; IF score.style = NIL THEN score.style ← zone.NEW[StyleRec[20]]}; END; Free: PUBLIC PROCEDURE[score: ScorePTR] = BEGIN IF score.beamHeap # NIL THEN { -- free all of the beams FOR i: CARDINAL IN [0..score.beamHeap.length) DO zone.FREE[@score.beamHeap[i]]; -- no need to carefully dismantle ENDLOOP; Utility.FreeSegment[score.beamHeap]}; IF score.chordHeap # NIL THEN { -- free all of the chords FOR i: CARDINAL IN [0..score.chordHeap.length) DO zone.FREE[@score.chordHeap[i]]; -- no need to carefully dismantle ENDLOOP; Utility.FreeSegment[score.chordHeap]}; FOR i: CARDINAL IN [0..score.length) DO IF score.event[i] = NIL THEN LOOP; IF score.event[i].type = sync THEN { sync: SyncPTR = Event.Sync[score.event[i]]; FOR j: CARDINAL IN [0..sync.length) DO zone.FREE[@sync.note[j]]; ENDLOOP}; zone.FREE[@score.event[i]]; ENDLOOP; SetBackPointers[score, NIL]; Utility.FreeSegment[score]; END; SetBackPointers: PROC[old, new: ScorePTR] = BEGIN IF Selection.selection.score = old THEN Selection.selection.score ← new; IF Selection.selection.score2 = old THEN Selection.selection.score2 ← new; SIGNAL Overflow[old, new]; END; Sort: PUBLIC PROCEDURE[score: ScorePTR] = -- useful when going from physical to logical files BEGIN temp: EventPTR; FOR i: CARDINAL IN [0..score.length) DO FOR j: CARDINAL IN (i..score.length) DO IF score.event[i].time > score.event[j].time THEN {temp ← score.event[i]; score.event[i] ← score.event[j]; score.event[j] ← temp}; IF score.event[i].time = score.event[j].time THEN {IF score.event[i].type # sync OR score.event[j].type # sync THEN LOOP; IF Event.Sync[score.event[i]].note[0] = NIL THEN LOOP; IF Event.Sync[score.event[j]].note[0] = NIL THEN LOOP; IF Event.Sync[score.event[i]].note[0].toc < Event.Sync[score.event[j]].note[0].toc THEN LOOP; temp ← score.event[i]; score.event[i] ← score.event[j]; score.event[j] ← temp}; ENDLOOP; ENDLOOP; END; CleanUpEvents: PUBLIC PROCEDURE[score: ScorePTR] = BEGIN length: CARDINAL ← 0; FOR i: CARDINAL DECREASING IN [0..score.length) DO IF score.event[i] = NIL THEN LOOP; IF score.event[i].type = sync THEN IF Event.Sync[score.event[i]].length = 0 THEN zone.FREE[@score.event[i]] ELSE {score.event[length] ← score.event[i]; length ← length+1}; ENDLOOP; FOR i: CARDINAL IN [length..score.length) DO score.event[i] ← NIL; ENDLOOP; score.length ← length; score[score.length] ← NIL; END; CleanUpNotes: PUBLIC PROCEDURE[score: ScorePTR] = BEGIN sync: SyncPTR; FOR i: CARDINAL DECREASING IN [0..score.length) DO IF score.event[i].type # sync THEN LOOP; sync ← Event.Sync[score.event[i]]; FOR j: CARDINAL DECREASING IN [0..sync.length) DO IF sync.note[j].value # unknown THEN LOOP; IF sync.note[j].duration # 0 THEN LOOP; Note.Free[score, sync.note[j]]; -- may remove the sync from the score ENDLOOP; ENDLOOP; Piece.CleanUpEvents[score]; END; -- **************************************************************************** -- what is being pointed at? -- **************************************************************************** NearestEvent: PUBLIC PROCEDURE[score: ScorePTR, t: Time, syncsOnly: BOOLEAN ← FALSE] RETURNS[index: CARDINAL] = BEGIN delta: Time ← 10000; index ← 0; FOR binary: CARDINAL ← 8192, binary/2 UNTIL binary = 0 DO IF index+binary >= score.length THEN LOOP; IF score.event[index+binary].time <= t THEN index ← index+binary; IF score.event[index].time = t THEN EXIT; ENDLOOP; -- the above will fail when the p isn't strictly ordered FOR i: CARDINAL IN [(IF index < 5 THEN 0 ELSE index-5)..MIN[index+5, score.length]) DO IF syncsOnly AND score.event[i].type # sync THEN LOOP; IF ABS[score.event[i].time-t] > delta THEN LOOP; index ← i; delta ← ABS[score.event[i].time-t]; ENDLOOP; IF syncsOnly AND index # score.length AND score.event[index].type # sync THEN index ← score.length; END; NearestNote: PUBLIC PROCEDURE[score: ScorePTR, x, y: INTEGER] RETURNS[note: NotePTR] = BEGIN n: NotePTR; key: INTEGER; sync: SyncPTR; l, next: CARDINAL; time, dt, t: Time ← 11; sheet: SheetPTR ← score.sheet; height, dy, show: INTEGER ← 8; [time, height] ← Sheet.NearestTime[sheet, x, y]; next ← Sheet.FindLine[sheet, time]; time ← time - (sheet.width - sheet.section[next].x); l ← Sheet.FindLine[sheet, time]; height ← height + (sheet.section[next].y - sheet.section[l].y); FOR k: CARDINAL IN [0..2] DO FOR i: CARDINAL IN [0..score.length) DO IF score.event[i].type # sync THEN LOOP; IF score.event[i].time < time - 40 THEN LOOP; IF score.event[i].time > time + 40 THEN EXIT; IF score.event[i].time < sheet.section[l].time THEN LOOP; IF score.event[i].time >= sheet.section[next].time THEN EXIT; sync ← Event.Sync[score.event[i]]; FOR j: CARDINAL IN [0..sync.length) DO n ← sync.note[j]; IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP; t ← ABS[score[i].time+5+Note.Delta[score.sheet, n]-time]; key ← Score.GetKey[score, n.sync.time]; show ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key]; y ← ABS[height-Sheet.Height[sheet, n.sync.time, show, n.staff]]; IF y+t > dy+dt THEN LOOP; IF y+t = dy+dt AND t > dt THEN LOOP; dt ← t; dy ← y; note ← n; ENDLOOP; ENDLOOP; time ← time+ (sheet.width - sheet.section[next].x); height ← height- (sheet.section[next].y - sheet.section[l].y); l ← next; next ← Sheet.NextLine[sheet, l]; ENDLOOP; IF dt > 10 OR dy > 7 THEN note ← NIL; END; NearestObject: PUBLIC PROCEDURE[score: ScorePTR, x, y: INTEGER] RETURNS[obj: ObjectType, p: LONG POINTER ← NIL] = BEGIN n: NotePTR; l: CARDINAL; key: INTEGER; staves: StavesPTR; time, dt, t: Time ← 11; s, next: CARDINAL ← 0; sheet: SheetPTR ← score.sheet; height, dy, show: INTEGER ← 8; [time, height] ← Sheet.NearestTime[sheet, x, y]; next ← Sheet.FindLine[sheet, time]; time ← time - (sheet.width - sheet.section[next].x); l ← Sheet.FindLine[sheet, time]; height ← height + (sheet.section[next].y - sheet.section[l].y); FOR k: CARDINAL IN [0..2] DO -- left and right corners of a beam FOR i: CARDINAL IN [0..score.beamHeap.length) DO IF score.beamHeap.beam[i].beam # NIL AND score.beamHeap.beam[i].beam.beamed THEN LOOP; IF score.beamHeap.beam[i].sync1.time-5 > time THEN LOOP; IF score.beamHeap.beam[i].sync2.time+5 < time THEN LOOP; IF ~Beam.InVoice[score.beamHeap.beam[i], score.sheet.voice] THEN LOOP; y ← Beam.Height[sheet, score.beamHeap.beam[i], time]; IF ABS[y-height]+4 > dt+dy THEN LOOP; IF ABS[y-height]+4 = dt+dy THEN IF y > height THEN LOOP; dt ← 4; dy ← ABS[y-height]; IF time < (score.beamHeap.beam[i].sync1.time+score.beamHeap.beam[i].sync2.time+4)/2 THEN obj ← leftBeam ELSE obj ← rightBeam; p ← score.beamHeap.beam[i]; ENDLOOP; -- measure sections, signatures and other events FOR i: CARDINAL IN [s..score.length) DO IF score.event[i].time < time - 300 THEN LOOP; IF score.event[i].time > time + 300 THEN {s ← i; EXIT}; IF score.event[i].time < sheet.section[l].time THEN LOOP; IF score.event[i].type = sync THEN { -- ties sync: SyncPTR ← Event.Sync[score.event[i]]; FOR j: CARDINAL IN [0..sync.length) DO n ← sync.note[j]; IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP; IF n.tie = NIL THEN LOOP; IF n.sync.time < time OR n.tie.sync.time > time THEN LOOP; t ← ABS[(n.sync.time+n.tie.sync.time)/2-time]; key ← Score.GetKey[score, n.sync.time]; show ← Score.ShowPitch[score.sheet, n.pitch, n.spelled, key]; y ← ABS[height-(Sheet.Height[sheet, n.sync.time, show, n.staff]+n.tieHeight/2)]; IF y+t > dy+dt THEN LOOP; IF y+t = dy+dt THEN IF t > dt THEN LOOP; dt ← t; dy ← y; obj ← tie; p ← n; ENDLOOP}; IF score.event[i].time >= sheet[next].time THEN LOOP; IF ABS[score.event[i].time-time] > 40 THEN LOOP; IF score.event[i].type = staves THEN { octava: StavesPTR ← NIL; staves: StavesPTR ← Event.Staves[score.event[i]]; staff, top, bottom: INTEGER ← staves.value; t ← staves.time; IF staves.staves = clef THEN t ← t+6; IF ABS[t-time]+6 > dt + dy THEN LOOP; IF ABS[t-time]+6 = dt+dy AND t < time THEN LOOP; IF staves.staves = octava2 THEN staves ← Event.GetOctava[score, staves]; -- IF octava # NIL THEN staves ← LOOPHOLE[@octava.event] -- ELSE staves ← LOOPHOLE[@score[i].event]; SELECT staves.staff[staff].pitch FROM 60, 15 => {top ← staves.height+5; bottom ← staves.height-5}; ENDCASE => {top ← 32; bottom ← 0}; IF height > Sheet.Height[sheet, score.event[i].time, , staff]+top THEN LOOP; IF height < Sheet.Height[sheet, score.event[i].time, , staff]+bottom THEN LOOP; dt ← ABS[t-time]; dy ← 6; p ← score.event[i]; obj ← measure; LOOP}; IF score.event[i].type # sync THEN { IF ABS[score.event[i].time-time]+6 > dt + dy THEN LOOP; IF ABS[score.event[i].time-time]+6 = dt+dy AND score.event[i].time < time THEN LOOP; IF score.event[i].type = staves AND score.event[i].time < 1 THEN LOOP; -- don't touch the first one staves ← sheet.section[Sheet.FindSection[sheet, score[i].time]].staves; IF height > Sheet.Height[sheet, score.event[i].time, , 0]+32 THEN LOOP; IF height < Sheet.Height[sheet, score.event[i].time, , staves.length-1] THEN LOOP; dt ← ABS[score.event[i].time-time]; dy ← 6; p ← score.event[i]; obj ← measure; LOOP}; IF score.event[i].type = sync THEN { -- notes sync: SyncPTR ← Event.Sync[score.event[i]]; FOR j: CARDINAL IN [0..sync.length) DO IF (n ← sync.note[j]) = NIL THEN EXIT; IF ~Note.InVoice[n, score.sheet.voice] THEN LOOP; t ← ABS[score.event[i].time+5+Note.Delta[score.sheet, n]-time]; y ← ABS[height-Sheet.Height[sheet, n.sync.time, n.pitch, n.staff]]; IF y+t > dy+dt THEN LOOP; IF y+t = dy+dt AND t > dt THEN LOOP; dt ← t; dy ← y; p ← n; obj ← note; ENDLOOP}; ENDLOOP; -- end of this line, go to next time ← time+ (sheet.width - sheet.section[next].x); height ← height- (sheet.section[next].y - sheet.section[l].y); l ← next; next ← Sheet.NextLine[sheet, l]; ENDLOOP; IF dt > 10 OR dy > 7 THEN p ← NIL; END; END.. DeleteEvent: PUBLIC PROCEDURE[score: ScorePTR, event: EventPTR] = BEGIN octava: StavesPTR ← NIL; index: CARDINAL ← Event.GetScoreIndex[score, event]; IF Event.Octava[event] THEN octava ← Event.GetOctava[score, Event.Staves[event]]; Piece.RemoveEvent[score, event]; IF octava # NIL THEN { SetDirty[score, octava.time, octava.time]; Piece.RemoveEvent[score, octava]; zone.FREE[@octava]}; Utility.FreeEvent[@event]; END;