mtk 0.0.1 → 0.0.2

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.
Files changed (93) hide show
  1. data/.yardopts +9 -0
  2. data/INTRO.md +73 -0
  3. data/LICENSE.txt +27 -0
  4. data/README.md +93 -18
  5. data/Rakefile +13 -1
  6. data/examples/crescendo.rb +20 -0
  7. data/examples/dynamic_pattern.rb +39 -0
  8. data/examples/play_midi.rb +19 -0
  9. data/examples/print_midi.rb +13 -0
  10. data/examples/random_tone_row.rb +18 -0
  11. data/examples/tone_row_melody.rb +21 -0
  12. data/lib/mtk/_constants/durations.rb +80 -0
  13. data/lib/mtk/_constants/intensities.rb +81 -0
  14. data/lib/mtk/{constants → _constants}/intervals.rb +10 -1
  15. data/lib/mtk/_constants/pitch_classes.rb +35 -0
  16. data/lib/mtk/_constants/pitches.rb +49 -0
  17. data/lib/mtk/{numeric_extensions.rb → _numeric_extensions.rb} +0 -0
  18. data/lib/mtk/event.rb +14 -5
  19. data/lib/mtk/helper/collection.rb +114 -0
  20. data/lib/mtk/helper/event_builder.rb +85 -0
  21. data/lib/mtk/{constants → helper}/pseudo_constants.rb +7 -6
  22. data/lib/mtk/lang/grammar.rb +17 -0
  23. data/lib/mtk/lang/mtk_grammar.citrus +60 -0
  24. data/lib/mtk/midi/file.rb +10 -15
  25. data/lib/mtk/midi/jsound_input.rb +68 -0
  26. data/lib/mtk/midi/jsound_output.rb +80 -0
  27. data/lib/mtk/note.rb +22 -3
  28. data/lib/mtk/pattern/abstract_pattern.rb +132 -0
  29. data/lib/mtk/pattern/choice.rb +25 -9
  30. data/lib/mtk/pattern/cycle.rb +51 -0
  31. data/lib/mtk/pattern/enumerator.rb +26 -0
  32. data/lib/mtk/pattern/function.rb +46 -0
  33. data/lib/mtk/pattern/lines.rb +60 -0
  34. data/lib/mtk/pattern/palindrome.rb +42 -0
  35. data/lib/mtk/pattern/sequence.rb +15 -50
  36. data/lib/mtk/pitch.rb +45 -6
  37. data/lib/mtk/pitch_class.rb +36 -35
  38. data/lib/mtk/pitch_class_set.rb +46 -14
  39. data/lib/mtk/pitch_set.rb +20 -31
  40. data/lib/mtk/sequencer/abstract_sequencer.rb +85 -0
  41. data/lib/mtk/sequencer/rhythmic_sequencer.rb +29 -0
  42. data/lib/mtk/sequencer/step_sequencer.rb +26 -0
  43. data/lib/mtk/timeline.rb +75 -22
  44. data/lib/mtk/transform/invertible.rb +15 -0
  45. data/lib/mtk/{util → transform}/mappable.rb +6 -2
  46. data/lib/mtk/transform/set_theory_operations.rb +34 -0
  47. data/lib/mtk/transform/transposable.rb +14 -0
  48. data/lib/mtk.rb +56 -22
  49. data/spec/mtk/_constants/durations_spec.rb +118 -0
  50. data/spec/mtk/{constants/dynamics_spec.rb → _constants/intensities_spec.rb} +48 -17
  51. data/spec/mtk/{constants → _constants}/intervals_spec.rb +21 -0
  52. data/spec/mtk/_constants/pitch_classes_spec.rb +58 -0
  53. data/spec/mtk/_constants/pitches_spec.rb +52 -0
  54. data/spec/mtk/{numeric_extensions_spec.rb → _numeric_extensions_spec.rb} +0 -0
  55. data/spec/mtk/event_spec.rb +19 -0
  56. data/spec/mtk/helper/collection_spec.rb +291 -0
  57. data/spec/mtk/helper/event_builder_spec.rb +92 -0
  58. data/spec/mtk/helper/pseudo_constants_spec.rb +20 -0
  59. data/spec/mtk/lang/grammar_spec.rb +100 -0
  60. data/spec/mtk/midi/file_spec.rb +41 -6
  61. data/spec/mtk/note_spec.rb +53 -3
  62. data/spec/mtk/pattern/abstract_pattern_spec.rb +45 -0
  63. data/spec/mtk/pattern/choice_spec.rb +89 -3
  64. data/spec/mtk/pattern/cycle_spec.rb +133 -0
  65. data/spec/mtk/pattern/function_spec.rb +133 -0
  66. data/spec/mtk/pattern/lines_spec.rb +93 -0
  67. data/spec/mtk/pattern/note_cycle_spec.rb.bak +116 -0
  68. data/spec/mtk/pattern/palindrome_spec.rb +124 -0
  69. data/spec/mtk/pattern/pitch_cycle_spec.rb.bak +47 -0
  70. data/spec/mtk/pattern/pitch_sequence_spec.rb.bak +37 -0
  71. data/spec/mtk/pattern/sequence_spec.rb +128 -31
  72. data/spec/mtk/pitch_class_set_spec.rb +240 -7
  73. data/spec/mtk/pitch_class_spec.rb +84 -18
  74. data/spec/mtk/pitch_set_spec.rb +45 -10
  75. data/spec/mtk/pitch_spec.rb +59 -0
  76. data/spec/mtk/sequencer/abstract_sequencer_spec.rb +159 -0
  77. data/spec/mtk/sequencer/rhythmic_sequencer_spec.rb +49 -0
  78. data/spec/mtk/sequencer/step_sequencer_spec.rb +71 -0
  79. data/spec/mtk/timeline_spec.rb +118 -15
  80. data/spec/spec_helper.rb +4 -3
  81. metadata +59 -22
  82. data/lib/mtk/chord.rb +0 -47
  83. data/lib/mtk/constants/dynamics.rb +0 -56
  84. data/lib/mtk/constants/pitch_classes.rb +0 -18
  85. data/lib/mtk/constants/pitches.rb +0 -24
  86. data/lib/mtk/pattern/note_sequence.rb +0 -60
  87. data/lib/mtk/pattern/pitch_sequence.rb +0 -22
  88. data/lib/mtk/patterns.rb +0 -4
  89. data/spec/mtk/chord_spec.rb +0 -74
  90. data/spec/mtk/constants/pitch_classes_spec.rb +0 -35
  91. data/spec/mtk/constants/pitches_spec.rb +0 -23
  92. data/spec/mtk/pattern/note_sequence_spec.rb +0 -121
  93. data/spec/mtk/pattern/pitch_sequence_spec.rb +0 -47
