├── Animator.py ├── ConvertColor.py ├── RunBeethoven74Anim.py ├── RunShosti10Anim.py ├── MusAnimLexer.py ├── MidiLexer.py ├── RunGouldAnim1080.py ├── RunGouldAnim.py └── MusAnimRenderer.py /Animator.py: -------------------------------------------------------------------------------- 1 | import MidiLexer 2 | 3 | if __name__ == '__main__': 4 | midi_file = MidiLexer.lex("simplescale.MID") 5 | MidiLexer.add_time_seconds(midi_file) 6 | for event in midi_file.events: 7 | print str(event) -------------------------------------------------------------------------------- /ConvertColor.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | for num in sys.argv[1:]: 4 | num = int("0x"+num,0) 5 | red = (num & 0xFF0000) >> 16 6 | green = (num & 0x00FF00) >> 8 7 | blue = (num & 0x0000FF) 8 | red = round((red + 0.0) / 255, 3) 9 | green = round((green + 0.0) / 255, 3) 10 | blue = round((blue + 0.0) / 255, 3) 11 | print (red, green, blue) -------------------------------------------------------------------------------- /RunBeethoven74Anim.py: -------------------------------------------------------------------------------- 1 | from MusAnimRenderer import MusAnimRenderer 2 | 3 | def main(): 4 | # the filename of the midi file to read music from 5 | input_midi_filename = "beethoven74/beethoven74fin02.MID" 6 | # the directory to save the generated frames to 7 | frame_save_dir = "beethoven74/full01/" 8 | 9 | # rendering parameters of all midi tracks in the midi file 10 | tracks = [ 11 | {}, # dummy track if first track is just meta events 12 | { 'name': "vln1", 13 | 'color': (0.973, 0.129, 0.093), # red 14 | 'width': 24, 15 | 'z-index': 4 # higher is on top 16 | }, 17 | { 'name': "vln2", 18 | 'color': (0.929, 0.710, 0.137), # yellow 19 | 'width': 24, 20 | 'z-index': 3 21 | }, 22 | { 'name': "vla", 23 | 'color': (0.078, 0.659, 0.129), # green 24 | 'width': 24, 25 | 'z-index': 2 26 | }, 27 | { 'name': "vc", 28 | 'color': (0.098, 0.443, 1), # blue 29 | 'width': 24, 30 | 'z-index': 1 31 | }, 32 | ] 33 | 34 | # for changing the block speed in the middle of an animation 35 | speed_map = [ 36 | {'time': 0.0, 'speed': 5}, # time is in seconds from first event 37 | {'time': 99.217, 'speed': 12} 38 | ] 39 | 40 | # dimensions of generated image files 41 | dimensions = 1920, 1080 42 | # the intended fps of the animation 43 | fps = 29.97 44 | # pitches to be displayed at bottom and top of screen 45 | min_pitch, max_pitch = 34, 98 46 | 47 | # debugging options 48 | first_frame, last_frame = None, None 49 | every_nth_frame = 1 50 | do_render = True 51 | 52 | # enough config, let's render that shit 53 | renderer = MusAnimRenderer() 54 | renderer.render(input_midi_filename, frame_save_dir, tracks, speed_map=speed_map, 55 | dimensions=dimensions, first_frame=first_frame, last_frame=last_frame, 56 | min_pitch=min_pitch, max_pitch=max_pitch, 57 | every_nth_frame=every_nth_frame, do_render=do_render) 58 | 59 | 60 | if __name__ == '__main__': 61 | main() -------------------------------------------------------------------------------- /RunShosti10Anim.py: -------------------------------------------------------------------------------- 1 | from MusAnimRenderer import MusAnimRenderer 2 | 3 | def main(): 4 | # the filename of the midi file to read music from 5 | input_midi_filename = "shostakovich10/shostakovich10midi01.MID" 6 | # the directory to save the generated frames to 7 | frame_save_dir = "shostakovich10/cut01/" 8 | 9 | # rendering parameters of all midi tracks in the midi file 10 | tracks = [ 11 | {}, # dummy track if first track is just meta events 12 | { 'name': "picc", 13 | 'color': (0.725, 0.871, 1.0), 14 | 'width': 20, 15 | 'layer': 3, 16 | }, 17 | { 'name': "fl", 18 | 'color': (0.475, 0.749, 1.0), 19 | 'width': 20, 20 | 'layer': 3, 21 | }, 22 | { 'name': "ob", 23 | 'color': (0.231, 0.431, 1.0), 24 | 'width': 20, 25 | 'layer': 2, 26 | }, 27 | { 'name': "ebclar", 28 | 'color': (0.31, 0.82, 1.0), 29 | 'width': 20, 30 | 'layer': 2, 31 | }, 32 | { 'name': "bbclar", 33 | 'color': (0.0, 0.643, 0.933), 34 | 'width': 20, 35 | 'layer': 2, 36 | }, 37 | { 'name': "bsn", 38 | 'color': (0.098, 0.118, 1.0), 39 | 'width': 20, 40 | 'layer': 2, 41 | }, 42 | { 'name': "cbsn", 43 | 'color': (0.102, 0.118, 0.784), 44 | 'width': 20, 45 | 'layer': 2, 46 | }, 47 | { 'name': "horn", 48 | 'color': (1, .611, .098), 49 | 'width': 22, 50 | 'layer': 2, 51 | }, 52 | { 'name': "horn2", 53 | 'color': (1, .360, .004), 54 | 'width': 22, 55 | 'layer': 2, 56 | }, 57 | { 'name': "tpt", 58 | 'color': (1.0, 0.824, 0.176), 59 | 'width': 22, 60 | 'layer': 2, 61 | }, 62 | { 'name': "tpt1", 63 | 'color': (0.902, 0.667, 0.0), 64 | 'width': 22, 65 | 'layer': 2, 66 | }, 67 | { 'name': "tbn", 68 | 'color': (0.992, 0.157, 0.075), 69 | 'width': 22, 70 | 'layer': 2, 71 | }, 72 | { 'name': "tbn2", 73 | 'color': (0.886, 0.0, 0.086), 74 | 'width': 22, 75 | 'layer': 2, 76 | }, 77 | { 'name': "timp", 78 | 'color': (0.659, 0.318, 0.212), 79 | 'width': 36, 80 | 'layer': 0, 81 | }, 82 | { 'name': "xyl", 83 | 'color': (0.945, 0.671, 1.0), 84 | 'width': 22, 85 | 'layer': 6, 86 | }, 87 | { 'name': "snare", 88 | 'color': (0.816, 0.631, 0.361), 89 | 'width': 20, 90 | 'layer': 0, 91 | }, 92 | { 'name': "vln1", 93 | 'color': (0.725, .796, .043), 94 | 'width': 12, 95 | 'layer': 5, 96 | }, 97 | { 'name': "vln2", 98 | 'color': (0.53, .8, .054), 99 | 'width': 12, 100 | 'layer': 5, 101 | }, 102 | { 'name': "vla", 103 | 'color': (0.164, .67, .082), 104 | 'width': 12, 105 | 'layer': 5, 106 | }, 107 | { 'name': "vc", 108 | 'color': (0.016, .509, .180), 109 | 'width': 12, 110 | 'layer': 5, 111 | }, 112 | { 'name': "cb", 113 | 'color': (0.031, .463, .317), 114 | 'width': 12, 115 | 'layer': 5, 116 | }, 117 | ] 118 | 119 | # for changing the block speed in the middle of an animation 120 | speed_map = [ 121 | {'time': 0.0, 'speed': 10}, # time is in seconds from first event 122 | ] 123 | 124 | # dimensions of generated image files 125 | dimensions = 1920, 1080 126 | # the intended fps of the animation 127 | fps = 29.97 128 | # pitches to be displayed at bottom and top of screen 129 | min_pitch, max_pitch = 28, 108 130 | 131 | # debugging options 132 | first_frame, last_frame = None, None 133 | every_nth_frame = 1 134 | do_render = True 135 | 136 | # enough config, let's render that shit 137 | renderer = MusAnimRenderer() 138 | renderer.render(input_midi_filename, frame_save_dir, tracks, speed_map=speed_map, 139 | dimensions=dimensions, first_frame=first_frame, last_frame=last_frame, 140 | min_pitch=min_pitch, max_pitch=max_pitch, 141 | every_nth_frame=every_nth_frame, do_render=do_render) 142 | 143 | 144 | if __name__ == '__main__': 145 | main() -------------------------------------------------------------------------------- /MusAnimLexer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class MidiLexer: 4 | ticks_per_quarter = 0 5 | bpm = 120 6 | # midi events just stores all relevant midi events and their global time in 7 | # beats, without making any pretenses about figuring out timing in seconds. 8 | # That has to be done later, once we have all the timing events sorted 9 | midi_events = [] 10 | 11 | def get_v_time(self, data): 12 | """Picks off the variable-length time from a block of data and returns 13 | both pieces as a tuple, with the time in ticks""" 14 | i = 0 15 | time_bytes = [] 16 | while (ord(data[i]) & 0x80) >> 7 == 1: 17 | time_bytes.append(ord(data[i]) & 0x7F) 18 | i += 1 19 | time_bytes.append(ord(data[i]) & 0x7F) 20 | time_bytes.reverse() 21 | d_time = 0 22 | for j in range(0, len(time_bytes)): 23 | d_time += (time_bytes[j] << j * 7) 24 | return d_time, data[i+1:] 25 | 26 | def read_midi_event(self, track_data, time, track_num): 27 | # have to read off vtime first! 28 | d_time, track_data = self.get_v_time(track_data) 29 | time += d_time 30 | 31 | if track_data[0] == '\xff': 32 | # event is meta event, we do nothing unless it's a tempo event 33 | if ord(track_data[1]) == 0x51: 34 | #print track_num, list(track_data) 35 | # tempo event 36 | mpqn = ((ord(track_data[3]) << 16) + (ord(track_data[4]) << 8) 37 | + ord(track_data[5])) # microseconds per quarter note 38 | bpm = 60000000.0 / mpqn 39 | self.midi_events.append({'type': 'tempo', 'time': time, 40 | 'bpm': bpm}) 41 | return track_data[6:], time 42 | else: # just skip past it and do nothing 43 | length = ord(track_data[2]) 44 | return track_data[length+3:], time 45 | 46 | # otherwise we we assume it's a midi event 47 | elif ((ord(track_data[0]) & 0xF0) >> 4) == 0x8: 48 | # note off event 49 | # don't add a note off event if keyswitch (pitch below 12) 50 | pitch = ord(track_data[1]) 51 | if pitch >= 12: 52 | self.midi_events.append({'type': 'note_off', 'time': time, 53 | 'pitch': pitch, 'track_num': track_num}) 54 | return track_data[3:], time 55 | 56 | elif ((ord(track_data[0]) & 0xF0) >> 4) == 0x9: 57 | # note on event 58 | pitch = ord(track_data[1]) 59 | if pitch < 12: # it's a keyswitch! 60 | if pitch == 0: 61 | mode = "normal" 62 | elif pitch == 1: 63 | mode = "pizz" 64 | else: 65 | raise Exception("Unknown keyswitch") 66 | self.midi_events.append({'type': 'keyswitch', 'time': time, 67 | 'track_num': track_num, 'mode': mode}) 68 | else: 69 | self.midi_events.append({'type': 'note_on', 'time': time, 70 | 'pitch': ord(track_data[1]), 'track_num': track_num}) 71 | return track_data[3:], time 72 | elif ((ord(track_data[0]) & 0xF0) >> 4) == 0xC: 73 | return track_data[2:], time # ignore some other events 74 | elif ((ord(track_data[0]) & 0xF0) >> 4) == 0xB: 75 | return track_data[3:], time 76 | else: 77 | raise Exception("Unknown midi file data event: " + str(ord(track_data[0]))) 78 | 79 | def lex(self, filename): 80 | """Returns block list for musanim from a midi file given in filename""" 81 | import re 82 | 83 | # init stuff 84 | self.midi_events = [] 85 | self.bpm = 120 86 | self.ticks_per_quarter = 960 87 | blocks = [] 88 | 89 | # open and read file 90 | f = open(filename, 'rb') 91 | s = f.read() 92 | 93 | # grab header 94 | header = s[0:14] 95 | f_format = ord(s[8]) << 8 | ord(s[9]) 96 | num_tracks = ord(s[10]) << 8 | ord(s[11]) 97 | self.ticks_per_quarter = ord(s[12]) << 8 | ord(s[13]) 98 | 99 | tracks_chunk = s[14:] 100 | # individual track data as entries in list 101 | tracks = [track[4:] for track in re.split("MTrk", tracks_chunk)[1:]] 102 | track_num = 0 103 | for track in tracks: 104 | time = 0 105 | # parse midi events for a single track 106 | while len(track) > 0: 107 | # read off midi events and add to midi_events 108 | track, time = self.read_midi_event(track, time, track_num) 109 | track_num += 1 110 | 111 | # convert all times from ticks to beats, for convenience 112 | for event in self.midi_events: 113 | event['time'] = (event['time'] + 0.0) / 960 114 | 115 | self.midi_events.sort(lambda a, b: cmp(a['time'], b['time'])) 116 | return self.midi_events 117 | 118 | 119 | if __name__ == '__main__': 120 | lexer = MidiLexer() 121 | blocks = lexer.lex('multitrackmidi01.MID') 122 | print blocks -------------------------------------------------------------------------------- /MidiLexer.py: -------------------------------------------------------------------------------- 1 | from itertools import tee, izip_longest 2 | 3 | # midi event types 4 | NOTE_OFF = 0x8 5 | NOTE_ON = 0x9 6 | KEY_AFTERTOUCH = 0xA 7 | CONTROL_CHANGE = 0xB 8 | PROGRAM_CHANGE = 0xC 9 | CHANNEL_AFTERTOUCH = 0xD 10 | PITCH_BEND = 0xE 11 | META = 0xF 12 | 13 | # meta event types 14 | SEQUENCE_NUMBER = 0x00 15 | TEXT_EVENT = 0x01 16 | COPYRIGHT_NOTICE = 0x02 17 | TRACK_NAME = 0x03 18 | INSTRUMENT_NAME = 0x04 19 | LYRICS = 0x05 20 | MARKER = 0x06 21 | CUE_POINT = 0x07 22 | CHANNEL_PREFIX = 0x20 23 | END_OF_TRACK = 0x2F 24 | SET_TEMPO = 0x51 25 | SMPTE_OFFSET = 0x54 26 | TIME_SIGNATURE = 0x58 27 | KEY_SIGNATURE = 0x59 28 | 29 | class MidiFile(): 30 | def __init__(self, tpqn, events=[]): 31 | self.tpqn = tpqn 32 | self.events = events 33 | 34 | class MidiEvent(): 35 | def __init__(self, event_type, params): 36 | self.event_type = event_type 37 | self.params = params 38 | 39 | def __str__(self): 40 | class_name = str(self.__class__) 41 | class_name = class_name[class_name.find('.') + 1:] 42 | return "{:20}{}".format(class_name, self.__dict__) 43 | 44 | class NoteOnEvent(MidiEvent): 45 | def __init__(self, pitch, velocity): 46 | self.pitch = pitch 47 | self.velocity = velocity 48 | 49 | class NoteOffEvent(MidiEvent): 50 | def __init__(self, pitch, velocity): 51 | self.pitch = pitch 52 | self.velocity = velocity 53 | 54 | class ControlChangeEvent(MidiEvent): 55 | def __init__(self, control_number, control_value): 56 | self.control_number = control_number 57 | self.control_value = control_value 58 | 59 | class ProgramChangeEvent(MidiEvent): 60 | def __init__(self, program): 61 | self.program = program 62 | 63 | class TrackNameEvent(MidiEvent): 64 | def __init__(self, name): 65 | self.name = name 66 | 67 | class EndOfTrackEvent(MidiEvent): 68 | def __init__(self): 69 | pass 70 | 71 | class TempoEvent(MidiEvent): 72 | def __init__(self, microspqn): 73 | self.microspqn = microspqn 74 | self.bpm = 60000000.0 / self.microspqn 75 | 76 | class TimeSignatureEvent(MidiEvent): 77 | def __init__(self, numerator, denominator_power, metronome, thirtyseconds): 78 | self.numerator = numerator 79 | self.denominator = 2 ** denominator_power 80 | self.metronome = metronome 81 | self.thirtyseconds = thirtyseconds 82 | 83 | class KeySignatureEvent(MidiEvent): 84 | def __init__(self, num_sharps, minor): 85 | # convert signed byte num_sharps into int: 86 | self.num_sharps = num_sharps - 256 * (num_sharps > 127) 87 | self.major = not bool(minor) 88 | 89 | def pop_bytes(li, num_bytes): 90 | """Removes num_bytes number of bytes off the left side of list, then 91 | returns those bytes.""" 92 | popped_bytes = li[:num_bytes] 93 | del li[:num_bytes] 94 | return popped_bytes 95 | 96 | def pop_all_bytes(li): 97 | """Pops all the bytes of the list, returns basically a clone of the list 98 | while leaving the original list empty.""" 99 | popped_bytes = li[:] 100 | del li[:] 101 | return popped_bytes 102 | 103 | def pop_byte(li): 104 | return pop_bytes(li, 1)[0] 105 | 106 | def bytes_to_int(li): 107 | """Converts a bytearray to an int, treating all the bytes as a single 108 | integer most significant byte first.""" 109 | num = 0 110 | for i, b in enumerate(reversed(li)): 111 | num += (int(b) << (8 * i)) 112 | return num 113 | 114 | def open_midi_file(filename): 115 | f = open(filename, 'rb') 116 | return bytearray(f.read()) 117 | 118 | def get_header(midi_data): 119 | """Pops off the (always) 14-byte header from the midi file.""" 120 | return pop_bytes(midi_data, 14) 121 | 122 | def get_tpqn(header): 123 | """Grabs the ticks per quarter note data from the header chunk. The number 124 | is the last two bytes of the chunk, compute from values in the bytearray.""" 125 | return bytes_to_int(header[12:]) 126 | 127 | def pop_track_chunk(midi_data): 128 | """Pops off bytes from midi_data until MTrk is seen, to return a block of 129 | data for a single midi track. If a following MTrk not seen, returns data to 130 | the end of the midi file.""" 131 | if midi_data[:4] != 'MTrk': return None 132 | del midi_data[:8] # remove 'MTrk' and 4 more bytes for track chunk size 133 | num_bytes = midi_data.find('MTrk') 134 | if num_bytes == -1: 135 | return pop_all_bytes(midi_data) 136 | else: 137 | return pop_bytes(midi_data, num_bytes) 138 | 139 | def highest_bit(byte): 140 | return byte >> 7 141 | 142 | def lowest_seven_bits(byte): 143 | return byte & 0x7F 144 | 145 | def highest_four_bits(byte): 146 | return byte >> 4 147 | 148 | def lowest_four_bits(byte): 149 | return byte & 0x0F 150 | 151 | def pop_dtime(midi_data): 152 | dtime = 0 153 | while True: 154 | dtime <<= 7 155 | byte = pop_byte(midi_data) 156 | dtime += lowest_seven_bits(byte) 157 | if not highest_bit(byte): break 158 | return dtime 159 | 160 | def pop_command_byte(midi_data): 161 | byte = pop_byte(midi_data) 162 | command = highest_four_bits(byte) 163 | channel = lowest_four_bits(byte) 164 | return command, channel 165 | 166 | def make_midi_event_obj(event_type, params): 167 | if event_type == NOTE_ON: 168 | return NoteOnEvent(*params) 169 | elif event_type == NOTE_OFF: 170 | return NoteOffEvent(*params) 171 | elif event_type == CONTROL_CHANGE: 172 | return ControlChangeEvent(*params) 173 | elif event_type == PROGRAM_CHANGE: 174 | return ProgramChangeEvent(*params) 175 | elif event_type == TRACK_NAME: 176 | return TrackNameEvent(str(params)) 177 | elif event_type == END_OF_TRACK: 178 | return EndOfTrackEvent() 179 | elif event_type == SET_TEMPO: 180 | return TempoEvent(bytes_to_int(params)) 181 | elif event_type == TIME_SIGNATURE: 182 | return TimeSignatureEvent(*params) 183 | elif event_type == KEY_SIGNATURE: 184 | return KeySignatureEvent(*params) 185 | return MidiEvent(event_type, params) 186 | 187 | def pop_midi_event(midi_data): 188 | """Pops a midi event off the midi_data bytearray after dtime has already 189 | been popped. Begins constructing a MidiEvent object to put in the MidiFile's 190 | events list.""" 191 | command_type, channel = pop_command_byte(midi_data) 192 | if command_type == META: 193 | meta_type, num_bytes = pop_bytes(midi_data, 2) 194 | data = pop_bytes(midi_data, num_bytes) 195 | return make_midi_event_obj(meta_type, data) 196 | else: 197 | if command_type in (PROGRAM_CHANGE, CHANNEL_AFTERTOUCH): 198 | num_bytes = 1 199 | else: 200 | num_bytes = 2 201 | params = list(pop_bytes(midi_data, num_bytes)) 202 | return make_midi_event_obj(command_type, params) 203 | 204 | def lex(filename): 205 | """Does lexical analysis on a midi file, returning a MidiFile object 206 | that contains the midi data in Python data structures. All midi events are 207 | stored in the midi_file.events list. lex() first pops off the header chunk, 208 | then while more data is found in the midi file, continues popping off track 209 | chunks, from each of those alternatively popping off delta-time and midi 210 | event blocks. MidiEvent objects are constructed and stored in 211 | midi_file.events after being labelled with their time and track number.""" 212 | midi_data = open_midi_file(filename) 213 | header = get_header(midi_data) 214 | tpqn = get_tpqn(header) 215 | midi_file = MidiFile(tpqn) 216 | track_num = 0 217 | while midi_data: 218 | time_ticks = 0 219 | track_data = pop_track_chunk(midi_data) 220 | while track_data: 221 | time_ticks += pop_dtime(track_data) 222 | event = pop_midi_event(track_data) 223 | event.time_ticks = time_ticks 224 | event.track = track_num 225 | midi_file.events.append(event) 226 | track_num += 1 227 | return midi_file 228 | 229 | def pairwise(iterable): 230 | """From itertools recipes. s -> (s0,s1), (s1,s2), (s2, s3), ...""" 231 | a, b = tee(iterable) 232 | next(b, None) 233 | return izip_longest(a, b, fillvalue=None) 234 | 235 | def get_tempo_map(midi_file): 236 | return filter(lambda e: isinstance(e, TempoEvent), midi_file.events) 237 | 238 | def ticks_to_seconds(time_ticks, tempo_event, tpqn): 239 | quarters = (time_ticks + 0.0) / tpqn 240 | micros = quarters * tempo_event.microspqn 241 | return micros / 1000000 242 | 243 | def ticks_to_seconds_multi(time_ticks, tempo_map, tpqn): 244 | seconds = 0.0 245 | for cur_tempo, next_tempo in pairwise(tempo_map): 246 | if not next_tempo or time_ticks < next_tempo.time_ticks: 247 | return seconds + ticks_to_seconds(time_ticks - cur_tempo.time_ticks, 248 | cur_tempo, tpqn) 249 | seconds += ticks_to_seconds(next_tempo.time_ticks, cur_tempo, tpqn) 250 | 251 | def add_time_seconds(midi_file): 252 | tempo_map = get_tempo_map(midi_file) 253 | for event in midi_file.events: 254 | event.time_seconds = ticks_to_seconds_multi(event.time_ticks, tempo_map, 255 | midi_file.tpqn) 256 | 257 | if __name__ == '__main__': 258 | midi_file = lex("testsimplemidi01.MID") 259 | add_time_seconds(midi_file) 260 | for event in midi_file.events: 261 | print str(event) -------------------------------------------------------------------------------- /RunGouldAnim1080.py: -------------------------------------------------------------------------------- 1 | from MusAnimRenderer import MusAnimRenderer 2 | import string 3 | 4 | def main(): 5 | # the filename of the midi file to read music from 6 | input_midi_filename = "gould/gouldsyncfull04.MID" 7 | # the directory to save the generated frames to, relative to this file 8 | frame_save_dir = "gould/1080p01/" 9 | 10 | soprano_lyrics = '''So you want to write a fugue? * * You've got the urge to 11 | write a fugue. * * You've ^got the nerve to write * a * fugue. So go * a- * 12 | ^head, go a- head and * write a fugue. * Write a fugue that * we can sing. Give 13 | no mind to what we've told you. Give no heed to what we've told you. Pay no mind 14 | to what we've told you, what _we've told you. Just for- get all that we've told 15 | you and the theo- ry that you've read. Pay no mind, give no heed to * what we've 16 | * told you. Pay no mind to what we've told you, what we've said. Come and write 17 | one. ^Oh, do come and write one, oh, write us a fugue. Yes, write a fugue that we 18 | can sing, that we can sing. ^For ^the on- ly way to ^write ^one is just to plunge 19 | right in and ^write * one. ^So just for get the rules * and * write one, have _a 20 | try. * For the on- ly way to write * one is just to plunge right in and ^write * 21 | ^one. So just ig- nore the rules * and * write one. It's * a * pleas- ure that is 22 | bound * to * sat- is- fy. And the fun of it ^will ^get you, And the _joy of ^it will 23 | _fetch you, it will fetch you. ^You'll de- cide ^that John Se- bas- ^tian must _have 24 | been a ver- y per- son- a- ble guy. And * a * bit of aug- ^men- _ta- tion is a se- 25 | ri- ous temp- ta- tion, While a stret- ti dim- in- u- * tion, is an ob- vi- ous 26 | so- lu- * tion, While a stret- ti, stret- ti ^stret- _ti ^dim- _in- u- tion is a ver- 27 | y, ver- y ob- vi- ous sol- u- tion. Nev- er be clev- er for the sake of show- 28 | ing off. No, ^nev- er be clev- er for the sake of be- ing clev- ^er. But do try to 29 | write a fugue that we can sing, * * that we can sing. * * Just write a fugue 30 | that we * can sing. * Now, why don't you try to write ^one? Try to write a fugue 31 | for sing- ing. Write us a ^fugue _that we can sing. Come a- long now. It's rath- 32 | er awe- some is- n't it? Well? Yes. Now _we're going to write a fugue. * _We're 33 | going to write a fugue _right now. 34 | ''' 35 | alto_lyrics = '''^So you want to write a _fugue? * * You've ^got the urge to 36 | ^write a fugue. * * You've ^got the nerve to write * a * fugue. * You've got the 37 | nerve to write * a * fugue that we * can * sing. Come a- long, go a- ^head and 38 | _write one. Go a- head and write one, and write a good fugue, one that we can 39 | sing. Go a- head * * write a fugue that * we can sing. Go a- head * and write a 40 | fugue for sing- ing. Pay no ^heed to what we've told you, Pay no ^mind ^to what 41 | we've told you. Give no heed to what we've told you, ^to what we've told you. 42 | Just for- get all that we've told you and the theo- ry that * you've read, the 43 | theo- ry that you've ^read. Pay no mind, give no heed to * what we've * told you. 44 | Oh, give you mind to what we've * said. Pay no mind, give no heed to what we've 45 | told you, what we've said. ^For the _on- _ly _way _to write one is just to _plunge 46 | right in and write * one. So just for _get the _rules * and * write one. Why * 47 | don't * you * have a try? For the on- ly way to write * one is just to 48 | _plunge right in and write * one. So just ig- nore the rules * and * write a 49 | fugue. It's a pleas- ure that is bound _to sat- is- fy. And the fun of it will 50 | get you, And _the _joy of it will _fetch you. _You'll de- cide that _John Se- bas- 51 | tian, _You'll de- cide that _John Se- bas- _tian was a per- son- a- ble guy. 52 | But nev- er be clev- er for the sake of be- ing clev- er, For a can- on in in- 53 | ver- sion is a dan- ger- ous di- ver- sion. A bit of aug- ^men- _ta- tion is a se- 54 | ri- ous temp- ta- * tion. Nev- er be clev- er for _the sake of show- * ing off. 55 | But nev- er be clev- er for the sake of be- ing clev- 56 | er, for the sake of * show- * * * * * * * * * * ing _off. Now, ^why don't you 57 | write _a ^fugue, * * _why don't ^you try to ^write * one? * Write us a fugue * that 58 | we * can sing. * Now come a- long. And when you've fin- ished writ- ing it I 59 | think you'll ^find a great joy in it. Well? Why not? Now _we're going to write a 60 | fugue. * _We're going to write a fugue _right now. 61 | ''' 62 | tenor_lyrics = '''So you want to write a fugue? * * You've got the urge to 63 | write a fugue. * * You've ^got the nerve to write * a * fugue. So go * a- * ^head, 64 | * so go a- head, * and write a fugue. * * * * * So go a- head and ^write ^a fugue 65 | that we * can * _sing. Go a- head, write a fugue, and write ^a fugue that we can 66 | ^sing. Go a- head, _write _a fugue that _we can sing * * that we can sing * * and 67 | write a fugue that we can sing. Come a- long, go a- head, write a _fugue ^that we 68 | can sing. Go a- head, write a fugue * that * we can sing, * * * * and write a 69 | fugue that we can _sing. Write _a good fugue, one for sing- ing. Write a fugue 70 | that we can sing. So * go a- head * and write a fugue, and ^write a fugue that * 71 | we can sing. _Come, write a good fugue. Come, write ^a good fugue. _Come, write a 72 | good fugue. Pay no mind to what we've told you. Just for- get all that we've 73 | told you and the theo- ry * that you've * read, the theo- ry that you've ^read. 74 | * _Pay no * mind, give no heed to * what we've * told you. ^For the on- ly way _to 75 | write * one is just to plunge right in and write * one. So just for- get the 76 | rules * and * write one. Have * a * try, have a try, * have a _try. For the on 77 | ly way to write one is to plunge right _in and write one. Just ig- nore the rules 78 | * and * try. And the fun of it will get you, _And the joy of it will fetch _you, 79 | It's a pleas- ure that ^is bound to sat- is- fy, to sat- is- fy. So why don't you 80 | try? For the on- ly way to write one is to ^plunge right in. * * * * * * * * * 81 | You'll _de- cide that John Se- bas- _tian must have been a ver- y per- son- a- 82 | ble, been a ver- y per- son- a- ble guy. So nev- er be clev- er For the sake of 83 | be- ing clev- er, For the sake of show- ing off. Nev- er be clev- er for the 84 | sake of show- ing off. ^So you want _to write a fugue. * * You've got the urge to 85 | write a fugue. * * _You've got the nerve to write * a * fugue. * You've got ^the 86 | urge to write * a * fugue that we * can * ^sing. _So write a fugue _that ^we can 87 | sing. Why _don't you _try to write _one? _Write a fugue _that we can _sing. * Write 88 | a fugue that we can sing. * Oh, come a long. Well? Yes. Now _We're going to write 89 | a good _one. _We're going to write a fugue _right now. 90 | ''' 91 | bass_lyrics = '''So you want to write a fugue? * * You've got the urge to 92 | write a fugue, * * You've ^got the nerve to write * a * fugue. So go * a- * head, 93 | so go * a- * head, go a- head ^and write a fugue, Go a- head, go a- head, go a- 94 | head and write a fugue, write ^a fugue. Go a- head, write a fugue. You've ^got the 95 | nerve to write ^a fugue. So come a- * long * and * write a * fugue. * Go a- head, 96 | * write ^a fugue. * Oh, come ^a- long and write _a fugue * that * we * can * sing, 97 | that we can sing. Go a- head, write a fugue that we can sing. Go a- head, write 98 | a fugue, write a fugue _that we can sing. Write a good fugue, one that we can 99 | sing, And write a good fugue, one that we can sing. Come a- long, write a fugue, 100 | * that * we can * sing. Write a good fugue, one that we can * sing, that we can 101 | sing. Come, write a fugue, come, write a fugue for sing- ing. Come, write a 102 | fugue, come a- long and write a fugue for sing- ing. Come, write a good fugue. 103 | Come, write a good fugue. Come, write a good fugue. Come, write ^a fugue that we 104 | can sing. * _Come, write a good fugue, Come write a good fugue. _Come write a 105 | fugue that we can sing. Oh, come, * * come, * * come and write one. Come write 106 | ^a fugue that * we can * sing. * Come, write ^a fugue that * we can sing. * _Come, 107 | write a fugue that * we can sing. * Come, write ^a fugue, write a fugue that we 108 | can sing, that * we can * sing. _Come, for the on- ly way to write * one is just 109 | to plunge right in and write * one. So just for- get the rules, * and * write 110 | one. Have * a * _try, have a try, have a try. Plunge ^right in, have a try. Try to 111 | write one. Yes, try to write a fugue. Have a try, plunge _right in and write one. 112 | Yes, just for- get all * that we've told you. Yes, plunge right _in, have a try, 113 | and write one. Yes, _plunge right in, have _a try. Oh yes! Why don't you? Why 114 | don't you write a fugue * now? For the on- ly way to write one is to plunge 115 | right in. Just ig- ^nore _the rules ^and write one. _Have a try, * have ^a try. The 116 | fun of it will get ^you, Joy of it will fetch you. You'll de- cide that John Se- 117 | bas- tian was a per- son- a- ble guy. So nev- er be clev- er For the sake of 118 | be- ing clev- er, For the sake of show- ing off. Nev- er be clev- * er for the 119 | sake of show- ing off. So you want to write a fugue? * * _You've got the urge to 120 | write a fugue. * * _You've got the nerve to write * a * fugue. So go * a- * head 121 | and try to * write one, try to * write one. _Write us a good fugue, one that * we 122 | * can * sing. Oh, come * and try. Oh, why don't you try? Oh, _won't you try to 123 | write one * we can sing? Yes, come, let's try. Write us a fugue right now. 124 | (Hope so.) _Well, noth- ^ing ven- tured noth- ing gained, _they say. But still it 125 | is rath- er hard to start. Let us try. Right now? _We're going to write a good 126 | _one. _We're going to write a fugue _right now. 127 | ''' 128 | 129 | # rendering parameters of all midi tracks in the midi file 130 | tracks = [ 131 | {}, # dummy track if first track is just meta events 132 | { 'name': "soprano", 133 | 'color': (0.973, 0.129, 0.093), # red 134 | 'width': 25, 135 | 'z-index': 4, # higher is on top 136 | 'lyrics': soprano_lyrics 137 | }, 138 | { 'name': "alto", 139 | 'color': (0.929, 0.710, 0.137), # yellow 140 | 'width': 25, 141 | 'z-index': 3, 142 | 'lyrics': alto_lyrics 143 | }, 144 | { 'name': "tenor", 145 | 'color': (0.078, 0.659, 0.129), # green 146 | 'width': 25, 147 | 'z-index': 2, 148 | 'lyrics': tenor_lyrics 149 | }, 150 | { 'name': "bass", 151 | 'color': (0.098, 0.443, 1), # blue 152 | 'width': 25, 153 | 'z-index': 1, 154 | 'lyrics': bass_lyrics 155 | }, 156 | { 'name': "vln1", 157 | 'color': (0.917, 0.384, 0.149), # red 158 | 'width': 25, 159 | 'z-index': 0, 160 | }, 161 | { 'name': "vln2", 162 | 'color': (0.776, 0.769, 0.098), # yellow 163 | 'width': 25, 164 | 'z-index': -1, 165 | }, 166 | { 'name': "viola", 167 | 'color': (0.075, 0.568, 0.353), # green 168 | 'width': 25, 169 | 'z-index': -2, 170 | }, 171 | { 'name': "cello", 172 | 'color': (0.188, 0.153, 0.945), # blue 173 | 'width': 25, 174 | 'z-index': -3, 175 | }, 176 | ] 177 | 178 | # for changing the block speed in the middle of an animation 179 | # time is in seconds from first event, speeds are in pixels per second 180 | speed_map = [ 181 | {'time': 0.0, 'speed': 12}, 182 | ] 183 | 184 | # dimensions of generated image files 185 | dimensions = 1920, 1080 186 | # the intended fps of the animation 187 | fps = 29.97 188 | # pitches to be displayed at bottom and top of screen 189 | min_pitch, max_pitch = 34, 86 190 | 191 | # debugging options 192 | first_frame, last_frame = None, None 193 | every_nth_frame = 1 194 | do_render = True 195 | 196 | # enough config, let's render that shit 197 | renderer = MusAnimRenderer() 198 | renderer.render(input_midi_filename, frame_save_dir, tracks, speed_map=speed_map, 199 | dimensions=dimensions, first_frame=first_frame, last_frame=last_frame, 200 | every_nth_frame=every_nth_frame, do_render=do_render) 201 | 202 | 203 | if __name__ == '__main__': 204 | main() 205 | -------------------------------------------------------------------------------- /RunGouldAnim.py: -------------------------------------------------------------------------------- 1 | from MusAnimRenderer import MusAnimRenderer 2 | import string 3 | 4 | def main(): 5 | # the filename of the midi file to read music from 6 | input_midi_filename = "gould/gouldsyncfull04.MID" 7 | # the directory to save the generated frames to, relative to this file 8 | frame_save_dir = "gould/full03/" 9 | 10 | soprano_lyrics = '''So you want to write a fugue? * * You've got the urge to 11 | write a fugue. * * You've ^got the nerve to write * a * fugue. So go * a- * 12 | ^head, go a- head and * write a fugue. * Write a fugue that * we can sing. Give 13 | no mind to what we've told you. Give no heed to what we've told you. Pay no mind 14 | to what we've told you, what _we've told you. Just for- get all that we've told 15 | you and the theo- ry that you've read. Pay no mind, give no heed to * what we've 16 | * told you. Pay no mind to what we've told you, what we've said. Come and write 17 | one. ^Oh, do come and write one, oh, write us a fugue. Yes, write a fugue that we 18 | can sing, that we can sing. ^For ^the on- ly way to ^write ^one is just to plunge 19 | right in and ^write * one. ^So just for get the rules * and * write one, have _a 20 | try. * For the on- ly way to write * one is just to plunge right in and ^write * 21 | ^one. So just ig- nore the rules * and * write one. It's * ^a * pleas- ure that is 22 | bound * to * sat- is- fy. And the fun of it ^will ^get you, And the _joy of ^it will 23 | _fetch you, it will fetch you. ^You'll _de- cide ^that John Se- bas- ^tian must have 24 | been a ver- y per- son- a- ble guy. And * a * bit of aug- men- ta- tion is a se- 25 | ri- ous temp- ta- tion, While a stret- ti dim- in- u- * tion, is an ob- vi- ous 26 | so- lu- * tion, While a stret- ti, stret- ti stret- _ti dim- in- u- tion is a ver- 27 | y, ver- y ob- vi- ous sol- u- tion. Nev- er be clev- er for the sake of show- 28 | ing off. No, ^nev- er be clev- er for the sake of be- ing clev- ^er. But do try to 29 | write a fugue that we can sing, * * that we can sing. * * Just write a fugue 30 | that we * can sing. * Now, why don't you try to write ^one? Try to write a fugue 31 | for sing- ing. Write us a fugue _that we can sing. Come a- _long now. It's rath- 32 | er awe- some is- n't it? Well? Yes. Now _we're going to write a fugue. * _We're 33 | going to write a fugue right now. 34 | ''' 35 | alto_lyrics = '''^So you want to write a _fugue? * * You've ^got the urge to 36 | ^write a fugue. * * You've ^got the nerve to write * a * fugue. * You've got the 37 | nerve to write * a * fugue that we * can * sing. Come a- long, go a- ^head and 38 | _write one. Go a- head and write one, and write a good fugue, one that we can 39 | sing. Go a- head * * write a fugue that * we can sing. Go a- head * and write a 40 | fugue for sing- ing. Pay no ^heed to what we've told you, Pay no ^mind ^to what 41 | we've told you. Give no heed to what we've told you, ^to what we've told you. 42 | Just for- get all that we've told you and the theo- ry that * you've read, the 43 | theo- ry that you've ^read. Pay no mind, give no heed to * what we've * told you. 44 | Oh, give you mind to what we've * said. Pay no mind, give no heed to what we've 45 | told you, what we've said. ^For the _on- _ly _way _to write one is just to _plunge 46 | right in and write * one. So just for _get the _rules * and * write one. Why * 47 | don't * you * have a try? For the on- ly way to write * one is just to 48 | _plunge right in and write * one. So just ig- nore the rules * and * write a 49 | fugue. It's a pleas- ure that is bound _to sat- is- fy. And the fun of it will 50 | get you, And _the _joy of it will _fetch you. _You'll de- cide that _John Se- bas- 51 | tian, _You'll de- cide that _John Se- bas- tian _was _a per- son- a- ble guy. 52 | But nev- er be clev- er for the sake of be- ing clev- er, For a can- on in in- 53 | ver- sion is a dan- ger- ous di- ver- sion. A bit of aug- men- ta- tion is a se- 54 | ri- ous temp- ta- * tion. Nev- er be clev- er for _the sake of show- * ing off. 55 | But nev- er be clev- er for the sake of be- ing clev- 56 | er, for the sake of * show- * * * * * * * * * * ing _off. Now, ^why don't you 57 | write _a ^fugue, * * _why don't _you try to ^write * one? * Write us a fugue * that 58 | we * can sing. * Now come a- long. And when you've fin- ished writ- ing it I 59 | think you'll ^find a great joy in it. Well? Why not? Now _we're going to write a 60 | fugue. * _We're going to write a fugue right now. 61 | ''' 62 | tenor_lyrics = '''So you want to write a fugue? * * You've got the urge to 63 | write a fugue. * * You've ^got the nerve to write * a * fugue. So go * a- * ^head, 64 | * so go a- head, * and write a fugue. * * * * * So go a- head and write a fugue 65 | that we * can * _sing. Go a- head, write a fugue, and write ^a fugue that we can 66 | ^sing. Go a- head, _write _a fugue that _we can sing * * that we can sing * * and 67 | write a fugue that we can sing. Come a- long, go a- head, write a _fugue that we 68 | can sing. Go a- head, write a fugue * that * we can sing, * * * * and write a 69 | fugue that we can _sing. Write a good fugue, one for sing- ing. Write a fugue 70 | that we can sing. So * go a- head * and write a fugue, and write a fugue that * 71 | we can sing. _Come, write a good fugue. Come, write a good fugue. _Come, write a 72 | good fugue. Pay no mind to what we've told you. Just for- get all that we've 73 | told you and the theo- ry * that you've * read, the theo- ry that you've ^read. 74 | * _Pay no * mind, give no heed to * what we've * told you. ^For the on- ly way _to 75 | write * one is just to plunge right in and write * one. So just for- get the 76 | rules * and * write one. Have * a * try, have a try, * have a _try. For the on 77 | ly way to write one is to plunge right _in and write one. Just ig- nore the rules 78 | * and * try. And the fun of it will get you, _And the joy of it will fetch _you, 79 | It's a pleas- ure that ^is bound to sat- is- fy, to sat- is- fy. So why don't you 80 | try? For the on- ly way to write one is to ^plunge right in. * * * * * * * * * 81 | You'll de- cide that John Se- bas- _tian must have been a ver- y per- son- a- 82 | ble, been a ver- y per- son- a- ble guy. So nev- er be clev- er For the sake of 83 | be- ing clev- er, For the sake of show- ing off. Nev- er be clev- er for the 84 | sake of show- ing off. ^So you want _to write a fugue. * * You've got the urge to 85 | write a fugue. * * _You've got the nerve to write * a * fugue. * You've got the 86 | urge to write * a * fugue that we * can * sing. _So write a fugue _that ^we can 87 | sing. Why _don't you _try to write _one? _Write a fugue _that we can _sing. * Write 88 | a fugue that we can sing. * Oh, come a long. Well? Yes. Now _We're going to write 89 | a good one. _We're going to write a fugue right now. 90 | ''' 91 | bass_lyrics = '''So you want to write a fugue? * * You've got the urge to 92 | write a fugue, * * You've ^got the nerve to write * a * fugue. So go * a- * head, 93 | so go * a- * head, go a- head ^and write a fugue, Go a- head, go a- head, go a- 94 | head and write a fugue, write ^a fugue. Go a- head, write a fugue. You've ^got the 95 | nerve to write ^a fugue. So come a- * long * and * write a * fugue. * Go a- head, 96 | * write ^a fugue. * Oh, come ^a- long and write _a fugue * that * we * can * sing, 97 | that we can sing. Go a- head, write a fugue that we can sing. Go a- head, write 98 | a fugue, write a fugue _that we can sing. Write a good fugue, one that we can 99 | sing, And write a good fugue, one that we can sing. Come a- long, write a fugue, 100 | * that * we can * sing. Write a good fugue, one that we can * sing, that we can 101 | sing. Come, write a fugue, come, write a fugue for sing- ing. Come, write a 102 | fugue, come a- long and write a fugue for sing- ing. Come, write a good fugue. 103 | Come, write a good fugue. Come, write a good fugue. Come, write ^a fugue that we 104 | can sing. * _Come, write a good fugue, Come write a good fugue. _Come write a 105 | fugue that we can sing. Oh, come, * * come, * * come and write one. Come write 106 | a fugue that * we can * sing. * Come, write ^a fugue that * we can sing. * _Come, 107 | write a fugue that * we can sing. * Come, write ^a fugue, write a fugue that we 108 | can sing, that * we can * sing. _Come, for the on- ly way to write * one is just 109 | to plunge right in and write * one. So just for- get the rules, * and * write 110 | one. Have * a * _try, have a try, have a try. Plunge right in, have a try. Try to 111 | write one. Yes, try to write a fugue. Have a try, plunge _right in and write one. 112 | Yes, just for- get all * that we've told you. Yes, plunge right _in, have a try, 113 | and write one. Yes, _plunge right in, have a try. Oh yes! Why don't you? Why 114 | don't you write a fugue * now? For the on- ly way to write one is to plunge 115 | right in. Just ig- ^nore _the rules and write one. _Have a try, * have ^a try. The 116 | fun of it will get ^you, Joy of it will fetch you. You'll de- cide that John Se- 117 | bas- tian was a per- son- a- ble guy. So nev- er be clev- er For the sake of 118 | be- ing clev- er, For the sake of show- ing off. Nev- er be clev- * er for the 119 | sake of show- ing off. So you want to write a fugue? * * _You've got the urge to 120 | write a fugue. * * _You've got the nerve to write * a * fugue. So go * a- * head 121 | and try to * write one, try to * write one. _Write us a good fugue, one that * we 122 | * can * sing. Oh, come * and try. Oh, why don't you try? Oh, _won't you try to 123 | write one * we can sing? Yes, come, let's try. Write us a fugue right now. 124 | (Hope so.) _Well, noth- ^ing ven- tured noth- ing gained, _they say. But still it 125 | is rath- er hard to start. Let us try. Right now? _We're going to write a good 126 | one. _We're going to write a fugue right now. 127 | ''' 128 | 129 | # rendering parameters of all midi tracks in the midi file 130 | tracks = [ 131 | {}, # dummy track if first track is just meta events 132 | { 'name': "soprano", 133 | 'color': (0.973, 0.129, 0.093), # red 134 | 'width': 10, 135 | 'z-index': 4, # higher is on top 136 | 'lyrics': soprano_lyrics 137 | }, 138 | { 'name': "alto", 139 | 'color': (0.929, 0.710, 0.137), # yellow 140 | 'width': 10, 141 | 'z-index': 3, 142 | 'lyrics': alto_lyrics 143 | }, 144 | { 'name': "tenor", 145 | 'color': (0.078, 0.659, 0.129), # green 146 | 'width': 10, 147 | 'z-index': 2, 148 | 'lyrics': tenor_lyrics 149 | }, 150 | { 'name': "bass", 151 | 'color': (0.098, 0.443, 1), # blue 152 | 'width': 10, 153 | 'z-index': 1, 154 | 'lyrics': bass_lyrics 155 | }, 156 | { 'name': "vln1", 157 | 'color': (0.917, 0.384, 0.149), # red 158 | 'width': 10, 159 | 'z-index': 0, 160 | }, 161 | { 'name': "vln2", 162 | 'color': (0.776, 0.769, 0.098), # yellow 163 | 'width': 10, 164 | 'z-index': -1, 165 | }, 166 | { 'name': "viola", 167 | 'color': (0.075, 0.568, 0.353), # green 168 | 'width': 10, 169 | 'z-index': -2, 170 | }, 171 | { 'name': "cello", 172 | 'color': (0.188, 0.153, 0.945), # blue 173 | 'width': 10, 174 | 'z-index': -3, 175 | }, 176 | ] 177 | 178 | # for changing the block speed in the middle of an animation 179 | # time is in seconds from first event, speeds are in pixels per second 180 | speed_map = [ 181 | {'time': 0.0, 'speed': 4}, 182 | ] 183 | 184 | # dimensions of generated image files 185 | dimensions = 720, 480 186 | # the intended fps of the animation 187 | fps = 29.97 188 | # pitches to be displayed at bottom and top of screen 189 | min_pitch, max_pitch = 34, 86 190 | 191 | # debugging options 192 | first_frame, last_frame = None, None 193 | every_nth_frame = 1 194 | do_render = True 195 | 196 | # enough config, let's render that shit 197 | renderer = MusAnimRenderer() 198 | renderer.render(input_midi_filename, frame_save_dir, tracks, 199 | first_frame=first_frame, last_frame=last_frame, 200 | every_nth_frame=every_nth_frame, do_render=do_render) 201 | 202 | 203 | if __name__ == '__main__': 204 | main() 205 | -------------------------------------------------------------------------------- /MusAnimRenderer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import colorsys 4 | import cairo 5 | import math 6 | from collections import deque 7 | from MusAnimLexer import MidiLexer 8 | 9 | class MusAnimRenderer: 10 | def lyrics_deque(self, lyrics): 11 | """Turns lyrics as a string into a lyrics deque, splitting by spaces and 12 | removing newlines.""" 13 | lyrics = lyrics.replace("\n", " ") 14 | lyrics_list = lyrics.split(" ") 15 | lyrics_list2 = [] 16 | for word in lyrics_list: 17 | """ 18 | if word and word[-1] == '-': 19 | word = word[0:-1] + ' -'""" 20 | lyrics_list2.append(word) 21 | return deque(lyrics_list2) 22 | 23 | def blockify(self, midi_events): 24 | """Converts list of midi events given by the lexer into block data used 25 | for animating.""" 26 | blocks = [] 27 | bpm = 120.0 28 | time_seconds = 0 29 | time_beats = 0 30 | tracks_mode = ['normal'] * 100 31 | for event in midi_events: 32 | # increment times based on elapsed time in beats 33 | d_time_beats = event['time'] - time_beats 34 | time_seconds += (d_time_beats * 60.0) / bpm 35 | time_beats = event['time'] 36 | if event['type'] == 'tempo': # set tempo 37 | bpm = event['bpm'] 38 | elif event['type'] == 'note_on': # create new block in list 39 | blocks.append({'start_time': time_seconds, 'pitch': 40 | event['pitch'], 'track_num': event['track_num']}) 41 | # set shape of last block to bar or circle 42 | if tracks_mode[event['track_num']] == 'normal': 43 | blocks[-1]['shape'] = 'bar' 44 | elif tracks_mode[event['track_num']] == 'pizz': 45 | blocks[-1]['shape'] = 'circle' 46 | else: 47 | raise Exception('Unknown track mode') 48 | elif event['type'] == 'note_off': # add end_time to existing block 49 | pitch = event['pitch'] 50 | track_num = event['track_num'] 51 | blocks_w_pitch = [block for block in blocks 52 | if block['pitch'] == pitch and block['track_num'] 53 | == track_num and 'end_time' not in block] 54 | assert(blocks_w_pitch) # assume it has at least one element 55 | # otherwise we have a faulty midi file! 56 | blocks_w_pitch[0]['end_time'] = time_seconds 57 | elif event['type'] == 'keyswitch': 58 | tracks_mode[event['track_num']] = event['mode'] 59 | else: 60 | raise Exception('Unknown midi event') 61 | return blocks 62 | 63 | def add_block_info(self, blocks, tracks, fps, speed_map, dimensions, 64 | min_pitch, max_pitch): 65 | """Adds essential information to each block dict in blocks, also returns 66 | last_block_end to tell when animation is over""" 67 | # need: start_time (seconds), end_time (seconds), pitch, track_num for 68 | # each block 69 | last_block_end = 0 70 | cur_speed = self.get_speed(speed_map, 0.0) 71 | 72 | for block in blocks: 73 | # get track object that corresponds to block 74 | track = tracks[block['track_num']] 75 | block['width'] = track['width'] # set width 76 | # get speed and calculate x offset from functions 77 | cur_speed = self.get_speed(speed_map, block['start_time']) 78 | x_offset = self.calc_offset(speed_map, block['start_time'], fps) 79 | block['start_x'] = x_offset + dimensions[0] 80 | # length of the block in time (time it stays highlighted) 81 | block['length'] = block['end_time'] - block['start_time'] + 0.0 82 | # if a circle, length is same as width, otherwise length 83 | # corresponds to time length 84 | if block['shape'] == 'circle': 85 | block['x_length'] = block['width'] 86 | else: 87 | block['x_length'] = block['length'] * fps * cur_speed 88 | block['end_x'] = block['start_x'] + block['x_length'] 89 | # set last_block_end as the end_x of the very rightmost block 90 | if block['end_x'] > last_block_end: 91 | last_block_end = block['end_x'] 92 | # figure out draw coordinates 93 | y_middle = ((0.0 + max_pitch - block['pitch']) / (max_pitch - 94 | min_pitch)) * dimensions[1] 95 | block['top_y'] = y_middle - (block['width'] / 2) 96 | block['bottom_y'] = y_middle + (block['width'] / 2) 97 | if 'z-index' not in track: 98 | track['z-index'] = 0 99 | block['z-index'] = track['z-index'] 100 | if 'layer' not in track: 101 | track['layer'] = 0 102 | block['layer'] = track['layer'] 103 | # round stuff for crisp rendering 104 | #block['x_length'] = round(block['x_length']) 105 | block['top_y'] = round(block['top_y']) 106 | #block['start_x'] = round(block['start_x']) 107 | 108 | # sort by track_num so we get proper melisma length counting 109 | blocks.sort(lambda a, b: cmp(a['track_num'], b['track_num'])) 110 | 111 | # can't add lyrics until we add in end_x for all blocks 112 | block_num = 0 113 | for block in blocks: 114 | track_num = block['track_num'] 115 | track = tracks[block['track_num']] 116 | if 'lyrics' in track and track['lyrics'][0]: 117 | lyrics_text = track['lyrics'][0] 118 | if lyrics_text[0] == '^': 119 | lyrics_text = lyrics_text[1:] 120 | block['lyrics_position'] = 'above' 121 | elif lyrics_text[0] == '_': 122 | lyrics_text = lyrics_text[1:] 123 | block['lyrics_position'] = 'below' 124 | else: 125 | block['lyrics_position'] = 'middle' 126 | 127 | if track['lyrics'][0] != '*': 128 | # for detecting melismas (* in lyrics text) 129 | i = 0 130 | while (len(track['lyrics']) > (i + 1) 131 | and track['lyrics'][i+1] == '*'): 132 | i += 1 133 | block['lyrics_end_x'] = blocks[block_num+i]['end_x'] 134 | block['lyrics'] = lyrics_text 135 | 136 | track['lyrics'].popleft() 137 | 138 | block_num += 1 139 | 140 | # go back to sorting by start time 141 | blocks.sort(lambda a, b: cmp(a['start_time'], b['start_time'])) 142 | 143 | return blocks, last_block_end 144 | 145 | def calc_offset(self, speed_map, time_offset, fps): 146 | """Calculates the x-offset of a block given its time offset and a speed 147 | map. Needed for laying out blocks because of variable block speed in the 148 | animation.""" 149 | x_offset = 0 150 | i = 0 151 | # speed is a dict with a speed and a time when we switch to speed 152 | speeds = ([speed for speed in speed_map if speed['time'] < time_offset] 153 | [0:-1]) 154 | # add offsets from previous speed intervals 155 | if speeds: 156 | for speed in speeds: 157 | x_offset += ((speed_map[i+1]['time'] - speed_map[i]['time']) 158 | * fps * speed_map[i]['speed']) 159 | i += 1 160 | # add offset from current speed 161 | if time_offset > 0: 162 | x_offset += ((time_offset - speed_map[i]['time']) * fps 163 | * speed_map[i]['speed']) 164 | return x_offset 165 | 166 | def get_speed(self, speed_map, time): 167 | """Retrieves the correct block speed for a given point in time from the 168 | speed map.""" 169 | i = len(speed_map) - 1 170 | while time < speed_map[i]['time'] and i > 0: 171 | i -= 1 172 | return speed_map[i]['speed'] 173 | 174 | def draw_block_cairo(self, block, tracks, dimensions, cr, transparent=False): 175 | if block['start_x'] < (dimensions[0] / 2) and (block['end_x'] > 176 | (dimensions[0] / 2)): 177 | color = tracks[block['track_num']]['high_color'] 178 | else: 179 | color = tracks[block['track_num']]['color'] 180 | if transparent: 181 | r, g, b = color 182 | cr.set_source_rgba(r, g, b, 0.5) 183 | else: 184 | cr.set_source_rgb(*color) 185 | if block['shape'] == 'circle': 186 | cr.arc(block['start_x'] + block['width']/2, block['top_y'] + block['width']/2, block['width']/2, 0, 2 * math.pi) 187 | else: 188 | cr.rectangle(block['start_x'], block['top_y'], block['x_length'], 189 | block['width']) 190 | cr.fill() 191 | 192 | def draw_lyrics_cairo(self, block, tracks, dimensions, cr): 193 | cr.set_font_size(1.9*block['width']) 194 | text = block['lyrics'] 195 | x_bearing, y_bearing, width, height = cr.text_extents(text)[:4] 196 | cr.set_source_rgba(0, 0, 0, 0.5) 197 | if block['lyrics_position'] == 'above': 198 | rect = (block['start_x'], int(block['top_y'])-0.7*block['width'], width + 2, block['width']+1) 199 | elif block['lyrics_position'] == 'below': 200 | rect = (block['start_x'], int(block['top_y'])+0.7*block['width'], width + 2, block['width']+1) 201 | else: 202 | rect = (block['start_x'], int(block['top_y']), width + 2, block['width']+1) 203 | cr.rectangle(*rect) 204 | cr.fill() 205 | if block['start_x'] < (dimensions[0] / 2) and (block['lyrics_end_x'] > 206 | (dimensions[0] / 2)): 207 | color = (1, 1, 1) 208 | else: 209 | color = tracks[block['track_num']]['lyrics_color'] 210 | cr.set_source_rgb(*color) 211 | if block['lyrics_position'] == 'above': 212 | corner = (block['start_x'] + 1, block['top_y']+0.18*block['width']) 213 | elif block['lyrics_position'] == 'below': 214 | corner = (block['start_x'] + 1, block['top_y']+1.58*block['width']) 215 | else: 216 | corner = (block['start_x'] + 1, block['top_y']+0.88*block['width']) 217 | cr.move_to(*corner) 218 | cr.show_text(text) 219 | 220 | def render(self, input_midi_filename, frame_save_dir, tracks, speed_map=[{'time':0.0,'speed':4}], 221 | dimensions=(720,480), fps=29.97, min_pitch=34, max_pitch=86, first_frame=None, 222 | last_frame=None, every_nth_frame=1, do_render=1): 223 | """Render the animation!""" 224 | 225 | print "Beginning render..." 226 | speed = speed_map[0]['speed'] 227 | if first_frame == None: 228 | first_frame = 0 229 | if last_frame == None: 230 | last_frame = 10000000 # just a large number 231 | 232 | print "Lexing midi..." 233 | blocks = [] 234 | lexer = MidiLexer() 235 | midi_events = lexer.lex(input_midi_filename) 236 | 237 | print "Blockifying midi..." 238 | blocks = self.blockify(midi_events) # convert into list of blocks 239 | 240 | for track in tracks: 241 | if 'color' in track: 242 | base_color = colorsys.rgb_to_hls(*track['color']) 243 | track['high_color'] = colorsys.hls_to_rgb(base_color[0], 0.95, 244 | base_color[2]) 245 | track['lyrics_color'] = colorsys.hls_to_rgb(base_color[0], 0.7, 246 | base_color[2]) 247 | if 'lyrics' in track: 248 | track['lyrics'] = self.lyrics_deque(track['lyrics']) 249 | 250 | # do some useful calculations on all blocks 251 | blocks, last_block_end = self.add_block_info(blocks, tracks, 252 | fps, speed_map, dimensions, min_pitch, max_pitch) 253 | 254 | # following used for calculating percentage done to print to console 255 | original_end = last_block_end 256 | percent = 0 257 | last_percent = -1 258 | 259 | # sort by z-index descending 260 | blocks.sort(lambda a, b: cmp(b['z-index'], a['z-index'])) 261 | 262 | # for naming image files: 263 | frame = 0 264 | # for keeping track of speed changes: 265 | # need to initialize time 266 | time = -dimensions[0]/(2.0*fps*speed_map[0]['speed']) 267 | 268 | if not do_render: 269 | print "Skipping render pass, Done!" 270 | return 271 | 272 | print "Rendering frames..." 273 | # generate frames while there are blocks on the screen: 274 | while last_block_end > (0 - speed): 275 | # code only for rendering blocks 276 | if frame >= first_frame and frame <= last_frame and frame % every_nth_frame == 0: 277 | # cairo setup stuff 278 | filename = frame_save_dir + ("frame%05i.png" % frame) 279 | surface = cairo.ImageSurface(cairo.FORMAT_RGB24, *dimensions) 280 | #surface = cairo.SVGSurface(filename, *dimensions) 281 | cr = cairo.Context(surface) 282 | cr.set_antialias(cairo.ANTIALIAS_GRAY) 283 | cr.select_font_face("Garamond", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD) 284 | cr.set_font_size(19) 285 | # add black background 286 | cr.set_source_rgb(0, 0, 0) 287 | cr.rectangle(0, 0, *dimensions) 288 | cr.fill() 289 | 290 | # need to do two passes of drawing blocks, once in reverse order 291 | # in full opacity, and a second time in ascending order in half- 292 | # opacityto get fully-colored bars that blend together when 293 | # overlapping 294 | 295 | # get list of blocks that are on screen 296 | on_screen_blocks = [block for block in blocks 297 | if block['start_x'] < dimensions[0] and block['end_x'] > 0] 298 | on_screen_layers = list(set([block['layer'] for block in on_screen_blocks])) 299 | 300 | for layer in on_screen_layers: 301 | layer_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, *dimensions) 302 | layer_context = cairo.Context(layer_surface) 303 | 304 | in_layer_blocks = [block for block in on_screen_blocks if block['layer'] == layer] 305 | 306 | # do first drawing pass 307 | for block in in_layer_blocks: 308 | self.draw_block_cairo(block, tracks, dimensions, layer_context) 309 | 310 | # do second drawing pass 311 | on_screen_blocks.reverse() 312 | for block in in_layer_blocks: 313 | self.draw_block_cairo(block, tracks, dimensions, layer_context, transparent=True) 314 | 315 | cr.set_source_surface(layer_surface) 316 | cr.paint() 317 | 318 | # do lyrics pass, sort by start x so starts of words are on top 319 | on_screen_blocks.sort(lambda a, b: cmp(a['start_x'], b['start_x'])) 320 | for block in on_screen_blocks: 321 | if ('lyrics' in block): 322 | self.draw_lyrics_cairo(block, tracks, dimensions, cr) 323 | 324 | #cr.save() 325 | surface.write_to_png(filename) 326 | 327 | # other code needed to advance animation 328 | frame += 1 329 | # need to set speed 330 | speed = self.get_speed(speed_map, time) 331 | for block in blocks: # move blocks to left 332 | block['start_x'] -= speed 333 | block['end_x'] -= speed 334 | if 'lyrics_end_x' in block: 335 | block['lyrics_end_x'] -= speed 336 | last_block_end -= speed # move video endpoint left as well 337 | percent = int(min((original_end - last_block_end) * 100.0 338 | / original_end, 100)) 339 | if percent != last_percent: 340 | print percent, "% done" 341 | last_percent = percent 342 | 343 | time += (1/fps) 344 | 345 | print "Done!" 346 | 347 | 348 | if __name__ == '__main__': 349 | print ("Sorry, I don't really do anything useful as an executable, see " 350 | "RunAnim.py for usage") --------------------------------------------------------------------------------