--Author: John Maxwell --last modified: December 8, 1981 8:40 AM DIRECTORY InlineDefs USING [LowHalf], Heuristic USING [MakeChords], MusicDefs, Note USING [Duration], Score USING [GetTimeSignature], Selection USING [AddLine,AddNote,Clear,MakeBeam,MakeChord,MakeSync], Voice USING [ClearState,SetState,State]; HeuristicImpl:PROGRAM IMPORTS InlineDefs, Heuristic, MusicDefs, Note, Score, Selection, Voice EXPORTS Heuristic = BEGIN OPEN MusicDefs; --*************************************************************************** --guessing structures --*************************************************************************** MakeSyncs:PUBLIC PROCEDURE[begin,end:Time] = BEGIN i,j:CARDINAL; n:NotePTR; current:Time_0; Selection.Clear[]; FOR i DECREASING IN [0..scoreLength) DO IF score[i].time end THEN LOOP; IF ABS[current-score[i].time]>3 THEN BEGIN IF selectionLength>1 THEN Selection.MakeSync[]; Selection.Clear[]; current _ score[i].time; END; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF voice AND selectedVoice#n.voice THEN LOOP; Selection.AddNote[n]; ENDLOOP; ENDLOOP; Selection.MakeSync[]; Selection.AddLine[begin,end]; END; MakeChords:PUBLIC PROCEDURE[begin,end:Time] = BEGIN i,j,v:CARDINAL; n:NotePTR; FOR v IN [0..maxVoice] DO IF voice AND selectedVoice#v THEN LOOP; FOR i IN [0..scoreLength) DO IF score[i].time end THEN LOOP; Selection.Clear[]; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.beam#NIL THEN LOOP; IF n.voice#v THEN LOOP; Selection.AddNote[n]; ENDLOOP; IF selectionLength>1 THEN Selection.MakeChord[]; ENDLOOP; ENDLOOP; Selection.AddLine[begin,end]; END; MakeBeams:PUBLIC PROCEDURE[begin,end:Time] = BEGIN n:NotePTR; vs:Voice.State; --ts:TimeSignature; i,j,v:CARDINAL; change,found:BOOLEAN; sum,sumTS,start,duration:Time_0; count:INTEGER_0; Beam:PROCEDURE= BEGIN IF selection[1]#NIL THEN Selection.MakeBeam[]; Selection.Clear[]; count_0; duration_0; start_sum; change_FALSE; END; min _ begin; max _ end; FOR v IN [0..maxVoice] DO Voice.ClearState[@vs]; sum_0; change_FALSE; Selection.Clear[]; IF voice AND selectedVoice#v THEN LOOP; FOR i IN [0..scoreLength) DO IF score[i].timemax THEN LOOP; IF score[i].type IN [measure..repeat2] THEN BEGIN Voice.ClearState[@vs]; sum _ 0; Beam[]; LOOP; END; --determine the sync's logical position sum _ Voice.SetState[@vs,score[i]]; IF NOT vs[v].found THEN LOOP; --decide whether or not to break the current beam IF vs[v].duration>=4096 THEN Beam[]; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.voice#v THEN LOOP; IF n.beam#NIL THEN Beam[]; IF n.rest THEN Beam[]; IF n.value=unknown THEN Beam[]; ENDLOOP; IF change THEN SELECT sum FROM 4096 => Beam[]; 8192 => Beam[]; 12288 => Beam[]; 16384 => Beam[]; ENDCASE; IF duration#0 AND vs[v].duration#duration THEN change_TRUE; duration_vs[v].duration; IF count>3 THEN Beam[]; IF sum=4096 AND duration<2000 THEN Beam[]; IF sum=8192 AND duration<4000 THEN Beam[]; IF sum=12288 AND duration<2000 THEN Beam[]; IF sum=16384 AND duration<4000 THEN Beam[]; --select appropriate notes for beaming found_FALSE; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.voice#v THEN LOOP; IF n.beam#NIL THEN LOOP; IF n.rest THEN LOOP; IF n.value=unknown THEN LOOP; Selection.AddNote[n]; found_TRUE; ENDLOOP; IF found THEN count _ count+1; ENDLOOP; ENDLOOP; END; MakeNTuplets:PUBLIC PROCEDURE[nt,a:INTEGER,begin,end:Time] = BEGIN n:NotePTR; b:BeamPTR; i,j,v:CARDINAL; count:INTEGER_0; found,beamed:BOOLEAN; value:NoteValue_unknown; Beam:PROCEDURE= {IF selection[1]#NIL AND count=nt THEN Selection.MakeBeam[FALSE]; IF selection[0]#NIL THEN b_selection[0].beam ELSE b_NIL; IF b#NIL THEN BEGIN b.against_a; b.ntuple_nt; END; IF b#NIL THEN b.beamed_beamed; beamed_TRUE; Selection.Clear[]; count_0; value_unknown}; beamed_TRUE; min _ begin; max _ end; FOR v IN [0..maxVoice] DO IF voice AND selectedVoice#v THEN LOOP; Selection.Clear[]; FOR i IN [0..scoreLength) DO IF score[i].timemax THEN LOOP; IF score[i].type IN [measure..repeat2] THEN BEGIN Beam[]; LOOP; END; --decide whether or not to break the current beam FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.voice#v THEN LOOP; IF n.beam#NIL THEN Beam[]; IF n.value=unknown THEN Beam[]; IF value=unknown THEN value_n.value; IF value#n.value THEN Beam[]; value_n.value; ENDLOOP; IF count=nt THEN Beam[]; --select appropriate notes for beaming found_FALSE; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.voice#v THEN LOOP; IF n.beam#NIL THEN LOOP; IF n.rest THEN beamed_FALSE; IF n.valueend THEN LOOP; IF NOT Measure[score[i].type] THEN LOOP; stop _ start; start _ i; IF start>=end THEN LOOP; ts _ Score.GetTimeSignature[score[stop].time]; sumTS _ (ts.top*256)*(64/ts.bottom); GuessMeasure[start,stop,sumTS]; ENDLOOP; SetDirty[begin,end]; END; GuessMeasure:PROCEDURE[begin,end:CARDINAL,time:INTEGER] = BEGIN n:NotePTR; j,i:CARDINAL; firstTime,priorTime,totalTime:Time; delta,bestComplexity,current:INTEGER; firstValue,nextValue,priorValue:INTEGER; maxValue,bestValue,totalValue:INTEGER; IF begin=end OR time=0 THEN RETURN; --SUGGESTION: use a note in the prior measure as the beginning rather than the measure line vs_ALL[[0]]; firstValue _ InlineDefs.LowHalf[time+256]; firstTime _ score[begin].time; priorValue _ 0; priorTime _ score[end].time+3; --events are processed in DESCENDING order, and score[i].value>score[i+1].value FOR i DECREASING IN (begin..end) DO { IF score[i].type#notes THEN LOOP; --check for durations already given by the user FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.value=unknown THEN LOOP; delta _ InlineDefs.LowHalf[Note.Duration[n,128]]; bestValue _ delta+vs[n.voice].value; GOTO done; ENDLOOP; totalTime _ priorTime-firstTime; totalValue _ firstValue-priorValue; -- obtain the next note sync's value nextValue _ firstValue; FOR j DECREASING IN [begin..i) DO IF score[j].type#notes THEN LOOP; delta _ InlineDefs.LowHalf[(totalValue*(priorTime-score[j].time))/totalTime]; nextValue _ priorValue + Quantize[delta,32]; EXIT; ENDLOOP; --find the tic within a certain range that has the smallest complexity delta _ InlineDefs.LowHalf[(totalValue*(priorTime-score[i].time))/totalTime]; bestValue _ priorValue+Quantize[delta,64]; maxValue _ 32000; FOR j IN [0..maxVoice] DO maxValue _ MIN[maxValue,(bestValue-vs[j].value)/4]; ENDLOOP; bestComplexity _ Complexity[score[i],bestValue,@vs]; current _ bestValue-Quantize[maxValue,64]; maxValue _ bestValue+Quantize[maxValue,64]+256; delta _ bestValue; WHILE TRUE DO current _ current+256; IF current <= priorValue THEN LOOP; IF current >= maxValue THEN EXIT; IF current >= nextValue THEN EXIT; IF Complexity[score[i],current,@vs]>bestComplexity THEN LOOP; IF Complexity[score[i],current,@vs]=bestComplexity AND ABS[bestValue-delta]<=ABS[current-delta] THEN LOOP; bestComplexity _ Complexity[score[i],current,@vs]; bestValue _ current; ENDLOOP; GOTO done; EXITS done => { --event done, assign durations FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.value#unknown THEN LOOP; IF voice AND n.voice#selectedVoice THEN LOOP; maxValue _ 64*256; delta _ bestValue-vs[n.voice].value; FOR nv:NoteValue IN [whole..unknown) DO IF delta=3*(maxValue/2) THEN n.dotted _ TRUE; n.value _ nv; EXIT; ENDLOOP; IF n.value=unknown THEN n.value_thirtysecond; ENDLOOP; SetVoiceSyncs[score[i], bestValue, @vs]; priorTime _ score[i].time; priorValue _ bestValue}}; ENDLOOP; END; VoiceSyncs:TYPE = ARRAY [0..10) OF RECORD[value:INTEGER]; vs:VoiceSyncs; SetVoiceSyncs:PROCEDURE[s:SyncPTR,value:INTEGER,vs:POINTER TO VoiceSyncs]= INLINE BEGIN i:CARDINAL; FOR i IN [0..syncLength) DO IF s.event[i]=NIL THEN EXIT; vs[s.event[i].voice].value _ value; ENDLOOP; END; Quantize:PROCEDURE[d:Time,q:INTEGER] RETURNS[INTEGER] = BEGIN delta:Time_(256*(64/q)); RETURN[InlineDefs.LowHalf[delta*((d+delta/2)/delta)]]; END; Complexity:PROCEDURE[s:SyncPTR, value:INTEGER, vs:POINTER TO VoiceSyncs] RETURNS[INTEGER]= BEGIN x,d:Time_128; c,y:INTEGER_0; j,k,start:CARDINAL; v:ARRAY [0..10) OF BOOLEAN_ALL[FALSE]; FOR j IN [0..syncLength) DO IF s.event[j]=NIL THEN EXIT; IF s.event[j].value#unknown THEN LOOP; IF voice AND s.event[j].voice#selectedVoice THEN LOOP; IF v[s.event[j].voice] THEN LOOP; d _ value-vs[s.event[j].voice].value; v[s.event[j].voice]_TRUE; x_x*256; start_10; FOR k IN [0..7] DO x_x/2; IF x>d THEN LOOP; d_d-x; IF start>7 THEN start_k; IF d#0 THEN LOOP; c_c+(k-start); EXIT; ENDLOOP; ENDLOOP; FOR j IN [0..10) DO IF v[j] THEN y_y+1; ENDLOOP; RETURN[(c*10)/y]; END; END... GuessLogical:PROCEDURE[begin,end:CARDINAL,time:Time] = BEGIN i,j:CARDINAL; voice:ARRAY [0..10) OF CARDINAL _ ALL[0]; vs:Voice.State; max:Time; Voice.ClearState[@vs]; FOR i IN [begin..end] DO max _ Voice.SetState[@vs,score[i]]; FOR j IN [0..maxVoice] DO IF NOT vs[j].found THEN LOOP; voice[j] _ i; ENDLOOP; ENDLOOP; END; FOR v IN [0..maxVoice] DO duration _ (time*(vs[v]-score[i].time))/measure; SELECT TRUE FROM duration IN [15000..19000] => value _ whole; duration IN [7500..9000] => value _ half; duration IN [3500..4500] => value _ quarter; duration IN [1600..2400] => value _ eighth; duration IN [900..1200] => value _ sixteenth; duration IN [200..600] => value _ thirtysecond; ENDCASE => value _ unknown; FOR j IN [0..syncLength) DO IF (n_score[i].event[j])=NIL THEN EXIT; IF n.voice#v THEN LOOP; vs[v] _ score[i].time; IF n.value#unknown THEN LOOP; n.value _ value; ENDLOOP; ENDLOOP; ENDLOOP; END; PortionOutTime:PROCEDURE[voice,s1,s2:CARDINAL,duration,ratio:INTEGER] = BEGIN --duration is the logical duration of the interval (64*256=whole) --ratio is the number of tics that a quarter note occupies beats,units,d:INTEGER; i,j,r1,r2,count:CARDINAL_0; index_1; FOR i IN (s1..s2) DO IF NOT Sync.InVoice[score[i],voice] THEN LOOP; list[index]_[score[i],FALSE]; index _ index + 1; IF score[i].value=0 THEN LOOP; PortionOutTime[voice,s1,i,score[i].value-score[s1].value,ratio]; PortionOutTime[voice,i,s2,score[s2].value-score[i].value,ratio]; RETURN; ENDLOOP; IF index=1 THEN RETURN; list[0] _ [score[s1],FALSE]; list[index] _ [score[s2],FALSE]; index_index+1; [beats,units] _ Tranlate[duration]; --3/4, 6/8, 3/16, 15/24, etc. --look for runs of equally spaced notes --then try successively finer grids --all durations must be strictly ordered d _ list[1].value-list[0].value; FOR i IN [0..index) DO list[i].ts.top_0; ENDLOOP; FOR i IN [2..index) DO IF ABS[d-list[i].value-list[i-1].value]1 THEN FOR j IN (r1..r2] DO list[j].ts.top_1; d_list[i].value-list[i-1].value; r1_i-1; ENDLOOP; FOR i IN [0..index) DO IF NOT list[i].tuple THEN count_count+1; ENDLOOP; WHILE units<=128 DO IF count>beats THEN {units_units*2; beats_beats*2; LOOP}; IF units=128 THEN RETURN; d _ CheckGrid[s1,s2,beats,units,voice]; IF dsum DO sum_sum+d; ENDLOOP; IF NOT Sync.InVoice[score[i],voice] THEN LOOP; WHILE score[i].time*ratio-delta/2>sum DO sum_sum+d; ENDLOOP; delta_ delta+ABS[score[i].time*ratio-sum]; sum _ sum+d; ENDLOOP; RETURN[InlineDefs.LowHalf[delta]]; END; AssignGrid[s1,s2:CARDINAL,unit,voice:INTEGER]= BEGIN i:CARDINAL; d:Time_256*(64/unit); sum:Time_score[s1].value; FOR i IN (s1..s2) DO WHILE score[i].value>sum DO sum_sum+d; ENDLOOP; IF NOT Sync.InVoice[score[i],voice] THEN LOOP; WHILE score[i].time*ratio-delta/2>sum DO sum_sum+d; ENDLOOP; score[i].value_InlineDefs.LowHalf[sum]; sum _ sum+d; ENDLOOP; END; Translate:PROCEDURE[duration:INTEGER] RETURNS[INTEGER]= BEGIN units:INTEGER_64; duration_duration/256; WHILE (duration MOD 2)=0 DO duration_duration/2; units_units/2; IF units=1 THEN EXIT; ENDLOOP; RETURN[duration,units]; END; index:CARDINAL; list:ARRAY [0..30) OF RECORD[sync:SyncPTR,tuple:BOOLEAN]; ss:ARRAY [0..10) OF Time; (0,4032)(1,5056)(2,6080)\542i20I79b10B590b11B545b9B697i37I72i47I680i36I323b12B761i47I318i36I468i20I85b13B785i30I5i55I137i78I76i45I219i1I70i1I4i33I103i2I145i70I121i2I17i2I707i30I2760i123I22i1I254i1I227i146I82i1I END. e6(0,3648)(1,65535)(2,65535)