deftones 0.1.0 → 1.0.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -6
  3. data/README.md +5 -0
  4. data/Rakefile +50 -1
  5. data/lib/deftones/analysis/meter.rb +22 -2
  6. data/lib/deftones/component/channel.rb +1 -1
  7. data/lib/deftones/component/compressor.rb +127 -22
  8. data/lib/deftones/component/filter.rb +29 -19
  9. data/lib/deftones/component/merge.rb +14 -0
  10. data/lib/deftones/component/multiband_compressor.rb +1 -1
  11. data/lib/deftones/component/one_pole_filter.rb +10 -3
  12. data/lib/deftones/component/panner.rb +25 -2
  13. data/lib/deftones/component/panner3d.rb +0 -10
  14. data/lib/deftones/component/split.rb +14 -0
  15. data/lib/deftones/context.rb +90 -9
  16. data/lib/deftones/core/audio_block.rb +64 -5
  17. data/lib/deftones/core/audio_node.rb +98 -8
  18. data/lib/deftones/core/gain.rb +0 -8
  19. data/lib/deftones/core/instrument.rb +52 -10
  20. data/lib/deftones/core/param.rb +51 -1
  21. data/lib/deftones/core/signal.rb +79 -28
  22. data/lib/deftones/core/source.rb +71 -11
  23. data/lib/deftones/destination.rb +41 -17
  24. data/lib/deftones/draw.rb +6 -10
  25. data/lib/deftones/dsp/biquad.rb +9 -4
  26. data/lib/deftones/dsp/delay_line.rb +2 -2
  27. data/lib/deftones/dsp/helpers.rb +7 -0
  28. data/lib/deftones/effect/bit_crusher.rb +10 -2
  29. data/lib/deftones/effect/chebyshev.rb +7 -3
  30. data/lib/deftones/effect/distortion.rb +5 -3
  31. data/lib/deftones/effect/feedback_delay.rb +2 -1
  32. data/lib/deftones/effect/oversampling.rb +43 -0
  33. data/lib/deftones/effect/phaser.rb +2 -1
  34. data/lib/deftones/effect/pitch_shift.rb +1 -2
  35. data/lib/deftones/effect/reverb.rb +73 -5
  36. data/lib/deftones/event/callback_behavior.rb +7 -3
  37. data/lib/deftones/event/loop.rb +7 -2
  38. data/lib/deftones/event/part.rb +18 -3
  39. data/lib/deftones/event/pattern.rb +51 -6
  40. data/lib/deftones/event/sequence.rb +19 -5
  41. data/lib/deftones/event/tone_event.rb +7 -2
  42. data/lib/deftones/event/transport.rb +243 -21
  43. data/lib/deftones/instrument/poly_synth.rb +81 -15
  44. data/lib/deftones/instrument/sampler.rb +53 -10
  45. data/lib/deftones/io/buffer.rb +376 -55
  46. data/lib/deftones/io/buffers.rb +28 -4
  47. data/lib/deftones/io/recorder.rb +2 -1
  48. data/lib/deftones/music/frequency.rb +13 -8
  49. data/lib/deftones/music/midi.rb +132 -9
  50. data/lib/deftones/music/note.rb +13 -3
  51. data/lib/deftones/music/time.rb +42 -4
  52. data/lib/deftones/offline_context.rb +194 -17
  53. data/lib/deftones/portaudio_support.rb +68 -9
  54. data/lib/deftones/source/fat_oscillator.rb +28 -9
  55. data/lib/deftones/source/grain_player.rb +49 -2
  56. data/lib/deftones/source/noise.rb +42 -10
  57. data/lib/deftones/source/omni_oscillator.rb +1 -2
  58. data/lib/deftones/source/oscillator.rb +83 -19
  59. data/lib/deftones/source/player.rb +24 -6
  60. data/lib/deftones/source/players.rb +39 -6
  61. data/lib/deftones/source/tone_buffer_source.rb +12 -6
  62. data/lib/deftones/source/tone_oscillator_node.rb +4 -3
  63. data/lib/deftones/source/user_media.rb +83 -10
  64. data/lib/deftones/version.rb +1 -1
  65. data/lib/deftones.rb +108 -31
  66. metadata +3 -44
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deftones
4
+ module Effects
5
+ module Oversampling
6
+ OVERSAMPLE_FACTORS = [1, 2, 4].freeze
7
+
8
+ attr_reader :oversample
9
+
10
+ def oversample=(value)
11
+ normalized = value.to_i
12
+ raise ArgumentError, "Unsupported oversample factor: #{value}" unless OVERSAMPLE_FACTORS.include?(normalized)
13
+
14
+ @oversample = normalized
15
+ @oversample_previous = []
16
+ end
17
+
18
+ private
19
+
20
+ def process_oversampled(input_buffer, channel_index)
21
+ ensure_oversample_state(channel_index)
22
+ return input_buffer.map { |sample| yield sample } if @oversample == 1
23
+
24
+ input_buffer.map do |sample|
25
+ previous = @oversample_previous[channel_index]
26
+ current = sample.to_f
27
+ sum = 0.0
28
+ 1.upto(@oversample) do |step|
29
+ position = step.to_f / @oversample
30
+ sum += yield Deftones::DSP::Helpers.lerp(previous, current, position)
31
+ end
32
+ @oversample_previous[channel_index] = current
33
+ sum / @oversample
34
+ end
35
+ end
36
+
37
+ def ensure_oversample_state(channel_index)
38
+ required = [channel_index.to_i, 0].max
39
+ @oversample_previous.fill(0.0, @oversample_previous.length..required)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,7 +5,8 @@ module Deftones
5
5
  class Phaser < Core::Effect
