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,97 @@
|
|
1
|
+
module MTK
|
2
|
+
module IO
|
3
|
+
|
4
|
+
# Common behavior for realtime MIDI input.
|
5
|
+
#
|
6
|
+
class MIDIInput
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
available_input_types << subclass
|
12
|
+
end
|
13
|
+
|
14
|
+
def available_input_types
|
15
|
+
@available_input_types ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def input_types_by_device
|
19
|
+
@input_types_by_device ||= (
|
20
|
+
available_input_types.each_with_object( Hash.new ) do |input_type,hash|
|
21
|
+
input_type.devices.each{|device| hash[device] = input_type }
|
22
|
+
end
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# All available input devices.
|
27
|
+
def devices
|
28
|
+
@devices ||= available_input_types.map{|input_type| input_type.devices }.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
# Maps input device names to the input device.
|
32
|
+
def devices_by_name
|
33
|
+
@devices_by_name ||= (
|
34
|
+
available_input_types.each_with_object( Hash.new ) do |input_type,hash|
|
35
|
+
hash.merge!( input_type.devices_by_name )
|
36
|
+
end
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_by_name(name)
|
41
|
+
if name.is_a? Regexp
|
42
|
+
matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
|
43
|
+
device = devices_by_name[matching_name]
|
44
|
+
else
|
45
|
+
device = devices_by_name[name.to_s]
|
46
|
+
end
|
47
|
+
open(device) if device
|
48
|
+
end
|
49
|
+
|
50
|
+
def open(device)
|
51
|
+
input_type = input_types_by_device[device]
|
52
|
+
input_type.new(device) if input_type
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def initialize(input_device, options={})
|
58
|
+
@device = input_device
|
59
|
+
@device.open
|
60
|
+
@options = options
|
61
|
+
end
|
62
|
+
private_class_method :new
|
63
|
+
|
64
|
+
|
65
|
+
########################
|
66
|
+
public
|
67
|
+
|
68
|
+
# The underlying output device implementation wrapped by this class.
|
69
|
+
# The device type depends on the platform.
|
70
|
+
attr_reader :device
|
71
|
+
|
72
|
+
def name
|
73
|
+
@device.name
|
74
|
+
end
|
75
|
+
|
76
|
+
def record
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_timeline
|
83
|
+
Timeline.new
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
92
|
+
if RUBY_PLATFORM == 'java'
|
93
|
+
require 'mtk/io/jsound_input'
|
94
|
+
else
|
95
|
+
require 'mtk/io/unimidi_input'
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'gamelan'
|
3
|
+
|
4
|
+
module MTK
|
5
|
+
module IO
|
6
|
+
|
7
|
+
# Provides a scheduler and common behavior for realtime MIDI output, using the gamelan gem for scheduling.
|
8
|
+
#
|
9
|
+
# @abstract Subclasses must provide {.devices}, {.devices_by_name}, {#note_on}, {#note_off}, {#control}, {#channel_pressure}, {#poly_pressure}, {#bend}, and {#program} to implement a MIDI output.
|
10
|
+
#
|
11
|
+
class MIDIOutput
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def inherited(subclass)
|
16
|
+
available_output_types << subclass
|
17
|
+
end
|
18
|
+
|
19
|
+
def available_output_types
|
20
|
+
@available_output_types ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def output_types_by_device
|
24
|
+
@output_types_by_device ||= (
|
25
|
+
available_output_types.each_with_object( Hash.new ) do |output_type,hash|
|
26
|
+
output_type.devices.each{|device| hash[device] = output_type }
|
27
|
+
end
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
# All available output devices.
|
32
|
+
def devices
|
33
|
+
@devices ||= available_output_types.map{|output_type| output_type.devices }.flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
# Maps output device names to the output device.
|
37
|
+
def devices_by_name
|
38
|
+
@devices_by_name ||= (
|
39
|
+
available_output_types.each_with_object( Hash.new ) do |output_type,hash|
|
40
|
+
hash.merge!( output_type.devices_by_name )
|
41
|
+
end
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_by_name(name)
|
46
|
+
if name.is_a? Regexp
|
47
|
+
matching_name = devices_by_name.keys.find{|device_name| device_name =~ name }
|
48
|
+
device = devices_by_name[matching_name]
|
49
|
+
else
|
50
|
+
device = devices_by_name[name.to_s]
|
51
|
+
end
|
52
|
+
open(device) if device
|
53
|
+
end
|
54
|
+
|
55
|
+
def open(device)
|
56
|
+
output_type = output_types_by_device[device]
|
57
|
+
output_type.new(device) if output_type
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def initialize(output_device, options={})
|
63
|
+
@device = output_device
|
64
|
+
@device.open
|
65
|
+
@options = options
|
66
|
+
end
|
67
|
+
private_class_method :new
|
68
|
+
|
69
|
+
|
70
|
+
########################
|
71
|
+
public
|
72
|
+
|
73
|
+
# The underlying output device implementation wrapped by this class.
|
74
|
+
# The device type depends on the platform.
|
75
|
+
attr_reader :device
|
76
|
+
|
77
|
+
def name
|
78
|
+
@device.name
|
79
|
+
end
|
80
|
+
|
81
|
+
def play(anything, options={})
|
82
|
+
timeline = case anything
|
83
|
+
when MTK::Events::Timeline then anything
|
84
|
+
when Hash then MTK::Events::Timeline.from_h anything
|
85
|
+
when Enumerable,MTK::Events::Event then MTK::Events::Timeline.from_h(0 => anything)
|
86
|
+
else raise "#{self.class}.play() doesn't understand #{anything} (#{anything.class})"
|
87
|
+
end
|
88
|
+
timeline = timeline.flatten
|
89
|
+
|
90
|
+
scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
|
91
|
+
trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
|
92
|
+
in_background = options.fetch :background, false # default: don't run in background Thread
|
93
|
+
bpm = options.fetch :bmp, 120 # default: 120 beats per minute
|
94
|
+
|
95
|
+
@scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate
|
96
|
+
|
97
|
+
timeline.each do |time,events|
|
98
|
+
events.each do |event|
|
99
|
+
next if event.rest?
|
100
|
+
|
101
|
+
channel = event.channel || 0
|
102
|
+
|
103
|
+
case event.type
|
104
|
+
when :note
|
105
|
+
pitch = event.midi_pitch
|
106
|
+
velocity = event.velocity
|
107
|
+
duration = event.duration.to_f
|
108
|
+
@scheduler.at(time) { note_on(pitch,velocity,channel) }
|
109
|
+
@scheduler.at(time + duration) { note_off(pitch,velocity,channel) }
|
110
|
+
|
111
|
+
when :control
|
112
|
+
@scheduler.at(time) { control(event.number, event.midi_value, channel) }
|
113
|
+
|
114
|
+
when :pressure
|
115
|
+
if event.number
|
116
|
+
@scheduler.at(time) { poly_pressure(event.number, event.midi_value, channel) }
|
117
|
+
else
|
118
|
+
@scheduler.at(time) { channel_pressure(event.midi_value, channel) }
|
119
|
+
end
|
120
|
+
|
121
|
+
when :bend
|
122
|
+
@scheduler.at(time) { bend(event.midi_value, channel) }
|
123
|
+
|
124
|
+
when :program
|
125
|
+
@scheduler.at(time) { program(event.number, channel) }
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end_time = timeline.times.last + trailing_buffer
|
131
|
+
@scheduler.at(end_time) { @scheduler.stop }
|
132
|
+
|
133
|
+
thread = @scheduler.run
|
134
|
+
thread.join if not in_background
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
########################
|
139
|
+
protected
|
140
|
+
|
141
|
+
# these all return stubbed data for testing purposes
|
142
|
+
|
143
|
+
# Send a note on event to the MIDI output
|
144
|
+
def note_on(midi_pitch, velocity, channel)
|
145
|
+
[:note_on, midi_pitch, velocity, channel] # stubbed data for testing purposes
|
146
|
+
end
|
147
|
+
|
148
|
+
# Send a note off event to the MIDI output
|
149
|
+
def note_off(midi_pitch, velocity, channel)
|
150
|
+
[:note_off, midi_pitch, velocity, channel]
|
151
|
+
end
|
152
|
+
|
153
|
+
# Send a control change event to the MIDI output
|
154
|
+
def control(number, midi_value, channel)
|
155
|
+
[:control, number, midi_value, channel]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Send a poly pressure event to the MIDI output.
|
159
|
+
def poly_pressure(midi_pitch, midi_value, channel)
|
160
|
+
[:poly_pressure, midi_pitch, midi_value, channel]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Send a channel pressure event to the MIDI output.
|
164
|
+
def channel_pressure(midi_value, channel)
|
165
|
+
[:channel_pressure, midi_value, channel]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Send a pitch bend event to the MIDI output.
|
169
|
+
def bend(midi_value, channel)
|
170
|
+
[:bend, midi_value, channel]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Send a program change event to the MIDI output.
|
174
|
+
def program(number, channel)
|
175
|
+
[:program, number, channel]
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
unless $__RUNNING_RSPEC_TESTS__ # I can't get this working on Travis-CI, problem installing native dependencies
|
185
|
+
if RbConfig::CONFIG['host_os'] =~ /darwin/
|
186
|
+
# We're running on OS X
|
187
|
+
require 'mtk/io/dls_synth_output'
|
188
|
+
end
|
189
|
+
|
190
|
+
if RUBY_PLATFORM == 'java'
|
191
|
+
require 'mtk/io/jsound_output'
|
192
|
+
else
|
193
|
+
require 'mtk/io/unimidi_output'
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module MTK
|
6
|
+
module IO
|
7
|
+
|
8
|
+
# Uses {Events::Timeline}s to generates music notation graphics with {http://lilypond.org/ Lilypond}.
|
9
|
+
# @note This class is optional and only available if you require 'mtk/io/lilypond'.
|
10
|
+
# @note To make notation graphics, {http://lilypond.org/download.html Lilypond} must be installed
|
11
|
+
# and you must follow the "Running on the command-line" instructions (found on the download page for
|
12
|
+
# your operating system). If the lilypond command is not on your PATH, set the environment variable LILYPOND_PATH
|
13
|
+
class Notation
|
14
|
+
|
15
|
+
LILYPOND_PATH = ENV['LILYPOND_PATH'] || 'lilypond'
|
16
|
+
|
17
|
+
VALID_FORMATS = %w( png pdf ps )
|
18
|
+
|
19
|
+
|
20
|
+
def initialize(file, options={})
|
21
|
+
@file = file
|
22
|
+
@options = options
|
23
|
+
|
24
|
+
@format = File.extname(file)[1..-1].downcase
|
25
|
+
raise ArgumentError.new("Invalid file format '#{@format}'") unless VALID_FORMATS.include? @format
|
26
|
+
|
27
|
+
@dpi = options[:dpi]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.open(file, options={})
|
31
|
+
new(file,options)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def write(timeline)
|
36
|
+
lilypond_syntax = syntax_for_timeline(timeline)
|
37
|
+
puts lilypond_syntax
|
38
|
+
puts "_____________________________"
|
39
|
+
Tempfile.open('mtk_lilypond') do |lilypond_file|
|
40
|
+
Dir.mktmpdir do |tmpdir|
|
41
|
+
# use the directory...
|
42
|
+
#open("#{dir}/foo", "w") { ... }
|
43
|
+
|
44
|
+
lilypond_file.write(lilypond_syntax)
|
45
|
+
lilypond_file.flush
|
46
|
+
|
47
|
+
cmd = ['lilypond', '-dbackend=eps', "-f#{@format}", "--output=\"#{tmpdir}\""]
|
48
|
+
cmd << "-dresolution=#{@dpi}" if @dpi
|
49
|
+
cmd << lilypond_file.path
|
50
|
+
cmd = cmd.join(' ')
|
51
|
+
|
52
|
+
puts cmd if $DEBUG
|
53
|
+
lilypond_command_output = `#{cmd}`
|
54
|
+
puts lilypond_command_output if $DEBUG
|
55
|
+
|
56
|
+
output_file = Dir["#{tmpdir}/*.#{@format}"].first
|
57
|
+
|
58
|
+
FileUtils.cp output_file, @file
|
59
|
+
|
60
|
+
puts "Wrote #{@file}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
########################
|
67
|
+
private
|
68
|
+
|
69
|
+
QUANTIZATION_INTERVAL = 0.0625 # 64th note granularity
|
70
|
+
|
71
|
+
SYNTAX_PREFIX = '
|
72
|
+
\language "english"
|
73
|
+
\paper{
|
74
|
+
oddFooterMarkup=##f
|
75
|
+
oddHeaderMarkup=##f
|
76
|
+
}
|
77
|
+
\new PianoStaff {
|
78
|
+
\autochange {
|
79
|
+
'
|
80
|
+
|
81
|
+
SYNTAX_SUFFIX = '
|
82
|
+
}
|
83
|
+
}
|
84
|
+
'
|
85
|
+
|
86
|
+
def syntax_for_timeline(timeline)
|
87
|
+
quantized_timeline = timeline.flatten.quantize(QUANTIZATION_INTERVAL)
|
88
|
+
last_time = 0
|
89
|
+
last_duration = 0
|
90
|
+
|
91
|
+
s = ''
|
92
|
+
s << SYNTAX_PREFIX
|
93
|
+
|
94
|
+
for time,events in quantized_timeline
|
95
|
+
|
96
|
+
# handle rests between notes
|
97
|
+
delta = time - last_time
|
98
|
+
if delta > last_duration
|
99
|
+
s << 'r'+syntax_for_duration(delta - last_duration)
|
100
|
+
s << ' '
|
101
|
+
end
|
102
|
+
|
103
|
+
notes = events.find_all{|event| event.type == :note }
|
104
|
+
|
105
|
+
if notes.length > 1
|
106
|
+
# a chord
|
107
|
+
s << '<'
|
108
|
+
total_duration = 0
|
109
|
+
for note in notes
|
110
|
+
total_duration += note.duration
|
111
|
+
s << syntax_for_pitch(note.pitch)
|
112
|
+
s << ' '
|
113
|
+
end
|
114
|
+
s << '>'
|
115
|
+
duration = total_duration.to_f/notes.length
|
116
|
+
s << syntax_for_duration(duration)
|
117
|
+
|
118
|
+
else # a single note
|
119
|
+
note = notes.first
|
120
|
+
s << syntax_for_pitch(note.pitch)
|
121
|
+
duration = note.duration
|
122
|
+
s << syntax_for_duration(duration)
|
123
|
+
end
|
124
|
+
|
125
|
+
last_time = time
|
126
|
+
last_duration = duration
|
127
|
+
s << ' '
|
128
|
+
end
|
129
|
+
|
130
|
+
s << SYNTAX_SUFFIX
|
131
|
+
s
|
132
|
+
end
|
133
|
+
|
134
|
+
def syntax_for_pitch(pitch)
|
135
|
+
syntax = pitch.pitch_class.name.downcase
|
136
|
+
if syntax.length > 0
|
137
|
+
syntax = syntax[0] + syntax[1..-1].gsub('b','f') # .gsub('#','s') pitch class names never have '#'
|
138
|
+
end
|
139
|
+
oct = pitch.octave
|
140
|
+
while oct > 3
|
141
|
+
syntax << "'"
|
142
|
+
oct -= 1
|
143
|
+
end
|
144
|
+
while oct < 3
|
145
|
+
syntax << ","
|
146
|
+
oct += 1
|
147
|
+
end
|
148
|
+
syntax
|
149
|
+
end
|
150
|
+
|
151
|
+
def syntax_for_duration(duration)
|
152
|
+
# TODO: handle dots, triplets, and ties of arbitrary durations
|
153
|
+
duration = MTK::Events::Timeline.quantize_time(duration.to_f.abs, QUANTIZATION_INTERVAL)
|
154
|
+
syntax = (4.0/duration).round
|
155
|
+
syntax = 1 if syntax < 1
|
156
|
+
syntax.to_s
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|