Package jazzparser :: Package harmonical :: Package midi :: Module chords
[hide private]
[frames] | no frames]

Source Code for Module jazzparser.harmonical.midi.chords

  1  """Justly intoned chord sequence realization 
  2   
  3  Production of justly intoned chord sequences in the form of MIDI  
  4  files with tuning instructions included. 
  5   
  6  """ 
  7  """ 
  8  ============================== License ======================================== 
  9   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 10    
 11   This file is part of The Jazz Parser. 
 12    
 13   The Jazz Parser is free software: you can redistribute it and/or modify 
 14   it under the terms of the GNU General Public License as published by 
 15   the Free Software Foundation, either version 3 of the License, or 
 16   (at your option) any later version. 
 17    
 18   The Jazz Parser is distributed in the hope that it will be useful, 
 19   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 20   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 21   GNU General Public License for more details. 
 22    
 23   You should have received a copy of the GNU General Public License 
 24   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 25   
 26  ============================ End license ====================================== 
 27   
 28  """ 
 29  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"  
 30   
 31  import copy 
 32  from .. import CHORD_TYPES 
 33  from midi import write_midifile, EventStream, SetTempoEvent, \ 
 34                      ProgramChangeEvent, NoteOnEvent, NoteOffEvent 
 35  from . import tonal_space_note_events 
 36  from jazzparser.utils.base import group_pairs 
 37  from jazzparser.data import Fraction 
 38   
