jmtk 0.0.3.3-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +10 -0
- data/DEVELOPMENT_NOTES.md +115 -0
- data/INTRO.md +129 -0
- data/LICENSE.txt +27 -0
- data/README.md +50 -0
- data/Rakefile +102 -0
- data/bin/jmtk +250 -0
- data/bin/mtk +250 -0
- data/examples/crescendo.rb +20 -0
- data/examples/drum_pattern.rb +23 -0
- data/examples/dynamic_pattern.rb +36 -0
- data/examples/gets_and_play.rb +27 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +17 -0
- data/examples/print_midi.rb +13 -0
- data/examples/random_tone_row.rb +18 -0
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +7 -0
- data/examples/tone_row_melody.rb +23 -0
- data/lib/mtk.rb +76 -0
- data/lib/mtk/core/duration.rb +213 -0
- data/lib/mtk/core/intensity.rb +158 -0
- data/lib/mtk/core/interval.rb +157 -0
- data/lib/mtk/core/pitch.rb +154 -0
- data/lib/mtk/core/pitch_class.rb +194 -0
- data/lib/mtk/events/event.rb +119 -0
- data/lib/mtk/events/note.rb +112 -0
- data/lib/mtk/events/parameter.rb +54 -0
- data/lib/mtk/events/timeline.rb +232 -0
- data/lib/mtk/groups/chord.rb +56 -0
- data/lib/mtk/groups/collection.rb +196 -0
- data/lib/mtk/groups/melody.rb +96 -0
- data/lib/mtk/groups/pitch_class_set.rb +163 -0
- data/lib/mtk/groups/pitch_collection.rb +23 -0
- data/lib/mtk/io/dls_synth_device.rb +146 -0
- data/lib/mtk/io/dls_synth_output.rb +62 -0
- data/lib/mtk/io/jsound_input.rb +87 -0
- data/lib/mtk/io/jsound_output.rb +82 -0
- data/lib/mtk/io/midi_file.rb +209 -0
- data/lib/mtk/io/midi_input.rb +97 -0
- data/lib/mtk/io/midi_output.rb +195 -0
- data/lib/mtk/io/notation.rb +162 -0
- data/lib/mtk/io/unimidi_input.rb +117 -0
- data/lib/mtk/io/unimidi_output.rb +140 -0
- data/lib/mtk/lang/durations.rb +57 -0
- data/lib/mtk/lang/intensities.rb +61 -0
- data/lib/mtk/lang/intervals.rb +73 -0
- data/lib/mtk/lang/mtk_grammar.citrus +237 -0
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/lang/pitch_classes.rb +29 -0
- data/lib/mtk/lang/pitches.rb +52 -0
- data/lib/mtk/lang/pseudo_constants.rb +26 -0
- data/lib/mtk/lang/variable.rb +32 -0
- data/lib/mtk/numeric_extensions.rb +66 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/patterns/choice.rb +43 -0
- data/lib/mtk/patterns/cycle.rb +18 -0
- data/lib/mtk/patterns/for_each.rb +71 -0
- data/lib/mtk/patterns/function.rb +39 -0
- data/lib/mtk/patterns/lines.rb +54 -0
- data/lib/mtk/patterns/palindrome.rb +45 -0
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/sequencers/event_builder.rb +132 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/sequencers/sequencer.rb +111 -0
- data/lib/mtk/sequencers/step_sequencer.rb +26 -0
- data/spec/mtk/core/duration_spec.rb +372 -0
- data/spec/mtk/core/intensity_spec.rb +289 -0
- data/spec/mtk/core/interval_spec.rb +265 -0
- data/spec/mtk/core/pitch_class_spec.rb +343 -0
- data/spec/mtk/core/pitch_spec.rb +297 -0
- data/spec/mtk/events/event_spec.rb +234 -0
- data/spec/mtk/events/note_spec.rb +174 -0
- data/spec/mtk/events/parameter_spec.rb +220 -0
- data/spec/mtk/events/timeline_spec.rb +430 -0
- data/spec/mtk/groups/chord_spec.rb +85 -0
- data/spec/mtk/groups/collection_spec.rb +374 -0
- data/spec/mtk/groups/melody_spec.rb +225 -0
- data/spec/mtk/groups/pitch_class_set_spec.rb +340 -0
- data/spec/mtk/io/midi_file_spec.rb +243 -0
- data/spec/mtk/io/midi_output_spec.rb +102 -0
- data/spec/mtk/lang/durations_spec.rb +89 -0
- data/spec/mtk/lang/intensities_spec.rb +101 -0
- data/spec/mtk/lang/intervals_spec.rb +143 -0
- data/spec/mtk/lang/parser_spec.rb +603 -0
- data/spec/mtk/lang/pitch_classes_spec.rb +62 -0
- data/spec/mtk/lang/pitches_spec.rb +56 -0
- data/spec/mtk/lang/pseudo_constants_spec.rb +20 -0
- data/spec/mtk/lang/variable_spec.rb +52 -0
- data/spec/mtk/numeric_extensions_spec.rb +83 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/patterns/choice_spec.rb +97 -0
- data/spec/mtk/patterns/cycle_spec.rb +123 -0
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/patterns/function_spec.rb +120 -0
- data/spec/mtk/patterns/lines_spec.rb +77 -0
- data/spec/mtk/patterns/palindrome_spec.rb +108 -0
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/sequencers/event_builder_spec.rb +245 -0
- data/spec/mtk/sequencers/legato_sequencer_spec.rb +45 -0
- data/spec/mtk/sequencers/rhythmic_sequencer_spec.rb +84 -0
- data/spec/mtk/sequencers/sequencer_spec.rb +215 -0
- data/spec/mtk/sequencers/step_sequencer_spec.rb +93 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test.mid +0 -0
- metadata +226 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
module Events
|
4
|
+
|
5
|
+
# An abstract musical event
|
6
|
+
# @abstract
|
7
|
+
class Event
|
8
|
+
|
9
|
+
# The type of event: :note, :control, :pressure, :bend, or :program
|
10
|
+
attr_reader :type
|
11
|
+
|
12
|
+
# The specific element effected by this type of event, when applicable.
|
13
|
+
# Depends on the event type. For example, the number of a :note type Event is the pitch,
|
14
|
+
# and the number of a :control type Event is the controller (CC) number.
|
15
|
+
# This value is nil for inapplicable event types.
|
16
|
+
attr_accessor :number
|
17
|
+
|
18
|
+
# The value of event.
|
19
|
+
# Depends on event type. For example, the value of a :note type Event is the intensity,
|
20
|
+
# and the value of a :control type Event is the controller (CC) value.
|
21
|
+
attr_accessor :value
|
22
|
+
|
23
|
+
# Duration of the Event in beats (e.g. 1.0 is a quarter note in 4/4 time signatures)
|
24
|
+
# @see length
|
25
|
+
# @see rest?
|
26
|
+
# @see instantaneous?
|
27
|
+
# @see duration_in_pulses
|
28
|
+
attr_reader :duration
|
29
|
+
|
30
|
+
def duration= duration
|
31
|
+
@duration = duration
|
32
|
+
@duration = ::MTK::Core::Duration[@duration || 0] unless @duration.is_a? ::MTK::Core::Duration
|
33
|
+
@duration
|
34
|
+
end
|
35
|
+
|
36
|
+
# The channel of the event, for multi-tracked events.
|
37
|
+
attr_accessor :channel
|
38
|
+
|
39
|
+
|
40
|
+
def initialize(type, options={})
|
41
|
+
@type = type
|
42
|
+
@value = options[:value]
|
43
|
+
@number = options[:number]
|
44
|
+
@duration = options.fetch(:duration, 0)
|
45
|
+
@duration = ::MTK::Core::Duration[@duration] unless @duration.is_a? ::MTK::Core::Duration
|
46
|
+
@channel = options[:channel]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.from_h(hash)
|
50
|
+
new(hash[:type], hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_h
|
54
|
+
hash = {:type => @type}
|
55
|
+
hash[:value] = @value unless @value.nil?
|
56
|
+
hash[:duration] = @duration unless @duration.nil?
|
57
|
+
hash[:number] = @number unless @number.nil?
|
58
|
+
hash[:channel] = @channel unless @channel.nil?
|
59
|
+
hash
|
60
|
+
end
|
61
|
+
|
62
|
+
def midi_value
|
63
|
+
if @value and @value.respond_to? :to_midi
|
64
|
+
@value.to_midi
|
65
|
+
else
|
66
|
+
value = @value
|
67
|
+
midi_value = (127 * (value || 0)).round
|
68
|
+
midi_value = 0 if midi_value < 0
|
69
|
+
midi_value = 127 if midi_value > 127
|
70
|
+
midi_value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def midi_value= value
|
75
|
+
@value = value/127.0
|
76
|
+
end
|
77
|
+
|
78
|
+
# The magnitude (absolute value) of the duration.
|
79
|
+
# Indicate the "real" duration for rests.
|
80
|
+
# @see rest?
|
81
|
+
def length
|
82
|
+
@duration.length
|
83
|
+
end
|
84
|
+
|
85
|
+
# By convention, any events with negative durations are a rest
|
86
|
+
def rest?
|
87
|
+
@duration.rest?
|
88
|
+
end
|
89
|
+
|
90
|
+
# By convention, any events with 0 duration are instantaneous
|
91
|
+
def instantaneous?
|
92
|
+
@duration.nil? or @duration == 0
|
93
|
+
end
|
94
|
+
|
95
|
+
# Convert duration to an integer number of MIDI pulses, given the pulses_per_beat
|
96
|
+
def duration_in_pulses(pulses_per_beat)
|
97
|
+
(length.to_f * pulses_per_beat).round
|
98
|
+
end
|
99
|
+
|
100
|
+
def == other
|
101
|
+
other.respond_to? :type and @type == other.type and
|
102
|
+
other.respond_to? :number and @number == other.number and
|
103
|
+
other.respond_to? :value and @value == other.value and
|
104
|
+
other.respond_to? :duration and @duration == other.duration and
|
105
|
+
other.respond_to? :channel and @channel == other.channel
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
"Event(#@type" + (@number ? "[#@number]" : '') + ", #{sprintf '%.2f',@value}, #{sprintf '%.2f',@duration})"
|
110
|
+
end
|
111
|
+
|
112
|
+
def inspect
|
113
|
+
"Event(#@type" + (@number ? "[#@number]" : '') + ", #@value, #{@duration.to_f})"
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
module Events
|
4
|
+
|
5
|
+
# A musical {Event} defined by a {Pitch}, intensity, and duration
|
6
|
+
class Note < Event
|
7
|
+
|
8
|
+
DEFAULT_DURATION = MTK::Core::Duration[1]
|
9
|
+
DEFAULT_INTENSITY = MTK::Core::Intensity[0.75]
|
10
|
+
|
11
|
+
# Frequency of the note as a {Pitch}.
|
12
|
+
alias :pitch :number
|
13
|
+
alias :pitch= :number=
|
14
|
+
|
15
|
+
# Intensity of the note as a value in the range 0.0 - 1.0.
|
16
|
+
alias :intensity :value
|
17
|
+
alias :intensity= :value=
|
18
|
+
|
19
|
+
# intensity scaled to the MIDI range 0-127
|
20
|
+
alias :velocity :midi_value
|
21
|
+
alias :velocity= :midi_value=
|
22
|
+
|
23
|
+
def initialize(pitch, duration=DEFAULT_DURATION, intensity=DEFAULT_INTENSITY, channel=nil)
|
24
|
+
super :note, number:pitch, duration:duration, value:intensity, channel:channel
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_h(hash)
|
28
|
+
new(hash[:pitch]||hash[:number], hash[:duration], hash[:intensity]||hash[:value], hash[:channel])
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
super.merge({ pitch: @number, intensity: @value })
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.from_midi(pitch, velocity, duration_in_beats, channel=0)
|
36
|
+
new( MTK::Lang::Pitches::PITCHES[pitch.to_i], MTK::Core::Duration[duration_in_beats], MTK::Core::Intensity[velocity/127.0], channel )
|
37
|
+
end
|
38
|
+
|
39
|
+
def midi_pitch
|
40
|
+
pitch.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def transpose(interval)
|
44
|
+
self.pitch += interval
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def invert(around_pitch)
|
49
|
+
self.pitch = self.pitch.invert(around_pitch)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(other)
|
54
|
+
( other.respond_to? :pitch and pitch == other.pitch and
|
55
|
+
other.respond_to? :intensity and intensity == other.intensity and
|
56
|
+
other.respond_to? :duration and duration == other.duration
|
57
|
+
) or super
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
"Note(#{@number}, #{@duration}, #{@value.to_percent}%)"
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
"#<#{self.class}:#{object_id} @pitch=#{@number.inspect}, @duration=#{@duration.inspect}, @intensity=#{@value.inspect}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Construct a {Events::Note} from a list of any supported type for the arguments: pitch, intensity, duration, channel
|
72
|
+
def Note(*anything)
|
73
|
+
anything = anything.first if anything.size == 1
|
74
|
+
case anything
|
75
|
+
when MTK::Events::Note then anything
|
76
|
+
|
77
|
+
when MTK::Core::Pitch then MTK::Events::Note.new(anything)
|
78
|
+
|
79
|
+
when Array
|
80
|
+
pitch = nil
|
81
|
+
duration = nil
|
82
|
+
intensity = nil
|
83
|
+
channel = nil
|
84
|
+
unknowns = []
|
85
|
+
anything.each do |item|
|
86
|
+
case item
|
87
|
+
when MTK::Core::Pitch then pitch = item
|
88
|
+
when MTK::Core::Duration then duration = item
|
89
|
+
when MTK::Core::Intensity then intensity = item
|
90
|
+
else unknowns << item
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
pitch = MTK.Pitch(unknowns.shift) if pitch.nil? and not unknowns.empty?
|
95
|
+
raise "MTK::Note() couldn't find a pitch in arguments: #{anything.inspect}" if pitch.nil?
|
96
|
+
|
97
|
+
duration = MTK.Duration(unknowns.shift) if duration.nil? and not unknowns.empty?
|
98
|
+
intensity = MTK.Intensity(unknowns.shift) if intensity.nil? and not unknowns.empty?
|
99
|
+
channel = unknowns.shift.to_i if channel.nil? and not unknowns.empty?
|
100
|
+
|
101
|
+
duration ||= MTK::Events::Note::DEFAULT_DURATION
|
102
|
+
intensity ||= MTK::Events::Note::DEFAULT_INTENSITY
|
103
|
+
|
104
|
+
MTK::Events::Note.new( pitch, duration, intensity, channel )
|
105
|
+
|
106
|
+
else
|
107
|
+
raise "MTK::Note() doesn't understand #{anything.class}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
module_function :Note
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
module Events
|
4
|
+
|
5
|
+
class Parameter < Event
|
6
|
+
|
7
|
+
def self.from_midi(status, data1, data2)
|
8
|
+
if status.is_a? Array
|
9
|
+
type,channel = *status
|
10
|
+
else
|
11
|
+
type,channel = status & 0xF0, status & 0x0F
|
12
|
+
end
|
13
|
+
type, number, value = *(
|
14
|
+
case type
|
15
|
+
when 0xA0,:poly_pressure then [:pressure, data1, data2]
|
16
|
+
when 0xB0,:control_change then [:control, data1, data2]
|
17
|
+
when 0xC0,:program_change then [:program, data1]
|
18
|
+
when 0xD0,:channel_pressure then [:pressure, nil, data1] # no number means all notes on channel
|
19
|
+
when 0xE0,:pitch_bend then [:bend, nil, (data1 + (data2 << 7))]
|
20
|
+
else [:unknown, data1, data2]
|
21
|
+
end
|
22
|
+
)
|
23
|
+
if type == :bend
|
24
|
+
if value == 16383
|
25
|
+
value = 1.0 # special case since the math doesn't quite work out to convert to -1..1 for all values
|
26
|
+
else
|
27
|
+
value = (value / 8192.0) - 1.0
|
28
|
+
end
|
29
|
+
elsif value.is_a? Numeric
|
30
|
+
value /= 127.0
|
31
|
+
end
|
32
|
+
new type, :number => number, :value => value, :channel => channel
|
33
|
+
end
|
34
|
+
|
35
|
+
def midi_value
|
36
|
+
if @type == :bend
|
37
|
+
(16383*(@value+1)/2).round
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"Parameter(#@type" + (@number ? "[#@number], " : ', ') + "#{sprintf '%.2f', @value || Float::NAN})"
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"Parameter(#@type" + (@number ? "[#@number], " : ', ') + "#@value)"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module MTK
|
2
|
+
module Events
|
3
|
+
|
4
|
+
# A collection of timed events. The core data structure used to interface with input and output.
|
5
|
+
#
|
6
|
+
# Maps sorted floating point times to lists of events.
|
7
|
+
#
|
8
|
+
# Enumerable as [time,event_list] pairs.
|
9
|
+
#
|
10
|
+
class Timeline
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def initialize()
|
14
|
+
@timeline = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def from_a(enumerable)
|
19
|
+
new.merge enumerable
|
20
|
+
end
|
21
|
+
alias from_h from_a
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge enumerable
|
25
|
+
enumerable.each do |time,events|
|
26
|
+
add(time,events)
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear
|
32
|
+
@timeline.clear
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
@timeline
|
38
|
+
end
|
39
|
+
|
40
|
+
def == other
|
41
|
+
other = other.to_h unless other.is_a? Hash
|
42
|
+
@timeline == other
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](time)
|
46
|
+
@timeline[time.to_f]
|
47
|
+
end
|
48
|
+
|
49
|
+
def []=(time, events)
|
50
|
+
time = time.to_f unless time.is_a? Numeric
|
51
|
+
case events
|
52
|
+
when nil?
|
53
|
+
@timeline.delete time.to_f
|
54
|
+
when Array
|
55
|
+
@timeline[time.to_f] = events
|
56
|
+
else
|
57
|
+
@timeline[time.to_f] = [events]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add(time, event)
|
62
|
+
events = @timeline[time.to_f]
|
63
|
+
if events
|
64
|
+
if event.is_a? Array
|
65
|
+
events.concat event
|
66
|
+
else
|
67
|
+
events << event
|
68
|
+
end
|
69
|
+
else
|
70
|
+
self[time] = event
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(time)
|
75
|
+
@timeline.delete(time.to_f)
|
76
|
+
end
|
77
|
+
|
78
|
+
def has_time? time
|
79
|
+
@timeline.has_key? time.to_f
|
80
|
+
end
|
81
|
+
|
82
|
+
def times
|
83
|
+
@timeline.keys.sort
|
84
|
+
end
|
85
|
+
|
86
|
+
def length
|
87
|
+
last_time = times.last
|
88
|
+
events = @timeline[last_time]
|
89
|
+
last_time + events.map{|event| event.duration }.max
|
90
|
+
end
|
91
|
+
|
92
|
+
def empty?
|
93
|
+
@timeline.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
def events
|
97
|
+
times.map{|t| @timeline[t] }.flatten
|
98
|
+
end
|
99
|
+
|
100
|
+
def each
|
101
|
+
# this is similar to @timeline.each, but by iterating over #times, we yield the events in chronological order
|
102
|
+
times.each do |time|
|
103
|
+
yield time, @timeline[time]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# the original Enumerable#map implementation, which returns an Array
|
108
|
+
alias enumerable_map map
|
109
|
+
|
110
|
+
# Constructs a new Timeline by mapping each [time,event_list] pair
|
111
|
+
# @see #map!
|
112
|
+
def map &block
|
113
|
+
self.class.from_a enumerable_map(&block)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Perform #map in place
|
117
|
+
# @see #map
|
118
|
+
def map! &block
|
119
|
+
mapped = enumerable_map(&block)
|
120
|
+
clear
|
121
|
+
merge mapped
|
122
|
+
end
|
123
|
+
|
124
|
+
# Map every individual event, without regard for the time at which is occurs
|
125
|
+
def map_events
|
126
|
+
mapped_timeline = Timeline.new
|
127
|
+
self.each do |time,events|
|
128
|
+
mapped_timeline[time] = events.map{|event| yield event }
|
129
|
+
end
|
130
|
+
mapped_timeline
|
131
|
+
end
|
132
|
+
|
133
|
+
# Map every individual event in place, without regard for the time at which is occurs
|
134
|
+
def map_events!
|
135
|
+
each do |time,events|
|
136
|
+
self[time] = events.map{|event| yield event }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def clone
|
141
|
+
self.class.from_h(to_h)
|
142
|
+
end
|
143
|
+
|
144
|
+
def compact!
|
145
|
+
@timeline.delete_if {|t,events| events.empty? }
|
146
|
+
end
|
147
|
+
|
148
|
+
def flatten
|
149
|
+
flattened = Timeline.new
|
150
|
+
self.each do |time,events|
|
151
|
+
events.each do |event|
|
152
|
+
if event.is_a? Timeline
|
153
|
+
event.flatten.each do |subtime,subevent|
|
154
|
+
flattened.add(time+subtime, subevent)
|
155
|
+
end
|
156
|
+
else
|
157
|
+
flattened.add(time,event)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
flattened
|
162
|
+
end
|
163
|
+
|
164
|
+
# @return a new Timeline where all times have been quantized to multiples of the given interval
|
165
|
+
# @example timeline.quantize(0.5) # quantize to eight notes (assuming the beat is a quarter note)
|
166
|
+
# @see quantize!
|
167
|
+
def quantize interval
|
168
|
+
map{|time,events| [self.class.quantize_time(time,interval), events] }
|
169
|
+
end
|
170
|
+
|
171
|
+
def quantize! interval
|
172
|
+
map!{|time,events| [self.class.quantize_time(time,interval), events] }
|
173
|
+
end
|
174
|
+
|
175
|
+
# shifts all times by the given amount
|
176
|
+
# @see #shift!
|
177
|
+
# @see #shift_to
|
178
|
+
def shift time_delta
|
179
|
+
map{|time,events| [time+time_delta, events] }
|
180
|
+
end
|
181
|
+
|
182
|
+
# shifts all times in place by the given amount
|
183
|
+
# @see #shift
|
184
|
+
# @see #shift_to!
|
185
|
+
def shift! time_delta
|
186
|
+
map!{|time,events| [time+time_delta, events] }
|
187
|
+
end
|
188
|
+
|
189
|
+
# shifts the times so that the start of the timeline is at the given time
|
190
|
+
# @see #shift_to!
|
191
|
+
# @see #shift
|
192
|
+
def shift_to absolute_time
|
193
|
+
start = times.first
|
194
|
+
if start
|
195
|
+
shift absolute_time - start
|
196
|
+
else
|
197
|
+
clone
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# shifts the times in place so that the start of the timeline is at the given time
|
202
|
+
# @see #shift_to
|
203
|
+
# @see #shift!
|
204
|
+
def shift_to! absolute_time
|
205
|
+
start = times.first
|
206
|
+
if start
|
207
|
+
shift! absolute_time - start
|
208
|
+
end
|
209
|
+
self
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_s
|
213
|
+
times = self.times
|
214
|
+
last = times.last
|
215
|
+
width = sprintf("%d",last).length + 3 # nicely align the '=>' against the longest number
|
216
|
+
times.map{|t| sprintf("%#{width}.2f",t)+" => #{@timeline[t].join ', '}" }.join "\n"
|
217
|
+
end
|
218
|
+
|
219
|
+
def inspect
|
220
|
+
@timeline.inspect
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.quantize_time time, interval
|
224
|
+
upper = interval * (time.to_f/interval).ceil
|
225
|
+
lower = upper - interval
|
226
|
+
(time - lower) < (upper - time) ? lower : upper
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|