musa-dsl 0.14.32 → 0.21.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/Gemfile +0 -1
  4. data/README.md +5 -1
  5. data/lib/musa-dsl.rb +54 -11
  6. data/lib/musa-dsl/core-ext.rb +7 -13
  7. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +15 -23
  8. data/lib/musa-dsl/core-ext/arrayfy.rb +30 -12
  9. data/lib/musa-dsl/core-ext/attribute-builder.rb +194 -0
  10. data/lib/musa-dsl/core-ext/deep-copy.rb +185 -0
  11. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +44 -40
  12. data/lib/musa-dsl/core-ext/inspect-nice.rb +40 -22
  13. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +108 -0
  14. data/lib/musa-dsl/core-ext/with.rb +26 -0
  15. data/lib/musa-dsl/datasets.rb +8 -3
  16. data/lib/musa-dsl/datasets/dataset.rb +3 -0
  17. data/lib/musa-dsl/datasets/delta-d.rb +12 -0
  18. data/lib/musa-dsl/datasets/e.rb +61 -0
  19. data/lib/musa-dsl/datasets/gdv.rb +51 -411
  20. data/lib/musa-dsl/datasets/gdvd.rb +179 -0
  21. data/lib/musa-dsl/datasets/helper.rb +41 -0
  22. data/lib/musa-dsl/datasets/p.rb +68 -0
  23. data/lib/musa-dsl/datasets/packed-v.rb +19 -0
  24. data/lib/musa-dsl/datasets/pdv.rb +22 -15
  25. data/lib/musa-dsl/datasets/ps.rb +113 -0
  26. data/lib/musa-dsl/datasets/score.rb +210 -0
  27. data/lib/musa-dsl/datasets/score/queriable.rb +48 -0
  28. data/lib/musa-dsl/datasets/score/render.rb +31 -0
  29. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +160 -0
  30. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +51 -0
  31. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +153 -0
  32. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +158 -0
  33. data/lib/musa-dsl/datasets/v.rb +23 -0
  34. data/lib/musa-dsl/generative.rb +5 -5
  35. data/lib/musa-dsl/generative/backboner.rb +274 -0
  36. data/lib/musa-dsl/generative/darwin.rb +102 -96
  37. data/lib/musa-dsl/generative/generative-grammar.rb +182 -187
  38. data/lib/musa-dsl/generative/markov.rb +56 -53
  39. data/lib/musa-dsl/generative/variatio.rb +234 -222
  40. data/lib/musa-dsl/logger.rb +1 -0
  41. data/lib/musa-dsl/logger/logger.rb +32 -0
  42. data/lib/musa-dsl/matrix.rb +1 -0
  43. data/lib/musa-dsl/matrix/matrix.rb +210 -0
  44. data/lib/musa-dsl/midi.rb +2 -2
  45. data/lib/musa-dsl/midi/midi-recorder.rb +54 -52
  46. data/lib/musa-dsl/midi/midi-voices.rb +187 -182
  47. data/lib/musa-dsl/music.rb +5 -5
  48. data/lib/musa-dsl/music/chord-definition.rb +54 -50
  49. data/lib/musa-dsl/music/chord-definitions.rb +13 -9
  50. data/lib/musa-dsl/music/chords.rb +236 -238
  51. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +187 -183
  52. data/lib/musa-dsl/music/scales.rb +331 -332
  53. data/lib/musa-dsl/musicxml.rb +1 -0
  54. data/lib/musa-dsl/musicxml/builder/attributes.rb +155 -0
  55. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +45 -0
  56. data/lib/musa-dsl/musicxml/builder/direction.rb +322 -0
  57. data/lib/musa-dsl/musicxml/builder/helper.rb +90 -0
  58. data/lib/musa-dsl/musicxml/builder/measure.rb +137 -0
  59. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +152 -0
  60. data/lib/musa-dsl/musicxml/builder/note.rb +577 -0
  61. data/lib/musa-dsl/musicxml/builder/part-group.rb +44 -0
  62. data/lib/musa-dsl/musicxml/builder/part.rb +67 -0
  63. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +126 -0
  64. data/lib/musa-dsl/musicxml/builder/rest.rb +117 -0
  65. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +120 -0
  66. data/lib/musa-dsl/musicxml/builder/typed-text.rb +43 -0
  67. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +112 -0
  68. data/lib/musa-dsl/neumalang.rb +1 -1
  69. data/lib/musa-dsl/neumalang/datatypes.citrus +79 -0
  70. data/lib/musa-dsl/neumalang/neuma.citrus +165 -0
  71. data/lib/musa-dsl/neumalang/neumalang.citrus +32 -242
  72. data/lib/musa-dsl/neumalang/neumalang.rb +373 -142
  73. data/lib/musa-dsl/neumalang/process.citrus +21 -0
  74. data/lib/musa-dsl/neumalang/terminals.citrus +67 -0
  75. data/lib/musa-dsl/neumalang/vectors.citrus +23 -0
  76. data/lib/musa-dsl/neumas.rb +5 -0
  77. data/lib/musa-dsl/neumas/array-to-neumas.rb +34 -0
  78. data/lib/musa-dsl/neumas/neuma-decoder.rb +63 -0
  79. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +57 -0
  80. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +15 -0
  81. data/lib/musa-dsl/neumas/neumas.rb +37 -0
  82. data/lib/musa-dsl/neumas/string-to-neumas.rb +34 -0
  83. data/lib/musa-dsl/repl.rb +1 -1
  84. data/lib/musa-dsl/repl/repl.rb +122 -110
  85. data/lib/musa-dsl/sequencer.rb +1 -1
  86. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +163 -136
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +301 -286
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +550 -321
  89. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +198 -176
  90. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +75 -0
  91. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +75 -0
  92. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +105 -85
  93. data/lib/musa-dsl/sequencer/timeslots.rb +34 -0
  94. data/lib/musa-dsl/series.rb +1 -1
  95. data/lib/musa-dsl/{core-ext → series}/array-to-serie.rb +1 -1
  96. data/lib/musa-dsl/series/base-series.rb +171 -168
  97. data/lib/musa-dsl/series/hash-serie-splitter.rb +134 -132
  98. data/lib/musa-dsl/series/holder-serie.rb +1 -1
  99. data/lib/musa-dsl/series/main-serie-constructors.rb +6 -1
  100. data/lib/musa-dsl/series/main-serie-operations.rb +807 -797
  101. data/lib/musa-dsl/series/proxy-serie.rb +6 -6
  102. data/lib/musa-dsl/series/queue-serie.rb +5 -5
  103. data/lib/musa-dsl/series/series.rb +2 -0
  104. data/lib/musa-dsl/transcription.rb +4 -0
  105. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +227 -0
  106. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +36 -0
  107. data/lib/musa-dsl/transcription/from-gdv.rb +17 -0
  108. data/lib/musa-dsl/transcription/transcription.rb +55 -0
  109. data/lib/musa-dsl/transport.rb +6 -6
  110. data/lib/musa-dsl/transport/clock.rb +26 -26
  111. data/lib/musa-dsl/transport/dummy-clock.rb +32 -30
  112. data/lib/musa-dsl/transport/external-tick-clock.rb +21 -20
  113. data/lib/musa-dsl/transport/input-midi-clock.rb +89 -80
  114. data/lib/musa-dsl/transport/timer-clock.rb +72 -71
  115. data/lib/musa-dsl/transport/timer.rb +28 -26
  116. data/lib/musa-dsl/transport/transport.rb +111 -93
  117. data/musa-dsl.gemspec +3 -3
  118. metadata +73 -24
  119. data/lib/musa-dsl/core-ext/array-apply-get.rb +0 -18
  120. data/lib/musa-dsl/core-ext/array-to-neumas.rb +0 -28
  121. data/lib/musa-dsl/core-ext/as-context-run.rb +0 -44
  122. data/lib/musa-dsl/core-ext/duplicate.rb +0 -134
  123. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +0 -85
  124. data/lib/musa-dsl/core-ext/proc-nice.rb +0 -13
  125. data/lib/musa-dsl/core-ext/send-nice.rb +0 -21
  126. data/lib/musa-dsl/core-ext/string-to-neumas.rb +0 -27
  127. data/lib/musa-dsl/datasets/gdv-decorators.rb +0 -221
  128. data/lib/musa-dsl/generative/rules.rb +0 -282
  129. data/lib/musa-dsl/neuma.rb +0 -1
  130. data/lib/musa-dsl/neuma/neuma.rb +0 -181