6
6
  include ModulationControl
7
7
 
8
- attr_accessor :frequency, :octaves, :q, :base_frequency, :stages, :type
8
+ attr_accessor :frequency, :octaves, :q, :base_frequency, :type
9
+ attr_reader :stages
9
10
 
10
11
  def initialize(
11
12
  frequency: 0.5,
@@ -3,8 +3,7 @@
3
3
  module Deftones
4
4
  module Effects
5
5
  class PitchShift < Core::Effect
6
- attr_reader :delay_time, :feedback
7
- attr_accessor :pitch
6
+ attr_reader :delay_time, :feedback, :pitch
8
7
  attr_reader :window_size
9
8
 
10
9
  def initialize(
@@ -4,18 +4,46 @@ module Deftones
4
4
  module Effects
5
5
  class Reverb < Core::Effect
6
6
  attr_accessor :decay, :pre_delay
7
+ attr_reader :damping, :freeze, :wet_normalization, :width
7
8
 
8
- def initialize(decay: 0.7, pre_delay: 0.01, context: Deftones.context, **options)
9
+ def initialize(decay: 0.7, pre_delay: 0.01, damping: 0.0, freeze: false, wet_normalization: false,
10
+ width: 1.0, context: Deftones.context, **options)
9
11
  super(context: context, **options)
10
12
  @decay = decay.to_f
11
13
  @pre_delay = pre_delay.to_f
14
+ self.damping = damping
15
+ self.freeze = freeze
16
+ self.wet_normalization = wet_normalization
17
+ self.width = width
12
18
  @comb_times = [0.0297, 0.0371, 0.0411, 0.0437]
13
19
  @allpass_times = [0.005, 0.0017]
14
20
  @comb_lines = []
21
+ @comb_damping_state = []
15
22
  @allpass_lines = []
16
23
  @pre_delay_lines = []
17
24
  end
18
25
 
26
+ def damping=(value)
27
+ @damping = value.to_f.clamp(0.0, 1.0)
28
+ end
29
+
30
+ def freeze=(value)
31
+ @freeze = !!value
32
+ end
33
+
34
+ def wet_normalization=(value)
35
+ @wet_normalization = !!value
36
+ end
37
+
38
+ def width=(value)
39
+ @width = value.to_f.clamp(0.0, 1.0)
40
+ end
41
+
42
+ alias dampening damping
43
+ alias dampening= damping=
44
+ alias wetNormalization wet_normalization
45
+ alias wetNormalization= wet_normalization=
46
+
19
47
  private
20
48
 
21
49
  def process_effect_block(input_block, num_frames, start_frame, cache)
@@ -23,9 +51,9 @@ module Deftones
23
51
  source = input_block.fit_channels(output_channels)
24
52
 
25
53
  Core::AudioBlock.from_channel_data(
26
- source.channel_data.each_with_index.map do |channel, channel_index|
54
+ apply_stereo_width(source.channel_data.each_with_index.map do |channel, channel_index|
27
55
  process_effect(channel, num_frames, start_frame, cache, channel_index: channel_index)
28
- end
56
+ end)
29
57
  )
30
58
  end
31
59
 
@@ -40,23 +68,37 @@ module Deftones
40
68
  Array.new(num_frames) do |index|
41
69
  dry = input_buffer[index]
42
70
  delayed = pre_delay_line.tap(@pre_delay * context.sample_rate, input_sample: dry)
71
+ feedback = effective_decay
72
+ comb_input = @freeze ? 0.0 : delayed
43
73
  comb_sum = comb_lines.each_with_index.sum do |line, comb_index|
44
- line.tap(comb_times[comb_index] * context.sample_rate, input_sample: delayed, feedback: @decay)
74
+ process_comb(line, comb_times[comb_index] * context.sample_rate, comb_input, feedback, channel_index,
75
+ comb_index)
45
76
  end / comb_lines.length.to_f
46
77
 
47
- allpass_lines.each_with_index.reduce(comb_sum) do |sample, (line, allpass_index)|
78
+ wet_sample = allpass_lines.each_with_index.reduce(comb_sum) do |sample, (line, allpass_index)|
48
79
  tap = line.read(allpass_times[allpass_index] * context.sample_rate)
49
80
  line.write(sample + (tap * 0.5))
50
81
  tap - (sample * 0.5)
51
82
  end
83
+ normalize_wet_sample(wet_sample, feedback)
52
84
  end
53
85
  end
54
86
 
87
+ def process_comb(line, delay_samples, input_sample, feedback, channel_index, comb_index)
88
+ delayed_sample = line.read(delay_samples)
89
+ previous = @comb_damping_state[channel_index][comb_index]
90
+ filtered = DSP::Helpers.lerp(delayed_sample, previous, @damping)
91
+ @comb_damping_state[channel_index][comb_index] = filtered
92
+ line.write(input_sample + (filtered * feedback))
93
+ filtered
94
+ end
95
+
55
96
  def ensure_delay_network(channel_index)
56
97
  required = [channel_index.to_i, 0].max
57
98
  while @pre_delay_lines.length <= required
58
99
  @pre_delay_lines << DSP::DelayLine.new((0.1 * context.sample_rate).ceil)
59
100
  @comb_lines << @comb_times.map { |seconds| DSP::DelayLine.new((seconds * context.sample_rate).ceil + 2) }
101
+ @comb_damping_state << Array.new(@comb_times.length, 0.0)
60
102
  @allpass_lines << @allpass_times.map { |seconds| DSP::DelayLine.new((seconds * context.sample_rate).ceil + 2) }
61
103
  end
62
104
  end
@@ -66,6 +108,32 @@ module Deftones
66
108
 
67
109
  times.map { |time| time + (offset * channel_index) }
68
110
  end
111
+
112
+ def apply_stereo_width(channel_data)
113
+ return channel_data if channel_data.length < 2 || @width >= 1.0
114
+
115
+ left = channel_data[0]
116
+ right = channel_data[1]
117
+ widened_left = []
118
+ widened_right = []
119
+ left.each_index do |index|
120
+ mid = (left[index] + right[index]) * 0.5
121
+ side = (left[index] - right[index]) * 0.5 * @width
122
+ widened_left << (mid + side)
123
+ widened_right << (mid - side)
124
+ end
125
+ [widened_left, widened_right] + channel_data.drop(2)
126
+ end
127
+
128
+ def effective_decay
129
+ @freeze ? 0.995 : @decay.to_f.clamp(0.0, 0.995)
130
+ end
131
+
132
+ def normalize_wet_sample(sample, feedback)
133
+ return sample unless @wet_normalization
134
+
135
+ sample * (1.0 - ([feedback.abs, 0.95].min * 0.35))
136
+ end
69
137
  end
70
138
  end
71
139
  end
@@ -18,11 +18,13 @@ module Deftones
18
18
 
19
19
  private
20
20
 
21
- def initialize_callback_behavior(probability: 1.0, humanize: false, mute: false, playback_rate: 1.0)
21
+ def initialize_callback_behavior(probability: 1.0, humanize: false, mute: false, playback_rate: 1.0,
22
+ seed: nil, rng: nil)
22
23
  @probability = probability.to_f
23
24
  @humanize = humanize
24
25
  @mute = !!mute
25
26
  @playback_rate = playback_rate.to_f
27
+ @rng = rng || (seed.nil? ? Random : Random.new(seed))
26
28
  @state = :stopped
27
29
  end
28
30
 
@@ -46,15 +48,17 @@ module Deftones
46
48
 
47
49
  def callback_permitted?
48
50
  return false if @mute
51
+ return true if @probability >= 1.0
52
+ return false if @probability <= 0.0
49
53
 
50
- rand <= @probability
54
+ @rng.rand <= @probability
51
55
  end
52
56
 
53
57
  def humanized_time(time)
54
58
  return time unless @humanize
55
59
 
56
60
  amount = @humanize == true ? 0.01 : Deftones::Music::Time.parse(@humanize)
57
- time + (((rand * 2.0) - 1.0) * amount)
61
+ time + (((@rng.rand * 2.0) - 1.0) * amount)
58
62
  end
59
63
  end
60
64
  end
@@ -8,7 +8,10 @@ module Deftones
8
8
  attr_reader :interval, :iterations
9
9
 
10
10
  def initialize(interval:, iterations: nil, transport: Deftones.transport,
11
- probability: 1.0, humanize: false, mute: false, playback_rate: 1.0, &callback)
11
+ probability: 1.0, humanize: false, mute: false, playback_rate: 1.0,
12
+ seed: nil, rng: nil, &callback)
13
+ raise ArgumentError, "callback is required" unless callback
14
+
12
15
  @interval = interval
13
16
  @iterations = iterations
14
17
  @transport = transport
@@ -18,7 +21,9 @@ module Deftones
18
21
  probability: probability,
19
22
  humanize: humanize,
20
23
  mute: mute,
21
- playback_rate: playback_rate
24
+ playback_rate: playback_rate,
25
+ seed: seed,
26
+ rng: rng
22
27
  )
