mtk 0.0.2 → 0.0.3
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 +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)
|