#!/usr/bin/env python
from __future__ import division
import ipdb
import os
from pprint import pprint
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, deinterleave=True)
def main():
composition = {
"intro": {
"intro": {
"pan_flute": {
"channel": 8,
"octave": 5,
"duration": 60,
"vol_offset": -15,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C2' B2 | A3 D3 || B | C' | D | C2' C2' | z | (u)", "C2' C2' | C2' C2' | (x)"],
"v": ["G2 F2 | E2 F2 | D5 (u)", "B/4 C/4' B/4 A/4 | D2 D2 | z | (u)"],
"x": ["z8 | (v)"],
},
},
"taisho_koto": {
"channel": 11,
"octave": 5,
"duration": 60,
"sync": "pan_flute",
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 F/2 | D/2 z/2 | z4 | (u)", "C/2 F/2 | G/2 F/2 | (u)", "A/2 F/2 z/2 C/2 | z | (u)"],
"v": ["z4 (v)"],
"x": ["C/2 C/2 | F/2 G/2 | F/2, G/2 | z/2 C/2 | (x)", "C/2 F/2 | D/2 C/4 z/4 | F/2 G/2 A/2 C/2 | (x)"],
},
},
"vibraphone": {
"channel": 14,
"vol_offset": 27,
"octave": 5,
"sync": "pan_flute",
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["z4 (u)"],
"v": ["G2 F2 | E2 F2 | D5 (u)", "B/4 C/4' B/4 A/4 | D2 D2 | z | (u)"],
"x": ["z4 | (v)"],
},
},
"percusion": {
"channel": 9, # Orchestra kit
"octave": 4,
"duration": 60,
"grammars": {
"u": ["A ^A (u)"]
}
},
"bass": {
"channel": 4,
"sync": "pan_flute",
"octave": 2,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 C (u)"],
},
},
"horn_timbre1": {
"channel": 13, # 'Atmosphere'
"octave": 2,
"duration": 60,
"vol_offset": -15,
"grammars": { # Notes for this instrument to use in this piece
"u": ["[C]4 [D]4 (u)"],
},
},
},
"body": {
"percusion": {
"channel": 9, # Orchestra kit
"octave": 4,
"duration": 60,
"grammars": {
"u": ["A ^A (u)"]
}
},
"vibraphone": {
"channel": 14,
"vol_offset": 27,
"octave": 5,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C2' B2 | A3 D3 || B | C' | D | C2' C2' | z | (u)", "C2' C2' | C2' C2' | (x)"],
"v": ["G2 F2 | E2 F2 | D5 (u)", "B/4 C/4' B/4 A/4 | D2 D2 | z | (u)"],
"x": ["z4 | (v)"],
},
},
"bass": {
"channel": 4,
"sync": "vibraphone",
"octave": 2,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 C (u)"],
},
},
"horn_timbre1": {
"channel": 13, # 'Atmosphere'
"octave": 2,
"duration": 60,
"vol_offset": -15,
"grammars": { # Notes for this instrument to use in this piece
"u": ["[C]4 [D]4 (u)"],
},
},
},
},
"section1": {
"intro": {
"reverse_cymbal": {
"channel": 5,
"octave": 3,
"duration": 3,
"grammars": {
"u": ["B3"]
}
},
"bass": {
"channel": 4,
"octave": 2,
"duration": 3,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 C (u)"],
},
},
"percusion": {
"channel": 9, # Orchestra kit
"octave": 4,
"duration": 3,
"vol_offset": -10,
"grammars": {
"u": ["E,, | z4 | (u)"]
}
},
"atmosphere1": {
"channel": 13,
"octave": 2,
"duration": 3,
"vol_offset": -15,
"grammars": { # Notes for this instrument to use in this piece
"u": ["[C]4 (u)"],
},
},
},
"body": {
"guitar": { # Instrument 'melody'
"channel": 11,
"octave": 4,
"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)"],
},
},
"bass": { # Instrument 'bass'
"channel": 4,
"sync": "guitar",
"octave": 2,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C/2 C/2 | C/2 z/2 | (u)"],
},
},
"percusion": {
"channel": 9, # Orchestra kit
"octave": 4,
"duration": 60,
"vol_offset": -10,
"grammars": {
"u": ["^G,, | ^A,, | ^G,,/2 ^A,,/2 | E,, | z4 | (u)", "E,, | z4 | (u)"]
}
},
"atmosphere1": {
"channel": 13,
"octave": 2,
"duration": 60,
"vol_offset": -15,
"grammars": { # Notes for this instrument to use in this piece
"u": ["[C]4 [D]4 (u)"],
},
},
},
},
"blackmore1": {
"intro": {
"guitar": {
"channel": 6,
"octave": 3,
"duration": 60,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C3 | C/4 D/4 E | C/4 C | (u)"],
},
},
"contrabass": {
"channel": 13,
"octave": 2,
"duration": 60,
"vol_offset": -45,
"grammars": { # Notes for this instrument to use in this piece
"u": ["[C]4 [D]4 (u)"],
},
},
"choir": {
"channel": 7,
"octave": 3,
"duration": 60,
"vol_offset": -35,
"grammars": { # Notes for this instrument to use in this piece
"u": ["C2 G2 F2 B,2 C3 (u)"],
},
},
},
},
}
section_start = 0
for section_name in ["intro", "section1", "blackmore1"]:
print "Section " + section_name
subsection_start = section_start
section = composition[section_name]
for subsection in ["intro", "body", "outro"]:
if not section.has_key(subsection):
continue
print "\tSubsection " + 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
for sync_instr in ordered_instrs:
if sync_instr not in subsection.keys():
raise KeyError("The sync instrument '%s' does not exist in this subsection" % sync_instr)
instrs = []
syncs = {}
track = 0
for instr in ordered_instrs:
print "\t\tInstrument " + instr
# if instr == "guitar":
# ipdb.set_trace()
instr = subsection[instr]
max_time = instr["duration"]
instr_score, syncs = render_instr(instr, syncs, max_time)
instrs.append(instr_score)
volume = 100
if instr.has_key("vol_offset"):
volume += instr["vol_offset"]
mymidi.addTrackName(track, 0, instr["name"])
midify_instr_score(instr_score, track, instr["channel"], subsection_start, volume=volume)
track += 1
longest_score = max(instrs, key=lambda i: score_len(i))
subsection_start += score_len(longest_score)
section_start += score_len(longest_score)
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])):
try:
instr["grammars"][g][i] = parse.parse(instr["grammars"][g][i], default_octave=instr["octave"])
except topsort.CycleError:
print "Your syncs created a loop! Fix it."
sys.exit(1)
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["name"] == "taisho_koto":
# ipdb.set_trace()
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")
if grammar not in time_filtered_grammars.keys():
return [], syncs
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, volume):
# Assume get_midi_note()
global mymidi
for token in score:
if isinstance(token, parse.Chord):
for note in token.notes:
note = get_midi_note(note.octave, note.value)
mymidi.addNote(track=track, channel=channel,pitch=note, time=t, duration=token.duration, volume=volume)
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=volume)
elif isinstance(token, tree.Tree):
continue
t += token.duration
return []
if __name__ == "__main__": main()