jmtk 0.0.3.3-java
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.
- 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
|