deftones 0.1.0

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +5 -0
  3. data/CHANGELOG.md +12 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +197 -0
  6. data/Rakefile +8 -0
  7. data/examples/poly_chord.rb +14 -0
  8. data/examples/render_sampler.rb +16 -0
  9. data/examples/render_sequence.rb +13 -0
  10. data/examples/render_synth.rb +18 -0
  11. data/lib/deftones/analysis/analyser.rb +162 -0
  12. data/lib/deftones/analysis/dc_meter.rb +56 -0
  13. data/lib/deftones/analysis/fft.rb +128 -0
  14. data/lib/deftones/analysis/meter.rb +83 -0
  15. data/lib/deftones/analysis/waveform.rb +93 -0
  16. data/lib/deftones/component/amplitude_envelope.rb +8 -0
  17. data/lib/deftones/component/biquad_filter.rb +7 -0
  18. data/lib/deftones/component/channel.rb +109 -0
  19. data/lib/deftones/component/compressor.rb +64 -0
  20. data/lib/deftones/component/convolver.rb +99 -0
  21. data/lib/deftones/component/cross_fade.rb +67 -0
  22. data/lib/deftones/component/envelope.rb +160 -0
  23. data/lib/deftones/component/eq3.rb +73 -0
  24. data/lib/deftones/component/feedback_comb_filter.rb +75 -0
  25. data/lib/deftones/component/filter.rb +75 -0
  26. data/lib/deftones/component/follower.rb +55 -0
  27. data/lib/deftones/component/frequency_envelope.rb +24 -0
  28. data/lib/deftones/component/gate.rb +46 -0
  29. data/lib/deftones/component/lfo.rb +88 -0
  30. data/lib/deftones/component/limiter.rb +11 -0
  31. data/lib/deftones/component/lowpass_comb_filter.rb +43 -0
  32. data/lib/deftones/component/merge.rb +45 -0
  33. data/lib/deftones/component/mid_side_compressor.rb +43 -0
  34. data/lib/deftones/component/mid_side_merge.rb +53 -0
  35. data/lib/deftones/component/mid_side_split.rb +54 -0
  36. data/lib/deftones/component/mono.rb +8 -0
  37. data/lib/deftones/component/multiband_compressor.rb +70 -0
  38. data/lib/deftones/component/multiband_split.rb +133 -0
  39. data/lib/deftones/component/one_pole_filter.rb +71 -0
  40. data/lib/deftones/component/pan_vol.rb +26 -0
  41. data/lib/deftones/component/panner.rb +75 -0
  42. data/lib/deftones/component/panner3d.rb +322 -0
  43. data/lib/deftones/component/solo.rb +56 -0
  44. data/lib/deftones/component/split.rb +47 -0
  45. data/lib/deftones/component/volume.rb +31 -0
  46. data/lib/deftones/context.rb +213 -0
  47. data/lib/deftones/core/audio_block.rb +82 -0
  48. data/lib/deftones/core/audio_node.rb +262 -0
  49. data/lib/deftones/core/clock.rb +91 -0
  50. data/lib/deftones/core/computed_signal.rb +69 -0
  51. data/lib/deftones/core/delay.rb +44 -0
  52. data/lib/deftones/core/effect.rb +66 -0
  53. data/lib/deftones/core/emitter.rb +51 -0
  54. data/lib/deftones/core/gain.rb +39 -0
  55. data/lib/deftones/core/instrument.rb +109 -0
  56. data/lib/deftones/core/param.rb +31 -0
  57. data/lib/deftones/core/signal.rb +452 -0
  58. data/lib/deftones/core/signal_operator_methods.rb +73 -0
  59. data/lib/deftones/core/signal_operators.rb +138 -0
  60. data/lib/deftones/core/signal_shapers.rb +83 -0
  61. data/lib/deftones/core/source.rb +213 -0
  62. data/lib/deftones/core/synced_signal.rb +88 -0
  63. data/lib/deftones/destination.rb +132 -0
  64. data/lib/deftones/draw.rb +100 -0
  65. data/lib/deftones/dsp/biquad.rb +129 -0
  66. data/lib/deftones/dsp/delay_line.rb +41 -0
  67. data/lib/deftones/dsp/helpers.rb +25 -0
  68. data/lib/deftones/effect/auto_filter.rb +92 -0
  69. data/lib/deftones/effect/auto_panner.rb +57 -0
  70. data/lib/deftones/effect/auto_wah.rb +98 -0
  71. data/lib/deftones/effect/bit_crusher.rb +38 -0
  72. data/lib/deftones/effect/chebyshev.rb +36 -0
  73. data/lib/deftones/effect/chorus.rb +73 -0
  74. data/lib/deftones/effect/distortion.rb +22 -0
  75. data/lib/deftones/effect/feedback_delay.rb +38 -0
  76. data/lib/deftones/effect/freeverb.rb +11 -0
  77. data/lib/deftones/effect/frequency_shifter.rb +89 -0
  78. data/lib/deftones/effect/jc_reverb.rb +11 -0
  79. data/lib/deftones/effect/modulation_control.rb +159 -0
  80. data/lib/deftones/effect/phaser.rb +72 -0
  81. data/lib/deftones/effect/ping_pong_delay.rb +40 -0
  82. data/lib/deftones/effect/pitch_shift.rb +156 -0
  83. data/lib/deftones/effect/reverb.rb +71 -0
  84. data/lib/deftones/effect/stereo_widener.rb +34 -0
  85. data/lib/deftones/effect/tremolo.rb +52 -0
  86. data/lib/deftones/effect/vibrato.rb +47 -0
  87. data/lib/deftones/event/callback_behavior.rb +61 -0
  88. data/lib/deftones/event/loop.rb +53 -0
  89. data/lib/deftones/event/part.rb +51 -0
  90. data/lib/deftones/event/pattern.rb +94 -0
  91. data/lib/deftones/event/sequence.rb +87 -0
  92. data/lib/deftones/event/tone_event.rb +77 -0
  93. data/lib/deftones/event/transport.rb +276 -0
  94. data/lib/deftones/instrument/am_synth.rb +56 -0
  95. data/lib/deftones/instrument/duo_synth.rb +68 -0
  96. data/lib/deftones/instrument/fm_synth.rb +60 -0
  97. data/lib/deftones/instrument/membrane_synth.rb +60 -0
  98. data/lib/deftones/instrument/metal_synth.rb +61 -0
  99. data/lib/deftones/instrument/mono_synth.rb +88 -0
  100. data/lib/deftones/instrument/noise_synth.rb +56 -0
  101. data/lib/deftones/instrument/pluck_synth.rb +41 -0
  102. data/lib/deftones/instrument/poly_synth.rb +96 -0
  103. data/lib/deftones/instrument/sampler.rb +97 -0
  104. data/lib/deftones/instrument/synth.rb +60 -0
  105. data/lib/deftones/io/buffer.rb +352 -0
  106. data/lib/deftones/io/buffers.rb +77 -0
  107. data/lib/deftones/io/recorder.rb +89 -0
  108. data/lib/deftones/listener.rb +120 -0
  109. data/lib/deftones/music/frequency.rb +128 -0
  110. data/lib/deftones/music/midi.rb +206 -0
  111. data/lib/deftones/music/note.rb +58 -0
  112. data/lib/deftones/music/ticks.rb +106 -0
  113. data/lib/deftones/music/time.rb +209 -0
  114. data/lib/deftones/music/transport_time.rb +94 -0
  115. data/lib/deftones/music/unit_helpers.rb +30 -0
  116. data/lib/deftones/offline_context.rb +46 -0
  117. data/lib/deftones/portaudio_support.rb +112 -0
  118. data/lib/deftones/source/am_oscillator.rb +42 -0
  119. data/lib/deftones/source/fat_oscillator.rb +49 -0
  120. data/lib/deftones/source/fm_oscillator.rb +47 -0
  121. data/lib/deftones/source/grain_player.rb +198 -0
  122. data/lib/deftones/source/karplus_strong.rb +51 -0
  123. data/lib/deftones/source/noise.rb +99 -0
  124. data/lib/deftones/source/omni_oscillator.rb +175 -0
  125. data/lib/deftones/source/oscillator.rb +74 -0
  126. data/lib/deftones/source/player.rb +228 -0
  127. data/lib/deftones/source/players.rb +133 -0
  128. data/lib/deftones/source/pulse_oscillator.rb +38 -0
  129. data/lib/deftones/source/pwm_oscillator.rb +49 -0
  130. data/lib/deftones/source/tone_buffer_source.rb +136 -0
  131. data/lib/deftones/source/tone_oscillator_node.rb +65 -0
  132. data/lib/deftones/source/user_media.rb +519 -0
  133. data/lib/deftones/version.rb +5 -0
  134. data/lib/deftones.rb +542 -0
  135. metadata +221 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module IO
