midilib 2.0.5 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/ChangeLog +2 -1
- data/Credits +39 -2
- data/README.rdoc +5 -6
- data/Rakefile +36 -49
- data/TODO.rdoc +13 -2
- data/examples/from_scratch.rb +1 -1
- data/examples/measures_mbt.rb +11 -11
- data/examples/print_program_changes.rb +2 -2
- data/examples/reader2text.rb +47 -47
- data/examples/seq2text.rb +1 -1
- data/examples/split.rb +4 -3
- data/examples/strings.rb +4 -4
- data/examples/transpose.rb +19 -20
- data/install.rb +21 -13
- data/lib/midilib/consts.rb +237 -239
- data/lib/midilib/event.rb +128 -100
- data/lib/midilib/info.rb +3 -5
- data/lib/midilib/io/midifile.rb +155 -182
- data/lib/midilib/io/seqreader.rb +80 -85
- data/lib/midilib/io/seqwriter.rb +93 -88
- data/lib/midilib/measure.rb +78 -80
- data/lib/midilib/mergesort.rb +39 -0
- data/lib/midilib/sequence.rb +40 -32
- data/lib/midilib/track.rb +16 -57
- data/lib/midilib/utils.rb +4 -7
- data/lib/midilib.rb +5 -5
- data/test/event_equality.rb +28 -30
- data/test/test_event.rb +9 -11
- data/test/test_io.rb +83 -3
- data/test/test_mergesort.rb +37 -0
- data/test/test_midifile.rb +6 -19
- data/test/test_sequence.rb +5 -4
- data/test/test_track.rb +9 -38
- data/test/test_varlen.rb +1 -3
- metadata +8 -95
- data/html/IO.html +0 -155
- data/html/MIDI/ActiveSense.html +0 -206
- data/html/MIDI/ChannelEvent.html +0 -231
- data/html/MIDI/ChannelPressure.html +0 -265
- data/html/MIDI/Clock.html +0 -206
- data/html/MIDI/Continue.html +0 -206
- data/html/MIDI/Controller.html +0 -280
- data/html/MIDI/Event.html +0 -489
- data/html/MIDI/IO/MIDIFile.html +0 -2024
- data/html/MIDI/IO/SeqReader.html +0 -904
- data/html/MIDI/IO/SeqWriter.html +0 -572
- data/html/MIDI/IO.html +0 -95
- data/html/MIDI/KeySig.html +0 -353
- data/html/MIDI/MIDI/MIDI/Array.html +0 -255
- data/html/MIDI/MIDI/MIDI.html +0 -95
- data/html/MIDI/MIDI.html +0 -95
- data/html/MIDI/Marker.html +0 -158
- data/html/MIDI/Measure.html +0 -328
- data/html/MIDI/Measures.html +0 -285
- data/html/MIDI/MetaEvent.html +0 -461
- data/html/MIDI/NoteEvent.html +0 -331
- data/html/MIDI/NoteOff.html +0 -228
- data/html/MIDI/NoteOn.html +0 -228
- data/html/MIDI/PitchBend.html +0 -266
- data/html/MIDI/PolyPressure.html +0 -277
- data/html/MIDI/ProgramChange.html +0 -265
- data/html/MIDI/Realtime.html +0 -242
- data/html/MIDI/Sequence.html +0 -896
- data/html/MIDI/SongPointer.html +0 -266
- data/html/MIDI/SongSelect.html +0 -265
- data/html/MIDI/Start.html +0 -206
- data/html/MIDI/Stop.html +0 -206
- data/html/MIDI/SystemCommon.html +0 -158
- data/html/MIDI/SystemExclusive.html +0 -268
- data/html/MIDI/SystemReset.html +0 -206
- data/html/MIDI/Tempo.html +0 -396
- data/html/MIDI/TimeSig.html +0 -388
- data/html/MIDI/Track.html +0 -695
- data/html/MIDI/TuneRequest.html +0 -242
- data/html/MIDI/Utils.html +0 -220
- data/html/MIDI.html +0 -547
- data/html/README_rdoc.html +0 -731
- data/html/TODO_rdoc.html +0 -125
- data/html/created.rid +0 -14
- data/html/css/fonts.css +0 -167
- data/html/css/rdoc.css +0 -590
- data/html/fonts/Lato-Light.ttf +0 -0
- data/html/fonts/Lato-LightItalic.ttf +0 -0
- data/html/fonts/Lato-Regular.ttf +0 -0
- data/html/fonts/Lato-RegularItalic.ttf +0 -0
- data/html/fonts/SourceCodePro-Bold.ttf +0 -0
- data/html/fonts/SourceCodePro-Regular.ttf +0 -0
- data/html/images/add.png +0 -0
- data/html/images/arrow_up.png +0 -0
- data/html/images/brick.png +0 -0
- data/html/images/brick_link.png +0 -0
- data/html/images/bug.png +0 -0
- data/html/images/bullet_black.png +0 -0
- data/html/images/bullet_toggle_minus.png +0 -0
- data/html/images/bullet_toggle_plus.png +0 -0
- data/html/images/date.png +0 -0
- data/html/images/delete.png +0 -0
- data/html/images/find.png +0 -0
- data/html/images/loadingAnimation.gif +0 -0
- data/html/images/macFFBgHack.png +0 -0
- data/html/images/package.png +0 -0
- data/html/images/page_green.png +0 -0
- data/html/images/page_white_text.png +0 -0
- data/html/images/page_white_width.png +0 -0
- data/html/images/plugin.png +0 -0
- data/html/images/ruby.png +0 -0
- data/html/images/tag_blue.png +0 -0
- data/html/images/tag_green.png +0 -0
- data/html/images/transparent.png +0 -0
- data/html/images/wrench.png +0 -0
- data/html/images/wrench_orange.png +0 -0
- data/html/images/zoom.png +0 -0
- data/html/index.html +0 -768
- data/html/js/darkfish.js +0 -161
- data/html/js/jquery.js +0 -4
- data/html/js/navigation.js +0 -142
- data/html/js/navigation.js.gz +0 -0
- data/html/js/search.js +0 -109
- data/html/js/search_index.js +0 -1
- data/html/js/search_index.js.gz +0 -0
- data/html/js/searcher.js +0 -228
- data/html/js/searcher.js.gz +0 -0
- data/html/table_of_contents.html +0 -1265
data/lib/midilib/io/seqreader.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
require_relative 'midifile'
|
|
2
|
+
require_relative '../track'
|
|
3
|
+
require_relative '../event'
|
|
4
4
|
|
|
5
5
|
module MIDI
|
|
6
|
-
|
|
7
6
|
module IO
|
|
8
|
-
|
|
9
7
|
# Reads MIDI files. As a subclass of MIDIFile, this class implements the
|
|
10
8
|
# callback methods for each MIDI event and use them to build Track and
|
|
11
9
|
# Event objects and give the tracks to a Sequence.
|
|
@@ -21,118 +19,119 @@ module MIDI
|
|
|
21
19
|
# track when it is output.
|
|
22
20
|
|
|
23
21
|
class SeqReader < MIDIFile
|
|
24
|
-
|
|
25
|
-
# The optional proc block is called once at the start of the file and
|
|
22
|
+
# The optional &block is called once at the start of the file and
|
|
26
23
|
# again at the end of each track. There are three arguments to the
|
|
27
24
|
# block: the track, the track number (1 through _n_), and the total
|
|
28
25
|
# number of tracks.
|
|
29
|
-
def initialize(seq,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
35
32
|
end
|
|
36
33
|
|
|
37
34
|
def header(format, ntrks, division)
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
@seq.format = format
|
|
36
|
+
@seq.ppqn = division
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
@ntrks = ntrks
|
|
39
|
+
@update_block.call(nil, @ntrks, 0) if @update_block
|
|
43
40
|
end
|
|
44
41
|
|
|
45
|
-
def start_track
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
def start_track
|
|
43
|
+
@track = Track.new(@seq)
|
|
44
|
+
@seq.tracks << @track
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
@pending = []
|
|
50
47
|
end
|
|
51
48
|
|
|
52
|
-
def end_track
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
def end_track
|
|
50
|
+
# Turn off any pending note on messages
|
|
51
|
+
@pending.each { |on| make_note_off(on, 64) }
|
|
52
|
+
@pending = nil
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
# Store bitmask of all channels used into track
|
|
63
|
+
@track.channels_used = @chan_mask
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
# call update block
|
|
66
|
+
@update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
|
|
70
67
|
end
|
|
71
68
|
|
|
72
69
|
def note_on(chan, note, vel)
|
|
73
|
-
|
|
70
|
+
if vel == 0
|
|
74
71
|
note_off(chan, note, 64)
|
|
75
72
|
return
|
|
76
|
-
|
|
73
|
+
end
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
on = NoteOn.new(chan, note, vel, @curr_ticks)
|
|
76
|
+
@track.events << on
|
|
77
|
+
@pending << on
|
|
78
|
+
track_uses_channel(chan)
|
|
82
79
|
end
|
|
83
80
|
|
|
84
81
|
def note_off(chan, note, vel)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
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
|
|
96
95
|
end
|
|
97
96
|
|
|
98
97
|
def make_note_off(on, vel)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
off = NoteOff.new(on.channel, on.note, vel, @curr_ticks)
|
|
99
|
+
@track.events << off
|
|
100
|
+
on.off = off
|
|
101
|
+
off.on = on
|
|
103
102
|
end
|
|
104
103
|
|
|
105
104
|
def pressure(chan, note, press)
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
@track.events << PolyPressure.new(chan, note, press, @curr_ticks)
|
|
106
|
+
track_uses_channel(chan)
|
|
108
107
|
end
|
|
109
108
|
|
|
110
109
|
def controller(chan, control, value)
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
@track.events << Controller.new(chan, control, value, @curr_ticks)
|
|
111
|
+
track_uses_channel(chan)
|
|
113
112
|
end
|
|
114
113
|
|
|
115
114
|
def pitch_bend(chan, lsb, msb)
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
@track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks)
|
|
116
|
+
track_uses_channel(chan)
|
|
118
117
|
end
|
|
119
118
|
|
|
120
119
|
def program(chan, program)
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
@track.events << ProgramChange.new(chan, program, @curr_ticks)
|
|
121
|
+
track_uses_channel(chan)
|
|
123
122
|
end
|
|
124
123
|
|
|
125
124
|
def chan_pressure(chan, press)
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
@track.events << ChannelPressure.new(chan, press, @curr_ticks)
|
|
126
|
+
track_uses_channel(chan)
|
|
128
127
|
end
|
|
129
128
|
|
|
130
129
|
def sysex(msg)
|
|
131
|
-
|
|
130
|
+
@track.events << SystemExclusive.new(msg, @curr_ticks)
|
|
132
131
|
end
|
|
133
132
|
|
|
134
133
|
def meta_misc(type, msg)
|
|
135
|
-
|
|
134
|
+
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
|
136
135
|
end
|
|
137
136
|
|
|
138
137
|
# --
|
|
@@ -144,18 +143,16 @@ module MIDI
|
|
|
144
143
|
# ++
|
|
145
144
|
|
|
146
145
|
def text(type, msg)
|
|
147
|
-
|
|
148
|
-
when META_TEXT, META_LYRIC, META_CUE
|
|
146
|
+
case type
|
|
147
|
+
when META_TEXT, META_LYRIC, META_CUE, META_SEQ_NAME, META_COPYRIGHT
|
|
149
148
|
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
|
150
|
-
|
|
151
|
-
@track.events << MetaEvent.new(type, msg, 0)
|
|
152
|
-
when META_INSTRUMENT
|
|
149
|
+
when META_INSTRUMENT
|
|
153
150
|
@track.instrument = msg
|
|
154
|
-
|
|
151
|
+
when META_MARKER
|
|
155
152
|
@track.events << Marker.new(msg, @curr_ticks)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
else
|
|
154
|
+
warn "text = #{msg}, type = #{type}" if $DEBUG
|
|
155
|
+
end
|
|
159
156
|
end
|
|
160
157
|
|
|
161
158
|
# --
|
|
@@ -163,13 +160,13 @@ module MIDI
|
|
|
163
160
|
# we don't have to worry about always making sure the last event is
|
|
164
161
|
# always a track end event. We just have to make sure to write one when
|
|
165
162
|
# the track is output back to a file.
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
163
|
+
# def eot()
|
|
164
|
+
# @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
|
|
165
|
+
# end
|
|
169
166
|
# ++
|
|
170
167
|
|
|
171
168
|
def time_signature(numer, denom, clocks, qnotes)
|
|
172
|
-
|
|
169
|
+
@seq.time_signature(numer, denom, clocks, qnotes)
|
|
173
170
|
@track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks)
|
|
174
171
|
end
|
|
175
172
|
|
|
@@ -179,7 +176,7 @@ module MIDI
|
|
|
179
176
|
# ++
|
|
180
177
|
|
|
181
178
|
def tempo(microsecs)
|
|
182
|
-
|
|
179
|
+
@track.events << Tempo.new(microsecs, @curr_ticks)
|
|
183
180
|
end
|
|
184
181
|
|
|
185
182
|
def key_signature(sharpflat, is_minor)
|
|
@@ -193,10 +190,8 @@ module MIDI
|
|
|
193
190
|
|
|
194
191
|
# Return true if the current track uses the specified channel.
|
|
195
192
|
def track_uses_channel(chan)
|
|
196
|
-
|
|
193
|
+
@chan_mask |= (1 << chan)
|
|
197
194
|
end
|
|
198
|
-
|
|
199
195
|
end
|
|
200
|
-
|
|
201
196
|
end
|
|
202
197
|
end
|
data/lib/midilib/io/seqwriter.rb
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
1
|
# Writes MIDI files.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative '../event'
|
|
4
|
+
require_relative '../utils'
|
|
5
5
|
|
|
6
6
|
module MIDI
|
|
7
|
-
|
|
8
7
|
module IO
|
|
9
|
-
|
|
10
8
|
class SeqWriter
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
def initialize(seq, midi_format = 1, &block) # :yields: num_tracks, index
|
|
10
|
+
@seq = seq
|
|
11
|
+
@midi_format = midi_format || 1
|
|
12
|
+
@update_block = block
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
# Writes a MIDI format 1 file.
|
|
18
16
|
def write_to(io)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
if @midi_format == 0
|
|
18
|
+
# merge tracks before writing
|
|
19
|
+
merged_seq = Sequence.new
|
|
20
|
+
merged_track = Track.new(merged_seq)
|
|
21
|
+
merged_seq.tracks << merged_track
|
|
22
|
+
@seq.each do |track|
|
|
23
|
+
merged_track.merge(track.events)
|
|
24
|
+
end
|
|
25
|
+
@seq = merged_seq # replace
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@io = io
|
|
29
|
+
@bytes_written = 0
|
|
30
|
+
write_header
|
|
31
|
+
@update_block.call(nil, @seq.tracks.length, 0) if @update_block
|
|
32
|
+
@seq.tracks.each_with_index do |track, i|
|
|
24
33
|
write_track(track)
|
|
25
34
|
@update_block.call(track, @seq.tracks.length, i) if @update_block
|
|
26
|
-
|
|
35
|
+
end
|
|
27
36
|
end
|
|
28
37
|
|
|
29
38
|
def write_header
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
@io.print 'MThd'
|
|
40
|
+
write32(6)
|
|
41
|
+
write16(@midi_format) # Ignore sequence format; write as format 1 or 0, default 1
|
|
42
|
+
write16(@seq.tracks.length)
|
|
43
|
+
write16(@seq.ppqn)
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
def write_track(track)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
write_instrument(track.instrument)
|
|
44
|
-
|
|
45
|
-
prev_event = nil
|
|
46
|
-
prev_status = 0
|
|
47
|
-
track.events.each do |event|
|
|
48
|
-
if !event.kind_of?(Realtime)
|
|
49
|
-
write_var_len(event.delta_time)
|
|
50
|
-
end
|
|
47
|
+
@io.print 'MTrk'
|
|
48
|
+
track_size_file_pos = @io.tell
|
|
49
|
+
write32(0) # Dummy byte count; overwritten later
|
|
50
|
+
@bytes_written = 0 # Reset after previous write
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
write_instrument(track.instrument)
|
|
53
|
+
|
|
54
|
+
prev_status = 0
|
|
55
|
+
track.events.each do |event|
|
|
56
|
+
write_var_len(event.delta_time) unless event.is_a?(Realtime)
|
|
57
|
+
|
|
58
|
+
data = event.data_as_bytes
|
|
53
59
|
status = data[0] # status byte plus channel number, if any
|
|
54
60
|
|
|
55
61
|
# running status byte
|
|
@@ -57,96 +63,95 @@ module MIDI
|
|
|
57
63
|
|
|
58
64
|
@bytes_written += write_bytes(data)
|
|
59
65
|
|
|
60
|
-
prev_event = event
|
|
61
66
|
prev_status = status
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Write track end event.
|
|
70
|
+
event = MetaEvent.new(META_TRACK_END)
|
|
71
|
+
write_var_len(0)
|
|
72
|
+
@bytes_written += write_bytes(event.data_as_bytes)
|
|
73
|
+
|
|
74
|
+
# Go back to beginning of track data and write number of bytes,
|
|
75
|
+
# then come back here to end of file.
|
|
76
|
+
@io.seek(track_size_file_pos)
|
|
77
|
+
write32(@bytes_written)
|
|
78
|
+
@io.seek(0, ::IO::SEEK_END)
|
|
74
79
|
end
|
|
75
80
|
|
|
76
81
|
# If we can use a running status byte, delete the status byte from
|
|
77
82
|
# the given data. Return the status to remember for next time as the
|
|
78
83
|
# running status byte for this event.
|
|
79
84
|
def possibly_munge_due_to_running_status_byte(data, prev_status)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
data[0,1] = []
|
|
94
|
-
|
|
95
|
-
|
|
85
|
+
status = data[0]
|
|
86
|
+
return status if status >= 0xf0 || prev_status >= 0xf0
|
|
87
|
+
|
|
88
|
+
chan = (status & 0x0f)
|
|
89
|
+
return status if chan != (prev_status & 0x0f)
|
|
90
|
+
|
|
91
|
+
status = (status & 0xf0)
|
|
92
|
+
prev_status = (prev_status & 0xf0)
|
|
93
|
+
|
|
94
|
+
# Both events are on the same channel. If the two status bytes are
|
|
95
|
+
# exactly the same, the rest is trivial. If it's note on/note off,
|
|
96
|
+
# we can combine those further.
|
|
97
|
+
if status == prev_status
|
|
98
|
+
data[0, 1] = [] # delete status byte from data
|
|
99
|
+
status + chan
|
|
100
|
+
elsif status == NOTE_OFF && data[2] == 64
|
|
96
101
|
# If we see a note off and the velocity is 64, we can store
|
|
97
102
|
# a note on with a velocity of 0. If the velocity isn't 64
|
|
98
103
|
# then storing a note on would be bad because the would be
|
|
99
104
|
# changed to 64 when reading the file back in.
|
|
100
|
-
data[2] = 0
|
|
105
|
+
data[2] = 0 # set vel to 0; do before possible shrinking
|
|
101
106
|
status = NOTE_ON + chan
|
|
102
107
|
if prev_status == NOTE_ON
|
|
103
|
-
data[0,1] = []
|
|
108
|
+
data[0, 1] = [] # delete status byte
|
|
104
109
|
else
|
|
105
110
|
data[0] = status
|
|
106
111
|
end
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
status
|
|
113
|
+
else
|
|
109
114
|
# Can't compress data
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
status + chan
|
|
116
|
+
end
|
|
112
117
|
end
|
|
113
118
|
|
|
114
119
|
def write_instrument(instrument)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
return if instrument.nil?
|
|
121
|
+
|
|
122
|
+
event = MetaEvent.new(META_INSTRUMENT, instrument)
|
|
123
|
+
write_var_len(0)
|
|
124
|
+
data = event.data_as_bytes
|
|
125
|
+
@bytes_written += write_bytes(data)
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
def write_var_len(val)
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
buffer = Utils.as_var_len(val)
|
|
130
|
+
@bytes_written += write_bytes(buffer)
|
|
124
131
|
end
|
|
125
132
|
|
|
126
133
|
def write16(val)
|
|
127
|
-
|
|
134
|
+
val = (-val | 0x8000) if val < 0
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@bytes_written += 2
|
|
136
|
+
@io.putc((val >> 8) & 0xff)
|
|
137
|
+
@io.putc(val & 0xff)
|
|
138
|
+
@bytes_written += 2
|
|
133
139
|
end
|
|
134
140
|
|
|
135
141
|
def write32(val)
|
|
136
|
-
|
|
142
|
+
val = (-val | 0x80000000) if val < 0
|
|
137
143
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
@io.putc((val >> 24) & 0xff)
|
|
145
|
+
@io.putc((val >> 16) & 0xff)
|
|
146
|
+
@io.putc((val >> 8) & 0xff)
|
|
147
|
+
@io.putc(val & 0xff)
|
|
148
|
+
@bytes_written += 4
|
|
143
149
|
end
|
|
144
150
|
|
|
145
151
|
def write_bytes(bytes)
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
bytes.each { |b| @io.putc(b) }
|
|
153
|
+
bytes.length
|
|
148
154
|
end
|
|
149
155
|
end
|
|
150
|
-
|
|
151
156
|
end
|
|
152
157
|
end
|
data/lib/midilib/measure.rb
CHANGED
|
@@ -1,80 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module MIDI
|
|
4
|
-
|
|
5
|
-
# The
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
@
|
|
28
|
-
@
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# Returns the
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
b
|
|
75
|
-
b
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
end
|
|
1
|
+
require_relative 'consts'
|
|
2
|
+
|
|
3
|
+
module MIDI
|
|
4
|
+
# The Measure class contains information about a measure from the sequence.
|
|
5
|
+
# The measure data is based on the time signature information from the sequence
|
|
6
|
+
# and is not stored in the sequence itself
|
|
7
|
+
class Measure
|
|
8
|
+
# The numerator (top digit) for the measure's time signature
|
|
9
|
+
attr_reader :numerator
|
|
10
|
+
# The denominator for the measure's time signature
|
|
11
|
+
attr_reader :denominator
|
|
12
|
+
# Start clock tick for the measure
|
|
13
|
+
attr_reader :start
|
|
14
|
+
# End clock tick for the measure (inclusive)
|
|
15
|
+
attr_reader :end
|
|
16
|
+
# The measure number (1-based)
|
|
17
|
+
attr_reader :measure_number
|
|
18
|
+
# The metronome tick for the measure
|
|
19
|
+
attr_reader :metronome_ticks
|
|
20
|
+
|
|
21
|
+
# Constructor
|
|
22
|
+
def initialize(meas_no, start_time, duration, numer, denom, met_ticks)
|
|
23
|
+
@measure_number = meas_no
|
|
24
|
+
@start = start_time
|
|
25
|
+
@end = start_time + duration - 1
|
|
26
|
+
@numerator = numer
|
|
27
|
+
@denominator = denom
|
|
28
|
+
@metronome_ticks = met_ticks
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns a detailed string with information about the measure
|
|
32
|
+
def to_s
|
|
33
|
+
t = "#{@numerator}/#{2**@denominator}"
|
|
34
|
+
m = @metronome_ticks.to_f / 24
|
|
35
|
+
"measure #{@measure_number} #{@start}-#{@end} #{t} #{m} qs metronome"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns +true+ if the event is in the measure
|
|
39
|
+
def contains_event?(e)
|
|
40
|
+
(e.time_from_start >= @start) && (e.time_from_start <= @end)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# A specialized container for MIDI::Measure objects, which can be use to map
|
|
45
|
+
# event times to measure numbers. Please note that this object has to be remade
|
|
46
|
+
# when events are deleted/added in the sequence.
|
|
47
|
+
class Measures < Array
|
|
48
|
+
# The highest event time in the sequence (at the time when the
|
|
49
|
+
# object was created)
|
|
50
|
+
attr_reader :max_time
|
|
51
|
+
|
|
52
|
+
# The ppqd from the sequence
|
|
53
|
+
attr_reader :ppqd
|
|
54
|
+
|
|
55
|
+
# Constructor
|
|
56
|
+
def initialize(max_time, ppqd)
|
|
57
|
+
super(0)
|
|
58
|
+
@max_time = max_time
|
|
59
|
+
@ppqd = ppqd
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns the MIDI::Measure object where the event is located.
|
|
63
|
+
# Returns +nil+ if the event isn't found in the container (should
|
|
64
|
+
# never happen if the MIDI::Measures object is up to date).
|
|
65
|
+
def measure_for_event(e)
|
|
66
|
+
detect { |m| m.contains_event?(e) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the event's time as a formatted MBT string (Measure:Beat:Ticks)
|
|
70
|
+
# as found in MIDI sequencers.
|
|
71
|
+
def to_mbt(e)
|
|
72
|
+
m = measure_for_event(e)
|
|
73
|
+
b = (e.time_from_start.to_f - m.start.to_f) / @ppqd
|
|
74
|
+
b *= 24 / m.metronome_ticks
|
|
75
|
+
format('%d:%02d:%03d', m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|