@@ -0,0 +1 @@
1
+ require_relative 'logger/logger'
@@ -0,0 +1,32 @@
1
+ require 'logger'
2
+
3
+ module Musa; module Logger
4
+ class Logger < ::Logger
5
+ def initialize(sequencer: nil, position_format: nil)
6
+ super STDERR, level: WARN
7
+
8
+ @sequencer = sequencer
9
+ @position_format = position_format || 3.3
10
+
11
+
12
+ self.formatter = proc do |severity, time, progname, msg|
13
+ level = "[#{severity}] " unless severity == 'DEBUG'
14
+
15
+ if msg
16
+ position = if @sequencer
17
+ integer_digits = @position_format.to_i
18
+ decimal_digits = ((@position_format - integer_digits) * 10).round
19
+
20
+ "%#{integer_digits + decimal_digits + 1}s: " % ("%.#{decimal_digits}f" % sequencer.position.to_f)
21
+ end
22
+
23
+ progname = "[#{progname}] " if progname
24
+
25
+ "#{position}#{level}#{progname}#{msg}\n"
26
+ else
27
+ "\n"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end; end
@@ -0,0 +1 @@
1
+ require_relative 'matrix/matrix'
@@ -0,0 +1,210 @@
1
+ require 'matrix'
2
+
3
+ require_relative '../datasets/p'
4
+ require_relative '../sequencer'
5
+
6
+ module Musa
7
+ module Matrix
8
+ ## TODO should exist this module?
9
+ end
10
+
11
+ module Extension
12
+ module Matrix
13
+ refine Array do
14
+ def indexes_of_values
15
+ indexes = {}
16
+
17
+ size.times do |i|
18
+ indexes[self[i]] ||= []
19
+ indexes[self[i]] << i
20
+ end
21
+
22
+ indexes
23
+ end
24
+
25
+ def to_p(time_dimension, keep_time: nil)
26
+ condensed_matrices.collect { |m| m.to_p(time_dimension, keep_time: keep_time) }
27
+ end
28
+
29
+ def condensed_matrices
30
+ condensed = []
31
+
32
+ each do |other|
33
+ if condensed.empty?
34
+ condensed << other
35
+ else
36
+ done = false
37
+ condensed.each do |matrix|
38
+ if matrix._rows.first == other._rows.first
39
+ other._rows.shift
40
+ matrix._rows.prepend other._rows.shift until other._rows.empty?
41
+ done = true
42
+
43
+ elsif matrix._rows.first == other._rows.last
44
+ other._rows.pop
45
+ matrix._rows.prepend other._rows.pop until other._rows.empty?
46
+ done = true
47
+
48
+ elsif matrix._rows.last == other._rows.first
49
+ other._rows.shift
50
+ matrix._rows.append other._rows.shift until other._rows.empty?
51
+ done = true
52
+
53
+ elsif matrix._rows.last == other._rows.last
54
+ other._rows.pop
55
+ matrix._rows.append other._rows.pop until other._rows.empty?
56
+ done = true
57
+ end
58
+
59
+ break if done
60
+ end
61
+ condensed << other unless done
62
+ end
63
+ end
64
+
65
+ condensed
66
+ end
67
+ end
68
+
69
+ refine ::Matrix do
70
+ include Musa::Datasets
71
+
72
+ def to_p(time_dimension, keep_time: nil)
73
+
74
+ decompose(self.to_a, time_dimension).collect do |points|
75
+ line = []
76
+
77
+ start_point = points[0]
78
+ start_time = start_point[time_dimension]
79
+
80
+ line << start_point.tap { |_| _.delete_at(time_dimension) unless keep_time; _ }.extend(Datasets::V)
81
+
82
+ (1..points.size-1).each do |i|
83
+ end_point = points[i]
84
+
85
+ end_time = end_point[time_dimension]
86
+
87
+ line << end_time - start_time
88
+ line << end_point.tap { |_| _.delete_at(time_dimension) unless keep_time; _ }.extend(Datasets::V)
89
+
90
+ start_time = end_time
91
+ end
92
+
93
+ line.extend(Datasets::P)
94
+ end
95
+ end
96
+
97
+ def to_score(time_dimension,
98
+ mapper: nil,
99
+ score: nil,
100
+ position: nil,
101
+ sequencer: nil,
102
+ beats_per_bar: nil, ticks_per_beat: nil,
103
+ right_open: nil,
104
+ do_log: nil,
105
+ &block)
106
+
107
+ raise ArgumentError,
108
+ "'beats_per_bar' and 'ticks_per_beat' parameters should be both nil or both have values" \
109
+ unless beats_per_bar && ticks_per_beat ||
110
+ beats_per_bar.nil? && ticks_per_beat.nil?
111
+
112
+ raise ArgumentError,
113
+ "'sequencer' parameter should not be used when 'beats_per_bar' and 'ticks_per_beat' parameters are used" \
114
+ if sequencer && beats_per_bar
115
+
116
+ raise ArgumentError,
117
+ "'time_dimension' parameter should be an index number if no 'mapping' is provided" \
118
+ unless time_dimension.is_a?(Symbol) && mapper&.include?(time_dimension) ||
119
+ time_dimension.is_a?(Integer) && (0..self.column_size-1).include(time_dimension)
120
+
121
+ time_dimension = mapper&.find_index(time_dimension) if time_dimension.is_a?(Symbol)
122
+
123
+ mapper = mapper.clone.tap { |_| _.delete_at(time_dimension) } if mapper
124
+
125
+ run_sequencer = sequencer.nil?
126
+
127
+ score ||= Musa::Datasets::Score.new
128
+
129
+ sequencer ||= Sequencer::Sequencer.new(beats_per_bar, ticks_per_beat, do_log: do_log)
130
+
131
+ right_open = right_open.clone.tap { |_| _.delete_at(time_dimension) } if right_open&.is_a?(Array)
132
+
133
+ sequencer.at(position || 1r) do |_|
134
+ to_p(time_dimension).each do |p|
135
+ p.to_score(score: score,
136
+ mapper: mapper,
137
+ sequencer: _,
138
+ position: _.position,
139
+ right_open: right_open,
140
+ do_log: do_log,
141
+ &block)
142
+ end
143
+ end
144
+
145
+ if run_sequencer
146
+ sequencer.run
147
+ score
148
+ else
149
+ nil
150
+ end
151
+ end
152
+
153
+ def _rows
154
+ @rows
155
+ end
156
+
157
+ private def decompose(array, time_dimension)
158
+ x_dim = array.collect { |v| v[time_dimension] }
159
+ x_dim_values_indexes = x_dim.indexes_of_values
160
+
161
+ used_indexes = Set[]
162
+
163
+ directional_segments = []
164
+
165
+ x_dim_values_indexes.keys.sort.each do |value|
166
+ x_dim_values_indexes[value].each do |index|
167
+ # hacia un lado
168
+
169
+ unless used_indexes.include?(index)
170
+ i = index
171
+ xx = array[i][time_dimension]
172
+
173
+ a = []
174
+
175
+ while i >= 0 && array[i][time_dimension] >= xx
176
+ used_indexes << i
177
+ a << array[i]
178
+
179
+ xx = array[i][time_dimension]
180
+ i -= 1
181
+ end
182
+
183
+ directional_segments << a if a.size > 1
184
+
185
+ # y hacia el otro
186
+
187
+ i = index
188
+ xx = array[i][time_dimension]
189
+
190
+ b = []
191
+
192
+ while i < array.size && array[i][time_dimension] >= xx
193
+ used_indexes << i
194
+ b << array[i]
195
+
196
+ xx = array[i][time_dimension]
197
+ i += 1
198
+ end
199
+
200
+ directional_segments << b if b.size > 1
201
+ end
202
+ end
203
+ end
204
+
205
+ return directional_segments
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -1,2 +1,2 @@
1
- require 'musa-dsl/midi/midi-voices'
2
- require 'musa-dsl/midi/midi-recorder'
1
+ require_relative 'midi/midi-voices'
2
+ require_relative 'midi/midi-recorder'
@@ -1,83 +1,85 @@
1
1
  require 'nibbler'
