midilib 2.0.2 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,202 +1,197 @@
1
- require 'midilib/io/midifile'
2
- require 'midilib/track'
3
- require 'midilib/event'
1
+ require_relative 'midifile'
2
+ require_relative '../track'
3
+ require_relative '../event'
4
4
 
5
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 = NoteOn.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 = NoteOff.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 << 7) + 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_TEXT, META_LYRIC, META_CUE
149
- @track.events << MetaEvent.new(type, msg, @curr_ticks)
150
- when META_SEQ_NAME, META_COPYRIGHT
151
- @track.events << MetaEvent.new(type, msg, 0)
152
- when META_INSTRUMENT
153
- @track.instrument = msg
154
- when META_MARKER
155
- @track.events << Marker.new(msg, @curr_ticks)
156
- else
157
- $stderr.puts "text = #{msg}, type = #{type}" if $DEBUG
158
- end
159
- end
160
-
161
- # --
162
- # Don't bother adding the META_TRACK_END event to the track. This way,
163
- # we don't have to worry about always making sure the last event is
164
- # always a track end event. We just have to make sure to write one when
165
- # the track is output back to a file.
166
- # def eot()
167
- # @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
168
- # end
169
- # ++
170
-
171
- def time_signature(numer, denom, clocks, qnotes)
172
- @seq.time_signature(numer, denom, clocks, qnotes)
6
+ module IO
7
+ # Reads MIDI files. As a subclass of MIDIFile, this class implements the
8
+ # callback methods for each MIDI event and use them to build Track and
9
+ # Event objects and give the tracks to a Sequence.
10
+ #
11
+ # We append new events to the end of a track's event list, bypassing a
12
+ # call to Track.#add. This means that we must call Track.recalc_times at
13
+ # the end of the track so it can update each event with its time from
14
+ # the track's start (see end_track below).
15
+ #
16
+ # META_TRACK_END events are not added to tracks. This way, we don't have
17
+ # to worry about making sure the last event is always a track end event.
18
+ # We rely on the SeqWriter to append a META_TRACK_END event to each
19
+ # track when it is output.
20
+
21
+ class SeqReader < MIDIFile
22
+ # The optional &block is called once at the start of the file and
23
+ # again at the end of each track. There are three arguments to the
24
+ # block: the track, the track number (1 through _n_), and the total
25
+ # number of tracks.
26
+ def initialize(seq, &block) # :yields: track, num_tracks, index
27
+ super()
28
+ @seq = seq
29
+ @track = nil
30
+ @chan_mask = 0
31
+ @update_block = block
32
+ end
33
+
34
+ def header(format, ntrks, division)
35
+ @seq.format = format
36
+ @seq.ppqn = division
37
+
38
+ @ntrks = ntrks
39
+ @update_block.call(nil, @ntrks, 0) if @update_block
40
+ end
41
+
42
+ def start_track
43
+ @track = Track.new(@seq)
44
+ @seq.tracks << @track
45
+
46
+ @pending = []
47
+ end
48
+
49
+ def end_track
50
+ # Turn off any pending note on messages
51
+ @pending.each { |on| make_note_off(on, 64) }
52
+ @pending = nil
53
+
54
+ # Don't bother adding the META_TRACK_END event to the track.
55
+ # This way, we don't have to worry about making sure the
56
+ # last event is always a track end event.
57
+
58
+ # Let the track calculate event times from start of track. This is
59
+ # in lieu of calling Track.add for each event.
60
+ @track.recalc_times
61
+
62
+ # Store bitmask of all channels used into track
63
+ @track.channels_used = @chan_mask
64
+
65
+ # call update block
66
+ @update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
67
+ end
68
+
69
+ def note_on(chan, note, vel)
70
+ if vel == 0
71
+ note_off(chan, note, 64)
72
+ return
73
+ end
74
+
75
+ on = NoteOn.new(chan, note, vel, @curr_ticks)
76
+ @track.events << on
77
+ @pending << on
78
+ track_uses_channel(chan)
79
+ end
80
+
81
+ def note_off(chan, note, vel)
82
+ # Find note on, create note off, connect the two, and remove
83
+ # note on from pending list.
84
+ @pending.each_with_index do |on, i|
85
+ next unless on.note == note && on.channel == chan
86
+
87
+ make_note_off(on, vel)
88
+ @pending.delete_at(i)
89
+ return
90
+ end
91
+ if $DEBUG
92
+ warn "note off with no earlier note on (ch #{chan}, note" +
93
+ " #{note}, vel #{vel})"
94
+ end
95
+ end
96
+
97
+ def make_note_off(on, vel)
98
+ off = NoteOff.new(on.channel, on.note, vel, @curr_ticks)
99
+ @track.events << off
100
+ on.off = off
101
+ off.on = on
102
+ end
103
+
104
+ def pressure(chan, note, press)
105
+ @track.events << PolyPressure.new(chan, note, press, @curr_ticks)
106
+ track_uses_channel(chan)
107
+ end
108
+
109
+ def controller(chan, control, value)
110
+ @track.events << Controller.new(chan, control, value, @curr_ticks)
111
+ track_uses_channel(chan)
112
+ end
113
+
114
+ def pitch_bend(chan, lsb, msb)
115
+ @track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks)
116
+ track_uses_channel(chan)
117
+ end
118
+
119
+ def program(chan, program)
120
+ @track.events << ProgramChange.new(chan, program, @curr_ticks)
121
+ track_uses_channel(chan)
122
+ end
123
+
124
+ def chan_pressure(chan, press)
125
+ @track.events << ChannelPressure.new(chan, press, @curr_ticks)
126
+ track_uses_channel(chan)
127
+ end
128
+
129
+ def sysex(msg)
130
+ @track.events << SystemExclusive.new(msg, @curr_ticks)
131
+ end
132
+
133
+ def meta_misc(type, msg)
134
+ @track.events << MetaEvent.new(type, msg, @curr_ticks)
135
+ end
136
+
137
+ # --
138
+ # def sequencer_specific(type, msg)
139
+ # end
140
+
141
+ # def sequence_number(num)
142
+ # end
143
+ # ++
144
+
145
+ def text(type, msg)
146
+ case type
147
+ when META_TEXT, META_LYRIC, META_CUE, META_SEQ_NAME, META_COPYRIGHT
148
+ @track.events << MetaEvent.new(type, msg, @curr_ticks)
149
+ when META_INSTRUMENT
150
+ @track.instrument = msg
151
+ when META_MARKER
152
+ @track.events << Marker.new(msg, @curr_ticks)
153
+ else
154
+ warn "text = #{msg}, type = #{type}" if $DEBUG
155
+ end
156
+ end
157
+
158
+ # --
159
+ # Don't bother adding the META_TRACK_END event to the track. This way,
160
+ # we don't have to worry about always making sure the last event is
161
+ # always a track end event. We just have to make sure to write one when
162
+ # the track is output back to a file.
163
+ # def eot()
164
+ # @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
165
+ # end
166
+ # ++
167
+
168
+ def time_signature(numer, denom, clocks, qnotes)
169
+ @seq.time_signature(numer, denom, clocks, qnotes)
173
170
  @track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks)
