#!/usr/bin/env python
from __future__ import division
#import ipdb
import os
import random
import sys
import time
from midiutil.MidiFile import MIDIFile as midifile
import parse
import topsort
import yaml
import tree
random.seed(time.time())
mymidi = midifile(15)
def main():
composition = {
"intro": {
"body": {
"lead_instr": { # Instrument 'melody'
"channel": 8,
"octave": 5,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C2' B2 A3 D3 B C' D C2' z (u)", "C2' C2' C2' C2' (x)"],
"v": ["G2 F2 E2 F2 D5 (u)", "B/4 C/4' B/4 A/4 D2 z"],
"x": ["z4 (v)"],
},
},
"follow_instr": { # Instrument 'bass'
"channel": 4,
"sync": "lead_instr",
"octave": 2,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 C/2 z/2 (u)"],
},
},
},
},
"section1": {
"body": {
"lead_instr": { # Instrument 'melody'
"channel": 6,
"octave": 5,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C E A F G z (u)", "C E A F G z (v)"],
"v": ["A/2 D/2 G/2 C/2 | F/2 B/2 E/2 z/2 | (u)"],
},
},
"follow_instr": { # Instrument 'bass'
"channel": 4,
"sync": "lead_instr",
"octave": 2,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 C/2 z/2 (u)"],
},
},
},
},
}
section_start = 0
for section in ["intro", "section1"]:
print "Section " + section
subsection_start = section_start
section = composition[section]
for subsection in ["intro", "body", "outro"]:
try:
print "Subsection " + subsection
subsection = section[subsection]
unordered_instrs = []
for instr in subsection:
subsection[instr]["name"] = instr
if not "sync" in subsection[instr].keys():
subsection[instr]["sync"] = None
unordered_instrs.append([subsection[instr]["sync"], instr])
ordered_instrs = topsort.topsort(unordered_instrs)
ordered_instrs.remove(None) # None used as a placeholder for sort order for instruments with no sync setting
instrs = []
syncs = {}
track = 0
for instr in ordered_instrs:
print "Instrument " + instr
instr = subsection[instr]
max_time = instr["duration"]
instr_score, syncs = render_instr(instr, syncs, max_time)
instrs.append(instr_score)
midify_instr_score(instr_score, track, instr["channel"], subsection_start)
longest_score = max(instrs, key=lambda i: score_len(i))
subsection_start += score_len(longest_score)
section_start += score_len(longest_score)
track += 1
except KeyError:
pass
with open("out.mid", "wb") as outfile:
mymidi.writeFile(outfile)
def render_instr(instr, syncs, max_time):
for g in instr["grammars"]:
for i in range(len(instr["grammars"][g])):
instr["grammars"][g][i] = parse.parse(instr["grammars"][g][i], default_octave=instr["octave"])
score= []
try:
score, syncs = choose_phrase(instr, syncs, 0, max_time, None)
while True:
score_index_to_replace = None
for item in range(len(score)): # Optimize this by caching the index of the last node I replaced and startng there
if isinstance(score[item], tree.Tree): # Also, make this use the find_next_node() function (or whatever I called it)
score_index_to_replace = item
if score_index_to_replace is None:
raise ValueError("No more nodes to fill in")
time_remaining = max_time - score_len(score)
new_phrase, syncs = choose_phrase(instr, syncs, score_len(score), time_remaining, score)
score = score[:score_index_to_replace] + new_phrase + score[score_index_to_replace+1:]
except ValueError:
return (score, syncs)
def choose_phrase(instr, syncs, current_time, time_remaining, score):
'''Filters grammars for ones that match the sync option, and phrases that fit the time remaining in the score'''
time_filtered_grammars = {}
for grammar in instr["grammars"]:
fitting_phrases = get_phrases_that_fit(instr["grammars"][grammar], time_remaining)
if len(fitting_phrases) > 0:
time_filtered_grammars[grammar] = fitting_phrases
if len(time_filtered_grammars.keys()) == 0:
raise ValueError("No available grammars that will fit in the score")
grammar = None
if instr["sync"] is not None:
guiding_instr = instr["sync"]
sync_node = get_sync_node_at_time(syncs[guiding_instr], current_time)
if sync_node in time_filtered_grammars.keys():
grammar = sync_node
else:
grammar = random.choice(time_filtered_grammars.keys())
if score is None:
grammar = random.choice(time_filtered_grammars.keys())
elif instr["sync"] is None:
grammar = get_next_node(score);
if grammar not in instr["grammars"].keys():
raise Exception("You tried to direct a grammar to a node that doesn't exist")
phrases = time_filtered_grammars[grammar]
if instr["name"] not in syncs.keys():
syncs[instr["name"]] = []
syncs[instr["name"]].append({"node": grammar, "time": current_time})
return random.choice(phrases), syncs
def get_phrases_that_fit(grammar, time_remaining):
valid_phrases = []
for phrase in grammar:
if score_len(phrase) <= time_remaining:
valid_phrases.append(phrase)
return valid_phrases
def get_sync_node_at_time(syncs, t):
for s in range(len(syncs)):
if syncs[s]["time"] >= t:
return syncs[s]["node"]
def get_next_node(score):
for token in score:
if isinstance(token, tree.Tree):
return token.name
def score_len(score):
total = 0
for n in score:
if not isinstance(n, tree.Tree):
total += n.duration
return total
def get_midi_note(octave, note):
return note + 12 * (octave+1)
def midify_instr_score(score, track, channel, t):
# Assume get_midi_note()
global mymidi
for token in score:
if isinstance(token, parse.Chord): # Chords
for note in token.chord:
note = get_midi_note(token.octave, note)
mymidi.addNote(track=track, channel=channel,pitch=note, time=t, duration=token.duration, volume=100)
elif isinstance(token, parse.Note): # Individual notes
note = get_midi_note(token.octave, token.value)
mymidi.addNote(track=track, channel=channel,pitch=note, time=t, duration=token.duration, volume=100)
elif isinstance(token, tree.Tree):
continue
t += token.duration
return []
if __name__ == "__main__": main()