2
2
 
3
3
  module Musa
4
- class MIDIRecorder
5
- def initialize(sequencer)
6
- @sequencer = sequencer
7
- @nibbler = Nibbler.new
4
+ module MIDIRecorder
5
+ class MIDIRecorder
6
+ def initialize(sequencer)
7
+ @sequencer = sequencer
8
+ @nibbler = Nibbler.new
8
9
 
9
- clear
10
- end
10
+ clear
11
+ end
11
12
 
12
- def clear
13
- @messages = []
14
- end
13
+ def clear
14
+ @messages = []
15
+ end
15
16
 
16
- def record(midi_bytes)
17
- m = @nibbler.parse midi_bytes
18
- m = [m] unless m.is_a? Array
17
+ def record(midi_bytes)
18
+ m = @nibbler.parse midi_bytes
19
+ m = [m] unless m.is_a? Array
19
20
 
20
- m.each do |mm|
21
- @messages << Message.new(@sequencer.position, mm)
21
+ m.each do |mm|
22
+ @messages << Message.new(@sequencer.position, mm)
23
+ end
22
24
  end
23
- end
24
25
 
25
- def raw
26
- @messages
27
- end
26
+ def raw
27
+ @messages
28
+ end
28
29
 
29
- def transcription
30
- note_on = {}
31
- last_note = {}
30
+ def transcription
31
+ note_on = {}
32
+ last_note = {}
32
33
 
