retrait TODO.
[minwii.git] / src / mxmMidi / MidiOutFile.py
1 # -*- coding: ISO-8859-1 -*-
2
3 from MidiOutStream import MidiOutStream
4 from RawOutstreamFile import RawOutstreamFile
5
6 from constants import *
7 from DataTypeConverters import fromBytes, writeVar
8
9 class MidiOutFile(MidiOutStream):
10
11
12 """
13 MidiOutFile is an eventhandler that subclasses MidiOutStream.
14 """
15
16
17 def __init__(self, raw_out=''):
18
19 self.raw_out = RawOutstreamFile(raw_out)
20 MidiOutStream.__init__(self)
21
22
23 def write(self):
24 self.raw_out.write()
25
26
27 def event_slice(self, slc):
28 """
29 Writes the slice of an event to the current track. Correctly
30 inserting a varlen timestamp too.
31 """
32 trk = self._current_track_buffer
33 trk.writeVarLen(self.rel_time())
34 trk.writeSlice(slc)
35
36
37 #####################
38 ## Midi events
39
40
41 def note_on(self, channel=0, note=0x40, velocity=0x40):
42
43 """
44 channel: 0-15
45 note, velocity: 0-127
46 """
47 slc = fromBytes([NOTE_ON + channel, note, velocity])
48 self.event_slice(slc)
49
50
51 def note_off(self, channel=0, note=0x40, velocity=0x40):
52
53 """
54 channel: 0-15
55 note, velocity: 0-127
56 """
57 slc = fromBytes([NOTE_OFF + channel, note, velocity])
58 self.event_slice(slc)
59
60
61 def aftertouch(self, channel=0, note=0x40, velocity=0x40):
62
63 """
64 channel: 0-15
65 note, velocity: 0-127
66 """
67 slc = fromBytes([AFTERTOUCH + channel, note, velocity])
68 self.event_slice(slc)
69
70
71 def continuous_controller(self, channel, controller, value):
72
73 """
74 channel: 0-15
75 controller, value: 0-127
76 """
77 slc = fromBytes([CONTINUOUS_CONTROLLER + channel, controller, value])
78 self.event_slice(slc)
79 # These should probably be implemented
80 # http://users.argonet.co.uk/users/lenny/midi/tech/spec.html#ctrlnums
81
82
83 def patch_change(self, channel, patch):
84
85 """
86 channel: 0-15
87 patch: 0-127
88 """
89 slc = fromBytes([PATCH_CHANGE + channel, patch])
90 self.event_slice(slc)
91
92
93 def channel_pressure(self, channel, pressure):
94
95 """
96 channel: 0-15
97 pressure: 0-127
98 """
99 slc = fromBytes([CHANNEL_PRESSURE + channel, pressure])
100 self.event_slice(slc)
101
102
103 def pitch_bend(self, channel, value):
104
105 """
106 channel: 0-15
107 value: 0-16383
108 """
109 msb = (value>>7) & 0xFF
110 lsb = value & 0xFF
111 slc = fromBytes([PITCH_BEND + channel, msb, lsb])
112 self.event_slice(slc)
113
114
115
116
117 #####################
118 ## System Exclusive
119
120 # def sysex_slice(sysex_type, data):
121 # ""
122 # sysex_len = writeVar(len(data)+1)
123 # self.event_slice(SYSTEM_EXCLUSIVE + sysex_len + data + END_OFF_EXCLUSIVE)
124 #
125 def system_exclusive(self, data):
126
127 """
128 data: list of values in range(128)
129 """
130 sysex_len = writeVar(len(data)+1)
131 self.event_slice(chr(SYSTEM_EXCLUSIVE) + sysex_len + data + chr(END_OFF_EXCLUSIVE))
132
133
134 #####################
135 ## Common events
136
137 def midi_time_code(self, msg_type, values):
138 """
139 msg_type: 0-7
140 values: 0-15
141 """
142 value = (msg_type<<4) + values
143 self.event_slice(fromBytes([MIDI_TIME_CODE, value]))
144
145
146 def song_position_pointer(self, value):
147
148 """
149 value: 0-16383
150 """
151 lsb = (value & 0x7F)
152 msb = (value >> 7) & 0x7F
153 self.event_slice(fromBytes([SONG_POSITION_POINTER, lsb, msb]))
154
155
156 def song_select(self, songNumber):
157
158 """
159 songNumber: 0-127
160 """
161 self.event_slice(fromBytes([SONG_SELECT, songNumber]))
162
163
164 def tuning_request(self):
165
166 """
167 No values passed
168 """
169 self.event_slice(chr(TUNING_REQUEST))
170
171
172 #########################
173 # header does not really belong here. But anyhoo!!!
174
175 def header(self, format=0, nTracks=1, division=96):
176
177 """
178 format: type of midi file in [0,1,2]
179 nTracks: number of tracks. 1 track for type 0 file
180 division: timing division ie. 96 ppq.
181
182 """
183 raw = self.raw_out
184 raw.writeSlice('MThd')
185 bew = raw.writeBew
186 bew(6, 4) # header size
187 bew(format, 2)
188 bew(nTracks, 2)
189 bew(division, 2)
190
191
192 def eof(self):
193
194 """
195 End of file. No more events to be processed.
196 """
197 # just write the file then.
198 self.write()
199
200
201 #####################
202 ## meta events
203
204
205 def meta_slice(self, meta_type, data_slice):
206 "Writes a meta event"
207 slc = fromBytes([META_EVENT, meta_type]) + \
208 writeVar(len(data_slice)) + data_slice
209 self.event_slice(slc)
210
211
212 def meta_event(self, meta_type, data):
213 """
214 Handles any undefined meta events
215 """
216 self.meta_slice(meta_type, fromBytes(data))
217
218
219 def start_of_track(self, n_track=0):
220 """
221 n_track: number of track
222 """
223 self._current_track_buffer = RawOutstreamFile()
224 self.reset_time()
225 self._current_track += 1
226
227
228 def end_of_track(self):
229 """
230 Writes the track to the buffer.
231 """
232 raw = self.raw_out
233 raw.writeSlice(TRACK_HEADER)
234 track_data = self._current_track_buffer.getvalue()
235 # wee need to know size of track data.
236 eot_slice = writeVar(self.rel_time()) + fromBytes([META_EVENT, END_OF_TRACK, 0])
237 raw.writeBew(len(track_data)+len(eot_slice), 4)
238 # then write
239 raw.writeSlice(track_data)
240 raw.writeSlice(eot_slice)
241
242
243
244 def sequence_number(self, value):
245
246 """
247 value: 0-65535
248 """
249 self.meta_slice(meta_type, writeBew(value, 2))
250
251
252 def text(self, text):
253 """
254 Text event
255 text: string
256 """
257 self.meta_slice(TEXT, text)
258
259
260 def copyright(self, text):
261
262 """
263 Copyright notice
264 text: string
265 """
266 self.meta_slice(COPYRIGHT, text)
267
268
269 def sequence_name(self, text):
270 """
271 Sequence/track name
272 text: string
273 """
274 self.meta_slice(SEQUENCE_NAME, text)
275
276
277 def instrument_name(self, text):
278
279 """
280 text: string
281 """
282 self.meta_slice(INSTRUMENT_NAME, text)
283
284
285 def lyric(self, text):
286
287 """
288 text: string
289 """
290 self.meta_slice(LYRIC, text)
291
292
293 def marker(self, text):
294
295 """
296 text: string
297 """
298 self.meta_slice(MARKER, text)
299
300
301 def cuepoint(self, text):
302
303 """
304 text: string
305 """
306 self.meta_slice(CUEPOINT, text)
307
308
309 def midi_ch_prefix(self, channel):
310
311 """
312 channel: midi channel for subsequent data
313 (deprecated in the spec)
314 """
315 self.meta_slice(MIDI_CH_PREFIX, chr(channel))
316
317
318 def midi_port(self, value):
319
320 """
321 value: Midi port (deprecated in the spec)
322 """
323 self.meta_slice(MIDI_CH_PREFIX, chr(value))
324
325
326 def tempo(self, value):
327
328 """
329 value: 0-2097151
330 tempo in us/quarternote
331 (to calculate value from bpm: int(60,000,000.00 / BPM))
332 """
333 hb, mb, lb = (value>>16 & 0xff), (value>>8 & 0xff), (value & 0xff)
334 self.meta_slice(TEMPO, fromBytes([hb, mb, lb]))
335
336
337 def smtp_offset(self, hour, minute, second, frame, framePart):
338
339 """
340 hour,
341 minute,
342 second: 3 bytes specifying the hour (0-23), minutes (0-59) and
343 seconds (0-59), respectively. The hour should be
344 encoded with the SMPTE format, just as it is in MIDI
345 Time Code.
346 frame: A byte specifying the number of frames per second (one
347 of : 24, 25, 29, 30).
348 framePart: A byte specifying the number of fractional frames,
349 in 100ths of a frame (even in SMPTE-based tracks
350 using a different frame subdivision, defined in the
351 MThd chunk).
352 """
353 self.meta_slice(SMTP_OFFSET, fromBytes([hour, minute, second, frame, framePart]))
354
355
356
357 def time_signature(self, nn, dd, cc, bb):
358
359 """
360 nn: Numerator of the signature as notated on sheet music
361 dd: Denominator of the signature as notated on sheet music
362 The denominator is a negative power of 2: 2 = quarter
363 note, 3 = eighth, etc.
364 cc: The number of MIDI clocks in a metronome click
365 bb: The number of notated 32nd notes in a MIDI quarter note
366 (24 MIDI clocks)
367 """
368 self.meta_slice(TIME_SIGNATURE, fromBytes([nn, dd, cc, bb]))
369
370
371
372
373 def key_signature(self, sf, mi):
374
375 """
376 sf: is a byte specifying the number of flats (-ve) or sharps
377 (+ve) that identifies the key signature (-7 = 7 flats, -1
378 = 1 flat, 0 = key of C, 1 = 1 sharp, etc).
379 mi: is a byte specifying a major (0) or minor (1) key.
380 """
381 self.meta_slice(KEY_SIGNATURE, fromBytes([sf, mi]))
382
383
384
385 def sequencer_specific(self, data):
386
387 """
388 data: The data as byte values
389 """
390 self.meta_slice(SEQUENCER_SPECIFIC, data)
391
392
393
394
395
396 # #####################
397 # ## realtime events
398
399 # These are of no use in a midi file, so they are ignored!!!
400
401 # def timing_clock(self):
402 # def song_start(self):
403 # def song_stop(self):
404 # def song_continue(self):
405 # def active_sensing(self):
406 # def system_reset(self):
407
408
409
410 if __name__ == '__main__':
411
412 out_file = 'test/midifiles/midiout.mid'
413 midi = MidiOutFile(out_file)
414
415 #format: 0, nTracks: 1, division: 480
416 #----------------------------------
417 #
418 #Start - track #0
419 #sequence_name: Type 0
420 #tempo: 500000
421 #time_signature: 4 2 24 8
422 #note_on - ch:00, note:48, vel:64 time:0
423 #note_off - ch:00, note:48, vel:40 time:480
424 #End of track
425 #
426 #End of file
427
428
429 midi.header(0, 1, 480)
430
431 midi.start_of_track()
432 midi.sequence_name('Type 0')
433 midi.tempo(750000)
434 midi.time_signature(4, 2, 24, 8)
435 ch = 0
436 for i in range(127):
437 midi.note_on(ch, i, 0x64)
438 midi.update_time(96)
439 midi.note_off(ch, i, 0x40)
440 midi.update_time(0)
441
442 midi.update_time(0)
443 midi.end_of_track()
444
445 midi.eof() # currently optional, should it do the write instead of write??
446
447
448 midi.write()