Package jazzparser :: Package misc :: Package raphsto :: Module midi
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.misc.raphsto.midi

  1  from __future__ import absolute_import 
  2  """Midi input handling for Raphsto models 
  3   
  4  """ 
  5  """ 
  6  ============================== License ======================================== 
  7   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
  8    
  9   This file is part of The Jazz Parser. 
 10    
 11   The Jazz Parser is free software: you can redistribute it and/or modify 
 12   it under the terms of the GNU General Public License as published by 
 13   the Free Software Foundation, either version 3 of the License, or 
 14   (at your option) any later version. 
 15    
 16   The Jazz Parser is distributed in the hope that it will be useful, 
 17   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 18   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 19   GNU General Public License for more details. 
 20    
 21   You should have received a copy of the GNU General Public License 
 22   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 23   
 24  ============================ End license ====================================== 
 25   
 26  """ 
 27  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 28   
 29  import os.path, csv 
 30  from midi import EventStream, NoteOnEvent, read_midifile, NoteOffEvent, \ 
 31                      ProgramChangeEvent, LyricsEvent 
 32  from . import constants 
 33   
34 -class MidiHandler(object):
35 """ 36 Class to encapsulate all the midi processing needed for getting 37 input to a Raphsto model. Read in midi files using the L{midi} 38 library and load an L{midi.EventStream}. Then use this class to 39 get whatever form of data out of it you need for your input. 40 41 """
42 - def __init__(self, stream, time_unit=4, remove_drums=False, \ 43 tick_offset=0):
44 """ 45 @type stream: L{midi.EventStream} 46 @param stream: the midi data to get input from 47 @type time_unit: int or float 48 @param time_unit: number of beats to take as the basic unit 49 of time for observations 50 51 """ 52 self.stream = stream 53 self.time_unit = time_unit 54 self.filtered_stream = list(sorted(self.stream.trackpool)) 55 if remove_drums: 56 self.filtered_stream = [ev for ev in self.filtered_stream if ev.channel != 9] 57 if tick_offset != 0: 58 self.filtered_stream = [ev for ev in self.filtered_stream if ev.tick >= tick_offset] 59 self.tick_offset = tick_offset
60
61 - def get_emission_stream(self):
62 """ 63 Get a list of emissions from the midi stream's note on events. 64 65 Returns a 2-tuple of the list of emissions and their 66 corresponding start times in midi ticks. 67 68 """ 69 note_ons = [ev for ev in self.filtered_stream if isinstance(ev, NoteOnEvent)] 70 tick_unit = int(self.stream.resolution*self.time_unit) 71 # Divide up the note-ons into chucks of this size 72 chunks = [] 73 current_chunk = [] 74 for ev in note_ons: 75 while ev.tick-self.tick_offset >= (len(chunks)+1)*tick_unit: 76 chunks.append(current_chunk) 77 current_chunk = [] 78 bar_start = len(chunks)*tick_unit + self.tick_offset 79 if ev.tick == bar_start: 80 rhythm = 0 81 elif ev.tick == bar_start + (tick_unit/2): 82 rhythm = 1 83 elif ev.tick == bar_start + (tick_unit/4) or \ 84 ev.tick == bar_start + (tick_unit*3/4): 85 rhythm = 2 86 else: 87 rhythm = 3 88 pc = ev.pitch % 12 89 current_chunk.append((pc, rhythm)) 90 # Get the last chunk 91 chunks.append(current_chunk) 92 93 # Get rid of duplicate values in the chunks (octaves) 94 chunks = [list(set(c)) for c in chunks] 95 96 start_times = [i*tick_unit for i in range(len(chunks))] 97 stream = zip(*( (em,time) for (em,time) in zip(chunks,start_times) if len(em) > 0 )) 98 return stream
99
100 - def get_slices(self):
101 """ 102 Get a list of L{midi.slice.EventStreamSlice}s corresponding to the 103 chunks that this midi stream will be divided into. 104 This includes all midi events, not just note-ons. 105 106 """ 107 from midi.slice import EventStreamSlice 108 109 tick_unit = int(self.stream.resolution*self.time_unit) 110 if len(self.stream.trackpool) == 0: 111 end_time = 0 112 else: 113 end_time = max(self.stream.trackpool).tick 114 115 slices = [EventStreamSlice(self.stream, 116 chunk_start, 117 chunk_start+tick_unit-1) 118 for chunk_start in range(self.tick_offset, end_time, tick_unit)] 119 return slices
120
121 -class InputSourceFile(object):
122 """ 123 File reader to get midi input files listed in a single file format. 124 The format is CSV, each row containing a midi filename (relative 125 to the source file's location) and the parameters to use to process 126 the midi data as input: 127 128 C{filename,time_unit,tick_offset,remove_drums} 129 130 See L{MidiHandler} for the meaning of these parameters. 131 132 Once the file's loaded, the (parsed) files and parameters are 133 available in the list obj.inputs. You can also get directly at 134 a list of L{MidiHandler}s using L{get_handlers}. 135 136 """
137 - def __init__(self, filename):
138 """ 139 @type filename: str 140 @param filename: filename of the file to read from 141 142 """ 143 infile = open(filename, 'r') 144 reader = csv.reader(infile) 145 self.data = list(reader) 146 infile.close() 147 148 self.base_path = os.path.abspath(os.path.dirname(filename)) 149 150 # Read the file's data and process it 151 self.inputs = [] 152 for row in self.data: 153 # Optional col 4 allows us to ignore rows for training while 154 # keeping their parameters in the file 155 if len(row) > 4: 156 ignore = bool(row[4]) 157 else: 158 ignore = False 159 160 if not ignore: 161 filename = row[0] 162 # Read in the midi file 163 midi = os.path.join(self.base_path, filename) 164 165 # Prepare the parameters 166 if row[1]: 167 time_unit = int(row[1]) 168 else: 169 time_unit = 4 170 171 if row[2]: 172 tick_offset = int(row[2]) 173 else: 174 tick_offset = 0 175 176 remove_drums = bool(row[3]) 177 178 self.inputs.append((midi, time_unit, tick_offset, remove_drums))
179
180 - def get_handlers(self):
181 """ 182 Generator to get L{MidiHandler}s, one for each line of the 183 input file. 184 185 """ 186 for line in self.inputs: 187 yield MidiHandler(read_midifile(line[0]), time_unit=line[1], 188 tick_offset=line[2], remove_drums=line[3])
189
190 -class ChordSequenceRealizer(object):
191 """ 192 Factory to take the output from the labeller and realize the chord sequence 193 as a midi file. 194 195 Very basic - not going to sound great, but it's easier than playing it 196 myself. 197 198 """
199 - def __init__(self, labels, resolution=120, chord_length=2, times=None, \ 200 text_events=False, state_formatter=None):
201 """ 202 @type labels: list of tuples 203 @param labels: state label tuples output by the model 204 @type resolution: int 205 @param resolution: midi resolution to give the result (midi ticks per beat) 206 @type chord_length: int 207 @param chord_length: length of each chord as a number of beats 208 @type times: list of ints 209 @param times: optional onset time for each chord in midi ticks. 210 If not given, it will be calculated by giving chord_length to 211 every chord 212 @type text_events: bool 213 @param text_events: include chord labels in the midi as text events 214 @type state_formatter: 1-arg function 215 @param state_formatter: function to take a state label and produce 216 the text label that will go in the midi data. Optional: 217 by default, will use L{jazzparser.misc.raphsto.format_state_as_chord}. 218 219 """ 220 self.labels = labels 221 self.resolution = resolution 222 self.chord_length = chord_length 223 self.times = times 224 self.text_events = text_events 225 226 from jazzparser.misc.raphsto import format_state_as_raphsto 227 if state_formatter is None: 228 self.formatter = format_state_as_raphsto 229 else: 230 self.formatter = state_formatter
231
232 - def generate(self, overlay=None, offset=0):
233 """ 234 Generates a midi stream. 235 236 """ 237 octaves = 1 238 239 if overlay is not None: 240 stream = overlay 241 # Use organ sound 242 instrument = 23 243 # Find the last channel used in the file we're overlaying 244 channel = max(ev.channel for ev in stream.trackpool) + 1 245 volume = 30 246 else: 247 stream = EventStream() 248 stream.resolution = self.resolution 249 # Just use piano 250 instrument = 0 251 channel = 0 252 volume = 127 253 stream.add_track() 254 pc = ProgramChangeEvent() 255 pc.value = instrument 256 pc.tick = 0 257 pc.channel = channel 258 stream.add_event(pc) 259 # Length of each chord in midi ticks 260 chord_length = int(self.resolution * self.chord_length) 261 262 if self.times is None: 263 times = [i*chord_length + offset for i in range(len(self.labels))] 264 else: 265 times = [t+offset for t in self.times] 266 267 formatter = getattr(self, 'formatter') 268 269 pending_note_offs = [] 270 for (tonic,mode,chord),time in zip(self.labels, times): 271 scale_chord_root = constants.CHORD_NOTES[mode][chord][0] 272 chord_root = (tonic+scale_chord_root) % 12 273 triad_type = constants.SCALE_TRIADS[mode][chord] 274 # Work out the notes for this chord 275 triad_notes = [(chord_root + note) % (octaves*12) + 72 for note in constants.TRIAD_NOTES[triad_type]] 276 # Add the root in the octave two below 277 triad_notes.append(chord_root + 48) 278 279 # Add note offs for notes already on 280 for noff in pending_note_offs: 281 noff.tick = time-1 282 stream.add_event(noff) 283 pending_note_offs = [] 284 285 if self.text_events: 286 # Add a text event to represent the chord label 287 tevent = LyricsEvent() 288 label = formatter((tonic,mode,chord)) 289 tevent.data = "%s\n" % label 290 tevent.tick = time 291 stream.add_event(tevent) 292 293 # Add a note-on and off event for each note 294 for note in triad_notes: 295 non = NoteOnEvent() 296 non.tick = time 297 non.pitch = note 298 non.channel = channel 299 non.velocity = volume 300 stream.add_event(non) 301 302 # Hold the note until the next chord is played 303 noff = NoteOffEvent() 304 noff.pitch = note 305 noff.channel = channel 306 noff.velocity = volume 307 pending_note_offs.append(noff) 308 309 # Add the last remaining note offs 310 for noff in pending_note_offs: 311 noff.tick = time+chord_length 312 stream.add_event(noff) 313 return stream
314