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
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 """
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
103 noteon = first_note_on(mid)
104 if noteon is None:
105
106
107 tick = 0
108 else:
109
110 tick = noteon.tick + self.sequence_start
111
112 cursor = 0
113
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
125 chord = sequence[cursor]
126
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
137 tick += chord.duration * ticks_per_seq_beat
138 if cursor in repeats:
139
140 new_cursor = repeats[cursor].pop(0)
141 if len(repeats[cursor]) == 0:
142
143 del repeats[cursor]
144 cursor = new_cursor
145 else:
146
147 cursor += 1
148 mid.timesort()
149 return mid
150
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
168