midilib 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +87 -0
- data/Credits +8 -0
- data/README +361 -0
- data/Rakefile +68 -0
- data/TODO +25 -0
- data/examples/NoFences.mid +0 -0
- data/examples/from_scratch.mid +0 -0
- data/examples/from_scratch.rb +56 -0
- data/examples/reader2text.rb +220 -0
- data/examples/seq2text.rb +41 -0
- data/examples/strings.rb +34 -0
- data/examples/transpose.rb +75 -0
- data/html/classes/MIDI.html +738 -0
- data/html/classes/MIDI/ActiveSense.html +156 -0
- data/html/classes/MIDI/ActiveSense.src/M000136.html +18 -0
- data/html/classes/MIDI/ActiveSense.src/M000137.html +18 -0
- data/html/classes/MIDI/ChannelEvent.html +179 -0
- data/html/classes/MIDI/ChannelEvent.src/M000124.html +20 -0
- data/html/classes/MIDI/ChannelEvent.src/M000125.html +18 -0
- data/html/classes/MIDI/ChannelPressure.html +184 -0
- data/html/classes/MIDI/ChannelPressure.src/M000088.html +19 -0
- data/html/classes/MIDI/ChannelPressure.src/M000089.html +21 -0
- data/html/classes/MIDI/ChannelPressure.src/M000090.html +18 -0
- data/html/classes/MIDI/Clock.html +156 -0
- data/html/classes/MIDI/Clock.src/M000134.html +18 -0
- data/html/classes/MIDI/Clock.src/M000135.html +18 -0
- data/html/classes/MIDI/Continue.html +156 -0
- data/html/classes/MIDI/Continue.src/M000132.html +18 -0
- data/html/classes/MIDI/Continue.src/M000133.html +18 -0
- data/html/classes/MIDI/Controller.html +189 -0
- data/html/classes/MIDI/Controller.src/M000129.html +21 -0
- data/html/classes/MIDI/Controller.src/M000130.html +22 -0
- data/html/classes/MIDI/Controller.src/M000131.html +18 -0
- data/html/classes/MIDI/Event.html +424 -0
- data/html/classes/MIDI/Event.src/M000145.html +27 -0
- data/html/classes/MIDI/Event.src/M000146.html +18 -0
- data/html/classes/MIDI/Event.src/M000147.html +18 -0
- data/html/classes/MIDI/Event.src/M000148.html +18 -0
- data/html/classes/MIDI/Event.src/M000149.html +18 -0
- data/html/classes/MIDI/Event.src/M000150.html +18 -0
- data/html/classes/MIDI/Event.src/M000151.html +18 -0
- data/html/classes/MIDI/Event.src/M000152.html +18 -0
- data/html/classes/MIDI/Event.src/M000153.html +18 -0
- data/html/classes/MIDI/Event.src/M000154.html +22 -0
- data/html/classes/MIDI/Event.src/M000155.html +18 -0
- data/html/classes/MIDI/Event.src/M000156.html +18 -0
- data/html/classes/MIDI/Event.src/M000157.html +18 -0
- data/html/classes/MIDI/IO.html +121 -0
- data/html/classes/MIDI/IO/MIDIFile.html +925 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000028.html +22 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000029.html +24 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000030.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000031.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000032.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000033.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000034.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000035.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000036.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000037.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000038.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000039.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000040.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000041.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000042.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000043.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000044.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000045.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000046.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000047.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000048.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000049.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000050.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000051.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000052.html +17 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000053.html +43 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000054.html +34 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000055.html +96 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000056.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000057.html +48 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000058.html +42 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000059.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000060.html +19 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000061.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000062.html +21 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000063.html +31 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000064.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000065.html +22 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000066.html +30 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000067.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000068.html +20 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000069.html +18 -0
- data/html/classes/MIDI/IO/MIDIFile.src/M000070.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.html +460 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000001.html +22 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000002.html +22 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000003.html +21 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000004.html +34 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000005.html +26 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000006.html +28 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000007.html +21 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000008.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000009.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000010.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000011.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000012.html +19 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000013.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000014.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000015.html +27 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000016.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000017.html +18 -0
- data/html/classes/MIDI/IO/SeqReader.src/M000018.html +18 -0
- data/html/classes/MIDI/IO/SeqWriter.html +267 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000019.html +19 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000020.html +25 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000021.html +22 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000022.html +54 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000023.html +49 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000024.html +21 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000025.html +19 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000026.html +24 -0
- data/html/classes/MIDI/IO/SeqWriter.src/M000027.html +26 -0
- data/html/classes/MIDI/Marker.html +139 -0
- data/html/classes/MIDI/Marker.src/M000107.html +18 -0
- data/html/classes/MIDI/MetaEvent.html +189 -0
- data/html/classes/MIDI/MetaEvent.src/M000167.html +21 -0
- data/html/classes/MIDI/MetaEvent.src/M000168.html +23 -0
- data/html/classes/MIDI/MetaEvent.src/M000169.html +53 -0
- data/html/classes/MIDI/NoteEvent.html +233 -0
- data/html/classes/MIDI/NoteEvent.src/M000091.html +21 -0
- data/html/classes/MIDI/NoteEvent.src/M000092.html +20 -0
- data/html/classes/MIDI/NoteEvent.src/M000093.html +18 -0
- data/html/classes/MIDI/NoteEvent.src/M000094.html +22 -0
- data/html/classes/MIDI/NoteOffEvent.html +169 -0
- data/html/classes/MIDI/NoteOffEvent.src/M000086.html +19 -0
- data/html/classes/MIDI/NoteOffEvent.src/M000087.html +19 -0
- data/html/classes/MIDI/NoteOnEvent.html +169 -0
- data/html/classes/MIDI/NoteOnEvent.src/M000102.html +19 -0
- data/html/classes/MIDI/NoteOnEvent.src/M000103.html +19 -0
- data/html/classes/MIDI/PitchBend.html +184 -0
- data/html/classes/MIDI/PitchBend.src/M000104.html +19 -0
- data/html/classes/MIDI/PitchBend.src/M000105.html +22 -0
- data/html/classes/MIDI/PitchBend.src/M000106.html +18 -0
- data/html/classes/MIDI/PolyPressure.html +186 -0
- data/html/classes/MIDI/PolyPressure.src/M000170.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000171.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000172.html +18 -0
- data/html/classes/MIDI/PolyPressure.src/M000173.html +19 -0
- data/html/classes/MIDI/ProgramChange.html +184 -0
- data/html/classes/MIDI/ProgramChange.src/M000099.html +19 -0
- data/html/classes/MIDI/ProgramChange.src/M000100.html +21 -0
- data/html/classes/MIDI/ProgramChange.src/M000101.html +18 -0
- data/html/classes/MIDI/Realtime.html +171 -0
- data/html/classes/MIDI/Realtime.src/M000083.html +19 -0
- data/html/classes/MIDI/Realtime.src/M000084.html +20 -0
- data/html/classes/MIDI/Realtime.src/M000085.html +18 -0
- data/html/classes/MIDI/Sequence.html +469 -0
- data/html/classes/MIDI/Sequence.src/M000108.html +28 -0
- data/html/classes/MIDI/Sequence.src/M000109.html +21 -0
- data/html/classes/MIDI/Sequence.src/M000110.html +22 -0
- data/html/classes/MIDI/Sequence.src/M000113.html +18 -0
- data/html/classes/MIDI/Sequence.src/M000114.html +27 -0
- data/html/classes/MIDI/Sequence.src/M000115.html +18 -0
- data/html/classes/MIDI/Sequence.src/M000116.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000117.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000118.html +20 -0
- data/html/classes/MIDI/Sequence.src/M000119.html +19 -0
- data/html/classes/MIDI/Sequence.src/M000120.html +18 -0
- data/html/classes/MIDI/SongPointer.html +184 -0
- data/html/classes/MIDI/SongPointer.src/M000138.html +19 -0
- data/html/classes/MIDI/SongPointer.src/M000139.html +22 -0
- data/html/classes/MIDI/SongPointer.src/M000140.html +18 -0
- data/html/classes/MIDI/SongSelect.html +184 -0
- data/html/classes/MIDI/SongSelect.src/M000126.html +19 -0
- data/html/classes/MIDI/SongSelect.src/M000127.html +21 -0
- data/html/classes/MIDI/SongSelect.src/M000128.html +18 -0
- data/html/classes/MIDI/Start.html +156 -0
- data/html/classes/MIDI/Start.src/M000097.html +18 -0
- data/html/classes/MIDI/Start.src/M000098.html +18 -0
- data/html/classes/MIDI/Stop.html +156 -0
- data/html/classes/MIDI/Stop.src/M000095.html +18 -0
- data/html/classes/MIDI/Stop.src/M000096.html +18 -0
- data/html/classes/MIDI/SystemCommon.html +139 -0
- data/html/classes/MIDI/SystemCommon.src/M000144.html +19 -0
- data/html/classes/MIDI/SystemExclusive.html +184 -0
- data/html/classes/MIDI/SystemExclusive.src/M000141.html +19 -0
- data/html/classes/MIDI/SystemExclusive.src/M000142.html +23 -0
- data/html/classes/MIDI/SystemExclusive.src/M000143.html +18 -0
- data/html/classes/MIDI/SystemReset.html +156 -0
- data/html/classes/MIDI/SystemReset.src/M000081.html +18 -0
- data/html/classes/MIDI/SystemReset.src/M000082.html +18 -0
- data/html/classes/MIDI/Tempo.html +250 -0
- data/html/classes/MIDI/Tempo.src/M000158.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000159.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000160.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000161.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000162.html +18 -0
- data/html/classes/MIDI/Tempo.src/M000163.html +25 -0
- data/html/classes/MIDI/Tempo.src/M000164.html +18 -0
- data/html/classes/MIDI/Track.html +380 -0
- data/html/classes/MIDI/Track.src/M000071.html +23 -0
- data/html/classes/MIDI/Track.src/M000072.html +21 -0
- data/html/classes/MIDI/Track.src/M000073.html +26 -0
- data/html/classes/MIDI/Track.src/M000074.html +18 -0
- data/html/classes/MIDI/Track.src/M000075.html +22 -0
- data/html/classes/MIDI/Track.src/M000076.html +20 -0
- data/html/classes/MIDI/Track.src/M000077.html +22 -0
- data/html/classes/MIDI/Track.src/M000078.html +22 -0
- data/html/classes/MIDI/Track.src/M000079.html +18 -0
- data/html/classes/MIDI/Track.src/M000080.html +19 -0
- data/html/classes/MIDI/TuneRequest.html +171 -0
- data/html/classes/MIDI/TuneRequest.src/M000121.html +18 -0
- data/html/classes/MIDI/TuneRequest.src/M000122.html +20 -0
- data/html/classes/MIDI/TuneRequest.src/M000123.html +18 -0
- data/html/classes/MIDI/Utils.html +190 -0
- data/html/classes/MIDI/Utils.src/M000165.html +20 -0
- data/html/classes/MIDI/Utils.src/M000166.html +25 -0
- data/html/created.rid +1 -0
- data/html/files/README.html +599 -0
- data/html/files/TODO.html +142 -0
- data/html/files/lib/midilib/consts_rb.html +107 -0
- data/html/files/lib/midilib/event_rb.html +109 -0
- data/html/files/lib/midilib/info_rb.html +101 -0
- data/html/files/lib/midilib/io/midifile_rb.html +108 -0
- data/html/files/lib/midilib/io/seqreader_rb.html +110 -0
- data/html/files/lib/midilib/io/seqwriter_rb.html +115 -0
- data/html/files/lib/midilib/sequence_rb.html +109 -0
- data/html/files/lib/midilib/track_rb.html +108 -0
- data/html/files/lib/midilib/utils_rb.html +101 -0
- data/html/files/lib/midilib_rb.html +124 -0
- data/html/fr_class_index.html +59 -0
- data/html/fr_file_index.html +38 -0
- data/html/fr_method_index.html +199 -0
- data/html/index.html +24 -0
- data/html/rdoc-style.css +208 -0
- data/install.rb +57 -0
- data/lib/midilib.rb +16 -0
- data/lib/midilib/consts.rb +422 -0
- data/lib/midilib/event.rb +559 -0
- data/lib/midilib/info.rb +9 -0
- data/lib/midilib/io/midifile.rb +446 -0
- data/lib/midilib/io/seqreader.rb +198 -0
- data/lib/midilib/io/seqwriter.rb +151 -0
- data/lib/midilib/sequence.rb +144 -0
- data/lib/midilib/track.rb +115 -0
- data/lib/midilib/utils.rb +36 -0
- data/test/event_equality.rb +81 -0
- data/test/test_event.rb +116 -0
- data/test/test_io.rb +55 -0
- data/test/test_sequence.rb +68 -0
- data/test/test_track.rb +111 -0
- data/test/test_varlen.rb +40 -0
- metadata +330 -0
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'midilib/io/midifile'
|
2
|
+
require 'midilib/track'
|
3
|
+
require 'midilib/event'
|
4
|
+
|
5
|
+
module MIDI
|
6
|
+
|
7
|
+
module IO
|
8
|
+
|
9
|
+
# Reads MIDI files. As a subclass of MIDIFile, this class implements
|
10
|
+
# the callback methods for each MIDI event and use them to build Track
|
11
|
+
# and Event objects and give the tracks to a Sequence.
|
12
|
+
#
|
13
|
+
# We append new events to the end of a track's event list, bypassing a call
|
14
|
+
# to Track.#add. This means that we must call Track.recalc_times at the end
|
15
|
+
# of the track so it can update each event with its time from the track's
|
16
|
+
# start (see end_track below).
|
17
|
+
#
|
18
|
+
# META_TRACK_END events are not added to tracks. This way, we don't have to
|
19
|
+
# worry about making sure the last event is always a track end event. We
|
20
|
+
# rely on the SeqWriter to append a META_TRACK_END event to each track when
|
21
|
+
# it is output.
|
22
|
+
|
23
|
+
class SeqReader < MIDIFile
|
24
|
+
|
25
|
+
# The optional proc block is called once at the start of the file
|
26
|
+
# and again at the end of each track. There are three arguments
|
27
|
+
# to the block: the track, the track number (1 through _n_), and
|
28
|
+
# the total number of tracks.
|
29
|
+
def initialize(seq, proc = nil) # :yields: track, num_tracks, index
|
30
|
+
super()
|
31
|
+
@seq = seq
|
32
|
+
@track = nil
|
33
|
+
@chan_mask = 0
|
34
|
+
@update_block = block_given?() ? Proc.new() : proc
|
35
|
+
end
|
36
|
+
|
37
|
+
def header(format, ntrks, division)
|
38
|
+
@seq.format = format
|
39
|
+
@seq.ppqn = division
|
40
|
+
|
41
|
+
@ntrks = ntrks
|
42
|
+
@update_block.call(nil, @ntrks, 0) if @update_block
|
43
|
+
end
|
44
|
+
|
45
|
+
def start_track()
|
46
|
+
@track = Track.new(@seq)
|
47
|
+
@seq.tracks << @track
|
48
|
+
|
49
|
+
@pending = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def end_track()
|
53
|
+
# Turn off any pending note on messages
|
54
|
+
@pending.each { | on | make_note_off(on, 64) }
|
55
|
+
@pending = nil
|
56
|
+
|
57
|
+
# Don't bother adding the META_TRACK_END event to the track.
|
58
|
+
# This way, we don't have to worry about making sure the
|
59
|
+
# last event is always a track end event.
|
60
|
+
|
61
|
+
# Let the track calculate event times from start of track. This is
|
62
|
+
# in lieu of calling Track.add for each event.
|
63
|
+
@track.recalc_times()
|
64
|
+
|
65
|
+
# Store bitmask of all channels used into track
|
66
|
+
@track.channels_used = @chan_mask
|
67
|
+
|
68
|
+
# call update block
|
69
|
+
@update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
|
70
|
+
end
|
71
|
+
|
72
|
+
def note_on(chan, note, vel)
|
73
|
+
if vel == 0
|
74
|
+
note_off(chan, note, 64)
|
75
|
+
return
|
76
|
+
end
|
77
|
+
|
78
|
+
on = NoteOnEvent.new(chan, note, vel, @curr_ticks)
|
79
|
+
@track.events << on
|
80
|
+
@pending << on
|
81
|
+
track_uses_channel(chan)
|
82
|
+
end
|
83
|
+
|
84
|
+
def note_off(chan, note, vel)
|
85
|
+
# Find note on, create note off, connect the two, and remove
|
86
|
+
# note on from pending list.
|
87
|
+
@pending.each_with_index { | on, i |
|
88
|
+
if on.note == note && on.channel == chan
|
89
|
+
make_note_off(on, vel)
|
90
|
+
@pending.delete_at(i)
|
91
|
+
return
|
92
|
+
end
|
93
|
+
}
|
94
|
+
$stderr.puts "note off with no earlier note on (ch #{chan}, note" +
|
95
|
+
" #{note}, vel #{vel})" if $DEBUG
|
96
|
+
end
|
97
|
+
|
98
|
+
def make_note_off(on, vel)
|
99
|
+
off = NoteOffEvent.new(on.channel, on.note, vel, @curr_ticks)
|
100
|
+
@track.events << off
|
101
|
+
on.off = off
|
102
|
+
off.on = on
|
103
|
+
end
|
104
|
+
|
105
|
+
def pressure(chan, note, press)
|
106
|
+
@track.events << PolyPressure.new(chan, note, press, @curr_ticks)
|
107
|
+
track_uses_channel(chan)
|
108
|
+
end
|
109
|
+
|
110
|
+
def controller(chan, control, value)
|
111
|
+
@track.events << Controller.new(chan, control, value, @curr_ticks)
|
112
|
+
track_uses_channel(chan)
|
113
|
+
end
|
114
|
+
|
115
|
+
def pitch_bend(chan, msb, lsb)
|
116
|
+
@track.events << PitchBend.new(chan, (msb << 8) + lsb, @curr_ticks)
|
117
|
+
track_uses_channel(chan)
|
118
|
+
end
|
119
|
+
|
120
|
+
def program(chan, program)
|
121
|
+
@track.events << ProgramChange.new(chan, program, @curr_ticks)
|
122
|
+
track_uses_channel(chan)
|
123
|
+
end
|
124
|
+
|
125
|
+
def chan_pressure(chan, press)
|
126
|
+
@track.events << ChannelPressure.new(chan, press, @curr_ticks)
|
127
|
+
track_uses_channel(chan)
|
128
|
+
end
|
129
|
+
|
130
|
+
def sysex(msg)
|
131
|
+
@track.events << SystemExclusive.new(msg, @curr_ticks)
|
132
|
+
end
|
133
|
+
|
134
|
+
def meta_misc(type, msg)
|
135
|
+
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
136
|
+
end
|
137
|
+
|
138
|
+
# --
|
139
|
+
# def sequencer_specific(type, msg)
|
140
|
+
# end
|
141
|
+
|
142
|
+
# def sequence_number(num)
|
143
|
+
# end
|
144
|
+
# ++
|
145
|
+
|
146
|
+
def text(type, msg)
|
147
|
+
case type
|
148
|
+
when META_SEQ_NAME
|
149
|
+
@track.events << MetaEvent.new(META_SEQ_NAME, msg, 0)
|
150
|
+
when META_INSTRUMENT
|
151
|
+
@track.instrument = msg
|
152
|
+
when META_MARKER
|
153
|
+
@track.events << Marker.new(msg, @curr_ticks)
|
154
|
+
else
|
155
|
+
$stderr.puts "text = #{msg}, type = #{type}" if $DEBUG
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# --
|
160
|
+
# Don't bother adding the META_TRACK_END event to the track. This way,
|
161
|
+
# we don't have to worry about always making sure the last event is
|
162
|
+
# always a track end event. We just have to make sure to write one when
|
163
|
+
# the track is output back to a file.
|
164
|
+
# def eot()
|
165
|
+
# @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
|
166
|
+
# end
|
167
|
+
# ++
|
168
|
+
|
169
|
+
def time_signature(numer, denom, clocks, qnotes)
|
170
|
+
@seq.time_signature(numer, denom, clocks, qnotes)
|
171
|
+
end
|
172
|
+
|
173
|
+
# --
|
174
|
+
# def smpte(hour, min, sec, frame, fract)
|
175
|
+
# end
|
176
|
+
# ++
|
177
|
+
|
178
|
+
def tempo(microsecs)
|
179
|
+
@track.events << Tempo.new(microsecs, @curr_ticks)
|
180
|
+
end
|
181
|
+
|
182
|
+
# --
|
183
|
+
# def key_signature(sharpflat, is_minor)
|
184
|
+
# end
|
185
|
+
|
186
|
+
# def arbitrary(msg)
|
187
|
+
# end
|
188
|
+
# ++
|
189
|
+
|
190
|
+
# Return true if the current track uses the specified channel.
|
191
|
+
def track_uses_channel(chan)
|
192
|
+
@chan_mask = @chan_mask | (1 << chan)
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# Writes MIDI files.
|
2
|
+
|
3
|
+
require 'midilib/event'
|
4
|
+
require 'midilib/utils'
|
5
|
+
|
6
|
+
module MIDI
|
7
|
+
|
8
|
+
module IO
|
9
|
+
|
10
|
+
class SeqWriter
|
11
|
+
|
12
|
+
def initialize(seq, proc = nil) # :yields: num_tracks, index
|
13
|
+
@seq = seq
|
14
|
+
@update_block = block_given?() ? Proc.new() : proc
|
15
|
+
end
|
16
|
+
|
17
|
+
# Writes a MIDI format 1 file.
|
18
|
+
def write_to(io)
|
19
|
+
@io = io
|
20
|
+
@bytes_written = 0
|
21
|
+
write_header()
|
22
|
+
@update_block.call(nil, @seq.tracks.length, 0) if @update_block
|
23
|
+
@seq.tracks.each_with_index { | track, i |
|
24
|
+
write_track(track)
|
25
|
+
@update_block.call(track, @seq.tracks.length, i) if @update_block
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_header
|
30
|
+
@io.print 'MThd'
|
31
|
+
write32(6)
|
32
|
+
write16(1) # Ignore sequence format; write as format 1
|
33
|
+
write16(@seq.tracks.length)
|
34
|
+
write16(@seq.ppqn)
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_track(track)
|
38
|
+
@io.print 'MTrk'
|
39
|
+
track_size_file_pos = @io.tell()
|
40
|
+
write32(0) # Dummy byte count; overwritten later
|
41
|
+
@bytes_written = 0 # Reset after previous write
|
42
|
+
|
43
|
+
write_instrument(track.instrument)
|
44
|
+
|
45
|
+
prev_event = nil
|
46
|
+
prev_status = 0
|
47
|
+
track.events.each { | event |
|
48
|
+
if !event.kind_of?(Realtime)
|
49
|
+
write_var_len(event.delta_time)
|
50
|
+
end
|
51
|
+
|
52
|
+
data = event.data_as_bytes()
|
53
|
+
status = data[0] # status byte plus channel number, if any
|
54
|
+
|
55
|
+
# running status byte
|
56
|
+
status = possibly_munge_due_to_running_status_byte(data,
|
57
|
+
prev_status)
|
58
|
+
|
59
|
+
@bytes_written += @io.write(data)
|
60
|
+
|
61
|
+
prev_event = event
|
62
|
+
prev_status = status
|
63
|
+
}
|
64
|
+
|
65
|
+
# Write track end event.
|
66
|
+
event = MetaEvent.new(META_TRACK_END)
|
67
|
+
write_var_len(0)
|
68
|
+
@bytes_written += @io.write(event.data_as_bytes())
|
69
|
+
|
70
|
+
# Go back to beginning of track data and write number of bytes,
|
71
|
+
# then come back here to end of file.
|
72
|
+
@io.seek(track_size_file_pos)
|
73
|
+
write32(@bytes_written)
|
74
|
+
@io.seek(0, ::IO::SEEK_END)
|
75
|
+
end
|
76
|
+
|
77
|
+
# If we can use a running status byte, delete the status byte from
|
78
|
+
# the given data. Return the status to remember for next time as the
|
79
|
+
# running status byte for this event.
|
80
|
+
def possibly_munge_due_to_running_status_byte(data, prev_status)
|
81
|
+
status = data[0]
|
82
|
+
return status if status >= 0xf0 || prev_status >= 0xf0
|
83
|
+
|
84
|
+
chan = (status & 0x0f)
|
85
|
+
return status if chan != (prev_status & 0x0f)
|
86
|
+
|
87
|
+
status = (status & 0xf0)
|
88
|
+
prev_status = (prev_status & 0xf0)
|
89
|
+
|
90
|
+
# Both events are on the same channel. If the two status bytes are
|
91
|
+
# exactly the same, the rest is trivial. If it's note on/note off,
|
92
|
+
# we can combine those further.
|
93
|
+
if status == prev_status
|
94
|
+
data[0] = '' # delete status byte from data
|
95
|
+
return status + chan
|
96
|
+
elsif status == NOTE_OFF && data[2] == 64
|
97
|
+
# If we see a note off and the velocity is 64, we can store
|
98
|
+
# a note on with a velocity of 0. If the velocity isn't 64
|
99
|
+
# then storing a note on would be bad because the would be
|
100
|
+
# changed to 64 when reading the file back in.
|
101
|
+
data[2] = 0 # set vel to 0; do before possible shrinking
|
102
|
+
status = NOTE_ON + chan
|
103
|
+
if prev_status == NOTE_ON
|
104
|
+
data[0] = '' # delete status byte
|
105
|
+
else
|
106
|
+
data[0] = status
|
107
|
+
end
|
108
|
+
return status
|
109
|
+
else
|
110
|
+
# Can't compress data
|
111
|
+
return status + chan
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def write_instrument(instrument)
|
116
|
+
event = MetaEvent.new(META_INSTRUMENT, instrument)
|
117
|
+
write_var_len(0)
|
118
|
+
data = event.data_as_bytes()
|
119
|
+
@bytes_written += @io.write(data)
|
120
|
+
end
|
121
|
+
|
122
|
+
def write_var_len(val)
|
123
|
+
buffer = Utils.as_var_len(val)
|
124
|
+
@bytes_written += @io.write(buffer)
|
125
|
+
end
|
126
|
+
|
127
|
+
def write16(val)
|
128
|
+
val = (-val | 0x8000) if val < 0
|
129
|
+
|
130
|
+
buffer = ''
|
131
|
+
buffer << ((val >> 8) & 0xff)
|
132
|
+
buffer << (val & 0xff)
|
133
|
+
|
134
|
+
@bytes_written += @io.write(buffer)
|
135
|
+
end
|
136
|
+
|
137
|
+
def write32(val)
|
138
|
+
val = (-val | 0x80000000) if val < 0
|
139
|
+
|
140
|
+
buffer = ''
|
141
|
+
buffer << ((val >> 24) & 0xff)
|
142
|
+
buffer << ((val >> 16) & 0xff)
|
143
|
+
buffer << ((val >> 8) & 0xff)
|
144
|
+
buffer << (val & 0xff)
|
145
|
+
|
146
|
+
@bytes_written += @io.write(buffer)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'midilib/io/seqreader'
|
2
|
+
require 'midilib/io/seqwriter'
|
3
|
+
|
4
|
+
module MIDI
|
5
|
+
|
6
|
+
# A MIDI::Sequence contains MIDI::Track objects.
|
7
|
+
class Sequence
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
UNNAMED = 'Unnamed Sequence'
|
12
|
+
DEFAULT_TEMPO = 120
|
13
|
+
|
14
|
+
NOTE_TO_LENGTH = {
|
15
|
+
'whole' => 4.0,
|
16
|
+
'half' => 2.0,
|
17
|
+
'quarter' => 1.0,
|
18
|
+
'eighth' => 0.5,
|
19
|
+
'8th' => 0.5,
|
20
|
+
'sixteenth' => 0.25,
|
21
|
+
'16th' => 0.25,
|
22
|
+
'thirty second' => 0.125,
|
23
|
+
'thirtysecond' => 0.125,
|
24
|
+
'32nd' => 0.125,
|
25
|
+
'sixty fourth' => 0.0625,
|
26
|
+
'sixtyfourth' => 0.0625,
|
27
|
+
'64th' => 0.0625
|
28
|
+
}
|
29
|
+
|
30
|
+
attr_accessor :tracks, :ppqn, :format
|
31
|
+
attr_accessor :numer, :denom, :clocks, :qnotes
|
32
|
+
# The class to use for reading MIDI from a stream. The default is
|
33
|
+
# MIDI::IO::SeqReader. You can change this at any time.
|
34
|
+
attr_accessor :reader_class
|
35
|
+
# The class to use for writeing MIDI from a stream. The default is
|
36
|
+
# MIDI::IO::SeqWriter. You can change this at any time.
|
37
|
+
attr_accessor :writer_class
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@tracks = Array.new()
|
41
|
+
@ppqn = 480
|
42
|
+
|
43
|
+
# Time signature
|
44
|
+
@numer = 4 # Numer + denom = 4/4 time default
|
45
|
+
@denom = 2
|
46
|
+
@clocks = @ppqn
|
47
|
+
@qnotes = 8
|
48
|
+
|
49
|
+
@reader_class = IO::SeqReader
|
50
|
+
@writer_class = IO::SeqWriter
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the time signature.
|
54
|
+
def time_signature(numer, denom, clocks, qnotes)
|
55
|
+
@numer = numer
|
56
|
+
@denom = denom
|
57
|
+
@clocks = clocks
|
58
|
+
@qnotes = qnotes
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the song tempo in beats per minute.
|
62
|
+
def beats_per_minute
|
63
|
+
return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
|
64
|
+
event = @tracks.first.events.detect { | e |
|
65
|
+
e.kind_of?(MIDI::Tempo)
|
66
|
+
}
|
67
|
+
return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO
|
68
|
+
end
|
69
|
+
alias_method :bpm, :beats_per_minute
|
70
|
+
alias_method :tempo, :beats_per_minute
|
71
|
+
|
72
|
+
# Given a note length name like "whole", "dotted quarter", or "8th
|
73
|
+
# triplet", return the length of that note in quarter notes as a delta
|
74
|
+
# time.
|
75
|
+
def note_to_delta(name)
|
76
|
+
return length_to_delta(note_to_length(name))
|
77
|
+
end
|
78
|
+
|
79
|
+
# Given a note length name like "whole", "dotted quarter", or "8th
|
80
|
+
# triplet", return the length of that note in quarter notes as a
|
81
|
+
# floating-point number, suitable for use as an argument to
|
82
|
+
# length_to_delta.
|
83
|
+
#
|
84
|
+
# Legal names are any value in NOTE_TO_LENGTH, optionally prefixed by
|
85
|
+
# "dotted_" and/or suffixed by "_triplet". So, for example,
|
86
|
+
# "dotted_quarter_triplet" returns the length of a dotted quarter-note
|
87
|
+
# triplet and "32nd" returns 1/32.
|
88
|
+
def note_to_length(name)
|
89
|
+
name.strip!
|
90
|
+
name =~ /^(dotted)?(.*?)(triplet)?$/
|
91
|
+
dotted, note_name, triplet = $1, $2, $3
|
92
|
+
note_name.strip!
|
93
|
+
mult = 1.0
|
94
|
+
mult = 1.5 if dotted
|
95
|
+
mult /= 3.0 if triplet
|
96
|
+
len = NOTE_TO_LENGTH[note_name]
|
97
|
+
raise "Sequence.note_to_length: \"#{note_name}\" not understood in \"#{name}\"" unless len
|
98
|
+
return len * mult
|
99
|
+
end
|
100
|
+
|
101
|
+
# Translates +length+ (a multiple of a quarter note) into a delta time.
|
102
|
+
# For example, 1 is a quarter note, 1.0/32.0 is a 32nd note, 1.5 is a
|
103
|
+
# dotted quarter, etc. Be aware when using division; 1/32 is zero due to
|
104
|
+
# integer mathematics and rounding. Use floating-point numbers like 1.0
|
105
|
+
# and 32.0. This method always returns an integer.
|
106
|
+
#
|
107
|
+
# See also note_to_delta and note_to_length.
|
108
|
+
def length_to_delta(length)
|
109
|
+
return (@ppqn * length).to_i
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns the name of the first track (track zero). If there are no
|
113
|
+
# tracks, returns UNNAMED.
|
114
|
+
def name
|
115
|
+
return UNNAMED if @tracks.empty?
|
116
|
+
return @tracks.first.name()
|
117
|
+
end
|
118
|
+
|
119
|
+
# Hands the name to the first track. Does nothing if there are no tracks.
|
120
|
+
def name=(name)
|
121
|
+
return if @tracks.empty?
|
122
|
+
@tracks.first.name = name
|
123
|
+
end
|
124
|
+
|
125
|
+
# Reads a MIDI stream.
|
126
|
+
def read(io, proc = nil) # :yields: track, num_tracks, index
|
127
|
+
@tracks = Array.new()
|
128
|
+
reader = @reader_class.new(self, block_given?() ? Proc.new() : proc)
|
129
|
+
reader.read_from(io)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Writes to a MIDI stream.
|
133
|
+
def write(io, proc = nil) # :yields: track, num_tracks, index
|
134
|
+
writer = @writer_class.new(self, block_given?() ? Proc.new() : proc)
|
135
|
+
writer.write_to(io)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Iterates over the tracks.
|
139
|
+
def each # :yields: track
|
140
|
+
@tracks.each { | track | yield track }
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|