23
28
  end
24
29
 
@@ -6,8 +6,11 @@ module Deftones
6
6
  include CallbackBehavior
7
7
 
8
8
  def initialize(events:, transport: Deftones.transport,
9
- probability: 1.0, humanize: false, mute: false, playback_rate: 1.0, &callback)
10
- @events = events
9
+ probability: 1.0, humanize: false, mute: false, playback_rate: 1.0,
10
+ seed: nil, rng: nil, &callback)
11
+ raise ArgumentError, "callback is required" unless callback
12
+
13
+ @events = normalize_events(events)
11
14
  @transport = transport
12
15
  @callback = callback
13
16
  @event_ids = []
@@ -15,7 +18,9 @@ module Deftones
15
18
  probability: probability,
16
19
  humanize: humanize,
17
20
  mute: mute,
18
- playback_rate: playback_rate
21
+ playback_rate: playback_rate,
22
+ seed: seed,
23
+ rng: rng
19
24
  )
20
25
  end
21
26
 
@@ -46,6 +51,16 @@ module Deftones
46
51
  cancel
47
52
  self
48
53
  end
54
+
55
+ private
56
+
57
+ def normalize_events(events)
58
+ Array(events).map do |event|
59
+ next { time: event[0], value: event[1] } if event.is_a?(Array)
60
+
61
+ event
62
+ end
63
+ end
49
64
  end