@@ -0,0 +1,49 @@
1
+ module MTK
2
+
3
+ # Defines a constants for each {Pitch} in the standard MIDI range using scientific pitch notation.
4
+ #
5
+ # See http://en.wikipedia.org/wiki/Scientific_pitch_notation
6
+ #
7
+ # @note Because the character '#' cannot be used in the name of a constant,
8
+ # the "black key" pitches are all named as flats with 'b' (for example, Gb3 or Cb4)
9
+ # @note Because the character '-' (minus) cannot be used in the name of a constant,
10
+ # the low pitches use '_' (underscore) in place of '-' (minus) (for example C_1).
11
+ module Pitches
12
+
13
+ # The values of all "psuedo constants" defined in this module
14
+ PITCHES = []
15
+
16
+ # The names of all "psuedo constants" defined in this module
17
+ PITCH_NAMES = []
18
+
19
+ 128.times do |note_number|
20
+ pitch = Pitch.from_i( note_number )
21
+ PITCHES << pitch
22
+
23
+ octave_str = pitch.octave.to_s.sub(/-/,'_') # '_1' for -1
24
+ name = "#{pitch.pitch_class}#{octave_str}"
25
+ PITCH_NAMES << name
26
+
27
+ const_set name, pitch
28
+ end
29
+
30
+ PITCHES.freeze
31
+ PITCH_NAMES.freeze
32
+
33
+ # Lookup the value of an pitch constant by name.
34
+ # @example lookup value of 'C3'
35
+ # MTK::Pitches['C3']
36
+ # @see Pitch.from_s
37
+ # @note Unlike {Pitch.from_s} this method will accept either '_' (underscore) or '-' (minus) and treat it like '-' (minus)
38
+ # @note Unlike {Pitch.from_s} this method only accepts the accidental 'b'
39
+ def self.[](name)
40
+ begin
41
+ const_get name.sub('-','_')
42
+ rescue
43
+ nil
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
data/lib/mtk/event.rb CHANGED
@@ -7,9 +7,6 @@ module MTK
7
7
  # intensity of the note as a value in the range 0.0 - 1.0