174
- end
171
+ end
175
172
 
176
- # --
177
- # def smpte(hour, min, sec, frame, fract)
178
- # end
179
- # ++
173
+ # --
174
+ # def smpte(hour, min, sec, frame, fract)
175
+ # end
176
+ # ++
180
177
 
181
- def tempo(microsecs)
182
- @track.events << Tempo.new(microsecs, @curr_ticks)
183
- end
178
+ def tempo(microsecs)
179
+ @track.events << Tempo.new(microsecs, @curr_ticks)
180
+ end
184
181
 
185
- def key_signature(sharpflat, is_minor)
186
- @track.events << KeySig.new(sharpflat, is_minor, @curr_ticks)
187
- end
182
+ def key_signature(sharpflat, is_minor)
183
+ @track.events << KeySig.new(sharpflat, is_minor, @curr_ticks)
184
+ end
188
185
 
189
- # --
190
- # def arbitrary(msg)
191
- # end
192
- # ++
186
+ # --
187
+ # def arbitrary(msg)
188
+ # end
189
+ # ++
193
190
 
194
- # Return true if the current track uses the specified channel.
195
- def track_uses_channel(chan)
196
- @chan_mask = @chan_mask | (1 << chan)
191
+ # Return true if the current track uses the specified channel.
192
+ def track_uses_channel(chan)
193
+ @chan_mask |= (1 << chan)
194
+ end
197
195
  end
198
-
199
- end
200
-
201
- end
196
+ end
202
197
  end