50
65
  end
51
66
  end
@@ -5,11 +5,23 @@ module Deftones
5
5
  class Pattern
6
6
  include CallbackBehavior
7
7
 
8
- PATTERNS = %i[up down up_down random].freeze
8
+ PATTERNS = %i[up down up_down down_up alternate_up alternate_down random random_walk].freeze
9
+ PATTERN_ALIASES = {
10
+ upDown: :up_down,
11
+ downUp: :down_up,
12
+ alternateUp: :alternate_up,
13
+ alternateDown: :alternate_down,
14
+ randomWalk: :random_walk
15
+ }.freeze
9
16
 
10
17
  def initialize(values:, pattern: :up, interval: "4n", transport: Deftones.transport,
11
- probability: 1.0, humanize: false, mute: false, playback_rate: 1.0, &callback)
12
- @values = values
18
+ probability: 1.0, humanize: false, mute: false, playback_rate: 1.0,
19
+ seed: nil, rng: nil, &callback)
20
+ raise ArgumentError, "callback is required" unless callback
21
+
22
+ @values = Array(values)
23
+ raise ArgumentError, "Pattern values must not be empty" if @values.empty?
24
+
13
25
  @pattern = normalize_pattern(pattern)
14
26
  @interval = interval
15
27
  @transport = transport
@@ -21,7 +33,9 @@ module Deftones
21
33
  probability: probability,
22
34
  humanize: humanize,
23
35
  mute: mute,
24
- playback_rate: playback_rate
36
+ playback_rate: playback_rate,
37
+ seed: seed,
38
+ rng: rng
25
39
  )
26
40
  end
27
41
 
@@ -58,8 +72,16 @@ module Deftones
58
72
  descending_value
59
73
  when :up_down
60
74
  bounce_value
75
+ when :down_up
76
+ descending_bounce_value
77
+ when :alternate_up
78
+ alternate_value(:up)
79
+ when :alternate_down
80
+ alternate_value(:down)
61
81
  when :random