5
+ class Buffers
6
+ include Enumerable
7
+
8
+ def initialize(buffers = {})
9
+ @buffers = {}
10
+ @disposed = false
11
+ merge(buffers)
12
+ end
13
+
14
+ def add(name, buffer)
15
+ @buffers[key_for(name)] = normalize_buffer(buffer)
16
+ self
17
+ end
18
+
19
+ def get(name)
20
+ self[name]
21
+ end
22
+
23
+ def [](name)
24
+ @buffers[key_for(name)]
25
+ end
26
+
27
+ def fetch(name)
28
+ @buffers.fetch(key_for(name))
29
+ end
30
+
31
+ def has?(name)
32
+ @buffers.key?(key_for(name))
33
+ end
34
+
35
+ def loaded?
36
+ !@disposed
37
+ end
38
+
39
+ def each(&block)
40
+ return enum_for(:each) unless block
41
+
42
+ @buffers.each(&block)
43
+ end
44
+
45
+ def keys
46
+ @buffers.keys
47
+ end
48
+
49
+ def to_h
50
+ @buffers.dup
51
+ end
52
+
53
+ def merge(buffers)
54
+ buffers.each { |name, buffer| add(name, buffer) }
55
+ self
56
+ end
57
+
58
+ def dispose
59
+ @buffers.clear
60
+ @disposed = true
61
+ self
62
+ end
63
+
64
+ alias loaded loaded?
65
+
66
+ private
67
+
68
+ def key_for(name)
69
+ name.to_sym
70
+ end
71
+
72
+ def normalize_buffer(buffer)
73
+ buffer.is_a?(Buffer) ? buffer : Buffer.load(buffer)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module IO
5
+ class Recorder
6
+ attr_reader :state, :mime_type, :captured_buffer
7
+
8
+ MIME_TYPES = {
9
+ wav: "audio/wav",
10
+ mp3: "audio/mpeg",
11
+ ogg: "audio/ogg"
12
+ }.freeze
13
+
14
+ def initialize(context:, node: nil, mime_type: "audio/wav")
15
+ @context = context
16
+ @node = node || context.output
17
+ @captured_buffer = nil
18
+ @mime_type = mime_type
19
+ @state = :stopped
20
+ @started_at = nil
21
+ @recorded_duration = 0.0
22
+ end
23
+
24
+ def start
25
+ @captured_buffer = nil
26
+ @started_at = @context.current_time
27
+ @state = :started
28
+ self
29
+ end
30
+
31
+ def stop(path: nil, format: nil, duration: nil)
32
+ return @captured_buffer if @state == :stopped && @captured_buffer
33
+
34
+ @recorded_duration = duration || elapsed_duration
35
+ @captured_buffer = capture_buffer(duration: @recorded_duration)
36
+ @state = :stopped
37
+ save(path, format: resolve_format(format, path)) if path
38
+ @captured_buffer
39
+ end
40
+
41
+ def record(duration: nil, format: :wav, path: nil)
42
+ start
43
+ stop(path: path, format: format, duration: duration)
44
+ end
45
+
46
+ def save(path, format: :wav)
47
+ raise ArgumentError, "Nothing recorded yet" unless @captured_buffer
48
+
49
+ @captured_buffer.save(path, format: format)
50
+ end
51
+
52
+ def dispose
53
+ @captured_buffer = nil
54
+ @state = :stopped
55
+ @started_at = nil
56
+ @recorded_duration = 0.0
57
+ self
58
+ end
59
+
60
+ alias mimeType mime_type
61
+
62
+ private
63
+
64
+ def capture_buffer(duration:)
65
+ if @context.is_a?(Deftones::OfflineContext)
66
+ @context.render
67
+ else
68
+ seconds = [duration.to_f, 1.0 / @context.sample_rate].max
69
+ frames = (seconds * @context.sample_rate).ceil
70
+ block = @node.send(:render_block, frames, 0, {}).fit_channels(@context.channels)
71
+ Buffer.new(block.interleaved, channels: @context.channels, sample_rate: @context.sample_rate)
72
+ end
73
+ end
74
+
75
+ def elapsed_duration
76
+ return 0.0 unless @started_at
77
+
78
+ [@context.current_time - @started_at, 0.0].max
79
+ end
80
+
81
+ def resolve_format(format, path)
82
+ return format if format
83
+ return File.extname(path).delete_prefix(".").downcase.to_sym if path
84
+
85
+ MIME_TYPES.key(@mime_type) || :wav
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ class Listener
5
+ attr_reader :forward_x, :forward_y, :forward_z, :position_x, :position_y, :position_z, :up_x, :up_y, :up_z
6
+
7
+ def initialize(context: Deftones.context)
8
+ @position_x = Core::Signal.new(value: 0.0, units: :number, context: context)
9
+ @position_y = Core::Signal.new(value: 0.0, units: :number, context: context)
10
+ @position_z = Core::Signal.new(value: 0.0, units: :number, context: context)
11
+ @forward_x = Core::Signal.new(value: 0.0, units: :number, context: context)
12
+ @forward_y = Core::Signal.new(value: 0.0, units: :number, context: context)
13
+ @forward_z = Core::Signal.new(value: -1.0, units: :number, context: context)
14
+ @up_x = Core::Signal.new(value: 0.0, units: :number, context: context)
15
+ @up_y = Core::Signal.new(value: 1.0, units: :number, context: context)
16
+ @up_z = Core::Signal.new(value: 0.0, units: :number, context: context)
17
+ end
18
+
19
+ def position_x=(value)
20
+ @position_x.value = value
21
+ end
22
+
23
+ def position_y=(value)
24
+ @position_y.value = value
25
+ end
26
+
27
+ def position_z=(value)
28
+ @position_z.value = value
29
+ end
30
+
31
+ def forward_x=(value)
32
+ @forward_x.value = value
33
+ end
34
+
35
+ def forward_y=(value)
36
+ @forward_y.value = value
37
+ end
38
+
39
+ def forward_z=(value)
40
+ @forward_z.value = value
41
+ end
42
+
43
+ def up_x=(value)
44
+ @up_x.value = value
45
+ end
46
+
47
+ def up_y=(value)
48
+ @up_y.value = value
49
+ end
50
+
51
+ def up_z=(value)
52
+ @up_z.value = value
53
+ end
54
+
55
+ def set_position(x, y, z)
56
+ self.position_x = x
57
+ self.position_y = y
58
+ self.position_z = z
59
+ self
60
+ end
61
+
62
+ def set_orientation(forward_x, forward_y, forward_z, up_x = 0.0, up_y = 1.0, up_z = 0.0)
63
+ self.forward_x = forward_x
64
+ self.forward_y = forward_y
65
+ self.forward_z = forward_z
66
+ self.up_x = up_x
67
+ self.up_y = up_y
68
+ self.up_z = up_z
69
+ self
70
+ end
71
+
72
+ alias positionX position_x
73
+ alias positionY position_y
74
+ alias positionZ position_z
75
+ alias forwardX forward_x
76
+ alias forwardY forward_y
77
+ alias forwardZ forward_z
78
+ alias upX up_x
79
+ alias upY up_y
80
+ alias upZ up_z
81
+ alias setPosition set_position
82
+ alias setOrientation set_orientation
83
+
84
+ def positionX=(value)
85
+ self.position_x = value
86
+ end
87
+
88
+ def positionY=(value)
89
+ self.position_y = value
90
+ end
91
+
92
+ def positionZ=(value)
93
+ self.position_z = value
94
+ end
95
+
96
+ def forwardX=(value)
97
+ self.forward_x = value
98
+ end
99
+
100
+ def forwardY=(value)
101
+ self.forward_y = value
102
+ end
103
+
104
+ def forwardZ=(value)
105
+ self.forward_z = value
106
+ end
107
+
108
+ def upX=(value)
109
+ self.up_x = value
110
+ end
111
+
112
+ def upY=(value)
113
+ self.up_y = value
114
+ end
115
+
116
+ def upZ=(value)
117
+ self.up_z = value
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ class Frequency
6
+ attr_reader :value, :transport
7
+
8
+ def initialize(value, transport: Deftones.transport)
9
+ @value = value
10
+ @transport = transport
11
+ @disposed = false
12
+ end
13
+
14
+ def to_hz
15
+ self.class.parse(value)
16
+ end
17
+
18
+ def to_frequency
19
+ to_hz
20
+ end
21
+
22
+ def to_seconds
23
+ self.class.to_period(value)
24
+ end
25
+
26
+ def to_midi
27
+ self.class.to_midi(value)
28
+ end
29
+
30
+ def to_ticks
31
+ transport.seconds_to_ticks(to_seconds)
32
+ end
33
+
34
+ def to_bars_beats_sixteenths
35
+ transport.seconds_to_position(to_seconds)
36
+ end
37
+
38
+ def to_milliseconds
39
+ to_seconds * 1000.0
40
+ end
41
+
42
+ def to_samples(sample_rate = Deftones.context.sample_rate)
43
+ UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
44
+ end
45
+
46
+ def to_notation
47
+ UnitHelpers.closest_notation(to_seconds, transport: transport)
48
+ end
49
+
50
+ def to_note
51
+ Note.from_frequency(to_hz)
52
+ end
53
+
54
+ def transpose(interval)
55
+ self.class.new(Note.from_midi(to_midi + interval.to_i), transport: transport)
56
+ end
57
+
58
+ def harmonize(intervals)
59
+ Array(intervals).map { |interval| transpose(interval) }
60
+ end
61
+
62
+ def quantize(subdiv, percent = 1.0)
63
+ quantized_seconds = UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
64
+ 1.0 / [quantized_seconds, 1.0e-6].max
65
+ end
66
+
67
+ def from_type(type)
68
+ @value =
69
+ if type.respond_to?(:to_frequency)
70
+ type.to_frequency
71
+ elsif type.respond_to?(:value_of)
72
+ type.value_of
73
+ else
74
+ type
75
+ end
76
+ self
77
+ end
78
+
79
+ def dispose
80
+ @disposed = true
81
+ self
82
+ end
83
+
84
+ def disposed?
85
+ @disposed
86
+ end
87
+
88
+ def to_s
89
+ value.to_s
90
+ end
91
+
92
+ alias toString to_s
93
+
94
+ def value_of
95
+ to_hz
96
+ end
97
+
98
+ class << self
99
+ def mtof(value)
100
+ Deftones.mtof(value)
101
+ end
102
+
103
+ def ftom(value)
104
+ Deftones.ftom(value)
105
+ end
106
+
107
+ def parse(value)
108
+ case value
109
+ when Numeric
110
+ value.to_f
111
+ when /\A(\d+(?:\.\d+)?)hz\z/i
112
+ Regexp.last_match(1).to_f
113
+ else
114
+ Note.to_frequency(value)
115
+ end
116
+ end
117
+
118
+ def to_period(value)
119
+ 1.0 / parse(value)
120
+ end
121
+
122
+ def to_midi(value)
123
+ Note.to_midi(Note.from_frequency(parse(value)))
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "unimidi"
5
+ rescue LoadError
6
+ nil
7
+ end
8
+
9
+ module Deftones
10
+ module Music
11
+ class Midi
12
+ attr_reader :value, :transport
13
+
14
+ def initialize(value, transport: Deftones.transport)
15
+ @value = value
16
+ @transport = transport
17
+ @disposed = false
18
+ end
19
+
20
+ def to_i
21
+ self.class.parse(value)
22
+ end
23
+
24
+ def to_frequency
25
+ Note.to_frequency(to_note)
26
+ end
27
+
28
+ def to_seconds
29
+ 1.0 / to_frequency
30
+ end
31
+
32
+ def to_ticks
33
+ transport.seconds_to_ticks(to_seconds)
34
+ end
35
+
36
+ def to_bars_beats_sixteenths
37
+ transport.seconds_to_position(to_seconds)
38
+ end
39
+
40
+ def to_milliseconds
41
+ to_seconds * 1000.0
42
+ end
43
+
44
+ def to_samples(sample_rate = Deftones.context.sample_rate)
45
+ UnitHelpers.samples_for_seconds(to_seconds, sample_rate)
46
+ end
47
+
48
+ def to_notation
49
+ UnitHelpers.closest_notation(to_seconds, transport: transport)
50
+ end
51
+
52
+ def transpose(interval)
53
+ self.class.new(to_i + interval.to_i, transport: transport)
54
+ end
55
+
56
+ def harmonize(intervals)
57
+ Array(intervals).map { |interval| transpose(interval) }
58
+ end
59
+
60
+ def quantize(subdiv, percent = 1.0)
61
+ quantized_seconds = UnitHelpers.quantize_seconds(to_seconds, subdiv, transport: transport, percent: percent)
62
+ Note.to_midi(Note.from_frequency(1.0 / [quantized_seconds, 1.0e-6].max))
63
+ end
64
+
65
+ def from_type(type)
66
+ @value =
67
+ if type.respond_to?(:to_midi)
68
+ type.to_midi
69
+ elsif type.respond_to?(:value_of)
70
+ type.value_of
71
+ else
72
+ type
73
+ end
74
+ self
75
+ end
76
+
77
+ def dispose
78
+ @disposed = true
79
+ self
80
+ end
81
+
82
+ def disposed?
83
+ @disposed
84
+ end
85
+
86
+ def to_s
87
+ value.to_s
88
+ end
89
+
90
+ alias toString to_s
91
+
92
+ def to_note
93
+ Note.from_midi(to_i)
94
+ end
95
+
96
+ def value_of
97
+ to_i
98
+ end
99
+
100
+ class << self
101
+ def parse(value)
102
+ return value.to_i if value.is_a?(Numeric)
103
+
104
+ Note.to_midi(value)
105
+ end
106
+
107
+ def available?
108
+ !!defined?(UniMIDI)
109
+ end
110
+
111
+ def input_devices
112
+ return [] unless available?
113
+
114
+ UniMIDI::Input.all
115
+ end
116
+
117
+ def output_devices
118
+ return [] unless available?
119
+
120
+ UniMIDI::Output.all
121
+ end
122
+
123
+ def find_input(name = nil)
124
+ find_device(input_devices, name)
125
+ end
126
+
127
+ def find_output(name = nil)
128
+ find_device(output_devices, name)
129
+ end
130
+
131
+ def open_input(name = nil, *args, &block)
132
+ open_device(find_input(name), *args, &block)
133
+ end
134
+
135
+ def open_output(name = nil, *args, &block)
136
+ open_device(find_output(name), *args, &block)
137
+ end
138
+
139
+ def receive(name = nil, *args)
140
+ open_input(name) do |input|
141
+ input.gets(*args)
142
+ end
143
+ end
144
+
145
+ def send_message(message, device: nil)
146
+ open_output(device) do |output|
147
+ output.puts(message)
148
+ end
149
+ end
150
+
151
+ def note_on(note, velocity: 100, channel: 1, device: nil)
152
+ send_message([status_byte(0x90, channel), normalize_note(note), normalize_data_byte(velocity)], device: device)
153
+ end
154
+
155
+ def note_off(note, velocity: 0, channel: 1, device: nil)
156
+ send_message([status_byte(0x80, channel), normalize_note(note), normalize_data_byte(velocity)], device: device)
157
+ end
158
+
159
+ def control_change(controller, value, channel: 1, device: nil)
160
+ send_message(
161
+ [status_byte(0xB0, channel), normalize_data_byte(controller), normalize_data_byte(value)],
162
+ device: device
163
+ )
164
+ end
165
+
166
+ private
167
+
168
+ def find_device(devices, name)
169
+ return devices.first if name.nil?
170
+
171
+ devices.find { |device| device.name == name.to_s }
172
+ end
173
+
174
+ def open_device(device, *args, &block)
175
+ raise ArgumentError, "MIDI support is unavailable" unless available?
176
+ raise ArgumentError, "No matching MIDI device found" unless device
177
+
178
+ return device.open(*args) unless block
179
+
180
+ device.open(*args)
181
+ begin
182
+ block.call(device)
183
+ ensure
184
+ device.close if device.respond_to?(:close)
185
+ end
186
+ end
187
+
188
+ def normalize_note(note)
189
+ normalize_data_byte(parse(note))
190
+ end
191
+
192
+ def normalize_data_byte(value)
193
+ value.to_i.clamp(0, 127)
194
+ end
195
+
196
+ def status_byte(base, channel)
197
+ base + normalize_channel(channel)
198
+ end
199
+
200
+ def normalize_channel(channel)
201
+ [[channel.to_i - 1, 0].max, 15].min
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Music
5
+ class Note
6
+ NOTE_NAMES = %w[C C# D D# E F F# G G# A A# B].freeze
7
+ FLAT_MAP = {
8
+ "Cb" => "B",
9
+ "Db" => "C#",
10
+ "Eb" => "D#",
11
+ "Fb" => "E",
12
+ "Gb" => "F#",
13
+ "Ab" => "G#",
14
+ "Bb" => "A#"
15
+ }.freeze
16
+
17
+ class << self
18
+ def to_frequency(note_name)
19
+ midi_number = to_midi(note_name)
20
+ 440.0 * (2.0**((midi_number - 69) / 12.0))
21
+ end
22
+
23
+ def to_midi(note_name)
24
+ name, octave = parse_note_name(note_name)
25
+ NOTE_NAMES.index(name) + ((octave + 1) * 12)
26
+ end
27
+
28
+ def from_midi(midi_number)
29
+ integer = midi_number.to_i
30
+ octave = (integer / 12) - 1
31
+ "#{NOTE_NAMES[integer % 12]}#{octave}"
32
+ end
33
+
34
+ def from_frequency(frequency)
35
+ midi_number = (12 * Math.log2(frequency.to_f / 440.0) + 69).round
36
+ from_midi(midi_number)
37
+ end
38
+
39
+ private
40
+
41
+ def parse_note_name(note_name)
42
+ match = note_name.to_s.match(/\A([A-Ga-g][#b]?)(-?\d+)\z/)
43
+ raise ArgumentError, "Invalid note: #{note_name}" unless match
44
+
45
+ normalized_name = normalize_name(match[1])
46
+ raise ArgumentError, "Unsupported note name: #{note_name}" unless NOTE_NAMES.include?(normalized_name)
47
+
48
+ [normalized_name, match[2].to_i]
49
+ end
50
+
51
+ def normalize_name(token)
52
+ canonical = token[0].upcase + token[1..]
53
+ FLAT_MAP.fetch(canonical, canonical.upcase)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end