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/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
|