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

Source Code for Module jazzparser.data.midi

  1  from __future__ import absolute_import 
  2  """Processing of MIDI data. 
  3   
  4  Tools for processing MIDI data. This makes use of the L{midi} library,  
  5  but is not itself generic MIDI processing code (or else I'd add it to  
  6  the library). 
  7   
  8  This has nothing to do with tonal space MIDI generation. For that,  
  9  see L{jazzparser.harmonical.midi}. 
 10   
 11  """ 
 12  """ 
 13  ============================== License ======================================== 
 14   Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding 
 15    
 16   This file is part of The Jazz Parser. 
 17    
 18   The Jazz Parser is free software: you can redistribute it and/or modify 
 19   it under the terms of the GNU General Public License as published by 
 20   the Free Software Foundation, either version 3 of the License, or 
 21   (at your option) any later version. 
 22    
 23   The Jazz Parser is distributed in the hope that it will be useful, 
 24   but WITHOUT ANY WARRANTY; without even the implied warranty of 
 25   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 26   GNU General Public License for more details. 
 27    
 28   You should have received a copy of the GNU General Public License 
 29   along with The Jazz Parser.  If not, see <http://www.gnu.org/licenses/>. 
 30   
 31  ============================ End license ====================================== 
 32   
 33  """ 
 34  __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>" 
 35   
 36  from midi import MarkerEvent, LyricsEvent, TextEvent, CuePointEvent 
 37   
38 -class SequenceMidiAlignment(object):
39 """ 40 Specification of parameters to align a chord sequence with a 41 MIDI file. This includes things like where repeats should occur 42 in the chord sequence and how many beats to each MIDI beat, 43 so that the chords end up at the right place in the music. 44 45 Aligns a L{ChordSequence<jazzparser.data.db_mirrors.ChordSequence>} 46 with a L{midi.EventStream}. 47 48 """
49 - def __init__(self):
50 self.midi_beats_per_beat = 1 51 """ 52 Number of beats in the MIDI file to align with each chord 53 sequence beat. Use negative numbers (<-1) to specify reciprocals 54 (i.e. -4 -> 1/4). 55 """ 56 self.sequence_start = 0 57 """ 58 Number of MIDI ticks between the first note-on event in the 59 MIDI file and where the chord sequence starts. 60 """ 61 self.repeat_spans = [] 62 """ 63 Definitions of repeats. Given as (start,end,count). The first 64 I{count} times the I{end}th chord is finished, the 65 sequence will return to the I{start}th chord. Repeats with the 66 same end point will be used in the order they occur in this list. 67 68 @note: Counts for inner repeats will not be reset in the outer 69 loop of nested spans. 70 """
71
72 - def align(self, sequence, mid, lyrics=False):
73 """ 74 Aligns the sequence with the midi file and adds lyric events 75 into the midi data to indicate where the chords occur. 76 This ought to function like a karaoke midi file, showing the 77 chords as the occur. 78 79 Note that this modifies the midi sequence in place. 80 81 @type lyrics: bool 82 @param lyrics: use lyrics events the mark the chords. By default 83 uses marker events, which are more appropriate but may not 84 be supported by your player. 85 @type sequence: L{ChordSequence<jazzparser.data.db_mirrors.ChordSequence>} 86 @param sequence: the chord sequence to take chords from 87 @type mid: L{midi.EventStream} 88 @param sequence: input midi sequence 89 @rtype: L{midi.EventStream} 90 @return: the original midi sequence with text added in for the 91 chords. 92 93 """ 94 if self.midi_beats_per_beat > 0: 95 ticks_per_seq_beat = self.midi_beats_per_beat * mid.resolution 96 elif self.midi_beats_per_beat < -1: 97 ticks_per_seq_beat = mid.resolution / abs(self.midi_beats_per_beat) 98 else: 99 raise ValueError, "midi_beats_per_beat should be >0 or <-1: "\ 100 "not %s" % self.midi_beats_per_beat 101 102 # Look for the default start time for the chords 103 noteon = first_note_on(mid) 104 if noteon is None: 105 # No note-on events found at all: we'll start at tick 0, 106 # but the result's going to be nonsensical 107 tick = 0 108 else: 109 # Shift our start point by the requested number of ticks 110 tick = noteon.tick + self.sequence_start 111 112 cursor = 0 113 # Keep track of what repeats we've got to do and where they go 114 repeats = {} 115 for start,end,count in self.repeat_spans: 116 if start >= end: 117 raise MidiAlignmentError, "nonsensical repeat span "\ 118 "ends before it starts: (%d,%d,%d)" % (start,end,count) 119 repeats.setdefault(end, []).extend([start] * count) 120 121 mid.curtrack = 0 122 sequence = list(sequence.iterator()) 123 while cursor < len(sequence): 124 # Get the chord for the current cursor from the sequence 125 chord = sequence[cursor] 126 # Add a text event to say what the chord is 127 if lyrics: 128 ev = LyricsEvent() 129 ev.data = "%s " % chord 130 else: 131 ev = MarkerEvent() 132 ev.data = "<%d> %s" % (cursor,chord) 133 ev.tick = tick 134 mid.add_event(ev) 135 136 # Move the midi tick cursor on 137 tick += chord.duration * ticks_per_seq_beat 138 if cursor in repeats: 139 # A repeat span ends at this chord: go back to the start 140 new_cursor = repeats[cursor].pop(0) 141 if len(repeats[cursor]) == 0: 142 # No more spans with this end point 143 del repeats[cursor] 144 cursor = new_cursor 145 else: 146 # No repeats: just move to the next chord 147 cursor += 1 148 mid.timesort() 149 return mid
150
151 -def first_note_on(mid):
152 """ 153 Returns the earliest note-on event in the MIDI file 154 (L{midi.EventStream}). If no note-on events are found, returns 155 None. 156 157 """ 158 from midi import NoteOnEvent 159 160 evs = sorted(mid.trackpool) 161 for ev in evs: 162 if isinstance(ev, NoteOnEvent): 163 return ev 164 return None
165
166 -class MidiAlignmentError(Exception):
167 pass
168