midilib 2.0.2 → 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 +7 -0
- data/ChangeLog +2 -1
- data/Credits +44 -2
- data/README.rdoc +42 -33
- data/Rakefile +36 -53
- data/TODO.rdoc +13 -2
- data/examples/from_scratch.rb +4 -6
- data/examples/measures_mbt.rb +11 -11
- data/examples/print_program_changes.rb +11 -11
- data/examples/reader2text.rb +191 -190
- data/examples/seq2text.rb +18 -18
- data/examples/split.rb +21 -20
- data/examples/strings.rb +15 -15
- data/examples/transpose.rb +41 -42
- data/install.rb +53 -34
- data/lib/midilib/consts.rb +406 -408
- data/lib/midilib/event.rb +335 -306
- data/lib/midilib/info.rb +5 -7
- data/lib/midilib/io/midifile.rb +424 -452
- data/lib/midilib/io/seqreader.rb +187 -192
- data/lib/midilib/io/seqwriter.rb +151 -147
- data/lib/midilib/measure.rb +78 -80
- data/lib/midilib/mergesort.rb +39 -0
- data/lib/midilib/sequence.rb +99 -86
- data/lib/midilib/track.rb +71 -118
- data/lib/midilib/utils.rb +17 -20
- data/lib/midilib.rb +5 -5
- data/test/event_equality.rb +50 -52
- data/test/test_event.rb +120 -124
- data/test/test_io.rb +107 -40
- data/test/test_mergesort.rb +37 -0
- data/test/test_midifile.rb +6 -19
- data/test/test_sequence.rb +64 -52
- data/test/test_track.rb +126 -155
- data/test/test_varlen.rb +23 -27
- metadata +20 -22
data/examples/reader2text.rb
CHANGED
@@ -20,201 +20,202 @@ require 'midilib/io/midifile'
|
|
20
20
|
DEFAULT_MIDI_TEST_FILE = 'NoFences.mid'
|
21
21
|
|
22
22
|
class TextTranslator < MIDI::IO::MIDIFile
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
23
|
+
def initialize(seq, &block)
|
24
|
+
super()
|
25
|
+
@seq = seq
|
26
|
+
@track = nil
|
27
|
+
@update_block = block
|
28
|
+
end
|
29
|
+
|
30
|
+
# Generate a unique number for a channel/note combination. This is used
|
31
|
+
# to remember pending note on events.
|
32
|
+
def note_hash(chan, note)
|
33
|
+
(chan << 8) + note
|
34
|
+
end
|
35
|
+
|
36
|
+
# Print a delta time.
|
37
|
+
def pdelta
|
38
|
+
print "#{@curr_ticks}: "
|
39
|
+
end
|
40
|
+
|
41
|
+
# The remaining methods are overrides of methods in MIDI::IO::MIDIFile.
|
42
|
+
|
43
|
+
def header(format, ntrks, division)
|
44
|
+
puts "header: format = #{format}, ntrks = #{ntrks}," +
|
45
|
+
" division = #{division}"
|
46
|
+
|
47
|
+
@ntrks = ntrks
|
48
|
+
@update_block.call(nil, @ntrks, 0) if @update_block
|
49
|
+
end
|
50
|
+
|
51
|
+
def start_track
|
52
|
+
pdelta
|
53
|
+
puts 'track start'
|
54
|
+
|
55
|
+
@pending = []
|
56
|
+
@chan_mask = 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def end_track
|
60
|
+
pdelta
|
61
|
+
puts "track end; chans used bitmask = #{@chan_mask}"
|
62
|
+
# Write message for any pending note on messages
|
63
|
+
@pending.each_with_index do |num, chan|
|
64
|
+
if note_obj
|
65
|
+
puts "pending note off missing for chan #{num >> 8}," +
|
66
|
+
" note #{num & 0xff}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@pending = nil
|
70
|
+
|
71
|
+
# call update block
|
72
|
+
@update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
|
73
|
+
end
|
74
|
+
|
75
|
+
def note_on(chan, note, vel)
|
76
|
+
pdelta
|
77
|
+
if vel == 0
|
78
|
+
print '(note on, vel 0) '
|
79
|
+
note_off(chan, note, 64)
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
puts "note on chan #{chan}, note #{note}, vel #{vel}"
|
84
|
+
@pending << note_hash(chan, note)
|
85
|
+
track_uses_channel(chan)
|
86
|
+
end
|
87
|
+
|
88
|
+
def note_off(chan, note, vel)
|
89
|
+
pdelta
|
90
|
+
# Find note on, create note off, connect the two, and remove
|
91
|
+
# note on from pending list.
|
92
|
+
pnum = note_hash(chan, note)
|
93
|
+
@pending.each_with_index do |num, i|
|
94
|
+
next unless pnum == num
|
95
|
+
|
96
|
+
puts "note off chan #{chan}, note #{note}, vel #{vel}"
|
97
|
+
@pending.delete_at(i)
|
98
|
+
return
|
99
|
+
end
|
100
|
+
puts "note off with no earlier note on (ch #{chan}, note" +
|
101
|
+
" #{note}, vel #{vel})"
|
102
|
+
end
|
103
|
+
|
104
|
+
def pressure(chan, note, press)
|
105
|
+
pdelta
|
106
|
+
puts "pressure chan #{chan}, note #{note}, press #{press}"
|
107
|
+
track_uses_channel(chan)
|
108
|
+
end
|
109
|
+
|
110
|
+
def controller(chan, control, value)
|
111
|
+
pdelta
|
112
|
+
puts "controller chan #{chan}, control #{control}, value #{value}"
|
113
|
+
track_uses_channel(chan)
|
114
|
+
end
|
115
|
+
|
116
|
+
def pitch_bend(chan, msb, lsb)
|
117
|
+
pdelta
|
118
|
+
puts "pitch bend chan #{chan}, msb #{msb}, lsb #{lsb}"
|
119
|
+
track_uses_channel(chan)
|
120
|
+
end
|
121
|
+
|
122
|
+
def program(chan, program)
|
123
|
+
pdelta
|
124
|
+
puts "program chan #{chan}, program #{program}"
|
125
|
+
track_uses_channel(chan)
|
126
|
+
end
|
127
|
+
|
128
|
+
def chan_pressure(chan, press)
|
129
|
+
pdelta
|
130
|
+
puts "chan press chan #{chan}, press #{press}"
|
131
|
+
track_uses_channel(chan)
|
132
|
+
end
|
133
|
+
|
134
|
+
def sysex(msg)
|
135
|
+
pdelta
|
136
|
+
puts "sysex size #{msg.length}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def meta_misc(type, msg)
|
140
|
+
pdelta
|
141
|
+
puts "meta misc type #{type}, length #{msg.length}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def sequencer_specific(type, msg)
|
145
|
+
pdelta
|
146
|
+
puts "sequencer specific type #{type}, msg #{msg.length}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def sequence_number(num)
|
150
|
+
pdelta
|
151
|
+
puts "sequence number #{num}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def text(type, msg)
|
155
|
+
pdelta
|
156
|
+
msg = MIDI::MetaEvent.bytes_as_str(msg)
|
157
|
+
case type
|
158
|
+
when MIDI::META_SEQ_NAME
|
159
|
+
puts "seq or track name #{msg}"
|
160
|
+
when MIDI::META_INSTRUMENT
|
161
|
+
puts "instrument name #{msg}"
|
162
|
+
when MIDI::META_MARKER
|
163
|
+
puts "marker #{msg}"
|
164
|
+
else
|
165
|
+
puts "text = #{msg}, type = #{type}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def eot
|
170
|
+
pdelta
|
171
|
+
puts 'end of track event'
|
172
|
+
end
|
173
|
+
|
174
|
+
def time_signature(numer, denom, clocks, qnotes)
|
175
|
+
pdelta
|
176
|
+
puts "time sig numer #{numer}, denom #{denom}, clocks #{clocks}," +
|
177
|
+
" qnotes #{qnotes}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def smpte(hour, min, sec, frame, fract)
|
181
|
+
pdelta
|
182
|
+
puts "smpte #{hour}:#{min}.#{sec}, frame #{frame}, fract #{fract}"
|
183
|
+
end
|
184
|
+
|
185
|
+
def tempo(microsecs)
|
186
|
+
pdelta
|
187
|
+
bpm = 1.0 / microsecs # quarter notes per microsecond
|
188
|
+
bpm *= 1_000_000.0 # quarter notes per second
|
189
|
+
bpm *= 60.0 # quarter notes per minute
|
190
|
+
puts "tempo microsecs pqn = #{microsecs} (#{bpm} bpm)"
|
191
|
+
end
|
192
|
+
|
193
|
+
def key_signature(sharpflat, is_minor)
|
194
|
+
pdelta
|
195
|
+
puts "key sig sharpflat #{sharpflat}, is_minor #{is_minor}"
|
196
|
+
end
|
197
|
+
|
198
|
+
def arbitrary(msg)
|
199
|
+
pdelta
|
200
|
+
puts "arbitrary length = #{msg.length}"
|
201
|
+
end
|
202
|
+
|
203
|
+
def track_uses_channel(chan)
|
204
|
+
@chan_mask |= (1 << chan)
|
205
|
+
end
|
205
206
|
end
|
206
207
|
|
207
208
|
# ================================================================
|
208
209
|
|
209
|
-
seq = MIDI::Sequence.new
|
210
|
+
seq = MIDI::Sequence.new
|
210
211
|
|
211
212
|
# Specify what class to use when reading the MIDI file.
|
212
213
|
seq.reader_class = TextTranslator
|
213
214
|
|
214
|
-
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb')
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
215
|
+
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb') do |file|
|
216
|
+
# The block we pass in to Sequence.read is called at the end of every
|
217
|
+
# track read. It is optional, but is useful for progress reports.
|
218
|
+
seq.read(file) do |track, num_tracks, i|
|
219
|
+
puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})"
|
220
|
+
end
|
221
|
+
end
|
data/examples/seq2text.rb
CHANGED
@@ -19,23 +19,23 @@ require 'midilib/sequence'
|
|
19
19
|
DEFAULT_MIDI_TEST_FILE = 'NoFences.mid'
|
20
20
|
|
21
21
|
# Read from MIDI file
|
22
|
-
seq = MIDI::Sequence.new
|
22
|
+
seq = MIDI::Sequence.new
|
23
23
|
|
24
|
-
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb')
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb') do |file|
|
25
|
+
# The block we pass in to Sequence.read is called at the end of every
|
26
|
+
# track read. It is optional, but is useful for progress reports.
|
27
|
+
seq.read(file) do |track, num_tracks, i|
|
28
|
+
puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})"
|
29
|
+
end
|
30
|
+
end
|
31
31
|
|
32
|
-
seq.each
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
seq.each do |track|
|
33
|
+
puts "*** track name \"#{track.name}\""
|
34
|
+
puts "instrument name \"#{track.instrument}\""
|
35
|
+
puts "#{track.events.length} events"
|
36
|
+
track.each do |e|
|
37
|
+
e.print_decimal_numbers = true # default = false (print hex)
|
38
|
+
e.print_note_names = true # default = false (print note numbers)
|
39
|
+
puts e
|
40
|
+
end
|
41
|
+
end
|
data/examples/split.rb
CHANGED
@@ -22,31 +22,32 @@ DEFAULT_MIDI_TEST_FILE = 'NoFences.mid'
|
|
22
22
|
filename = ARGV[0]
|
23
23
|
include_tempo_track = true
|
24
24
|
if filename == '-x'
|
25
|
-
|
26
|
-
|
25
|
+
include_tempo_track = false
|
26
|
+
filename = ARGV[1]
|
27
27
|
end
|
28
28
|
|
29
29
|
# Read from MIDI file
|
30
|
-
seq = MIDI::Sequence.new
|
30
|
+
seq = MIDI::Sequence.new
|
31
31
|
|
32
|
-
File.open(filename || DEFAULT_MIDI_TEST_FILE, 'rb')
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
File.open(filename || DEFAULT_MIDI_TEST_FILE, 'rb') do |file|
|
33
|
+
# The block we pass in to Sequence.read is called at the end of every
|
34
|
+
# track read. It is optional, but is useful for progress reports.
|
35
|
+
seq.read(file) do |track, num_tracks, i|
|
36
|
+
puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})"
|
37
|
+
end
|
38
|
+
end
|
39
39
|
|
40
40
|
t0 = seq.tracks[0]
|
41
41
|
unless include_tempo_track
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
s = MIDI::Sequence.new
|
43
|
+
s.tracks << t0
|
44
|
+
File.open('tempo_track.mid', 'wb') { |file| s.write(file) }
|
45
|
+
end
|
46
|
+
seq.each_with_index do |track, i|
|
47
|
+
next unless i > 0
|
48
|
+
|
49
|
+
s = MIDI::Sequence.new
|
50
|
+
s.tracks << t0 if include_tempo_track
|
51
|
+
s.tracks << track
|
52
|
+
File.open("#{track.name}.mid", 'wb') { |file| s.write(file) }
|
45
53
|
end
|
46
|
-
seq.each_with_index { | track, i |
|
47
|
-
next unless i > 0
|
48
|
-
s = MIDI::Sequence.new
|
49
|
-
s.tracks << t0 if include_tempo_track
|
50
|
-
s.tracks << track
|
51
|
-
File.open("#{track.name}.mid", 'wb') { | file | s.write(file) }
|
52
|
-
}
|
data/examples/strings.rb
CHANGED
@@ -15,20 +15,20 @@ require 'midilib/consts'
|
|
15
15
|
|
16
16
|
DEFAULT_MIDI_TEST_FILE = 'NoFences.mid'
|
17
17
|
|
18
|
-
seq = MIDI::Sequence.new
|
19
|
-
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb')
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
18
|
+
seq = MIDI::Sequence.new
|
19
|
+
File.open(ARGV[0] || DEFAULT_MIDI_TEST_FILE, 'rb') do |file|
|
20
|
+
# The block we pass in to Sequence.read is called at the end of every
|
21
|
+
# track read. It is optional, but is useful for progress reports.
|
22
|
+
seq.read(file) do |track, num_tracks, i|
|
23
|
+
puts "read track #{track ? track.name : ''} (#{i} of #{num_tracks})"
|
24
|
+
end
|
25
|
+
end
|
26
26
|
|
27
27
|
include MIDI
|
28
|
-
seq.each
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
28
|
+
seq.each do |track|
|
29
|
+
track.each do |event|
|
30
|
+
puts event.data if event.is_a?(MIDI::MetaEvent) &&
|
31
|
+
[META_TEXT, META_COPYRIGHT, META_SEQ_NAME, META_INSTRUMENT,
|
32
|
+
META_LYRIC, META_CUE, META_MARKER].include?(event.meta_type)
|
33
|
+
end
|
34
|
+
end
|
data/examples/transpose.rb
CHANGED
@@ -17,59 +17,58 @@ require 'midilib/sequence'
|
|
17
17
|
require 'midilib/io/seqreader'
|
18
18
|
require 'midilib/io/seqwriter'
|
19
19
|
|
20
|
-
|
21
20
|
def usage
|
22
|
-
|
23
|
-
usage: #{$0} [--channel|-c channel] [--transpose|-t half_steps]
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
EOF
|
29
|
-
|
21
|
+
$stderr.print <<~EOF
|
22
|
+
usage: #{$0} [--channel|-c channel] [--transpose|-t half_steps]
|
23
|
+
input_midi_file output_midi_file
|
24
|
+
#{' '}
|
25
|
+
--channel|-c channel 1-16; default is 1
|
26
|
+
--transpose|-t half_steps default = 12 (one octave up)
|
27
|
+
EOF
|
28
|
+
exit(1)
|
30
29
|
end
|
31
30
|
|
32
31
|
transpose = 12
|
33
32
|
channel = 0
|
34
33
|
|
35
34
|
g = GetoptLong.new(['--transpose', '-t', GetoptLong::REQUIRED_ARGUMENT],
|
36
|
-
|
37
|
-
g.each
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
35
|
+
['--channel', '-c', GetoptLong::REQUIRED_ARGUMENT])
|
36
|
+
g.each do |name, arg|
|
37
|
+
case name
|
38
|
+
when '--transpose'
|
39
|
+
transpose = arg.to_i
|
40
|
+
when '--channel'
|
41
|
+
channel = arg.to_i - 1
|
42
|
+
else
|
43
|
+
usage
|
44
|
+
end
|
45
|
+
end
|
47
46
|
|
48
|
-
usage
|
47
|
+
usage unless ARGV.length >= 2
|
49
48
|
|
50
|
-
seq = MIDI::Sequence.new
|
51
|
-
File.open(ARGV[0], 'rb')
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
49
|
+
seq = MIDI::Sequence.new
|
50
|
+
File.open(ARGV[0], 'rb') do |file|
|
51
|
+
# The block we pass in to Sequence.read is called at the end of every
|
52
|
+
# track read. It is optional, but is useful for progress reports.
|
53
|
+
seq.read(file) do |num_tracks, i|
|
54
|
+
puts "read track #{i} of #{num_tracks}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
seq.each do |track|
|
59
|
+
track.each do |event|
|
60
|
+
next unless event.is_a?(MIDI::NoteEvent) && event.channel == channel
|
58
61
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
}
|
70
|
-
}
|
62
|
+
val = event.note + transpose
|
63
|
+
if val < 0 || val > 127
|
64
|
+
warn 'transposition out of range; ignored'
|
65
|
+
else
|
66
|
+
event.note = val
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
71
70
|
|
72
71
|
# Output to named file or stdout.
|
73
72
|
file = ARGV[1] ? File.open(ARGV[1], 'wb') : $stdout
|
74
73
|
seq.write(file)
|
75
|
-
file.close
|
74
|
+
file.close if ARGV[1]
|