33
- notes = []
34
+ notes = []
34
35
 
35
- @messages.each do |m|
36
- mm = m.message
36
+ @messages.each do |m|
37
+ mm = m.message
37
38
 
38
- if mm.is_a?(MIDIMessage::NoteOn)
39
+ if mm.is_a?(MIDIMessage::NoteOn)
39
40
 
40
- if last_note[mm.channel]
41
- notes << { position: last_note[mm.channel], channel: mm.channel, pitch: :silence, duration: m.position - last_note[mm.channel] }
42
- last_note.delete mm.channel
43
- end
41
+ if last_note[mm.channel]
42
+ notes << { position: last_note[mm.channel], channel: mm.channel, pitch: :silence, duration: m.position - last_note[mm.channel] }
43
+ last_note.delete mm.channel
44
+ end
44
45
 
45
- note = { position: m.position, channel: mm.channel, pitch: mm.note, velocity: mm.velocity }
46
+ note = { position: m.position, channel: mm.channel, pitch: mm.note, velocity: mm.velocity }
46
47
 
47
- note_on[mm.channel] ||= {}
48
- note_on[mm.channel][mm.note] = note
48
+ note_on[mm.channel] ||= {}
49
+ note_on[mm.channel][mm.note] = note
49
50
 
50
- notes << note
51
+ notes << note
51
52
 
