midilib 2.0.4 → 2.0.5
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/README.rdoc +9 -4
- data/Rakefile +2 -6
- data/examples/from_scratch.rb +3 -5
- data/examples/measures_mbt.rb +4 -4
- data/examples/print_program_changes.rb +9 -9
- data/examples/reader2text.rb +188 -188
- data/examples/seq2text.rb +17 -17
- data/examples/split.rb +19 -19
- data/examples/strings.rb +14 -14
- data/examples/transpose.rb +31 -31
- data/html/IO.html +65 -169
- data/html/MIDI.html +138 -256
- data/html/MIDI/ActiveSense.html +89 -178
- data/html/MIDI/ChannelEvent.html +95 -183
- data/html/MIDI/ChannelPressure.html +105 -190
- data/html/MIDI/Clock.html +89 -178
- data/html/MIDI/Continue.html +89 -178
- data/html/MIDI/Controller.html +107 -192
- data/html/MIDI/Event.html +138 -222
- data/html/MIDI/IO.html +45 -157
- data/html/MIDI/IO/MIDIFile.html +596 -568
- data/html/MIDI/IO/SeqReader.html +272 -314
- data/html/MIDI/IO/SeqWriter.html +229 -305
- data/html/MIDI/KeySig.html +129 -211
- data/html/MIDI/MIDI.html +45 -154
- data/html/MIDI/MIDI/MIDI.html +45 -154
- data/html/MIDI/MIDI/MIDI/Array.html +87 -185
- data/html/MIDI/Marker.html +71 -170
- data/html/MIDI/Measure.html +95 -190
- data/html/MIDI/Measures.html +103 -193
- data/html/MIDI/MetaEvent.html +180 -253
- data/html/MIDI/NoteEvent.html +118 -204
- data/html/MIDI/NoteOff.html +95 -183
- data/html/MIDI/NoteOn.html +95 -183
- data/html/MIDI/PitchBend.html +106 -191
- data/html/MIDI/PolyPressure.html +106 -189
- data/html/MIDI/ProgramChange.html +105 -190
- data/html/MIDI/Realtime.html +98 -184
- data/html/MIDI/Sequence.html +246 -311
- data/html/MIDI/SongPointer.html +106 -191
- data/html/MIDI/SongSelect.html +105 -190
- data/html/MIDI/Start.html +89 -178
- data/html/MIDI/Stop.html +89 -178
- data/html/MIDI/SystemCommon.html +71 -170
- data/html/MIDI/SystemExclusive.html +108 -193
- data/html/MIDI/SystemReset.html +89 -178
- data/html/MIDI/Tempo.html +135 -213
- data/html/MIDI/TimeSig.html +135 -214
- data/html/MIDI/Track.html +217 -291
- data/html/MIDI/TuneRequest.html +98 -184
- data/html/MIDI/Utils.html +89 -189
- data/html/README_rdoc.html +237 -257
- data/html/TODO_rdoc.html +64 -139
- data/html/created.rid +14 -14
- data/html/css/fonts.css +167 -0
- data/html/{rdoc.css → css/rdoc.css} +265 -218
- 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/delete.png +0 -0
- data/html/images/tag_blue.png +0 -0
- data/html/index.html +187 -169
- data/html/js/darkfish.js +41 -33
- data/html/js/jquery.js +4 -18
- data/html/js/navigation.js.gz +0 -0
- data/html/js/search.js +20 -5
- data/html/js/search_index.js +1 -1
- data/html/js/search_index.js.gz +0 -0
- data/html/js/searcher.js.gz +0 -0
- data/html/table_of_contents.html +1111 -498
- data/install.rb +43 -32
- data/lib/midilib/consts.rb +407 -407
- data/lib/midilib/event.rb +295 -294
- data/lib/midilib/info.rb +5 -5
- data/lib/midilib/io/midifile.rb +266 -267
- data/lib/midilib/io/seqreader.rb +106 -106
- data/lib/midilib/io/seqwriter.rb +59 -60
- data/lib/midilib/measure.rb +69 -69
- data/lib/midilib/sequence.rb +68 -70
- data/lib/midilib/track.rb +96 -102
- data/lib/midilib/utils.rb +15 -15
- data/test/event_equality.rb +50 -50
- data/test/test_event.rb +120 -122
- data/test/test_io.rb +35 -48
- data/test/test_sequence.rb +60 -60
- data/test/test_track.rb +154 -154
- data/test/test_varlen.rb +23 -25
- metadata +65 -57
data/lib/midilib/io/seqreader.rb
CHANGED
@@ -4,54 +4,54 @@ require 'midilib/event'
|
|
4
4
|
|
5
5
|
module MIDI
|
6
6
|
|
7
|
-
module IO
|
8
|
-
|
9
|
-
# Reads MIDI files. As a subclass of MIDIFile, this class implements
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# We append new events to the end of a track's event list, bypassing a
|
14
|
-
# to Track.#add. This means that we must call Track.recalc_times at
|
15
|
-
# of the track so it can update each event with its time from
|
16
|
-
# start (see end_track below).
|
17
|
-
#
|
18
|
-
# META_TRACK_END events are not added to tracks. This way, we don't have
|
19
|
-
# worry about making sure the last event is always a track end event.
|
20
|
-
# rely on the SeqWriter to append a META_TRACK_END event to each
|
21
|
-
# it is output.
|
22
|
-
|
23
|
-
class SeqReader < MIDIFile
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
7
|
+
module IO
|
8
|
+
|
9
|
+
# Reads MIDI files. As a subclass of MIDIFile, this class implements the
|
10
|
+
# callback methods for each MIDI event and use them to build Track and
|
11
|
+
# 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
|
14
|
+
# call to Track.#add. This means that we must call Track.recalc_times at
|
15
|
+
# the end of the track so it can update each event with its time from
|
16
|
+
# the track's start (see end_track below).
|
17
|
+
#
|
18
|
+
# META_TRACK_END events are not added to tracks. This way, we don't have
|
19
|
+
# to worry about making sure the last event is always a track end event.
|
20
|
+
# We rely on the SeqWriter to append a META_TRACK_END event to each
|
21
|
+
# track when it is output.
|
22
|
+
|
23
|
+
class SeqReader < MIDIFile
|
24
|
+
|
25
|
+
# The optional proc block is called once at the start of the file and
|
26
|
+
# again at the end of each track. There are three arguments to the
|
27
|
+
# block: the track, the track number (1 through _n_), and the total
|
28
|
+
# number of tracks.
|
29
|
+
def initialize(seq, proc = nil) # :yields: track, num_tracks, index
|
30
30
|
super()
|
31
31
|
@seq = seq
|
32
32
|
@track = nil
|
33
33
|
@chan_mask = 0
|
34
34
|
@update_block = block_given?() ? Proc.new() : proc
|
35
|
-
|
35
|
+
end
|
36
36
|
|
37
|
-
|
37
|
+
def header(format, ntrks, division)
|
38
38
|
@seq.format = format
|
39
39
|
@seq.ppqn = division
|
40
40
|
|
41
41
|
@ntrks = ntrks
|
42
42
|
@update_block.call(nil, @ntrks, 0) if @update_block
|
43
|
-
|
43
|
+
end
|
44
44
|
|
45
|
-
|
45
|
+
def start_track()
|
46
46
|
@track = Track.new(@seq)
|
47
47
|
@seq.tracks << @track
|
48
48
|
|
49
49
|
@pending = []
|
50
|
-
|
50
|
+
end
|
51
51
|
|
52
|
-
|
52
|
+
def end_track()
|
53
53
|
# Turn off any pending note on messages
|
54
|
-
@pending.each { |
|
54
|
+
@pending.each { |on| make_note_off(on, 64) }
|
55
55
|
@pending = nil
|
56
56
|
|
57
57
|
# Don't bother adding the META_TRACK_END event to the track.
|
@@ -67,136 +67,136 @@ class SeqReader < MIDIFile
|
|
67
67
|
|
68
68
|
# call update block
|
69
69
|
@update_block.call(@track, @ntrks, @seq.tracks.length) if @update_block
|
70
|
-
|
70
|
+
end
|
71
71
|
|
72
|
-
|
72
|
+
def note_on(chan, note, vel)
|
73
73
|
if vel == 0
|
74
|
-
|
75
|
-
|
74
|
+
note_off(chan, note, 64)
|
75
|
+
return
|
76
76
|
end
|
77
77
|
|
78
78
|
on = NoteOn.new(chan, note, vel, @curr_ticks)
|
79
79
|
@track.events << on
|
80
80
|
@pending << on
|
81
81
|
track_uses_channel(chan)
|
82
|
-
|
82
|
+
end
|
83
83
|
|
84
|
-
|
84
|
+
def note_off(chan, note, vel)
|
85
85
|
# Find note on, create note off, connect the two, and remove
|
86
86
|
# note on from pending list.
|
87
|
-
@pending.each_with_index
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
87
|
+
@pending.each_with_index do |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
|
+
end
|
94
94
|
$stderr.puts "note off with no earlier note on (ch #{chan}, note" +
|
95
|
-
|
96
|
-
|
95
|
+
" #{note}, vel #{vel})" if $DEBUG
|
96
|
+
end
|
97
97
|
|
98
|
-
|
98
|
+
def make_note_off(on, vel)
|
99
99
|
off = NoteOff.new(on.channel, on.note, vel, @curr_ticks)
|
100
100
|
@track.events << off
|
101
101
|
on.off = off
|
102
102
|
off.on = on
|
103
|
-
|
103
|
+
end
|
104
104
|
|
105
|
-
|
105
|
+
def pressure(chan, note, press)
|
106
106
|
@track.events << PolyPressure.new(chan, note, press, @curr_ticks)
|
107
107
|
track_uses_channel(chan)
|
108
|
-
|
108
|
+
end
|
109
109
|
|
110
|
-
|
110
|
+
def controller(chan, control, value)
|
111
111
|
@track.events << Controller.new(chan, control, value, @curr_ticks)
|
112
112
|
track_uses_channel(chan)
|
113
|
-
|
113
|
+
end
|
114
114
|
|
115
|
-
|
115
|
+
def pitch_bend(chan, lsb, msb)
|
116
116
|
@track.events << PitchBend.new(chan, (msb << 7) + lsb, @curr_ticks)
|
117
117
|
track_uses_channel(chan)
|
118
|
-
|
118
|
+
end
|
119
119
|
|
120
|
-
|
120
|
+
def program(chan, program)
|
121
121
|
@track.events << ProgramChange.new(chan, program, @curr_ticks)
|
122
122
|
track_uses_channel(chan)
|
123
|
-
|
123
|
+
end
|
124
124
|
|
125
|
-
|
125
|
+
def chan_pressure(chan, press)
|
126
126
|
@track.events << ChannelPressure.new(chan, press, @curr_ticks)
|
127
127
|
track_uses_channel(chan)
|
128
|
-
|
128
|
+
end
|
129
129
|
|
130
|
-
|
130
|
+
def sysex(msg)
|
131
131
|
@track.events << SystemExclusive.new(msg, @curr_ticks)
|
132
|
-
|
132
|
+
end
|
133
133
|
|
134
|
-
|
134
|
+
def meta_misc(type, msg)
|
135
135
|
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
136
|
-
|
136
|
+
end
|
137
137
|
|
138
|
-
# --
|
139
|
-
# def sequencer_specific(type, msg)
|
140
|
-
# end
|
138
|
+
# --
|
139
|
+
# def sequencer_specific(type, msg)
|
140
|
+
# end
|
141
141
|
|
142
|
-
# def sequence_number(num)
|
143
|
-
# end
|
144
|
-
# ++
|
142
|
+
# def sequence_number(num)
|
143
|
+
# end
|
144
|
+
# ++
|
145
145
|
|
146
|
-
|
146
|
+
def text(type, msg)
|
147
147
|
case type
|
148
148
|
when META_TEXT, META_LYRIC, META_CUE
|
149
|
-
|
149
|
+
@track.events << MetaEvent.new(type, msg, @curr_ticks)
|
150
150
|
when META_SEQ_NAME, META_COPYRIGHT
|
151
|
-
|
152
|
-
when META_INSTRUMENT
|
153
|
-
|
151
|
+
@track.events << MetaEvent.new(type, msg, 0)
|
152
|
+
when META_INSTRUMENT
|
153
|
+
@track.instrument = msg
|
154
154
|
when META_MARKER
|
155
|
-
|
155
|
+
@track.events << Marker.new(msg, @curr_ticks)
|
156
156
|
else
|
157
|
-
|
157
|
+
$stderr.puts "text = #{msg}, type = #{type}" if $DEBUG
|
158
158
|
end
|
159
|
-
|
160
|
-
|
161
|
-
# --
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
# def eot()
|
167
|
-
# @track.events << MetaEvent.new(META_TRACK_END, nil, @curr_ticks)
|
168
|
-
# end
|
169
|
-
# ++
|
170
|
-
|
171
|
-
|
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
172
|
@seq.time_signature(numer, denom, clocks, qnotes)
|
173
173
|
@track.events << TimeSig.new(numer, denom, clocks, qnotes, @curr_ticks)
|
174
|
-
|
174
|
+
end
|
175
175
|
|
176
|
-
# --
|
177
|
-
# def smpte(hour, min, sec, frame, fract)
|
178
|
-
# end
|
179
|
-
# ++
|
176
|
+
# --
|
177
|
+
# def smpte(hour, min, sec, frame, fract)
|
178
|
+
# end
|
179
|
+
# ++
|
180
180
|
|
181
|
-
|
181
|
+
def tempo(microsecs)
|
182
182
|
@track.events << Tempo.new(microsecs, @curr_ticks)
|
183
|
-
|
183
|
+
end
|
184
184
|
|
185
|
-
|
186
|
-
|
187
|
-
|
185
|
+
def key_signature(sharpflat, is_minor)
|
186
|
+
@track.events << KeySig.new(sharpflat, is_minor, @curr_ticks)
|
187
|
+
end
|
188
188
|
|
189
|
-
# --
|
190
|
-
# def arbitrary(msg)
|
191
|
-
# end
|
192
|
-
# ++
|
189
|
+
# --
|
190
|
+
# def arbitrary(msg)
|
191
|
+
# end
|
192
|
+
# ++
|
193
193
|
|
194
|
-
|
195
|
-
|
194
|
+
# Return true if the current track uses the specified channel.
|
195
|
+
def track_uses_channel(chan)
|
196
196
|
@chan_mask = @chan_mask | (1 << chan)
|
197
|
-
|
197
|
+
end
|
198
198
|
|
199
|
-
end
|
199
|
+
end
|
200
200
|
|
201
|
-
end
|
201
|
+
end
|
202
202
|
end
|
data/lib/midilib/io/seqwriter.rb
CHANGED
@@ -5,36 +5,36 @@ require 'midilib/utils'
|
|
5
5
|
|
6
6
|
module MIDI
|
7
7
|
|
8
|
-
module IO
|
8
|
+
module IO
|
9
9
|
|
10
|
-
class SeqWriter
|
10
|
+
class SeqWriter
|
11
11
|
|
12
|
-
|
12
|
+
def initialize(seq, proc = nil) # :yields: num_tracks, index
|
13
13
|
@seq = seq
|
14
14
|
@update_block = block_given?() ? Proc.new() : proc
|
15
|
-
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
# Writes a MIDI format 1 file.
|
18
|
+
def write_to(io)
|
19
19
|
@io = io
|
20
20
|
@bytes_written = 0
|
21
21
|
write_header()
|
22
22
|
@update_block.call(nil, @seq.tracks.length, 0) if @update_block
|
23
|
-
@seq.tracks.each_with_index
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
@seq.tracks.each_with_index do |track, i|
|
24
|
+
write_track(track)
|
25
|
+
@update_block.call(track, @seq.tracks.length, i) if @update_block
|
26
|
+
end
|
27
|
+
end
|
28
28
|
|
29
|
-
|
29
|
+
def write_header
|
30
30
|
@io.print 'MThd'
|
31
31
|
write32(6)
|
32
32
|
write16(1) # Ignore sequence format; write as format 1
|
33
33
|
write16(@seq.tracks.length)
|
34
34
|
write16(@seq.ppqn)
|
35
|
-
|
35
|
+
end
|
36
36
|
|
37
|
-
|
37
|
+
def write_track(track)
|
38
38
|
@io.print 'MTrk'
|
39
39
|
track_size_file_pos = @io.tell()
|
40
40
|
write32(0) # Dummy byte count; overwritten later
|
@@ -44,23 +44,22 @@ class SeqWriter
|
|
44
44
|
|
45
45
|
prev_event = nil
|
46
46
|
prev_status = 0
|
47
|
-
track.events.each
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
track.events.each do |event|
|
48
|
+
if !event.kind_of?(Realtime)
|
49
|
+
write_var_len(event.delta_time)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
data = event.data_as_bytes()
|
53
|
+
status = data[0] # status byte plus channel number, if any
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
prev_status)
|
55
|
+
# running status byte
|
56
|
+
status = possibly_munge_due_to_running_status_byte(data, prev_status)
|
58
57
|
|
59
|
-
|
58
|
+
@bytes_written += write_bytes(data)
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
prev_event = event
|
61
|
+
prev_status = status
|
62
|
+
end
|
64
63
|
|
65
64
|
# Write track end event.
|
66
65
|
event = MetaEvent.new(META_TRACK_END)
|
@@ -72,12 +71,12 @@ class SeqWriter
|
|
72
71
|
@io.seek(track_size_file_pos)
|
73
72
|
write32(@bytes_written)
|
74
73
|
@io.seek(0, ::IO::SEEK_END)
|
75
|
-
|
74
|
+
end
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
# If we can use a running status byte, delete the status byte from
|
77
|
+
# the given data. Return the status to remember for next time as the
|
78
|
+
# running status byte for this event.
|
79
|
+
def possibly_munge_due_to_running_status_byte(data, prev_status)
|
81
80
|
status = data[0]
|
82
81
|
return status if status >= 0xf0 || prev_status >= 0xf0
|
83
82
|
|
@@ -91,49 +90,49 @@ class SeqWriter
|
|
91
90
|
# exactly the same, the rest is trivial. If it's note on/note off,
|
92
91
|
# we can combine those further.
|
93
92
|
if status == prev_status
|
94
|
-
|
95
|
-
|
93
|
+
data[0,1] = [] # delete status byte from data
|
94
|
+
return status + chan
|
96
95
|
elsif status == NOTE_OFF && data[2] == 64
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
96
|
+
# If we see a note off and the velocity is 64, we can store
|
97
|
+
# a note on with a velocity of 0. If the velocity isn't 64
|
98
|
+
# then storing a note on would be bad because the would be
|
99
|
+
# changed to 64 when reading the file back in.
|
100
|
+
data[2] = 0 # set vel to 0; do before possible shrinking
|
101
|
+
status = NOTE_ON + chan
|
102
|
+
if prev_status == NOTE_ON
|
103
|
+
data[0,1] = [] # delete status byte
|
104
|
+
else
|
105
|
+
data[0] = status
|
106
|
+
end
|
107
|
+
return status
|
109
108
|
else
|
110
|
-
|
111
|
-
|
109
|
+
# Can't compress data
|
110
|
+
return status + chan
|
112
111
|
end
|
113
|
-
|
112
|
+
end
|
114
113
|
|
115
|
-
|
114
|
+
def write_instrument(instrument)
|
116
115
|
event = MetaEvent.new(META_INSTRUMENT, instrument)
|
117
116
|
write_var_len(0)
|
118
117
|
data = event.data_as_bytes()
|
119
118
|
@bytes_written += write_bytes(data)
|
120
|
-
|
119
|
+
end
|
121
120
|
|
122
|
-
|
121
|
+
def write_var_len(val)
|
123
122
|
buffer = Utils.as_var_len(val)
|
124
123
|
@bytes_written += write_bytes(buffer)
|
125
|
-
|
124
|
+
end
|
126
125
|
|
127
|
-
|
126
|
+
def write16(val)
|
128
127
|
val = (-val | 0x8000) if val < 0
|
129
128
|
|
130
129
|
buffer = []
|
131
130
|
@io.putc((val >> 8) & 0xff)
|
132
131
|
@io.putc(val & 0xff)
|
133
132
|
@bytes_written += 2
|
134
|
-
|
133
|
+
end
|
135
134
|
|
136
|
-
|
135
|
+
def write32(val)
|
137
136
|
val = (-val | 0x80000000) if val < 0
|
138
137
|
|
139
138
|
@io.putc((val >> 24) & 0xff)
|
@@ -141,13 +140,13 @@ class SeqWriter
|
|
141
140
|
@io.putc((val >> 8) & 0xff)
|
142
141
|
@io.putc(val & 0xff)
|
143
142
|
@bytes_written += 4
|
144
|
-
|
143
|
+
end
|
145
144
|
|
146
|
-
|
145
|
+
def write_bytes(bytes)
|
147
146
|
bytes.each { |b| @io.putc(b) }
|
148
147
|
bytes.length
|
148
|
+
end
|
149
149
|
end
|
150
|
-
end
|
151
150
|
|
152
|
-
end
|
151
|
+
end
|
153
152
|
end
|