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
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
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
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
91 chunks.append(current_chunk)
92
93
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
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
189
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
242 instrument = 23
243
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
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
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
275 triad_notes = [(chord_root + note) % (octaves*12) + 72 for note in constants.TRIAD_NOTES[triad_type]]
276
277 triad_notes.append(chord_root + 48)
278
279
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
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
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
303 noff = NoteOffEvent()
304 noff.pitch = note
305 noff.channel = channel
306 noff.velocity = volume
307 pending_note_offs.append(noff)
308
309
310 for noff in pending_note_offs:
311 noff.tick = time+chord_length
312 stream.add_event(noff)
313 return stream
314