52
- elsif mm.is_a?(MIDIMessage::NoteOff)
53
+ elsif mm.is_a?(MIDIMessage::NoteOff)
53
54
 
54
- note_on[mm.channel] ||= {}
55
+ note_on[mm.channel] ||= {}
55
56
 
56
- note = note_on[mm.channel][mm.note]
57
+ note = note_on[mm.channel][mm.note]
57
58
 
58
- if note
59
- note_on[mm.channel].delete mm.note
59
+ if note
60
+ note_on[mm.channel].delete mm.note
60
61
 
61
- note[:duration] = m.position - note[:position]
62
- note[:velocity_off] = mm.velocity
63
- end
62
+ note[:duration] = m.position - note[:position]
63
+ note[:velocity_off] = mm.velocity
64
+ end
64
65
 
65
- last_note[mm.channel] = m.position
66
+ last_note[mm.channel] = m.position
67
+ end
66
68
  end
67
- end
68
69
 
69
- notes
70
- end
70
+ notes
71
+ end
71
72
 
72
- class Message
73
- attr_accessor :position, :message
73
+ class Message
74
+ attr_accessor :position, :message
74
75
 
75
- def initialize(position, message)
76
- @position = position
77
- @message = message
76
+ def initialize(position, message)
77
+ @position = position
78
+ @message = message
79
+ end
78
80
  end
79
- end
80
81
 
81
- private_constant :Message
82
+ private_constant :Message
83
+ end
82
84
  end
83
85
  end
@@ -1,274 +1,279 @@
1
1
  require 'set'
2
2
  require 'midi-message'
3
3
 
4
- require 'musa-dsl/core-ext/array-apply-get'
5
- require 'musa-dsl/core-ext/arrayfy'
4
+ require_relative '../core-ext/arrayfy'
5
+ require_relative '../core-ext/array-explode-ranges'
6
+
7
+ using Musa::Extension::Arrayfy
8
+ using Musa::Extension::ExplodeRanges
6
9
 
7
10
  module Musa
8
- class MIDIVoices
9
- attr_accessor :log
11
+ module MIDIVoices
12
+ class MIDIVoices
13
+ attr_accessor :log
10
14
 
11
- def initialize(sequencer:, output:, channels:, do_log: nil)
12
- do_log ||= false
15
+ def initialize(sequencer:, output:, channels:, do_log: nil)
16
+ do_log ||= false
13
17
 
14
- @sequencer = sequencer
15
- @output = output
16
- @channels = channels.arrayfy.explode_ranges
17
- @do_log = do_log
18
+ @sequencer = sequencer
19
+ @output = output
20
+ @channels = channels.arrayfy.explode_ranges
21
+ @do_log = do_log
18
22
 
19
- reset
20
- end
23
+ reset
24
+ end
21
25
 
22
- def reset
23
- @voices = @channels.collect { |channel| MIDIVoice.new sequencer: @sequencer, output: @output, channel: channel, log: @do_log }.freeze
24
- end
26
+ def reset
27
+ @voices = @channels.collect { |channel| MIDIVoice.new sequencer: @sequencer, output: @output, channel: channel, log: @do_log }.freeze
28
+ end
25
29
 
26
- attr_reader :voices
30
+ attr_reader :voices
27
31
 
28
- def fast_forward=(enabled)
29
- @voices.apply :fast_forward=, enabled
30
- end
32
+ def fast_forward=(enabled)
33
+ @voices.each { |voice| voice.fast_forward = enabled }
34
+ end
31
35
 
32
- def panic(reset: nil)
33
- reset ||= false
36
+ def panic(reset: nil)
37
+ reset ||= false
34
38
 
35
- @voices.each(&:all_notes_off)
39
+ @voices.each(&:all_notes_off)
36
40
 
37
- @output.puts MIDIMessage::SystemRealtime.new(0xff) if reset
41
+ @output.puts MIDIMessage::SystemRealtime.new(0xff) if reset
42
+ end
38
43
  end
39
- end
40
44
 
41
- private
45
+ private
42
46
 
43
- class MIDIVoice
44
- attr_accessor :name, :do_log
45
- attr_reader :sequencer, :output, :channel, :active_pitches, :tick_duration
47
+ class MIDIVoice
48
+ attr_accessor :name, :do_log
49
+ attr_reader :sequencer, :output, :channel, :active_pitches, :tick_duration
46
50
 
47
- def initialize(sequencer:, output:, channel:, name: nil, log: nil)
48
- log ||= false
51
+ def initialize(sequencer:, output:, channel:, name: nil, log: nil)
52
+ log ||= false
49
53
 