8
8
  attr_reader :intensity
9
9
 
10
- # duration of the note in beats (e.g. 1.0 is a quarter note in 4/4 time signatures)
11
- attr_reader :duration
12
-
13
10
  def initialize(intensity, duration)
14
11
  @intensity, @duration = intensity, duration
15
12
  end
@@ -36,11 +33,23 @@ module MTK
36
33
 
37
34
  # intensity scaled to the MIDI range 0-127
38
35
  def velocity
39
- (127 * @intensity).round
36
+ @velocity ||= (127 * @intensity).round
37
+ end
38
+
39
+ # Duration of the Event in beats (e.g. 1.0 is a quarter note in 4/4 time signatures)
40
+ # This is the absolute value of the duration attribute used to construct the object.
41
+ # @see rest?
42
+ def duration
43
+ @abs_duration ||= @duration.abs
44
+ end
45
+
46
+ # By convention, any events with negative durations are considered a rest
47
+ def rest?
48
+ @duration < 0
40
49
  end
41
50
 
42
51
  def duration_in_pulses(pulses_per_beat)
43
- (@duration * pulses_per_beat).round
52
+ @duration_in_pulses ||= (duration * pulses_per_beat).round
44
53
  end
45
54
 
46
55
  def == other
@@ -0,0 +1,114 @@
1
+ module MTK::Helper
2
+
3
+ # Given a method #elements, which returns an Array of elements in the collection,
4
+ # including this module will make the class Enumerable and provide various methods you'd expect from an Array.
5
+ module Collection
6
+ include Enumerable
7
+
8
+ # A mutable array of elements in this collection
9
+ def to_a
10
+ Array.new(elements) # we construct a new array since some including classes make elements be immutable
11
+ end
12
+
13
+ # The number of elements in this collection
14
+ def size
15
+ elements.size
16
+ end
17
+ alias length size
18
+
19
+ def empty?
20
+ elements.nil? or elements.size == 0
21
+ end
22
+
23
+ # The each iterator for providing Enumerable functionality
24
+ def each &block
25
+ elements.each &block
26
+ end
27
+
28
+ # The first element
29
+ def first(n=nil)
30
+ n ? elements.first(n) : elements.first
31
+ end
32
+
33
+ # The last element
34
+ def last(n=nil)
35
+ n ? elements.last(n) : elements.last
36
+ end
37
+
38
+ # The element with the given index
39
+ def [](index)
40
+ elements[index]
41
+ end
42
+
43
+ def repeat(times=2)
44
+ full_repetitions, fractional_repetitions = times.floor, times%1 # split into int and fractional part
45
+ repeated = elements * full_repetitions
46
+ repeated += elements[0...elements.size*fractional_repetitions]
47
+ clone_with repeated
48
+ end
49
+
50
+ def permute
51
+ clone_with elements.shuffle
52
+ end
53
+ alias shuffle permute
54
+
55
+ def rotate(offset=1)
56
+ clone_with elements.rotate(offset)
57
+ end
58
+
59
+ def concat(other)
60
+ other_elements = (other.respond_to? :elements) ? other.elements : other
61
+ clone_with(elements + other_elements)
62
+ end
63
+
64
+ def reverse
65
+ clone_with elements.reverse
66
+ end
67
+ alias retrograde reverse
68
+
69
+ def ==(other)
70
+ if other.respond_to? :elements
71
+ if other.respond_to? :options
72
+ elements == other.elements and @options == other.options
73
+ else
74
+ elements == other.elements
75
+ end
76
+ else
77
+ elements == other
78
+ end
79
+ end
80
+
81
+ # Create a copy of the collection.
82
+ # In order to use this method, the including class must implement .from_a()
83
+ def clone
84
+ clone_with to_a
85
+ end
86
+
87
+ #################################
88
+ private
89
+
90
+ # "clones" the object with the given elements, attempting to maintain any @options
91
+ # This is designed to work with 2 argument constructors: def initialize(elements, options={})
92
+ def clone_with elements
93
+ from_a = self.class.method(:from_a)
94
+ if from_a.arity == -2
95
+ from_a[elements, (@options || {})]
96
+ else
97
+ from_a[elements]
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+
105
+ if not Array.instance_methods.include? :rotate
106
+ # Array#rotate is only available in Ruby 1.9, so we may have to implement it
107
+ class Array
108
+ def rotate(offset=1)
109
+ return self if empty?
110
+ offset %= length
111
+ self[offset..-1]+self[0...offset]
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,85 @@
1
+ module MTK
2
+ module Helper
3
+
4
+ # A helper class for {Sequencer}s.
5
+ # Takes a list of patterns and constructs a list of {Event}s from the next elements in each pattern.
6
+ class EventBuilder
7
+
8
+ DEFAULT_PITCH = MTK::Pitches::C4
9
+ DEFAULT_INTENSITY = MTK::Intensities::f
10
+ DEFAULT_DURATION = 1
11
+
12
+ def initialize(patterns, options={})
13
+ @patterns = patterns
14
+ @options = options
15
+ @max_interval = options.fetch :max_interval, 12
16
+ rewind
17
+ end
18
+
19
+ # Build a list of events from the next element in each {Pattern}
20
+ # @return [Array] an array of events
21
+ def next
22
+ pitches = []
23
+ intensity = @default_intensity
24
+ duration = @default_duration
25
+
26
+ for pattern in @patterns
27
+ element = pattern.next
28
+ case element
29
+ when Pitch then pitches << element
30
+ when PitchSet then pitches += element.pitches
31
+ when PitchClass then pitches += pitches_for_pitch_classes([element], @previous_pitch || @default_pitch)
32
+ when PitchClassSet then pitches += pitches_for_pitch_classes(element, @previous_pitch || @default_pitch)
33
+ else case pattern.type
34
+ when :pitch
35
+ if element.is_a? Numeric
36
+ # pitch interval case
37
+ if @previous_pitches
38
+ pitches += @previous_pitches.map{|pitch| pitch + element }
39
+ else
40
+ pitches << ((@previous_pitch || @default_pitch) + element)
41
+ end
42
+ end
43
+ when :intensity then intensity = element
44
+ when :duration then duration = element
45
+ end
46
+ end
47
+ end
48
+
49
+ if not pitches.empty?
50
+ @previous_pitch = pitches.last
51
+ @previous_pitches = pitches.length > 1 ? pitches : nil
52
+ pitches.map{|pitch| Note(pitch,intensity,duration) }
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ # Reset the EventBuilder to its initial state
59
+ def rewind
60
+ @default_pitch = @options.fetch :default_pitch, DEFAULT_PITCH
61
+ @default_intensity = @options.fetch :default_intensity, DEFAULT_INTENSITY
62
+ @default_duration = @options.fetch :default_duration, DEFAULT_DURATION
63
+ @previous_pitch = nil
64
+ @previous_pitches = nil
65
+ @patterns.each{|pattern| pattern.rewind }
66
+ end
67
+
68
+ ########################
69
+ private
70
+
71
+ def pitches_for_pitch_classes(pitch_classes, previous_pitch)
72
+ pitches = []
73
+ for pitch_class in pitch_classes
74
+ pitch = previous_pitch.nearest(pitch_class)
75
+ pitch -= 12 if pitch > @default_pitch+@max_interval # keep within max_distance of start (default is one octave)
76
+ pitch += 12 if pitch < @default_pitch-@max_interval
77
+ pitches << pitch
78
+ end
79
+ pitches
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
@@ -1,10 +1,11 @@
1
- module MTK
2
-
1
+ module MTK::Helper
2
+
3
+
3
4
  # Extension for modules that want to define pseudo-constants (constant-like values with lower-case names)
