% Music generation system % Jon Sneyers, September 2009 % TODO/IDEAS: % ----------- % todo: handle rests separately (not just as an option in note selection) % todo: add some global structure by adding a repetitiveness factor % (randomly copies (parts of) measures) % todo: add explicit global structure beyond repeats/1: something like % structure([a,a,b,a,b,a,b,variant(b),a,end]) % Example query: e :- play, show, t, write_notes. t :- t(8). t(M) :- meter(2,4), repeats(1), key(major), tempo(100), voice(drums), voice(bass), voice(chords), voice(melody), range(bass,g,1,c,3), range(melody,g,3,e,5), max_jump(bass,12), max_jump(melody,5), chord_style(offbeat), shortest_duration(drums,16), shortest_duration(bass,8), shortest_duration(chords,16), shortest_duration(melody,16), measures(M). /***************************** * * * Main program * * * ******************************/ % inputs :- chrism measures(+int), meter(+int,+duration), repeats(+int), key(+key), shortest_duration(+voice,+duration), tempo(+int), voice(+voice), range(+voice,+note,+int,+note,+int), max_jump(+voice,+int), instrument(+voice,+), chord_style(+cstyle), max_repeat(+voice,+int). :- chr_type key ---> major ; minor. :- chr_type voice ---> melody ; chords ; bass ; drums. :- chr_type note ---> c ; d ; e ; f ; g ; a ; b. :- chr_type duration ---> 2 ; 4 ; 8 ; 16 ; 32. :- chr_type cstyle ---> offbeat ; long ; onbeat. % outputs :- chrism measure(+measure), mchord(+int,+chord), beat(+voice,+measure,+int,+float,+duration), note(+voice,+measure,+int,+float,+), octave(+voice,+measure,+int,+float,+). :- chr_type chord ---> c ; d ; e ; f ; g ; a ; b ; cm ; dm ; em ; fm ; gm ; am ; bm. :- chr_type measure == int. % internals :- chrism make_measures(+int), next_measure(+measure,+measure), make_beats(+int,+duration,+measure,+voice), next_beat(+voice,+measure,+int,+float,+measure,+int,+float), phase(+), chord(+,+,+,+,+), octave_d(+voice,+measure,+int,+float,+), octave_rangecheck(+voice,+measure,+int,+float,+), same_note_counter(+voice,+measure,+int,+float,+int), make_notes_measure(+int), find_octave_d(+,+,+,+,+). % debug output: phase(X) ==> write(', '), write(X). key(major), measure(1) ==> mchord(1,c). key(major), measures(N) ==> mchord(N,c). key(minor), measure(1) ==> mchord(1,am). key(minor), measures(N) ==> mchord(N,am). measures(N) ==> write('[MUSIC GENERATOR] startup'), make_measures(N). make_measures(0) <=> phase(split_beats). make_measures(N) <=> N>0 | measure(N), N1 is N-1, next_measure(N1,N), make_measures(N1). mchord(A,X) \ mchord(A,Y) <=> true. % simple Markov chain chord progression mchord(A,Chord), next_measure(A,B), measures(M) ==> B < M | msw(chord_choice(Chord),NextChord), mchord(B,NextChord). % create one beat per beat meter(N,D), voice(V), measure(M) ==> make_beats(N,D,M,V). make_beats(0,_D,_M,_V) <=> true. make_beats(N,D,M,V) <=> N > 0 | N1 is N-1, next_beat(V,M,N1,0,M,N,0), beat(V,M,N1,0,D), make_beats(N1,D,M,V). meter(N,D), next_measure(M,M2) \ next_beat(V,A,B,C,M,N,E) <=> next_beat(V,A,B,C,M2,0,0). % split some of the beats in two split_beat(V) ?? meter(_,OD), phase(split_beats), shortest_duration(V,SD), measures(LastM) \ beat(V,M,N,X,D), next_beat(V,M,N,X,NM,NN,NX) <=> D phase(make_notes). % initialize first octave in middle of voice range range(V,_,L,_,U) ==> Octave is (L+U)//2, octave(V,1,0,0,Octave). max_repeat(V,_), voice(V) ==> same_note_counter(V,1,0,0,0). % compute next octave given previous and delta octave(V,M1,N1,X1,OO), next_beat(V,M1,N1,X1,M,N,X) \ octave_d(V,M,N,X,Delta) <=> NO is OO+Delta, octave_rangecheck(V,M,N,X,NO). % clip octave to the required voice range range(V,LN,LO,UN,UO) \ octave_rangecheck(V,M,N,X,O) <=> O < LO | octave(V,M,N,X,LO). range(V,LN,LO,UN,UO) \ octave_rangecheck(V,M,N,X,O) <=> O > UO | octave(V,M,N,X,UO). range(V,LN,LO,UN,UO), note(V,M,N,X,Note) \ octave_rangecheck(V,M,N,X,LO) <=> Note \== r, note_below(Note,LN) | LO1 is LO+1, octave(V,M,N,X,LO1). range(V,LN,LO,UN,UO), note(V,M,N,X,Note) \ octave_rangecheck(V,M,N,X,UO) <=> Note \== r, note_above(Note,UN) | UO1 is UO-1, octave(V,M,N,X,UO1). octave_rangecheck(V,M,N,X,O) <=> octave(V,M,N,X,O). % check max_jump constraint - fail (and backtrack) if it is violated max_jump(V,MInt), octave(V,M1,N1,X1,OO), note(V,M1,N1,X1,ON), note(V,M,N,X,NN), next_beat(V,M1,N1,X1,M,N,X) \ octave(V,M,N,X,NO) <=> interval(ON,OO,NN,NO,Int), Int > MInt | fail. max_jump(V,MInt), octave(V,M1,N1,X1,OO), note(V,M1,N1,X1,ON), note(V,M,N,X,NN), next_beat(V,M1,N1,X1,M,N,X) \ octave(V,M,N,X,NO) <=> interval(ON,OO,NN,NO,Int), Int < -MInt | fail. % maintain same_note_counter octave(V,M1,N1,X1,Octave), note(V,M1,N1,X1,Note), next_beat(V,M1,N1,X1,M,N,X), octave(V,M,N,X,NOctave), note(V,M,N,X,NNote) \ same_note_counter(V,M1,N1,X1,Count) <=> (Octave == NOctave, Note == NNote -> C1 is Count+1, same_note_counter(V,M,N,X,C1) ; same_note_counter(V,M,N,X,0) ). % check max_repeat constraint - fail (and backtrack) if it is violated max_repeat(V,N), same_note_counter(V,A,B,C,N) <=> fail. phase(make_notes) ==> make_notes_measure(1). % choose first note make_notes_measure(1), beat(V,1,0,0,D), mchord(1,C) ==> V \== drums, V \== chords | abstract_beat(1,0,0,AB), soft_msw(note_choice(V,C,AB),Note), note(V,1,0,0,Note). % choose next note and octave make_notes_measure(M), beat(V,M,N,X,D), mchord(M,C), octave(V,M1,N1,X1,OO), next_beat(V,M1,N1,X1,M,N,X) ==> V \== drums, V \== chords | abstract_beat(M,N,X,AB), soft_msw(note_choice(V,C,AB),Note), note(V,M,N,X,Note), (Note == r -> octave_d(V,M,N,X,0) ; find_octave_d(V,M,N,X,OO) ). range(V,_,Lower,_,Upper) \ find_octave_d(V,M,N,X,OO) <=> octave_compare(Lower,OO,Upper,Position), soft_msw(octave_choice(Position),Octave_Delta), octave_d(V,M,N,X,Octave_Delta). find_octave_d(V,M,N,X,OO) <=> writeln(oops-no-range-for(V)). octave_compare(L,L,_,low). octave_compare(_,U,U,high). octave_compare(L,X,U,mid) :- L < X, X < U. % force measure-per-measure note filling for search efficiency measures(Last) \ make_notes_measure(N) <=> N < Last | N1 is N+1, make_notes_measure(N1). measures(N) \ make_notes_measure(N) <=> true. phase(make_notes), beat(drums,M,N,X,D) ==> abstract_beat(M,N,X,AB), msw(drum_choice(AB),Note), note(drums,M,N,X,Note). phase(make_notes), chord_style(Style), beat(chords,M,N,X,D), mchord(M,C) ==> abstract_beat(M,N,X,AB), msw(chord_type(Style,AB),Chord), chord(C,M,N,X,Chord). chord(C,M,N,X,r) <=> note(chords,M,N,X,r). chord(c,M,N,X,0) <=> note(chords,M,N,X,c). chord(g,M,N,X,0) <=> note(chords,M,N,X,g). chord(f,M,N,X,0) <=> note(chords,M,N,X,f). chord(am,M,N,X,0) <=> note(chords,M,N,X,a+':m'). chord(em,M,N,X,0) <=> note(chords,M,N,X,e+':m'). chord(dm,M,N,X,0) <=> note(chords,M,N,X,d+':m'). chord(c,M,N,X,7) <=> note(chords,M,N,X,c+':7'). chord(g,M,N,X,7) <=> note(chords,M,N,X,g+':7'). chord(f,M,N,X,7) <=> note(chords,M,N,X,f+':7'). chord(am,M,N,X,7) <=> note(chords,M,N,X,a+':m7'). chord(em,M,N,X,7) <=> note(chords,M,N,X,e+':m7'). chord(dm,M,N,X,7) <=> note(chords,M,N,X,d+':m7'). phase(make_notes) <=> phase(join_notes). % two successive notes of the same pitch can be joined join_notes(V,cond M=M2,cond N=N2) ?? phase(join_notes), next_beat(V,M,N,X,M2,N2,X2), note(V,M2,N2,X2,Note) \ note(V,M,N,X,Note) <=> \+ has_tilde(Note), V \== drums | note(V,M,N,X,Note+' ~'). has_tilde(' ~'). has_tilde(' ~'+_). has_tilde(_ + Mods) :- has_tilde(Mods). is_rest(r). is_rest(r + ' ~'). phase(join_notes) <=> phase(output). /***************************** * * * Auxiliary predicates * * * ******************************/ % we use an abstracted beat position (first,strong,weak,prestrong,weakest) % instead of the concrete positions :- chrism abstract_beat(+,+,+,+). measures(N) \ abstract_beat(N,_,_,AB) <=> AB=first. meter(M,_) \ abstract_beat(Measure,Beat,Pos,AB) <=> abstract_beat1(M,Beat,Pos,AB). abstract_beat(_,_,_,_) <=> writeln(error_abstract_beat-measure_or_meter_missing). % abstract_beat1(+meter1, +Beat, +SubBeat, -AbstractBeat) abstract_beat1(_,0,0,first) :- !. abstract_beat1(M,N,0,strong) :- 0 is M mod 2, N is M//2, !. abstract_beat1(3,1,0,strong) :- !. abstract_beat1(3,2,0,strong) :- !. abstract_beat1(2,0,0.5,weak) :- !. abstract_beat1(2,1,0.5,weak) :- !. abstract_beat1(3,_,0.5,weak) :- !. abstract_beat1(4,1,0,weak) :- !. abstract_beat1(4,3,0,weak) :- !. abstract_beat1(6,_,0,weak) :- !. abstract_beat1(2,_,0.75,prestrong) :- !. abstract_beat1(4,1,0.5,prestrong) :- !. abstract_beat1(4,3,0.5,prestrong) :- !. abstract_beat1(3,2,0.5,prestrong) :- !. abstract_beat1(6,5,0.5,prestrong) :- !. abstract_beat1(_,_,_,weakest) :- !. % note_below(X,Y) should succeed iff X is below Y note_below(X,Y) :- note_dbelow(X,Y). note_below(X,Y) :- note_dbelow(X,Z), note_below(Z,Y). note_above(X,Y) :- note_below(Y,X). note_dbelow(c,d). note_dbelow(d,e). note_dbelow(e,f). note_dbelow(f,g). note_dbelow(g,a). note_dbelow(a,b). % interval(+note1,+octave1,+note2,+octave2,-interval) : % returns the interval (in semitones) between two pitches interval(N1,O1,N2,O2,Int) :- OctInt is 12*(O2-O1), interval(N1,N2,NoteInt), Int is OctInt + NoteInt. dinterval(c,d,2). dinterval(d,e,2). dinterval(e,f,1). dinterval(f,g,2). dinterval(g,a,2). dinterval(a,b,2). pinterval(A,B,I) :- dinterval(A,B,I). pinterval(A,B,I) :- dinterval(A,C,AC), pinterval(C,B,CB), I is AC+CB. interval(A,A,0). interval(A,B,I) :- pinterval(A,B,I). interval(A,B,-I) :- pinterval(B,A,I). % given a central note (Note,Oct), a negative number of semitones L % and a positive number of semitones U, compute the right range/5. set_range(V,Note,Oct,L,U) :- add(L,Note,Oct,LN,LO), add(U,Note,Oct,UN,UO), range(V,LN,LO,UN,UO). add(X,N,O,N2,O2) :- X > 11,!, X1 is X-12, O1 is O+1, add(X1,N,O1,N2,O2). add(X,N,O,N2,O2) :- X < 0,!, X1 is X+12, O1 is O-1, add(X1,N,O1,N2,O2). add(X,N,O,N2,O2) :- X>1, dinterval(N,N1,Y),!, NX is X-Y, add(NX,N1,O,N2,O2). add(1,b,O,c,O1) :- !,O1 is O+1. add(1,N,O,N,O). % roundoff add(0,N,O,N,O). % Backtrackable switch % this is based on the definition of msw/2, but rewritten so that % the probabilistic choice is not committed to. soft_msw(Sw,Val) :- $pp_get_distribution(Sw,Values,Pbs), !, zip(Values,Pbs,Candidates), soft_choose(Candidates,Val). zip([],[],[]). zip([Val|Vals],[Prob|Probs],[Val-Prob|Rest]) :- zip(Vals,Probs,Rest). soft_choose([],Val) :- !, fail. soft_choose(Candidates,V) :- zip(Vals,Probs,Candidates), sumlist(Probs,Sum), Sum > 0, random_uniform(Sum,R), $pp_choose(Probs,R,Vals,Val,Prob), delete(Candidates,Val-Prob,OtherOptions), (V=Val ; soft_choose(OtherOptions,V)). /****************************** * * * Output to LilyPond format * * * *******************************/ :- chrism write_notes, show(+,+,+), show_notes(+,+,+,+), voice_header(+), voice_footer(+), maybe_close_bracket, show_note(+), show_mods(+), maybe_show_octave(+,+,+,+). measures(N), meter(A,B), write_notes, voice(V) ==> voice_header(V), write('\\time '), write(A), write('/'), write(B), nl, show(V,1,N), maybe_close_bracket, writeln('}'). write_notes <=> true. show(V,N,M) <=> N>M | true. show(V,N,M) <=> N= show_note(N), maybe_show_octave(V,M,P,X), write(D), show_mods(N), write(' '). next_beat(V,M,P,X,M,P2,X2) \ show_notes(V,M,P,X) <=> show_notes(V,M,P2,X2). show_notes(V,M,P,X) <=> true. show_note(Note+Mods) <=> show_note(Note). show_note(Note) <=> write(Note). show_mods(Note+Mods) <=> show_mods(Note), write(Mods). show_mods(Note) <=> true. note(V,M,P,X,Rest) \ maybe_show_octave(V,M,P,X) <=> is_rest(Rest) | true. octave(V,M,P,X,O) \ maybe_show_octave(V,M,P,X) <=> octave_notation(O,Notation), write(Notation). maybe_show_octave(_,_,_,_) <=> true. octave_notation(7,'\'\'\'\''). octave_notation(6,'\'\'\''). %' syntax highlighter messes up octave_notation(5,'\'\''). octave_notation(4,'\''). %' syntax highlighter messes up octave_notation(3,''). octave_notation(2,','). octave_notation(1,',,'). octave_notation(0,',,,'). :- chrism tempo_declaration. meter(_,Unit), tempo(T) \ tempo_declaration <=> format(' \\tempo ~w=~w\n',[Unit,T]). tempo_declaration <=> format(' \\tempo 4=80\n',[]). voice_header(melody) ==> writeln('Melody = {'), tempo_declaration, writeln(' \\clef treble'). voice_header(bass) ==> writeln('Bass = {'), tempo_declaration, writeln(' \\clef \"bass_8\"'). voice_header(chords) ==> writeln('Chords = \\chordmode {'), tempo_declaration, writeln(' \\clef treble'). voice_header(drums) ==> writeln('Drums = \\drummode {'), tempo_declaration. repeats(N), voice_header(_) ==> write(' \\repeat unfold '),write(N),writeln(' {'). voice_header(_) <=> true. repeats(_) \ maybe_close_bracket <=> writeln(' }'). maybe_close_bracket <=> true. :- chrism play, show, playshow. play, show <=> playshow. phase(output) ==> nl. phase(output), playshow <=> tell('temp.ly'), header, write_notes, footer, show_footer, play_footer, end_footer, told. /* , lilypond, writeln('Invoking TiMidity++...'), system('timidity -iaqqq temp.midi 2>/dev/null &'), system('sleep 1'), % give timidity some startup time writeln('Invoking GV...'), system('gv -presentation temp.ps &'). */ phase(output), play <=> tell('temp.ly'), header, write_notes, footer, play_footer, end_footer, told. /* lilypond, writeln('Invoking TiMidity++...'), system('timidity -ia temp.midi'). % 2>/dev/null &'). */ phase(output), show <=> tell('temp.ly'), header, write_notes, footer, show_footer, end_footer, told. /* lilypond, writeln('Invoking GV...'), system('gv -presentation temp.ps &'). */ phase(output) <=> tell('temp.ly'), header, write_notes, footer, show_footer, play_footer, end_footer, told. %phase(output) <=> writeln(done). %phase(X) <=> writeln(should_not_happen(X)). lilypond :- writeln('Invoking GNU Lilypond...'), system('lilypond temp.ly 2>/dev/null'). % system('lilypond temp.ly'). header :- writeln('\\version "2.12.1"'). footer :- writeln('\\score{'), writeln(' <<'), show_voice_footers, writeln(' >>'). play_footer :- writeln(' \\midi {}'). show_footer :- writeln(' \\layout {}'). end_footer :- writeln('}'). :- chrism show_voice_footers. show_voice_footers, voice(V) ==> voice_footer(V). show_voice_footers <=> true. instrument(melody,I) \ voice_footer(melody) <=> write(' \\new Staff \\with {midiInstrument = #\"'),write(I),writeln('\"} \\Melody'). instrument(chords,I) \ voice_footer(chords) <=> write(' \\new Staff \\with {midiInstrument = #\"'),write(I),writeln('\"} \\Chords'). instrument(bass,I) \ voice_footer(bass) <=> write(' \\new Staff \\with {midiInstrument = #\"'),write(I),writeln('\"} \\Bass'). voice_footer(melody) <=> writeln(' \\new Staff \\with {midiInstrument = #\"soprano sax\"} \\Melody'). voice_footer(chords) <=> writeln(' \\new Staff \\with {midiInstrument = #\"acoustic guitar (steel)\"} \\Chords'). voice_footer(bass) <=> writeln(' \\new Staff \\with {midiInstrument = #\"baritone sax\"} \\Bass'). voice_footer(drums) <=> writeln(' \\new DrumStaff \\Drums'). % include msw/2 declarations and set_sw/2 statements :- include(values). %:- include(distributions). :- include('gui/saved_parameters.pl').