midilib 2.0.4 → 2.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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/measure.rb
CHANGED
@@ -2,79 +2,79 @@ require 'midilib/consts'
|
|
2
2
|
|
3
3
|
module MIDI
|
4
4
|
|
5
|
-
# The Measure class contains information about a measure from the sequence.
|
6
|
-
# The measure data is based on the time signature information from the sequence
|
7
|
-
# and is not stored in the sequence itself
|
8
|
-
class Measure
|
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
|
-
|
5
|
+
# The Measure class contains information about a measure from the sequence.
|
6
|
+
# The measure data is based on the time signature information from the sequence
|
7
|
+
# and is not stored in the sequence itself
|
8
|
+
class Measure
|
9
|
+
# The numerator (top digit) for the measure's time signature
|
10
|
+
attr_reader :numerator
|
11
|
+
# The denominator for the measure's time signature
|
12
|
+
attr_reader :denominator
|
13
|
+
# Start clock tick for the measure
|
14
|
+
attr_reader :start
|
15
|
+
# End clock tick for the measure (inclusive)
|
16
|
+
attr_reader :end
|
17
|
+
# The measure number (1-based)
|
18
|
+
attr_reader :measure_number
|
19
|
+
# The metronome tick for the measure
|
20
|
+
attr_reader :metronome_ticks
|
21
|
+
|
22
|
+
# Constructor
|
23
|
+
def initialize(meas_no, start_time, duration, numer, denom, met_ticks)
|
24
|
+
@measure_number = meas_no
|
25
|
+
@start = start_time
|
26
|
+
@end = start_time + duration - 1
|
27
|
+
@numerator = numer
|
28
|
+
@denominator = denom
|
29
|
+
@metronome_ticks = met_ticks
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a detailed string with information about the measure
|
33
|
+
def to_s
|
34
|
+
t = "#{@numerator}/#{2**@denominator}"
|
35
|
+
m = @metronome_ticks.to_f / 24
|
36
|
+
"measure #{@measure_number} #{@start}-#{@end} #{t} #{m} qs metronome"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns +true+ if the event is in the measure
|
40
|
+
def contains_event?(e)
|
41
|
+
(e.time_from_start >= @start) && (e.time_from_start <= @end)
|
42
|
+
end
|
42
43
|
end
|
43
|
-
end
|
44
44
|
|
45
|
-
# A specialized container for MIDI::Measure objects, which can be use to map
|
46
|
-
# event times to measure numbers. Please note that this object has to be remade
|
47
|
-
# when events are deleted/added in the sequence.
|
48
|
-
class Measures < Array
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
45
|
+
# A specialized container for MIDI::Measure objects, which can be use to map
|
46
|
+
# event times to measure numbers. Please note that this object has to be remade
|
47
|
+
# when events are deleted/added in the sequence.
|
48
|
+
class Measures < Array
|
49
|
+
# The highest event time in the sequence (at the time when the
|
50
|
+
# object was created)
|
51
|
+
attr_reader :max_time
|
52
|
+
|
53
|
+
# The ppqd from the sequence
|
54
|
+
attr_reader :ppqd
|
55
|
+
|
56
|
+
# Constructor
|
57
|
+
def initialize(max_time, ppqd)
|
58
58
|
super(0)
|
59
59
|
@max_time = max_time
|
60
60
|
@ppqd = ppqd
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
m = measure_for_event(e)
|
74
|
-
b = (e.time_from_start.to_f - m.start.to_f) / @ppqd
|
75
|
-
b *= 24 / m.metronome_ticks
|
76
|
-
sprintf("%d:%02d:%03d", m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd)
|
77
|
-
|
78
|
-
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the MIDI::Measure object where the event is located.
|
64
|
+
# Returns +nil+ if the event isn't found in the container (should
|
65
|
+
# never happen if the MIDI::Measures object is up to date).
|
66
|
+
def measure_for_event(e)
|
67
|
+
detect { |m| m.contains_event?(e) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the event's time as a formatted MBT string (Measure:Beat:Ticks)
|
71
|
+
# as found in MIDI sequencers.
|
72
|
+
def to_mbt(e)
|
73
|
+
m = measure_for_event(e)
|
74
|
+
b = (e.time_from_start.to_f - m.start.to_f) / @ppqd
|
75
|
+
b *= 24 / m.metronome_ticks
|
76
|
+
sprintf("%d:%02d:%03d", m.measure_number, b.to_i + 1, (b - b.to_i) * @ppqd)
|
77
|
+
end
|
78
|
+
end
|
79
79
|
|
80
80
|
end
|
data/lib/midilib/sequence.rb
CHANGED
@@ -4,8 +4,8 @@ require 'midilib/measure.rb'
|
|
4
4
|
|
5
5
|
module MIDI
|
6
6
|
|
7
|
-
# A MIDI::Sequence contains MIDI::Track objects.
|
8
|
-
class Sequence
|
7
|
+
# A MIDI::Sequence contains MIDI::Track objects.
|
8
|
+
class Sequence
|
9
9
|
|
10
10
|
include Enumerable
|
11
11
|
|
@@ -33,7 +33,7 @@ class Sequence
|
|
33
33
|
# Pulses (i.e. clocks) Per Quarter Note resolution for the sequence
|
34
34
|
attr_accessor :ppqn
|
35
35
|
# The MIDI file format (0, 1, or 2)
|
36
|
-
attr_accessor :format
|
36
|
+
attr_accessor :format
|
37
37
|
attr_accessor :numer, :denom, :clocks, :qnotes
|
38
38
|
# The class to use for reading MIDI from a stream. The default is
|
39
39
|
# MIDI::IO::SeqReader. You can change this at any time.
|
@@ -43,34 +43,32 @@ class Sequence
|
|
43
43
|
attr_accessor :writer_class
|
44
44
|
|
45
45
|
def initialize
|
46
|
-
|
47
|
-
|
46
|
+
@tracks = Array.new()
|
47
|
+
@ppqn = 480
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
# Time signature
|
50
|
+
@numer = 4 # Numer + denom = 4/4 time default
|
51
|
+
@denom = 2
|
52
|
+
@clocks = 24 # Bug fix Nov 11, 2007 - this is not the same as ppqn!
|
53
|
+
@qnotes = 8
|
54
54
|
|
55
|
-
|
56
|
-
|
55
|
+
@reader_class = IO::SeqReader
|
56
|
+
@writer_class = IO::SeqWriter
|
57
57
|
end
|
58
58
|
|
59
59
|
# Sets the time signature.
|
60
60
|
def time_signature(numer, denom, clocks, qnotes)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
@numer = numer
|
62
|
+
@denom = denom
|
63
|
+
@clocks = clocks
|
64
|
+
@qnotes = qnotes
|
65
65
|
end
|
66
66
|
|
67
67
|
# Returns the song tempo in beats per minute.
|
68
68
|
def beats_per_minute
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
}
|
73
|
-
return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO
|
69
|
+
return DEFAULT_TEMPO if @tracks.nil? || @tracks.empty?
|
70
|
+
event = @tracks.first.events.detect { |e| e.kind_of?(MIDI::Tempo) }
|
71
|
+
return event ? (Tempo.mpq_to_bpm(event.tempo)) : DEFAULT_TEMPO
|
74
72
|
end
|
75
73
|
alias_method :bpm, :beats_per_minute
|
76
74
|
alias_method :tempo, :beats_per_minute
|
@@ -125,74 +123,74 @@ class Sequence
|
|
125
123
|
# Returns the name of the first track (track zero). If there are no
|
126
124
|
# tracks, returns UNNAMED.
|
127
125
|
def name
|
128
|
-
|
129
|
-
|
126
|
+
return UNNAMED if @tracks.empty?
|
127
|
+
return @tracks.first.name()
|
130
128
|
end
|
131
129
|
|
132
130
|
# Hands the name to the first track. Does nothing if there are no tracks.
|
133
131
|
def name=(name)
|
134
|
-
|
135
|
-
|
132
|
+
return if @tracks.empty?
|
133
|
+
@tracks.first.name = name
|
136
134
|
end
|
137
135
|
|
138
136
|
# Reads a MIDI stream.
|
139
137
|
def read(io, proc = nil) # :yields: track, num_tracks, index
|
140
|
-
|
141
|
-
|
138
|
+
reader = @reader_class.new(self, block_given?() ? Proc.new() : proc)
|
139
|
+
reader.read_from(io)
|
142
140
|
end
|
143
141
|
|
144
142
|
# Writes to a MIDI stream.
|
145
143
|
def write(io, proc = nil) # :yields: track, num_tracks, index
|
146
|
-
|
147
|
-
|
144
|
+
writer = @writer_class.new(self, block_given?() ? Proc.new() : proc)
|
145
|
+
writer.write_to(io)
|
148
146
|
end
|
149
147
|
|
150
148
|
# Iterates over the tracks.
|
151
149
|
def each # :yields: track
|
152
|
-
|
150
|
+
@tracks.each { |track| yield track }
|
153
151
|
end
|
154
152
|
|
155
153
|
# Returns a Measures object, which is an array container for all measures
|
156
154
|
# in the sequence
|
157
|
-
def get_measures
|
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
|
-
|
155
|
+
def get_measures
|
156
|
+
# Collect time sig events and scan for last event time
|
157
|
+
time_sigs = []
|
158
|
+
max_pos = 0
|
159
|
+
@tracks.each do |t|
|
160
|
+
t.each do |e|
|
161
|
+
time_sigs << e if e.kind_of?(MIDI::TimeSig)
|
162
|
+
max_pos = e.time_from_start if e.time_from_start > max_pos
|
163
|
+
end
|
164
|
+
end
|
165
|
+
time_sigs.sort { |x,y| x.time_from_start <=> y.time_from_start }
|
166
|
+
|
167
|
+
# Add a "fake" time sig event at the very last position of the sequence,
|
168
|
+
# just to make sure the whole sequence is calculated.
|
169
|
+
t = MIDI::TimeSig.new(4, 2, 24, 8, 0)
|
170
|
+
t.time_from_start = max_pos
|
171
|
+
time_sigs << t
|
172
|
+
|
173
|
+
# Default to 4/4
|
174
|
+
measure_length = @ppqn * 4
|
175
|
+
oldnumer, olddenom, oldbeats = 4, 2, 24
|
176
|
+
|
177
|
+
measures = MIDI::Measures.new(max_pos, @ppqn)
|
178
|
+
curr_pos = 0
|
179
|
+
curr_meas_no = 1
|
180
|
+
time_sigs.each do |te|
|
181
|
+
meas_count = (te.time_from_start - curr_pos) / measure_length
|
182
|
+
meas_count += 1 if (te.time_from_start - curr_pos) % measure_length > 0
|
183
|
+
1.upto(meas_count) do |i|
|
184
|
+
measures << MIDI::Measure.new(curr_meas_no, curr_pos, measure_length,
|
185
|
+
oldnumer, olddenom, oldbeats)
|
186
|
+
curr_meas_no += 1
|
187
|
+
curr_pos += measure_length
|
188
|
+
end
|
189
|
+
oldnumer, olddenom, oldbeats = te.numerator, te.denominator, te.metronome_ticks
|
190
|
+
measure_length = te.measure_duration(@ppqn)
|
191
|
+
end
|
192
|
+
measures
|
195
193
|
end
|
196
|
-
|
197
|
-
end
|
194
|
+
|
195
|
+
end
|
198
196
|
end
|
data/lib/midilib/track.rb
CHANGED
@@ -2,58 +2,56 @@ require 'midilib/event'
|
|
2
2
|
|
3
3
|
module MIDI
|
4
4
|
|
5
|
-
# This is taken from
|
6
|
-
# http://github.com/adamjmurray/cosy/blob/master/lib/cosy/helper/midi_file_renderer_helper.rb
|
7
|
-
# with permission from Adam Murray, who originally suggested this fix.
|
8
|
-
# See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
|
9
|
-
# First we need to add some API infrastructure:
|
10
|
-
class MIDI::Array < ::Array
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
merge(*halves, &cmp)
|
5
|
+
# This is taken from
|
6
|
+
# http://github.com/adamjmurray/cosy/blob/master/lib/cosy/helper/midi_file_renderer_helper.rb
|
7
|
+
# with permission from Adam Murray, who originally suggested this fix.
|
8
|
+
# See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
|
9
|
+
# First we need to add some API infrastructure:
|
10
|
+
class MIDI::Array < ::Array
|
11
|
+
# This code borrowed from 'Moser' http://codesnippets.joyent.com/posts/show/1699
|
12
|
+
|
13
|
+
# A stable sorting algorithm that maintains the relative order of equal elements
|
14
|
+
def mergesort(&cmp)
|
15
|
+
if cmp == nil
|
16
|
+
cmp = lambda { |a, b| a <=> b }
|
17
|
+
end
|
18
|
+
if size <= 1
|
19
|
+
self.dup
|
20
|
+
else
|
21
|
+
halves = split.map { |half| half.mergesort(&cmp) }
|
22
|
+
merge(*halves, &cmp)
|
23
|
+
end
|
25
24
|
end
|
26
|
-
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
protected
|
27
|
+
def split
|
28
|
+
n = (length / 2).floor - 1
|
29
|
+
[self[0..n], self[n+1..-1]]
|
30
|
+
end
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
def merge(first, second, &predicate)
|
33
|
+
result = []
|
34
|
+
until first.empty? || second.empty?
|
35
|
+
if predicate.call(first.first, second.first) <= 0
|
36
|
+
result << first.shift
|
37
|
+
else
|
38
|
+
result << second.shift
|
39
|
+
end
|
41
40
|
end
|
41
|
+
result.concat(first).concat(second)
|
42
42
|
end
|
43
|
-
result.concat(first).concat(second)
|
44
43
|
end
|
45
|
-
end
|
46
44
|
|
47
|
-
# A Track is a list of events.
|
48
|
-
#
|
49
|
-
# When you modify the +events+ array, make sure to call recalc_times so
|
50
|
-
# each Event gets its +time_from_start+ recalculated.
|
51
|
-
#
|
52
|
-
# A Track also holds a bitmask that specifies the channels used by the track.
|
53
|
-
# This bitmask is set when the track is read from the MIDI file by an
|
54
|
-
# IO::SeqReader but is _not_ kept up to date by any other methods.
|
45
|
+
# A Track is a list of events.
|
46
|
+
#
|
47
|
+
# When you modify the +events+ array, make sure to call recalc_times so
|
48
|
+
# each Event gets its +time_from_start+ recalculated.
|
49
|
+
#
|
50
|
+
# A Track also holds a bitmask that specifies the channels used by the track.
|
51
|
+
# This bitmask is set when the track is read from the MIDI file by an
|
52
|
+
# IO::SeqReader but is _not_ kept up to date by any other methods.
|
55
53
|
|
56
|
-
class Track
|
54
|
+
class Track
|
57
55
|
|
58
56
|
include Enumerable
|
59
57
|
|
@@ -63,62 +61,58 @@ class Track
|
|
63
61
|
attr_reader :sequence
|
64
62
|
|
65
63
|
def initialize(sequence)
|
66
|
-
|
67
|
-
|
64
|
+
@sequence = sequence
|
65
|
+
@events = Array.new()
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
# Bitmask of all channels used. Set when track is read in from
|
68
|
+
# a MIDI file.
|
69
|
+
@channels_used = 0
|
72
70
|
end
|
73
71
|
|
74
72
|
# Return track name. If there is no name, return UNNAMED.
|
75
73
|
def name
|
76
|
-
|
77
|
-
|
78
|
-
}
|
79
|
-
return event ? event.data_as_str : UNNAMED
|
74
|
+
event = @events.detect { |e| e.kind_of?(MetaEvent) && e.meta_type == META_SEQ_NAME }
|
75
|
+
event ? event.data_as_str : UNNAMED
|
80
76
|
end
|
81
77
|
|
82
78
|
# Set track name. Replaces or creates a name meta-event.
|
83
79
|
def name=(name)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@events[0, 0] = event
|
92
|
-
end
|
80
|
+
event = @events.detect { |e| e.kind_of?(MetaEvent) && e.meta_type == META_SEQ_NAME }
|
81
|
+
if event
|
82
|
+
event.data = name
|
83
|
+
else
|
84
|
+
event = MetaEvent.new(META_SEQ_NAME, name, 0)
|
85
|
+
@events[0, 0] = event
|
86
|
+
end
|
93
87
|
end
|
94
88
|
|
95
89
|
def instrument
|
96
|
-
|
90
|
+
MetaEvent.bytes_as_str(@instrument)
|
97
91
|
end
|
98
92
|
|
99
93
|
def instrument=(str_or_bytes)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
94
|
+
@instrument = case str_or_bytes
|
95
|
+
when String
|
96
|
+
MetaEvent.str_as_bytes(str_or_bytes)
|
97
|
+
else
|
98
|
+
str_or_bytes
|
99
|
+
end
|
106
100
|
end
|
107
101
|
|
108
102
|
# Merges an array of events into our event list. After merging, the
|
109
103
|
# events' time_from_start values are correct so you don't need to worry
|
110
104
|
# about calling recalc_times.
|
111
105
|
def merge(event_list)
|
112
|
-
|
106
|
+
@events = merge_event_lists(@events, event_list)
|
113
107
|
end
|
114
108
|
|
115
109
|
# Merges two event arrays together. Does not modify this track.
|
116
110
|
def merge_event_lists(list1, list2)
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
111
|
+
recalc_times(0, list1)
|
112
|
+
recalc_times(0, list2)
|
113
|
+
list = list1 + list2
|
114
|
+
recalc_delta_from_times(0, list)
|
115
|
+
return list
|
122
116
|
end
|
123
117
|
|
124
118
|
# Quantize every event. length_or_note is either a length (1 = quarter,
|
@@ -128,24 +122,24 @@ class Track
|
|
128
122
|
# Since each event's time_from_start is modified, we call
|
129
123
|
# recalc_delta_from_times after each event quantizes itself.
|
130
124
|
def quantize(length_or_note)
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
125
|
+
delta = case length_or_note
|
126
|
+
when String
|
127
|
+
@sequence.note_to_delta(length_or_note)
|
128
|
+
else
|
129
|
+
@sequence.length_to_delta(length_or_note.to_i)
|
130
|
+
end
|
131
|
+
@events.each { |event| event.quantize_to(delta) }
|
132
|
+
recalc_delta_from_times
|
139
133
|
end
|
140
134
|
|
141
135
|
# Recalculate start times for all events in +list+ from starting_at to
|
142
136
|
# end.
|
143
137
|
def recalc_times(starting_at=0, list=@events)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
138
|
+
t = (starting_at == 0) ? 0 : list[starting_at - 1].time_from_start
|
139
|
+
list[starting_at .. -1].each do |e|
|
140
|
+
t += e.delta_time
|
141
|
+
e.time_from_start = t
|
142
|
+
end
|
149
143
|
end
|
150
144
|
|
151
145
|
# The opposite of recalc_times: recalculates delta_time for each event
|
@@ -153,23 +147,23 @@ class Track
|
|
153
147
|
# merging two event lists. As a side-effect, elements from starting_at
|
154
148
|
# are sorted by time_from_start.
|
155
149
|
def recalc_delta_from_times(starting_at=0, list=@events)
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
150
|
+
prev_time_from_start = 0
|
151
|
+
# We need to sort the sublist. sublist.sort! does not do what we want.
|
152
|
+
# We call mergesort instead of Array.sort because sort is not stable
|
153
|
+
# (it can mix up the order of events that have the same start time).
|
154
|
+
# See http://wiki.github.com/adamjmurray/cosy/midilib-notes for details.
|
155
|
+
list[starting_at .. -1] = MIDI::Array.new(list[starting_at .. -1]).mergesort do |e1, e2|
|
156
|
+
e1.time_from_start <=> e2.time_from_start
|
157
|
+
end
|
158
|
+
list[starting_at .. -1].each do |e|
|
159
|
+
e.delta_time = e.time_from_start - prev_time_from_start
|
160
|
+
prev_time_from_start = e.time_from_start
|
161
|
+
end
|
168
162
|
end
|
169
163
|
|
170
164
|
# Iterate over events.
|
171
165
|
def each # :yields: event
|
172
|
-
|
166
|
+
@events.each { |event| yield event }
|
173
167
|
end
|
174
168
|
|
175
169
|
# Sort events by their time_from_start. After sorting,
|
@@ -180,6 +174,6 @@ class Track
|
|
180
174
|
# the events first. This method may go away in a future release, or at
|
181
175
|
# least be aliased to recalc_delta_from_times.
|
182
176
|
alias_method :sort, :recalc_delta_from_times
|
183
|
-
end
|
177
|
+
end
|
184
178
|
|
185
179
|
end
|