62
- @values.sample
82
+ @values[@rng.rand(@values.length)]
83
+ when :random_walk
84
+ random_walk_value
63
85
  end
64
86
  end
65
87
 
@@ -83,8 +105,31 @@ module Deftones
83
105
  value
84
106
  end
85
107
 
108
+ def descending_bounce_value
109
+ value = @values.reverse[@index]
110
+ @direction = -1 if @index >= @values.length - 1
111
+ @direction = 1 if @index <= 0
112
+ @index += @direction
113
+ value
114
+ end
115
+
116
+ def alternate_value(start_direction)
117
+ cycle = @index / @values.length
118
+ offset = @index % @values.length
119
+ @index += 1
120
+ descending = start_direction == :down ? cycle.even? : cycle.odd?
121
+ descending ? @values.reverse[offset] : @values[offset]
122
+ end
123
+
124
+ def random_walk_value
125
+ value = @values[@index]
126
+ @direction = [-1, 1][@rng.rand(2)]
127
+ @index = (@index + @direction).clamp(0, @values.length - 1)
128
+ value
129
+ end
130
+
86
131
  def normalize_pattern(pattern)
87
- normalized = pattern.to_sym
132
+ normalized = PATTERN_ALIASES.fetch(pattern.to_sym, pattern.to_sym)
88
133
  return normalized if PATTERNS.include?(normalized)
89
134
 
90
135
  raise ArgumentError, "Unsupported pattern: #{pattern}"
@@ -7,8 +7,13 @@ module Deftones
7
7
  include CallbackBehavior
8
8
 
9
9
  def initialize(notes:, subdivision: "4n", loop: true, transport: Deftones.transport,
10
- probability: 1.0, humanize: false, mute: false, playback_rate: 1.0, &callback)
11
- @notes = notes
10
+ probability: 1.0, humanize: false, mute: false, playback_rate: 1.0,
11
+ seed: nil, rng: nil, &callback)
12
+ raise ArgumentError, "callback is required" unless callback
13
+
14
+ @notes = Array(notes)
15
+ raise ArgumentError, "Sequence notes must not be empty" if @notes.empty?
16
+
12
17
  @subdivision = subdivision
13
18
  @loop = loop
14
19
  @transport = transport
@@ -19,7 +24,9 @@ module Deftones
19
24
  probability: probability,
20
25
  humanize: humanize,
21
26
  mute: mute,
22
- playback_rate: playback_rate
27
+ playback_rate: playback_rate,
28
+ seed: seed,
29
+ rng: rng
23
30
  )
24
31
  end
25
32
 
@@ -76,12 +83,19 @@ module Deftones
76
83
  note.each_with_index do |nested_note, index|
77
84
  next if nested_note.nil?
78
85
 
79
- @callback.call(humanized_time(scheduled_time + (sub_duration * index)), nested_note)
86
+ process_note(humanized_time(scheduled_time + (sub_duration * index)), nested_note)
80
87
  end
81
88
  else
82
- @callback.call(humanized_time(scheduled_time), note)
89
+ process_note(humanized_time(scheduled_time), note)
83
90
  end
84
91
  end
92
+
93
+ def process_note(time, note)
94
+ payload = note.is_a?(Hash) ? note : { note: note }
95
+ return if payload.fetch(:probability, 1.0).to_f < @rng.rand
96
+
97
+ @callback.call(time, payload.fetch(:note, payload[:value]), payload)
98
+ end
85
99
  end
86
100
  end
87
101
  end
@@ -8,7 +8,10 @@ module Deftones
8
8
  attr_accessor :loop, :loop_start, :loop_end, :probability
9
9
 
10
10
  def initialize(transport: Deftones.transport, probability: 1.0, loop: false,
11
- loop_start: 0.0, loop_end: nil, humanize: false, mute: false, playback_rate: 1.0, &callback)
11
+ loop_start: 0.0, loop_end: nil, humanize: false, mute: false, playback_rate: 1.0,
12
+ seed: nil, rng: nil, &callback)
13
+ raise ArgumentError, "callback is required" unless callback
14
+
12
15
  @transport = transport
13
16
  @callback = callback
14
17
  @loop = loop
@@ -19,7 +22,9 @@ module Deftones
19
22
  probability: probability,
20
23
  humanize: humanize,
21
24
  mute: mute,
22
- playback_rate: playback_rate
25
+ playback_rate: playback_rate,
26
+ seed: seed,
27
+ rng: rng
23
28
  )
24
29
  end
25
30