39 -class ChordSequenceRealizer(object):
40 """ 41 A factory class to produce a MIDI file (represented using the midi 42 library package's C{EventStream})) from a sequence of tonal space 43 roots and chord types. 44 45 The resulting MIDI file will be tuned so that the realization is 46 justly intoned. 47 48 This provides an interface similar to 49 L{path_to_tones<jazzparser.harmonical.tones.path_to_tones>}, which 50 renders the path to raw wave data containing sine waves. 51 52 """
53 - def __init__(self, path, chord_types=None, tempo=120, \ 54 root_octave=0, double_root=False, equal_temperament=False, \ 55 instrument=0, bass_root=None):
56 """ 57 58 @type path: list of (3d-coordinate,length) tuples 59 @param path: coordinates of the points in the sequence with their 60 associated lengths, given in beats (ints or 61 L{Fractions<jazzparser.data.Fraction>}) 62 @type tempo: int 63 @param tempo: speed in beats per second (Maelzel's metronome) 64 @type chord_types: list of (string,length) tuples 65 @param chord_types: the type of chord to use for each tone, with 66 its associated length. See 67 L{..CHORD_TYPES<jazzparser.harmonical.CHORD_TYPES>} keys for possible values. 68 @type equal_temperament: bool 69 @param equal_temperament: render all the pitches as they would be 70 played in equal temperament. 71 @type root_octave: int 72 @param root_octave: octave to transpose the root to relative 73 to other notes. Default (0) has the other notes in the 74 octave above the root. 75 @type double_root: bool 76 @param double_root: if True, an extra tone will be added an 77 octave below the root 78 @type instrument: int 79 @param instrument: MIDI instrument to set at the beginning of 80 the file. 81 @type bass_root: int 82 @param bass_root: like double_root, adds an extra note an 83 octave below the root. In this case, adds it to a seperate 84 track, with the midi instrument number given. Set to None 85 not to add the note at all (default) 86 87 """ 88 self.path = path 89 90 if chord_types is None: 91 # Default chord type is just a major chord 92 self.chord_types = [('',length) for coord,length in path] 93 else: 94 # Check we've heard of all these chord types 95 types = set([type for type,length in chord_types]) 96 for ctype in types: 97 if ctype not in CHORD_TYPES: 98 raise ChordSequenceRealizationError, "don't know "\ 99 "how to realize the chord type '%s'" % ctype 100 self.chord_types = chord_types 101 102 self.tempo = tempo 103 self.root_octave = root_octave 104 self.double_root = double_root 105 self.equal_temperament = equal_temperament 106 self.instrument = instrument 107 self.bass_root = bass_root
108
109 - def render(self):
110 """ 111 Creates MIDI data from the path and chord types. 112 113 @rtype: midi.EventStream 114 @return: an event stream containing all the midi events 115 116 """ 117 mid = EventStream() 118 mid.add_track() 119 120 # Set the tempo at the beginning 121 tempo = SetTempoEvent() 122 tempo.tempo = self.tempo 123 mid.add_event(tempo) 124 125 # Set the instrument at the beginning 126 instr = ProgramChangeEvent() 127 instr.value = self.instrument 128 mid.add_event(instr) 129 130 beat_length = mid.resolution 131 # Work out when each root change occurs 132 time = Fraction(0) 133 root_times = [] 134 for root,length in self.path: 135 root_times.append((root,time)) 136 time += length 137 def _root_at_time(time): 138 current_root = root_times[0][0] 139 for root,rtime in root_times[1:]: 140 # Move through root until we get the first one that 141 # occurs after the previous time 142 if rtime > time: 143 return current_root 144 current_root = root 145 # If we're beyond the time of the last root, use that one 146 return current_root
147 148 # Add each chord 149 time = Fraction(0) 150 bass_events = [] 151 bass = self.bass_root is not None 152 for chord_type,length in self.chord_types: 153 tick_length = length * beat_length - 10 154 tick_time = time * beat_length 155 # Find out what root we're on at this time 156 root = _root_at_time(time) 157 # Add all the necessary events for this chord 158 chord_events = events_for_chord(root, chord_type, int(tick_time), 159 int(tick_length), 160 equal_temperament=self.equal_temperament, 161 root_octave=self.root_octave, 162 double_root=(self.double_root or bass)) 163 if bass: 164 # Add the bass note to the bass track 165 bass_events.extend([copy.copy(ev) for ev in chord_events[-1]]) 166 if bass and not self.double_root: 167 # Remove the doubled root that we got for the bass line 168 chord_events = sum(chord_events[:-1], []) 169 # Add the main chord notes to the midi track 170 for ev in chord_events: 171 mid.add_event(ev) 172 time += length 173 174 if bass: 175 bass_channel = 1 176 # Add another track to the midi file for the bass notes 177 mid.add_track() 178 # Select a bass instrument - picked bass 179 instr = ProgramChangeEvent() 180 instr.value = 33 181 instr.channel = bass_channel 182 mid.add_event(instr) 183 # Add all the bass notes 184 for ev in bass_events: 185 ev.channel = bass_channel 186 mid.add_event(ev) 187 return mid
188
189 - def write(self, outfile):
190 """ 191 Renders MIDI data and writes it out the the given file. 192 193 @type outfile: string or open file 194 @param outfile: filename to write to or an open file(-like) object 195 196 """ 197 mid = self.render() 198 write_midifile(mid, outfile)
199
200 -def events_for_chord(coord, chord_type, start, duration, origin_note=60, 201 equal_temperament=False, root_octave=0, double_root=False, 202 velocity=100):
203 """ 204 Builds a chord's midi events (note on and note off) from a root 205 coordinate and a chord type. Uses the chord definitions in 206 L{jazzparser.harmonical.CHORD_TYPES} to decide what notes to 207 play. 208 209 The list is grouped into sublists, one for each note. 210 For each note, three events are created: tuning, note on and note 211 off. 212 213 @type root_octave: int 214 @param root_octave: octave to transpose the root to relative 215 to other notes. Default (0) has the other notes in the 216 octave above the root. 217 @type double_root: bool 218 @param double_root: if True, an extra tone will be added an 219 octave below the root. This will be the last event in the list 220 221 """ 222 notes = copy.copy(CHORD_TYPES[chord_type]) 223 if root_octave != 0 or double_root: 224 # Do the special things with the root, if it's voiced 225 root = None 226 for (x,y,z) in notes: 227 if x == y == 0: 228 root = (x,y,z) 229 break 230 if root is not None: 231 # Remove the root from the other notes 232 notes.remove(root) 233 # Add a root shifted to the requested octave 234 notes.append((0,0,root[2]+root_octave)) 235 if double_root: 236 # Double the root an octave below 237 notes.append((0,0,root[2]+root_octave-1)) 238 notes = [(coord[0]+note[0], coord[1]+note[1], coord[2]+note[2]) for note in notes] 239 240 events = [] 241 for note in notes: 242 tuning,noteon,noteoff = tonal_space_note_events(note, start, duration, origin_note=origin_note, velocity=velocity) 243 note_events = [] 244 # Skip the tuning if we're in ET 245 if not equal_temperament: 246 note_events.append(tuning) 247 note_events.append(noteon) 248 note_events.append(noteoff) 249 events.append(note_events) 250 return events
251
252 -def render_path_to_file(filename, path, *args, **kwargs):
253 """ 254 Convenience function that takes a path and set of timings and 255 uses the harmonical to render MIDI from it and writes it to a 256 file. 257 258 Additional args/kwargs are passed to the L{ChordSequenceRealizer} 259 init. 260 261 """ 262 realizer = ChordSequenceRealizer(path, *args, **kwargs) 263 realizer.write(filename)
264
265 -class ChordSequenceRealizationError(Exception):
266 pass
267