mtk 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -2
- data/DEVELOPMENT_NOTES.md +114 -0
- data/INTRO.md +64 -8
- data/LICENSE.txt +1 -1
- data/README.md +31 -102
- data/Rakefile +56 -18
- data/bin/mtk +215 -0
- data/examples/crescendo.rb +5 -5
- data/examples/drum_pattern1.rb +23 -0
- data/examples/dynamic_pattern.rb +8 -11
- data/examples/gets_and_play.rb +26 -0
- data/examples/notation.rb +22 -0
- data/examples/play_midi.rb +8 -10
- data/examples/random_tone_row.rb +2 -2
- data/examples/syntax_to_midi.rb +28 -0
- data/examples/test_output.rb +8 -0
- data/examples/tone_row_melody.rb +6 -6
- data/lib/mtk.rb +52 -40
- data/lib/mtk/chord.rb +55 -0
- data/lib/mtk/constants/durations.rb +57 -0
- data/lib/mtk/constants/intensities.rb +61 -0
- data/lib/mtk/constants/intervals.rb +73 -0
- data/lib/mtk/constants/pitch_classes.rb +29 -0
- data/lib/mtk/constants/pitches.rb +52 -0
- data/lib/mtk/duration.rb +211 -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/helpers/collection.rb +164 -0
- data/lib/mtk/helpers/convert.rb +36 -0
- data/lib/mtk/helpers/lilypond.rb +162 -0
- data/lib/mtk/helpers/output_selector.rb +67 -0
- data/lib/mtk/helpers/pitch_collection.rb +23 -0
- data/lib/mtk/helpers/pseudo_constants.rb +26 -0
- data/lib/mtk/intensity.rb +156 -0
- data/lib/mtk/interval.rb +155 -0
- data/lib/mtk/lang/mtk_grammar.citrus +190 -13
- data/lib/mtk/lang/parser.rb +29 -0
- data/lib/mtk/melody.rb +94 -0
- data/lib/mtk/midi/dls_synth_device.rb +144 -0
- data/lib/mtk/midi/dls_synth_output.rb +62 -0
- data/lib/mtk/midi/file.rb +67 -32
- data/lib/mtk/midi/input.rb +97 -0
- data/lib/mtk/midi/jsound_input.rb +36 -17
- data/lib/mtk/midi/jsound_output.rb +48 -46
- data/lib/mtk/midi/output.rb +195 -0
- data/lib/mtk/midi/unimidi_input.rb +117 -0
- data/lib/mtk/midi/unimidi_output.rb +121 -0
- data/lib/mtk/{_numeric_extensions.rb → numeric_extensions.rb} +12 -0
- data/lib/mtk/patterns/chain.rb +49 -0
- data/lib/mtk/{pattern → patterns}/choice.rb +14 -8
- 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/{pattern → patterns}/lines.rb +11 -17
- data/lib/mtk/{pattern → patterns}/palindrome.rb +11 -8
- data/lib/mtk/patterns/pattern.rb +171 -0
- data/lib/mtk/patterns/sequence.rb +20 -0
- data/lib/mtk/pitch.rb +7 -6
- data/lib/mtk/pitch_class.rb +124 -46
- data/lib/mtk/pitch_class_set.rb +58 -35
- data/lib/mtk/sequencers/event_builder.rb +131 -0
- data/lib/mtk/sequencers/legato_sequencer.rb +24 -0
- data/lib/mtk/sequencers/rhythmic_sequencer.rb +28 -0
- data/lib/mtk/{sequencer/abstract_sequencer.rb → sequencers/sequencer.rb} +37 -11
- data/lib/mtk/{sequencer → sequencers}/step_sequencer.rb +4 -4
- data/lib/mtk/timeline.rb +39 -22
- data/lib/mtk/variable.rb +32 -0
- data/spec/mtk/chord_spec.rb +83 -0
- data/spec/mtk/{_constants → constants}/durations_spec.rb +12 -41
- data/spec/mtk/{_constants → constants}/intensities_spec.rb +13 -37
- data/spec/mtk/{_constants → constants}/intervals_spec.rb +14 -32
- data/spec/mtk/{_constants → constants}/pitch_classes_spec.rb +8 -4
- data/spec/mtk/{_constants → constants}/pitches_spec.rb +5 -1
- data/spec/mtk/duration_spec.rb +372 -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/{helper → helpers}/collection_spec.rb +86 -3
- data/spec/mtk/{helper → helpers}/pseudo_constants_spec.rb +2 -2
- data/spec/mtk/intensity_spec.rb +289 -0
- data/spec/mtk/interval_spec.rb +265 -0
- data/spec/mtk/lang/parser_spec.rb +597 -0
- data/spec/mtk/melody_spec.rb +223 -0
- data/spec/mtk/midi/file_spec.rb +16 -16
- data/spec/mtk/midi/jsound_input_spec.rb +11 -0
- data/spec/mtk/midi/jsound_output_spec.rb +11 -0
- data/spec/mtk/midi/output_spec.rb +102 -0
- data/spec/mtk/midi/unimidi_input_spec.rb +11 -0
- data/spec/mtk/midi/unimidi_output_spec.rb +11 -0
- data/spec/mtk/{_numeric_extensions_spec.rb → numeric_extensions_spec.rb} +1 -0
- data/spec/mtk/patterns/chain_spec.rb +110 -0
- data/spec/mtk/{pattern → patterns}/choice_spec.rb +20 -30
- data/spec/mtk/{pattern → patterns}/cycle_spec.rb +25 -35
- data/spec/mtk/patterns/for_each_spec.rb +136 -0
- data/spec/mtk/{pattern → patterns}/function_spec.rb +17 -30
- data/spec/mtk/{pattern → patterns}/lines_spec.rb +11 -27
- data/spec/mtk/{pattern → patterns}/palindrome_spec.rb +13 -29
- data/spec/mtk/patterns/pattern_spec.rb +132 -0
- data/spec/mtk/patterns/sequence_spec.rb +203 -0
- data/spec/mtk/pitch_class_set_spec.rb +23 -21
- data/spec/mtk/pitch_class_spec.rb +151 -39
- data/spec/mtk/pitch_spec.rb +22 -1
- 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/{sequencer → sequencers}/step_sequencer_spec.rb +35 -13
- data/spec/mtk/timeline_spec.rb +109 -16
- data/spec/mtk/variable_spec.rb +52 -0
- data/spec/spec_coverage.rb +2 -0
- data/spec/spec_helper.rb +3 -0
- metadata +188 -91
- data/lib/mtk/_constants/durations.rb +0 -80
- data/lib/mtk/_constants/intensities.rb +0 -81
- data/lib/mtk/_constants/intervals.rb +0 -85
- data/lib/mtk/_constants/pitch_classes.rb +0 -35
- data/lib/mtk/_constants/pitches.rb +0 -49
- data/lib/mtk/event.rb +0 -70
- data/lib/mtk/helper/collection.rb +0 -114
- data/lib/mtk/helper/event_builder.rb +0 -85
- data/lib/mtk/helper/pseudo_constants.rb +0 -26
- data/lib/mtk/lang/grammar.rb +0 -17
- data/lib/mtk/note.rb +0 -63
- data/lib/mtk/pattern/abstract_pattern.rb +0 -132
- data/lib/mtk/pattern/cycle.rb +0 -51
- data/lib/mtk/pattern/enumerator.rb +0 -26
- data/lib/mtk/pattern/function.rb +0 -46
- data/lib/mtk/pattern/sequence.rb +0 -30
- data/lib/mtk/pitch_set.rb +0 -84
- data/lib/mtk/sequencer/rhythmic_sequencer.rb +0 -29
- data/lib/mtk/transform/invertible.rb +0 -15
- data/lib/mtk/transform/mappable.rb +0 -18
- data/lib/mtk/transform/set_theory_operations.rb +0 -34
- data/lib/mtk/transform/transposable.rb +0 -14
- data/spec/mtk/event_spec.rb +0 -139
- data/spec/mtk/helper/event_builder_spec.rb +0 -92
- data/spec/mtk/lang/grammar_spec.rb +0 -100
- data/spec/mtk/note_spec.rb +0 -115
- data/spec/mtk/pattern/abstract_pattern_spec.rb +0 -45
- data/spec/mtk/pattern/note_cycle_spec.rb.bak +0 -116
- data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +0 -47
- data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +0 -37
- data/spec/mtk/pattern/sequence_spec.rb +0 -151
- data/spec/mtk/pitch_set_spec.rb +0 -198
- data/spec/mtk/sequencer/abstract_sequencer_spec.rb +0 -159
- data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +0 -49
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'citrus'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
class Citrus::Match
|
5
|
+
def values_of(token_name)
|
6
|
+
captures[token_name].map{|token| token.value }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
Citrus.load File.join(File.dirname(__FILE__),'mtk_grammar')
|
11
|
+
|
12
|
+
module MTK
|
13
|
+
module Lang
|
14
|
+
|
15
|
+
# Parser for the {file:lib/mtk/lang/mtk_grammar.citrus MTK grammar}
|
16
|
+
class Parser
|
17
|
+
|
18
|
+
def self.parse(syntax, root=:root, dump=false)
|
19
|
+
syntax = syntax.to_s.strip
|
20
|
+
return nil if syntax.empty?
|
21
|
+
matcher = ::MTK_Grammar.parse(syntax, :root => root)
|
22
|
+
puts matcher.dump if dump
|
23
|
+
matcher.value
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/mtk/melody.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module MTK
|
2
|
+
|
3
|
+
# An ordered collection of {Pitch}es.
|
4
|
+
#
|
5
|
+
# The "horizontal" (sequential) pitch collection.
|
6
|
+
#
|
7
|
+
# Unlike the strict definition of melody, this class is fairly abstract and only models a succession of pitches.
|
8
|
+
# To create a true, playable melody one must combine an MTK::Melody and rhythms into a {Timeline}.
|
9
|
+
#
|
10
|
+
# @see Chord
|
11
|
+
#
|
12
|
+
class Melody
|
13
|
+
include Helpers::PitchCollection
|
14
|
+
|
15
|
+
attr_reader :pitches
|
16
|
+
|
17
|
+
# @param pitches [#to_a] the collection of pitches
|
18
|
+
# @see MTK#Melody
|
19
|
+
#
|
20
|
+
def initialize(pitches)
|
21
|
+
@pitches = pitches.to_a.clone.freeze
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_pitch_classes(pitch_classes, start=Constants::Pitches::C4, max_distance=12)
|
25
|
+
pitch = start
|
26
|
+
pitches = []
|
27
|
+
pitch_classes.each do |pitch_class|
|
28
|
+
pitch = pitch.nearest(pitch_class)
|
29
|
+
pitch -= 12 if pitch > start+max_distance # keep within max_distance of start (default is one octave)
|
30
|
+
pitch += 12 if pitch < start-max_distance
|
31
|
+
pitches << pitch
|
32
|
+
end
|
33
|
+
new pitches
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see Helper::Collection
|
37
|
+
def elements
|
38
|
+
@pitches
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convert to an Array of pitches.
|
42
|
+
# @note this returns a mutable copy the underlying @pitches attribute, which is otherwise unmutable
|
43
|
+
alias :to_pitches :to_a
|
44
|
+
|
45
|
+
def self.from_a enumerable
|
46
|
+
new enumerable
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_pitch_class_set(remove_duplicates=true)
|
50
|
+
PitchClassSet.new(remove_duplicates ? pitch_classes.uniq : pitch_classes)
|
51
|
+
end
|
52
|
+
|
53
|
+
def pitch_classes
|
54
|
+
@pitch_classes ||= @pitches.map{|p| p.pitch_class }
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param other [#pitches, Enumerable]
|
58
|
+
def == other
|
59
|
+
if other.respond_to? :pitches
|
60
|
+
@pitches == other.pitches
|
61
|
+
elsif other.is_a? Enumerable
|
62
|
+
@pitches == other.to_a
|
63
|
+
else
|
64
|
+
@pitches == other
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compare for equality, ignoring order and duplicates
|
69
|
+
# @param other [#pitches, Array, #to_a]
|
70
|
+
def =~ other
|
71
|
+
@normalized_pitches ||= @pitches.uniq.sort
|
72
|
+
@normalized_pitches == case
|
73
|
+
when other.respond_to?(:pitches) then other.pitches.uniq.sort
|
74
|
+
when (other.is_a? Array and other.frozen?) then other
|
75
|
+
when other.respond_to?(:to_a) then other.to_a.uniq.sort
|
76
|
+
else other
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
'[' + @pitches.map{|pitch| pitch.to_s}.join(', ') + ']'
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
# Construct an ordered {Melody} that allows duplicates
|
87
|
+
# @see #Melody
|
88
|
+
# @see #Chord
|
89
|
+
def Melody(*anything)
|
90
|
+
Melody.new Helpers::Convert.to_pitches(*anything)
|
91
|
+
end
|
92
|
+
module_function :Melody
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module MTK
|
4
|
+
module MIDI
|
5
|
+
|
6
|
+
# An output device for Apple's built-in "DLS" synthesizer on OS X
|
7
|
+
class DLSSynthDevice
|
8
|
+
|
9
|
+
module AudioToolbox
|
10
|
+
extend FFI::Library
|
11
|
+
ffi_lib '/System/Library/Frameworks/AudioToolbox.framework/Versions/Current/AudioToolbox'
|
12
|
+
ffi_lib '/System/Library/Frameworks/AudioUnit.framework/Versions/Current/AudioUnit'
|
13
|
+
|
14
|
+
class ComponentDescription < FFI::Struct
|
15
|
+
layout :componentType, :int,
|
16
|
+
:componentSubType, :int,
|
17
|
+
:componentManufacturer, :int,
|
18
|
+
:componentFlags, :int,
|
19
|
+
:componentFlagsMask, :int
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.to_bytes(s)
|
23
|
+
bytes = 0
|
24
|
+
s.each_byte do |byte|
|
25
|
+
bytes <<= 8
|
26
|
+
bytes += byte
|
27
|
+
end
|
28
|
+
return bytes
|
29
|
+
end
|
30
|
+
|
31
|
+
AUDIO_UNIT_MANUFACTURER_APPLE = to_bytes('appl')
|
32
|
+
AUDIO_UNIT_TYPE_MUSIC_DEVICE = to_bytes('aumu')
|
33
|
+
AUDIO_UNIT_SUBTYPE_DLS_SYNTH = to_bytes('dls ')
|
34
|
+
AUDIO_UNIT_TYPE_OUTPUT = to_bytes('auou')
|
35
|
+
AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT = to_bytes('def ')
|
36
|
+
|
37
|
+
# int NewAUGraph(void *)
|
38
|
+
attach_function :NewAUGraph, [:pointer], :int
|
39
|
+
|
40
|
+
# int AUGraphAddNode(void *, ComponentDescription *, void *)
|
41
|
+
attach_function :AUGraphAddNode, [:pointer, :pointer, :pointer], :int
|
42
|
+
|
43
|
+
# int AUGraphOpen(void *)
|
44
|
+
attach_function :AUGraphOpen, [:pointer], :int
|
45
|
+
|
46
|
+
# int AUGraphConnectNodeInput(void *, void *, int, void *, int)
|
47
|
+
attach_function :AUGraphConnectNodeInput, [:pointer, :pointer, :int, :pointer, :int], :int
|
48
|
+
|
49
|
+
# int AUGraphNodeInfo(void *, void *, ComponentDescription *, void *)
|
50
|
+
attach_function :AUGraphNodeInfo, [:pointer, :pointer, :pointer, :pointer], :int
|
51
|
+
|
52
|
+
# int AUGraphInitialize(void *)
|
53
|
+
attach_function :AUGraphInitialize, [:pointer], :int
|
54
|
+
|
55
|
+
# int AUGraphStart(void *)
|
56
|
+
attach_function :AUGraphStart, [:pointer], :int
|
57
|
+
|
58
|
+
# int AUGraphStop(void *)
|
59
|
+
attach_function :AUGraphStop, [:pointer], :int
|
60
|
+
|
61
|
+
# int DisposeAUGraph(void *)
|
62
|
+
attach_function :DisposeAUGraph, [:pointer], :int
|
63
|
+
|
64
|
+
# void * CAShow(void *)
|
65
|
+
attach_function :CAShow, [:pointer], :void
|
66
|
+
|
67
|
+
# void * MusicDeviceMIDIEvent(void *, int, int, int, int)
|
68
|
+
attach_function :MusicDeviceMIDIEvent, [:pointer, :int, :int, :int, :int], :void
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
##################################
|
74
|
+
|
75
|
+
def name
|
76
|
+
'Apple DLS Synthesizer'
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def require_noerr(action_description, &block)
|
81
|
+
if block.call != 0
|
82
|
+
fail "Failed to #{action_description}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def open
|
88
|
+
synth_pointer = FFI::MemoryPointer.new(:pointer)
|
89
|
+
graph_pointer = FFI::MemoryPointer.new(:pointer)
|
90
|
+
synth_node_pointer = FFI::MemoryPointer.new(:pointer)
|
91
|
+
out_node_pointer = FFI::MemoryPointer.new(:pointer)
|
92
|
+
|
93
|
+
cd = AudioToolbox::ComponentDescription.new
|
94
|
+
cd[:componentManufacturer] = AudioToolbox::AUDIO_UNIT_MANUFACTURER_APPLE
|
95
|
+
cd[:componentFlags] = 0
|
96
|
+
cd[:componentFlagsMask] = 0
|
97
|
+
|
98
|
+
require_noerr('create AUGraph') { AudioToolbox.NewAUGraph(graph_pointer) }
|
99
|
+
@graph = graph_pointer.get_pointer(0)
|
100
|
+
|
101
|
+
cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_MUSIC_DEVICE
|
102
|
+
cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DLS_SYNTH
|
103
|
+
require_noerr('add synthNode') { AudioToolbox.AUGraphAddNode(@graph, cd, synth_node_pointer) }
|
104
|
+
synth_node = synth_node_pointer.get_pointer(0)
|
105
|
+
|
106
|
+
cd[:componentType] = AudioToolbox::AUDIO_UNIT_TYPE_OUTPUT
|
107
|
+
cd[:componentSubType] = AudioToolbox::AUDIO_UNIT_SUBTYPE_DEFAULT_OUTPUT
|
108
|
+
require_noerr('add outNode') { AudioToolbox.AUGraphAddNode(@graph, cd, out_node_pointer) }
|
109
|
+
out_node = out_node_pointer.get_pointer(0)
|
110
|
+
|
111
|
+
require_noerr('open graph') { AudioToolbox.AUGraphOpen(@graph) }
|
112
|
+
|
113
|
+
require_noerr('connect synth to out') { AudioToolbox.AUGraphConnectNodeInput(@graph, synth_node, 0, out_node, 0) }
|
114
|
+
|
115
|
+
require_noerr('graph info') { AudioToolbox.AUGraphNodeInfo(@graph, synth_node, nil, synth_pointer) }
|
116
|
+
@synth = synth_pointer.get_pointer(0)
|
117
|
+
|
118
|
+
require_noerr('init graph') { AudioToolbox.AUGraphInitialize(@graph) }
|
119
|
+
require_noerr('start graph') { AudioToolbox.AUGraphStart(@graph) }
|
120
|
+
|
121
|
+
# AudioToolbox.CAShow(@graph) # for debugging
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def message(*args)
|
126
|
+
arg0 = args[0] || 0
|
127
|
+
arg1 = args[1] || 0
|
128
|
+
arg2 = args[2] || 0
|
129
|
+
AudioToolbox.MusicDeviceMIDIEvent(@synth, arg0, arg1, arg2, 0)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def close
|
134
|
+
if @graph
|
135
|
+
AudioToolbox.AUGraphStop(@graph)
|
136
|
+
AudioToolbox.DisposeAUGraph(@graph)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mtk/midi/dls_synth_device'
|
2
|
+
|
3
|
+
module MTK
|
4
|
+
module MIDI
|
5
|
+
|
6
|
+
# Provides realtime MIDI output on OS X to the built-in "DLS" Synthesizer
|
7
|
+
# @note This class is optional and only available if you require 'mtk/midi/dls_synth_output'.
|
8
|
+
# It depends on the 'gamelan' gem.
|
9
|
+
class DLSSynthOutput < Output
|
10
|
+
|
11
|
+
public_class_method :new
|
12
|
+
|
13
|
+
def self.devices
|
14
|
+
@devices ||= [DLSSynthDevice.new]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.devices_by_name
|
18
|
+
@devices_by_name ||= {devices.first.name => devices.first}
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
######################
|
23
|
+
protected
|
24
|
+
|
25
|
+
# (see Output#note_on)
|
26
|
+
def note_on(pitch, velocity, channel)
|
27
|
+
@device.message(0x90|channel, pitch, velocity)
|
28
|
+
end
|
29
|
+
|
30
|
+
# (see Output#note_off)
|
31
|
+
def note_off(pitch, velocity, channel)
|
32
|
+
@device.message(0x80|channel, pitch, velocity)
|
33
|
+
end
|
34
|
+
|
35
|
+
# (see Output#control)
|
36
|
+
def control(number, midi_value, channel)
|
37
|
+
@device.message(0xB0|channel, number, midi_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# (see Output#channel_pressure)
|
41
|
+
def channel_pressure(midi_value, channel)
|
42
|
+
@device.message(0xD0|channel, midi_value, 0)
|
43
|
+
end
|
44
|
+
|
45
|
+
# (see Output#poly_pressure)
|
46
|
+
def poly_pressure(pitch, midi_value, channel)
|
47
|
+
@device.message(0xA0|channel, pitch, midi_value)
|
48
|
+
end
|
49
|
+
|
50
|
+
# (see Output#bend)
|
51
|
+
def bend(midi_value, channel)
|
52
|
+
@device.message(0xE0|channel, midi_value & 127, (midi_value >> 7) & 127)
|
53
|
+
end
|
54
|
+
|
55
|
+
# (see Output#program)
|
56
|
+
def program(number, channel)
|
57
|
+
@device.message(0xC0|channel, number, 0)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
data/lib/mtk/midi/file.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'midilib'
|
3
2
|
|
4
3
|
module MTK
|
5
4
|
module MIDI
|
6
5
|
|
6
|
+
# MIDI file I/O: reads MIDI files into {Timeline}s and writes {Timeline}s to MIDI files.
|
7
|
+
# @note This class is optional and only available if you require 'mtk/midi/file'.
|
8
|
+
# It depends on the 'midilib' gem.
|
7
9
|
class File
|
8
10
|
def initialize file
|
9
11
|
if file.respond_to? :path
|
@@ -15,7 +17,6 @@ module MTK
|
|
15
17
|
|
16
18
|
# Read a MIDI file into an Array of {Timeline}s
|
17
19
|
#
|
18
|
-
# @param filepath [String, #path] path of the file to be written
|
19
20
|
# @return [Timeline]
|
20
21
|
#
|
21
22
|
def to_timelines
|
@@ -27,25 +28,34 @@ module MTK
|
|
27
28
|
pulses_per_beat = sequence.ppqn.to_f
|
28
29
|
track_idx = -1
|
29
30
|
|
30
|
-
|
31
|
+
sequence.each do |track|
|
31
32
|
track_idx += 1
|
32
33
|
timeline = Timeline.new
|
33
34
|
note_ons = {}
|
34
35
|
#puts "TRACK #{track_idx}"
|
35
36
|
|
36
|
-
|
37
|
+
track.each do |event|
|
37
38
|
#puts "#{event.class}: #{event} @#{event.time_from_start}"
|
39
|
+
time = (event.time_from_start)/pulses_per_beat
|
40
|
+
|
38
41
|
case event
|
39
42
|
when ::MIDI::NoteOn
|
40
|
-
note_ons[event.note] = event
|
43
|
+
note_ons[event.note] = [time,event]
|
41
44
|
|
42
45
|
when ::MIDI::NoteOff
|
43
|
-
|
44
|
-
|
45
|
-
duration =
|
46
|
-
note = Note.from_midi
|
47
|
-
timeline.add
|
46
|
+
on_time,on_event = note_ons.delete(event.note)
|
47
|
+
if on_event
|
48
|
+
duration = time - on_time
|
49
|
+
note = MTK::Events::Note.from_midi(event.note, on_event.velocity, duration)
|
50
|
+
timeline.add on_time, note
|
48
51
|
end
|
52
|
+
|
53
|
+
when ::MIDI::Controller, ::MIDI::PolyPressure, ::MIDI::ChannelPressure, ::MIDI::PitchBend, ::MIDI::ProgramChange
|
54
|
+
timeline.add time, MTK::Events::Parameter.from_midi(*event.data_as_bytes)
|
55
|
+
|
56
|
+
when ::MIDI::Tempo
|
57
|
+
# Not sure if event.tempo needs to be converted? TODO: test!
|
58
|
+
timeline.add time, MTK::Events::Parameter.new(:tempo, :value => event.tempo)
|
49
59
|
end
|
50
60
|
end
|
51
61
|
timelines << timeline
|
@@ -56,43 +66,59 @@ module MTK
|
|
56
66
|
|
57
67
|
def write(anything)
|
58
68
|
case anything
|
59
|
-
when Timeline then
|
60
|
-
|
61
|
-
|
62
|
-
write_timelines(anything)
|
63
|
-
else
|
64
|
-
raise "#{self.class}#write doesn't understand #{anything.class}"
|
69
|
+
when Timeline then write_timeline(anything)
|
70
|
+
when Enumerable then write_timelines(anything)
|
71
|
+
else raise "#{self.class}#write doesn't understand #{anything.class}"
|
65
72
|
end
|
66
73
|
end
|
67
74
|
|
68
75
|
def write_timelines(timelines, parent_sequence=nil)
|
69
76
|
sequence = parent_sequence || ::MIDI::Sequence.new
|
70
|
-
|
71
|
-
write_timeline(timeline, sequence)
|
72
|
-
end
|
77
|
+
timelines.each{|timeline| write_timeline(timeline, sequence) }
|
73
78
|
write_to_disk sequence unless parent_sequence
|
74
79
|
end
|
75
80
|
|
76
81
|
# Write the Timeline as a MIDI file
|
77
82
|
#
|
78
|
-
# @param [Timeline]
|
83
|
+
# @param timeline [Timeline]
|
79
84
|
def write_timeline(timeline, parent_sequence=nil)
|
80
85
|
sequence = parent_sequence || ::MIDI::Sequence.new
|
81
86
|
clock_rate = sequence.ppqn
|
82
87
|
track = add_track sequence
|
83
|
-
channel = 1
|
84
88
|
|
85
|
-
|
89
|
+
timeline.each do |time,events|
|
86
90
|
time *= clock_rate
|
87
91
|
|
88
|
-
|
92
|
+
events.each do |event|
|
89
93
|
next if event.rest?
|
90
94
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
95
|
+
channel = event.channel || 0
|
96
|
+
|
97
|
+
case event.type
|
98
|
+
when :note
|
99
|
+
pitch, velocity = event.midi_pitch, event.velocity
|
100
|
+
add_event track, time => note_on(channel, pitch, velocity)
|
101
|
+
duration = event.duration_in_pulses(clock_rate)
|
102
|
+
add_event track, time+duration => note_off(channel, pitch, velocity)
|
103
|
+
|
104
|
+
when :control
|
105
|
+
add_event track, time => cc(channel, event.number, event.midi_value)
|
106
|
+
|
107
|
+
when :pressure
|
108
|
+
if event.number
|
109
|
+
add_event track, time => poly_pressure(channel, event.number, event.midi_value)
|
110
|
+
else
|
111
|
+
add_event track, time => channel_pressure(channel, event.midi_value)
|
112
|
+
end
|
113
|
+
|
114
|
+
when :bend
|
115
|
+
add_event track, time => pitch_bend(channel, event.midi_value)
|
116
|
+
|
117
|
+
when :program
|
118
|
+
add_event track, time => program(channel, event.midi_value)
|
119
|
+
|
120
|
+
when :tempo
|
121
|
+
add_event track, time => tempo(event.value)
|
96
122
|
end
|
97
123
|
end
|
98
124
|
end
|
@@ -106,14 +132,15 @@ module MTK
|
|
106
132
|
private
|
107
133
|
|
108
134
|
def write_to_disk(sequence)
|
135
|
+
puts "Writing file #{@file}"
|
109
136
|
::File.open(@file, 'wb') { |f| sequence.write f }
|
110
137
|
end
|
111
138
|
|
112
139
|
def print_midi sequence
|
113
|
-
|
140
|
+
sequence.each do |track|
|
114
141
|
puts "\n*** track \"#{track.name}\""
|
115
142
|
puts "#{track.events.length} events"
|
116
|
-
|
143
|
+
track.each do |event|
|
117
144
|
puts "#{event.to_s} (#{event.time_from_start})"
|
118
145
|
end
|
119
146
|
end
|
@@ -125,7 +152,7 @@ module MTK
|
|
125
152
|
::MIDI::Tempo.new(ms_per_quarter_note)
|
126
153
|
end
|
127
154
|
|
128
|
-
def program(program_number)
|
155
|
+
def program(channel, program_number)
|
129
156
|
::MIDI::ProgramChange.new(channel, program_number)
|
130
157
|
end
|
131
158
|
|
@@ -138,7 +165,15 @@ module MTK
|
|
138
165
|
end
|
139
166
|
|
140
167
|
def cc(channel, controller, value)
|
141
|
-
::MIDI::Controller.new(channel, controller
|
168
|
+
::MIDI::Controller.new(channel, controller, value)
|
169
|
+
end
|
170
|
+
|
171
|
+
def poly_pressure(channel, pitch, value)
|
172
|
+
::MIDI::PolyPressure(channel, pitch.to_i, value)
|
173
|
+
end
|
174
|
+
|
175
|
+
def channel_pressure(channel, value)
|
176
|
+
::MIDI::ChannelPressure(channel, value)
|
142
177
|
end
|
143
178
|
|
144
179
|
def pitch_bend(channel, value)
|