50
- @sequencer = sequencer
51
- @output = output
52
- @channel = channel
53
- @name = name
54
- @do_log = log
54
+ @sequencer = sequencer
55
+ @output = output
56
+ @channel = channel
57
+ @name = name
58
+ @do_log = log
55
59
 
56
- @tick_duration = Rational(1, @sequencer.ticks_per_bar)
60
+ @tick_duration = Rational(1, @sequencer.ticks_per_bar)
57
61
 
58
- @controllers_control = ControllersControl.new(@output, @channel)
62
+ @controllers_control = ControllersControl.new(@output, @channel)
59
63
 
60
- @active_pitches = []
61
- fill_active_pitches @active_pitches
64
+ @active_pitches = []
65
+ fill_active_pitches @active_pitches
62
66
 
63
- log 'Warning: voice without output' unless @output
67
+ log 'Warning: voice without output' unless @output
64
68
 
65
- self
66
- end
69
+ self
70
+ end
67
71
 
68
- def fast_forward=(enabled)
69
- if @fast_forward && !enabled
70
- (0..127).each do |pitch|
71
- @output.puts MIDIMessage::NoteOn.new(@channel, pitch, @active_pitches[pitch][:velocity]) unless @active_pitches[pitch][:note_controls].empty?
72
+ def fast_forward=(enabled)
73
+ if @fast_forward && !enabled
74
+ (0..127).each do |pitch|
75
+ @output.puts MIDIMessage::NoteOn.new(@channel, pitch, @active_pitches[pitch][:velocity]) unless @active_pitches[pitch][:note_controls].empty?
76
+ end
72
77
  end
73
- end
74
78
 
75
- @fast_forward = enabled
76
- end
79
+ @fast_forward = enabled
80
+ end
77
81
 
78
- def fast_forward?
79
- @fast_forward
80
- end
82
+ def fast_forward?
83
+ @fast_forward
84
+ end
81
85
 
82
- def note(pitchvalue = nil, pitch: nil, velocity: nil, duration: nil, duration_offset: nil, effective_duration: nil, velocity_off: nil)
83
- pitch ||= pitchvalue
86
+ def note(pitchvalue = nil, pitch: nil, velocity: nil, duration: nil, duration_offset: nil, note_duration: nil, velocity_off: nil)
87
+ pitch ||= pitchvalue
84
88
 
85
- if pitch
86
- velocity ||= 63
89
+ if pitch
90
+ velocity ||= 63
87
91
 
88
- duration_offset ||= -@tick_duration
89
- effective_duration ||= [0, duration + duration_offset].max
92
+ duration_offset ||= -@tick_duration
93
+ note_duration ||= [0, duration + duration_offset].max
90
94
 
91
- velocity_off ||= 63
95
+ velocity_off ||= 63
92
96
 
93
- NoteControl.new(self, pitch: pitch, velocity: velocity, duration: effective_duration, velocity_off: velocity_off).note_on
97
+ NoteControl.new(self, pitch: pitch, velocity: velocity, duration: note_duration, velocity_off: velocity_off).note_on
98
+ end
94
99
  end
95
- end
96
100
 
97
- def controller
98
- @controllers_control
99
- end
101
+ def controller
102
+ @controllers_control
103
+ end
100
104
 
101
- def sustain_pedal=(value)
102
- @controllers_control[:sustain_pedal] = value
103
- end
105
+ def sustain_pedal=(value)
106
+ @controllers_control[:sustain_pedal] = value
107
+ end
104
108
 
105
- def sustain_pedal
106
- @controllers_control[:sustain_pedal]
107
- end
109
+ def sustain_pedal
110
+ @controllers_control[:sustain_pedal]
111
+ end
108
112
 
109
- def all_notes_off
110
- @active_pitches.clear
111
- fill_active_pitches @active_pitches
113
+ def all_notes_off
114
+ @active_pitches.clear
115
+ fill_active_pitches @active_pitches
112
116
 
113
- @output.puts MIDIMessage::ChannelMessage.new(0xb, @channel, 0x7b, 0)
114
- end
117
+ @output.puts MIDIMessage::ChannelMessage.new(0xb, @channel, 0x7b, 0)
118
+ end
115
119
 
116
- def log(msg)
117
- @sequencer.log "voice #{name || @channel}: #{msg}" if @do_log
118
- end
120
+ def log(msg)
121
+ @sequencer.log "voice #{name || @channel}: #{msg}" if @do_log
122
+ end
119
123
 
120
- def to_s
121
- "voice #{@name} output: #{@output} channel: #{@channel}"
124
+ def to_s
125
+ "voice #{@name} output: #{@output} channel: #{@channel}"
122
126
  end
123
127
 
124
- private
128
+ private
125
129
 