4
5
  module PseudoConstants
5
-
6
- # Define a "constant" as a module method and module function (available both through the module namespace and as a mixin method),
7
- # in order to facility defining constant-like values with lower-case names.
6
+
7
+ # Define a module method and module function (available both through the module namespace and as a mixin method),
8
+ # to provide a constant with a lower-case name.
8
9
  #
9
10
  # @param name [Symbol] the name of the pseudo-constant
10
11
  # @param value [Object] the value of the pseudo-constant
@@ -21,5 +22,5 @@ module MTK
21
22
  end
22
23
 
23
24
  end
24
-
25
+
25
26
  end
@@ -0,0 +1,17 @@
1
+ require 'citrus'
2
+ Citrus.load File.join(File.dirname(__FILE__),'mtk_grammar')
3
+
4
+ module MTK
5
+ module Lang
6
+
7
+ # Parser for the {file:lib/mtk/lang/mtk_grammar.citrus MTK grammar}
8
+ class Grammar
9
+
10
+ def self.parse(syntax, root=:pitch)
11
+ MTK_Grammar.parse(syntax, :root => root).value
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ grammar MTK_Grammar
2
+ include MTK
3
+
4
+ rule pitch_sequence
5
+ ( pitch (space pitch)* ) {
6
+ Pattern.PitchSequence *captures[:pitch].map{|p| p.value }
7
+ }
8
+ end
9
+
10
+ rule pitch
11
+ ( pitch_class int ) {
12
+ Pitch[pitch_class.value, int.value]
13
+ }
14
+ end
15
+
16
+ rule pitch_class
17
+ ( [A-Ga-g] [#b]*2 ) {
18
+ PitchClass[to_s]
19
+ }
20
+ end
21
+
22
+ rule interval
23
+ ( 'P' [1458] | [Mm] [2367] | 'TT' ) {
24
+ Intervals[to_s]
25
+ }
26
+ end
27
+
28
+ rule intensity
29
+ ( ('p'1*3 | 'mp' | 'mf' | 'f'1*3) ('+'|'-')? ) {
30
+ Intensities[to_s]
31
+ }
32
+ end
33
+
34
+ rule duration
35
+ ( [whqesrx] ('.'|'t')* ) {
36
+ Durations[to_s]
37
+ }
38
+ end
39
+
40
+ rule number
41
+ float | int
42
+ end
43
+
44
+ rule float
45
+ ( '-'? [0-9]+ '.' [0-9]+ ) {
46
+ to_f
47
+ }
48
+ end
49
+
50
+ rule int
51
+ ( '-'? [0-9]+ ) {
52
+ to_i
53
+ }
54
+ end
55
+
56
+ rule space
57
+ [\s]+ { nil }
58
+ end
59
+
60
+ end
data/lib/mtk/midi/file.rb CHANGED
@@ -82,24 +82,18 @@ module MTK
82
82
  track = add_track sequence
83
83
  channel = 1
84
84
 
85
- for time, event in timeline
85
+ for time,events in timeline
86
86
  time *= clock_rate
87
- case event
88
- when Note
87
+
88
+ for event in events
89
+ next if event.rest?
90
+
91
+ if event.is_a? Note
89
92
  pitch, velocity = event.pitch, event.velocity
90
93
  add_event track, time => note_on(channel, pitch, velocity)
91
94
  duration = event.duration_in_pulses(clock_rate)
92
95
  add_event track, time+duration => note_off(channel, pitch, velocity)
93
-
94
- when Chord
95
- velocity = event.velocity
96
- duration = event.duration_in_pulses(clock_rate)
97
- for pitch in event.pitches
98
- pitch = pitch.to_i
99
- add_event track, time => note_on(channel, pitch, velocity)
100
- add_event track, time+duration => note_off(channel, pitch, velocity)
101
- end
102
-
96
+ end
103
97
  end
104
98
  end
105
99
  track.recalc_delta_from_times
@@ -160,16 +154,17 @@ module MTK
160
154
 
161
155
  def add_event track, event_hash
162
156
  for time, event in event_hash
163
- event.time_from_start = time
157
+ event.time_from_start = time.round # MIDI file event times must be in whole number pulses (typically 480 or 960 per quarter note)
164
158
  track.events << event
165
159
  event
166
160
  end
167
161
  end
168
162
 
169
163
  end
170
-
171
164
  end
172
165
 
166
+ # Shortcut for MTK::MIDI::File.new
167
+ # @note Only available if you require 'mtk/midi/file'
173
168
  def MIDI_File(f)
174
169
  MIDI::File.new(f)
175
170
  end
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'jsound'
3
+
4
+ module MTK
5
+ module MIDI
6
+
7
+ # Provides MIDI input for JRuby via the jsound gem
8
+ class JSoundInput
9
+
10
+ def initialize(input_device)
11
+ if input_device.is_a? ::JSound::Midi::Device
12
+ @input = input_device
13
+ else
14
+ @input = ::JSound::Midi::INPUTS.send input_device
15
+ end
16
+ @recorder = ::JSound::Midi::Devices::Recorder.new(false)
17
+ @input >> @recorder
18
+ end
19
+
20
+ def record
21
+ @input.open
22
+ @recorder.clear
23
+ @recorder.start
24
+ end
25
+
26
+ def stop
27
+ @recorder.stop
28
+ @input.close
29
+ end
30
+
31
+ def to_timeline(options={})
32
+ bpm = options.fetch :bmp, 120
33
+ beats_per_second = bpm.to_f/60
34
+ timeline = Timeline.new
35
+ note_ons = {}
36
+ start = nil
37
+
38
+ for message,time in @recorder.messages_with_timestamps
39
+ start = time unless start
40
+ time -= start
41
+ time /= beats_per_second
42
+
43
+ case message.type
44
+ when :note_on
45
+ note_ons[message.pitch] = [message,time]
46
+
47
+ when :note_off
48
+ if note_ons.has_key? message.pitch
49
+ note_on, start_time = note_ons[message.pitch]
50
+ duration = time - start_time
51
+ note = Note.from_midi note_on.pitch, note_on.velocity, duration
52
+ timeline.add time,note
53
+ end
54
+
55
+ else timeline.add time,message
56
+ end
57
+ end
58
+
59
+ timeline.quantize! options[:quantize] if options.key? :quantize
60
+ timeline.shift_to! options[:shift_to] if options.key? :shift_to
61
+
62
+ timeline
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+
@@ -0,0 +1,80 @@
1
+ require 'rubygems'
2
+ require 'jsound'
3
+ require 'gamelan'
4
+
5
+ module MTK
6
+ module MIDI
7
+
8
+ # Provides MIDI output for JRuby via the jsound gem
9
+ class JSoundOutput
10
+
11
+ attr_reader :device
12
+
13
+ def initialize(output, options={})
14
+ if output.is_a? ::JSound::Midi::Device
15
+ @device = output
16
+ else
17
+ @device = ::JSound::Midi::OUTPUTS.send output
18
+ end
19
+
20
+ @generator = ::JSound::Midi::Devices::Generator.new
21
+ if options[:monitor]
22
+ @monitor = ::JSound::Midi::Devices::Monitor.new
23
+ @generator >> [@monitor, @device]
24
+ else
25
+ @generator >> @device
26
+ end
27
+ @device.open
28
+ end
29
+
30
+ def play(timeline, options={})
31
+ scheduler_rate = options.fetch :scheduler_rate, 500 # default: 500 Hz
32
+ trailing_buffer = options.fetch :trailing_buffer, 2 # default: continue playing for 2 beats after the end of the timeline
33
+ in_background = options.fetch :background, false # default: don't run in background Thread
34
+ bpm = options.fetch :bmp, 120 # default: 120 beats per minute
35
+
36
+ @scheduler = Gamelan::Scheduler.new :tempo => bpm, :rate => scheduler_rate
37
+
38
+ for time,events in timeline
39
+ for event in events
40
+ case event
41
+ when Note
42
+ pitch, velocity, duration = event.to_midi
43
+ at time, note_on(pitch,velocity)
44
+ time += duration
45
+ at time, note_off(pitch,velocity)
46
+ end
47
+ end
48
+ end
49
+
50
+ end_time = timeline.times.last + trailing_buffer
51
+ @scheduler.at(end_time) { @scheduler.stop }
52
+
53
+ thread = @scheduler.run
54
+ thread.join if not in_background
55
+ end
56
+
57
+ ######################
58
+ private
59
+
60
+ # It's necessary to generate the events through methods and lambdas like this to create closures.
61
+ # Otherwise when the @generator methods are called, they might not be passed the values you expected.
62
+ # I suspect this may not a problem in MRI ruby, but I'm having trouble in JRuby
63
+ # (pitch and velocity were always the last scheduled values)
64
+
65
+ def note_on(pitch, velocity)
66
+ lambda { @generator.note_on(pitch, velocity) }
67
+ end
68
+
69
+ def note_off(pitch, velocity)
70
+ lambda { @generator.note_off(pitch, velocity) }
71
+ end
72
+
73
+ def at time, block
74
+ @scheduler.at(time) { block.call }
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+
data/lib/mtk/note.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module MTK
2
2
 
3
- # A musical #{Event} defined by a {Pitch}, intensity, and duration
3
+ # A musical {Event} defined by a {Pitch}, intensity, and duration
4
4
  class Note < Event
5
5
 
6
6
  # frequency of the note as a Pitch
@@ -15,18 +15,26 @@ module MTK
15
15
  new hash[:pitch], hash[:intensity], hash[:duration]
16
16
  end
17
17
 
18
+ def to_hash
19
+ super.merge({ :pitch => @pitch })
20
+ end
21
+
18
22
  def self.from_midi(pitch, velocity, beats)
19
23
  new Pitches::PITCHES[pitch], velocity/127.0, beats
20
24
  end
21
25
 
22
- def to_hash
23
- super.merge({ :pitch => @pitch })
26
+ def to_midi
27
+ [pitch.to_i, velocity, duration]
24
28
  end
25
29
 
26
30
  def transpose(interval)
27
31
  self.class.new(@pitch+interval, @intensity, @duration)
28
32
  end
29
33
 
34
+ def invert(around_pitch)
35
+ self.class.new(@pitch.invert(around_pitch), @intensity, @duration)
36
+ end
37
+
30
38
  def == other
31
39
  super and other.respond_to? :pitch and @pitch == other.pitch
32
40
  end
@@ -41,4 +49,15 @@ module MTK
41
49
 
42
50
  end
43
51
 
52
+ # Construct a {Note} from any supported type
53
+ def Note(*anything)
54
+ anything = anything.first if anything.size == 1
55
+ case anything
56
+ when Array then Note.new(*anything)
57
+ when Note then anything
58
+ else raise "Note doesn't understand #{anything.class}"
59
+ end
60
+ end
61
+ module_function :Note
62
+
44
63
  end