126
- def fill_active_pitches(pitches)
127
- (0..127).each do |pitch|
128
- pitches[pitch] = { note_controls: Set[], velocity: 0 }
130
+ def fill_active_pitches(pitches)
131
+ (0..127).each do |pitch|
132
+ pitches[pitch] = { note_controls: Set[], velocity: 0 }
133
+ end
129
134
  end
130
- end
131
135
 
132
- class ControllersControl
133
- def initialize(output, channel)
134
- @output = output
135
- @channel = channel
136
+ class ControllersControl
137
+ def initialize(output, channel)
138
+ @output = output
139
+ @channel = channel
136
140
 
137
- @controller_map = { sustain_pedal: 0x40 }
138
- @controller = []
139
- end
141
+ @controller_map = { sustain_pedal: 0x40 }
142
+ @controller = []
143
+ end
140
144
 
141
- def []=(controller_number_or_symbol, value)
142
- number = number_of(controller_number_or_symbol)
143
- value ||= 0
145
+ def []=(controller_number_or_symbol, value)
146
+ number = number_of(controller_number_or_symbol)
147
+ value ||= 0
144
148
 
145
- @controller[number] = [[0, value].max, 0xff].min
146
- @output.puts MIDIMessage::ChannelMessage.new(0xb, @channel, number, @controller[number])
147
- end
149
+ @controller[number] = [[0, value].max, 0xff].min
150
+ @output.puts MIDIMessage::ChannelMessage.new(0xb, @channel, number, @controller[number])
151
+ end
148
152
 
149
- def [](controller_number_or_symbol)
150
- @controller[number_of(controller_number_or_symbol)]
151
- end
153
+ def [](controller_number_or_symbol)
154
+ @controller[number_of(controller_number_or_symbol)]
155
+ end
152
156
 
153
- def number_of(controller_number_or_symbol)
154
- case controller_number_or_symbol
155
- when Numeric
156
- controller_number_or_symbol.to_i
157
- when Symbol
158
- @controller_map[controller_number_or_symbol]
159
- else
160
- raise ArgumentError, "#{controller_number_or_symbol} is not a Numeric nor a Symbol. Only MIDI controller numbers are allowed"
157
+ def number_of(controller_number_or_symbol)
158
+ case controller_number_or_symbol
159
+ when Numeric
160
+ controller_number_or_symbol.to_i
161
+ when Symbol
162
+ @controller_map[controller_number_or_symbol]
163
+ else
164
+ raise ArgumentError, "#{controller_number_or_symbol} is not a Numeric nor a Symbol. Only MIDI controller numbers are allowed"
165
+ end
161
166
  end
162
167
  end
163
- end
164
168
 
165
- private_constant :ControllersControl
169
+ private_constant :ControllersControl
166
170
 
167
- class NoteControl
168
- attr_reader :start_position, :end_position
171
+ class NoteControl
172
+ attr_reader :start_position, :end_position
169
173
 
170
- def initialize(voice, pitch:, velocity: nil, duration: nil, velocity_off: nil)
171
- raise ArgumentError, "MIDIVoice: note duration should be nil or Numeric: #{duration} (#{duration.class})" unless duration.nil? || duration.is_a?(Numeric)
174
+ def initialize(voice, pitch:, velocity: nil, duration: nil, velocity_off: nil)
175
+ raise ArgumentError, "MIDIVoice: note duration should be nil or Numeric: #{duration} (#{duration.class})" unless duration.nil? || duration.is_a?(Numeric)
172
176
 
173
- @voice = voice
177
+ @voice = voice
174
178
 
175
- @pitch = pitch.arrayfy.explode_ranges
179
+ @pitch = pitch.arrayfy.explode_ranges
176
180
 
177
- @velocity = velocity.arrayfy.explode_ranges
178
- @velocity_off = velocity_off.arrayfy.explode_ranges
181
+ @velocity = velocity.arrayfy.explode_ranges
182
+ @velocity_off = velocity_off.arrayfy.explode_ranges
179
183
 
180
- @duration = duration
184
+ @duration = duration
181
185
 
182
- @do_on_stop = []
183
- @do_after = []
186
+ @do_on_stop = []
187
+ @do_after = []
184
188
 
185
- @start_position = @end_position = nil
186
- end
189
+ @start_position = @end_position = nil
190
+ end
187
191
 
188
- def note_on
189
- @start_position = @voice.sequencer.position
190
- @end_position = nil
192
+ def note_on
193
+ @start_position = @voice.sequencer.position
194
+ @end_position = nil
191
195
 
192
- @pitch.each_index do |i|
193
- pitch = @pitch[i]
194
- velocity = @velocity[i % @velocity.size]
196
+ @pitch.each_index do |i|
197
+ pitch = @pitch[i]
198
+ velocity = @velocity[i % @velocity.size]
195
199
 
196
- if !silence?(pitch)
197
- @voice.active_pitches[pitch][:note_controls] << self
198
- @voice.active_pitches[pitch][:velocity] = velocity
200
+ if !silence?(pitch)
201
+ @voice.active_pitches[pitch][:note_controls] << self
202
+ @voice.active_pitches[pitch][:velocity] = velocity
199
203
 
200
- msg = MIDIMessage::NoteOn.new(@voice.channel, pitch, velocity)
201
- @voice.log "#{msg.verbose_name} velocity: #{velocity} duration: #{@duration}"
202
- @voice.output.puts msg if @voice.output && !@voice.fast_forward?
203
- else
204
- @voice.log "silence duration: #{@duration}"
204
+ msg = MIDIMessage::NoteOn.new(@voice.channel, pitch, velocity)
205
+ @voice.log "#{msg.verbose_name} velocity: #{velocity} duration: #{@duration}"
206
+ @voice.output.puts msg if @voice.output && !@voice.fast_forward?
207
+ else
208
+ @voice.log "silence duration: #{@duration}"
209
+ end
205
210
  end
206
- end
207
211
 
208
- return self unless @duration
212
+ return self unless @duration
213
+
214
+ this = self
215
+ @voice.sequencer.wait @duration do
216
+ this.note_off velocity: @velocity_off
217
+ end
209
218
 
210
- this = self
211
- @voice.sequencer.wait @duration do
212
- this.note_off velocity: @velocity_off
219
+ self
213
220
  end
214
221
 
215
- self
216
- end
222
+ def note_off(velocity: nil)
223
+ velocity ||= @velocity_off
217
224
 
218
- def note_off(velocity: nil)
219
- velocity ||= @velocity_off
225
+ velocity = velocity.arrayfy.explode_ranges
220
226
 
221
- velocity = velocity.arrayfy.explode_ranges
227
+ @pitch.each_index do |i|
228
+ pitch = @pitch[i]
229
+ velocity_off = velocity[i % velocity.size]
222
230
 
223
- @pitch.each_index do |i|
224
- pitch = @pitch[i]
225
- velocity_off = velocity[i % velocity.size]
231
+ next if silence?(pitch)
226
232
 
227
- next if silence?(pitch)
233
+ @voice.active_pitches[pitch][:note_controls].delete self
228
234
 
229
- @voice.active_pitches[pitch][:note_controls].delete self
235
+ next unless @voice.active_pitches[pitch][:note_controls].empty?
236
+
237
+ msg = MIDIMessage::NoteOff.new(@voice.channel, pitch, velocity_off)
238
+ @voice.log msg.verbose_name.to_s
239
+ @voice.output.puts msg if @voice.output && !@voice.fast_forward?
240
+ end
230
241
 
231
- next unless @voice.active_pitches[pitch][:note_controls].empty?
242
+ @end_position = @voice.sequencer.position
232
243
 
233
- msg = MIDIMessage::NoteOff.new(@voice.channel, pitch, velocity_off)
234
- @voice.log msg.verbose_name.to_s
235
- @voice.output.puts msg if @voice.output && !@voice.fast_forward?
236
- end
244
+ @do_on_stop.each do |do_on_stop|
245
+ @voice.sequencer.wait 0, &do_on_stop
246
+ end
237
247
 
238
- @end_position = @voice.sequencer.position
248
+ @do_after.each do |do_after|
249
+ @voice.sequencer.wait @voice.tick_duration + do_after[:bars], &do_after[:block]
250
+ end
239
251
 
240
- @do_on_stop.each do |do_on_stop|
241
- @voice.sequencer.wait 0, &do_on_stop
252
+ nil
242
253
  end
243
254
 
244
- @do_after.each do |do_after|
245
- @voice.sequencer.wait @voice.tick_duration + do_after[:bars], &do_after[:block]
255
+ def active?
256
+ @start_position && !@end_position
246
257
  end
247
258
 
248
- nil
249
- end
259
+ def on_stop(&block)
260
+ @do_on_stop << block
261
+ nil
262
+ end
250
263
 
251
- def active?
252
- @start_position && !@end_position
253
- end
264
+ def after(bars = 0, &block)
265
+ @do_after << { bars: bars.rationalize, block: block }
266
+ nil
267
+ end
254
268
 
255
- def on_stop(&block)
256
- @do_on_stop << block
257
- nil
258
- end
269
+ private
259
270
 
260
- def after(bars = 0, &block)
261
- @do_after << { bars: bars.rationalize, block: block }
262
- nil
271
+ def silence?(pitch)
272
+ pitch.nil? || pitch == :silence
273
+ end
263
274
  end
264
275
 
265
- private
266
-
267
- def silence?(pitch)
268
- pitch.nil? || pitch == :silence
269
- end
276
+ private_constant :NoteControl
270
277
  end
271
-
272
- private_constant :NoteControl
273
278
  end
274
279
  end