Class: Deftones::Core::Signal

Inherits:
Object
  • Object
show all
Includes:
SignalOperatorMethods
Defined in:
lib/deftones/core/signal.rb

Direct Known Subclasses

ComputedSignal, SyncedSignal, Zero

Constant Summary collapse

EXPONENTIAL_UNITS =
%i[frequency decibels].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from SignalOperatorMethods

#abs, #add, #equal_power_gain, #greater_than, #greater_than_zero, #modulo, #multiply, #negate, #normalize, #pow, #scale, #scale_exp, #subtract, #to_audio, #to_gain, #wave_shaper

Constructor Details

#initialize(value: 0.0, units: :number, context: Deftones.context) ⇒ Signal

Returns a new instance of Signal.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/deftones/core/signal.rb', line 14

def initialize(value: 0.0, units: :number, context: Deftones.context)
  @context = context
  @units = units
  @input = self
  @output = self
  @convert_values = true
  @base_value = coerce_value(value)
  @default_value = @base_value
  @min_value = -Float::INFINITY
  @max_value = Float::INFINITY
  @events = []
  @next_event_order = 0
  @clamp_values = false
  @disposed = false
end

Instance Attribute Details

#clamp_valuesObject Also known as: clampValues

Returns the value of attribute clamp_values.



12
13
14
# File 'lib/deftones/core/signal.rb', line 12

def clamp_values
  @clamp_values
end

#contextObject (readonly)

Returns the value of attribute context.



10
11
12
# File 'lib/deftones/core/signal.rb', line 10

def context
  @context
end

#convert_valuesObject

Returns the value of attribute convert_values.



11
12
13
# File 'lib/deftones/core/signal.rb', line 11

def convert_values
  @convert_values
end

#default_valueObject (readonly) Also known as: defaultValue

Returns the value of attribute default_value.



10
11
12
# File 'lib/deftones/core/signal.rb', line 10

def default_value
  @default_value
end

#inputObject (readonly)

Returns the value of attribute input.



10
11
12
# File 'lib/deftones/core/signal.rb', line 10

def input
  @input
end

#max_valueObject Also known as: maxValue

Returns the value of attribute max_value.



11
12
13
# File 'lib/deftones/core/signal.rb', line 11

def max_value
  @max_value
end

#min_valueObject Also known as: minValue

Returns the value of attribute min_value.



11
12
13
# File 'lib/deftones/core/signal.rb', line 11

def min_value
  @min_value
end

#outputObject (readonly)

Returns the value of attribute output.



10
11
12
# File 'lib/deftones/core/signal.rb', line 10

def output
  @output
end

#unitsObject (readonly)

Returns the value of attribute units.



10
11
12
# File 'lib/deftones/core/signal.rb', line 10

def units
  @units
end

Instance Method Details

#add_event(event) ⇒ Object (private)



353
354
355
356
357
# File 'lib/deftones/core/signal.rb', line 353

def add_event(event)
  event[:order] = @next_event_order
  @next_event_order += 1
  insert_event(event)
end

#apply(value) ⇒ Object



39
40
41
42
# File 'lib/deftones/core/signal.rb', line 39

def apply(value)
  self.value = value.respond_to?(:value_of) ? value.value_of : value
  self
end

#automation_anchor_time(event) ⇒ Object (private)



380
381
382
383
384
385
386
387
# File 'lib/deftones/core/signal.rb', line 380

def automation_anchor_time(event)
  case event[:type]
  when :linear, :exponential, :curve
    event[:end_time]
  else
    event.fetch(:time, event[:start_time])
  end
end

#automation_event_countObject Also known as: automationEventCount



289
290
291
# File 'lib/deftones/core/signal.rb', line 289

def automation_event_count
  @events.length
end

#automation_events?Boolean Also known as: automationEvents?

Returns:

  • (Boolean)


293
294
295
# File 'lib/deftones/core/signal.rb', line 293

def automation_events?
  @events.any?
end

#bounded_value(value) ⇒ Object (private)



495
496
497
498
499
500
# File 'lib/deftones/core/signal.rb', line 495

def bounded_value(value)
  return value unless @clamp_values
  return value unless value.is_a?(Numeric)

  value.clamp(@min_value, @max_value)
end

#cancel_and_hold_at_time(time) ⇒ Object Also known as: cancelAndHoldAtTime



140
141
142
143
144
145
# File 'lib/deftones/core/signal.rb', line 140

def cancel_and_hold_at_time(time)
  held_time = resolve_time(time)
  held_value = value_at(held_time)
  cancel_scheduled_values(held_time)
  set_value_at_time(held_value, held_time)
end

#cancel_scheduled_values(after_time = 0) ⇒ Object Also known as: cancelScheduledValues



132
133
134
135
136
137
138
# File 'lib/deftones/core/signal.rb', line 132

def cancel_scheduled_values(after_time = 0)
  threshold = resolve_time(after_time)
  @events.reject! do |event|
    event.fetch(:time, event[:start_time]) >= threshold
  end
  self
end

#coerce_value(value) ⇒ Object (private)



451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/deftones/core/signal.rb', line 451

def coerce_value(value)
  return convert_without_units(value) unless @convert_values

  case @units
  when :frequency
    convert_frequency(value)
  when :time
    Deftones::Music::Time.parse(value)
  when :decibels
    db_to_gain(value.to_f)
  else
    convert_generic(value)
  end
end

#connect(destination, output_index: 0, input_index: 0) ⇒ Object

Raises:

  • (ArgumentError)


181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/deftones/core/signal.rb', line 181

def connect(destination, output_index: 0, input_index: 0)
  raise ArgumentError, "destination is required" if destination.nil?
  raise ArgumentError, "output_index must be 0 for Signal connections" unless Integer(output_index).zero?
  raise ArgumentError, "input_index must be 0 for Signal connections" unless Integer(input_index).zero?

  if destination.respond_to?(:value=)
    destination.value = value
  elsif destination.respond_to?(:apply)
    destination.apply(self)
  end
  self
end

#convertObject



441
442
443
# File 'lib/deftones/core/signal.rb', line 441

def convert
  @convert_values
end

#convert=(value) ⇒ Object



445
446
447
# File 'lib/deftones/core/signal.rb', line 445

def convert=(value)
  @convert_values = !!value
end

#convert_frequency(value) ⇒ Object (private)



473
474
475
476
477
478
479
480
# File 'lib/deftones/core/signal.rb', line 473

def convert_frequency(value)
  return value.to_f if value.is_a?(Numeric)

  string_value = value.to_s
  return Regexp.last_match(1).to_f if string_value.match(/\A(\d+(?:\.\d+)?)hz\z/i)

  Deftones::Music::Note.to_frequency(string_value)
end

#convert_generic(value) ⇒ Object (private)



482
483
484
485
486
487
488
489
# File 'lib/deftones/core/signal.rb', line 482

def convert_generic(value)
  string_value = value.to_s
  if string_value.match?(/\A[A-Ga-g][#b]?-?\d+\z/)
    Deftones::Music::Note.to_frequency(string_value)
  else
    value.to_f
  end
end

#convert_without_units(value) ⇒ Object (private)



466
467
468
469
470
471
# File 'lib/deftones/core/signal.rb', line 466

def convert_without_units(value)
  return value.value_of if value.respond_to?(:value_of)
  return value.to_f if value.is_a?(Numeric) || value.respond_to?(:to_f)

  value
end

#curve_value(event, time) ⇒ Object (private)



405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/deftones/core/signal.rb', line 405

def curve_value(event, time)
  values = event[:values]
  return values.first if values.length <= 1

  progress = ((time - event[:start_time]) / event[:duration]).clamp(0.0, 1.0)
  scaled_index = progress * (values.length - 1)
  lower_index = scaled_index.floor
  upper_index = [lower_index + 1, values.length - 1].min
  fraction = scaled_index - lower_index
  lower = values[lower_index]
  upper = values[upper_index]
  lower + ((upper - lower) * fraction)
end

#db_to_gain(value) ⇒ Object (private)



491
492
493
# File 'lib/deftones/core/signal.rb', line 491

def db_to_gain(value)
  10.0**(value / 20.0)
end

#disconnect(_destination = nil) ⇒ Object



194
195
196
# File 'lib/deftones/core/signal.rb', line 194

def disconnect(_destination = nil)
  self
end

#disposeObject



160
161
162
163
164
# File 'lib/deftones/core/signal.rb', line 160

def dispose
  @events.clear
  @disposed = true
  self
end

#disposed?Boolean

Returns:

  • (Boolean)


166
167
168
# File 'lib/deftones/core/signal.rb', line 166

def disposed?
  @disposed
end

#event_sort_key(event) ⇒ Object (private)



367
368
369
# File 'lib/deftones/core/signal.rb', line 367

def event_sort_key(event)
  [event.fetch(:time, event[:start_time]), event.fetch(:order, 0)]
end

#exponential_approach_value_at_time(target_value, start_time, ramp_time) ⇒ Object Also known as: exponentialApproachValueAtTime



263
264
265
# File 'lib/deftones/core/signal.rb', line 263

def exponential_approach_value_at_time(target_value, start_time, ramp_time)
  target_ramp_to(target_value, ramp_time, start_time)
end

#exponential_ramp_to(target_value, duration) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/deftones/core/signal.rb', line 72

def exponential_ramp_to(target_value, duration)
  resolved_end = context.current_time + Deftones::Music::Time.parse(duration)
  schedule_automation(
    :exponential,
    bounded_value(coerce_value(target_value)),
    start_time: resolve_automation_start_time(resolved_end),
    end_time: resolved_end
  )
end

#exponential_ramp_to_value_at_time(target_value, end_time) ⇒ Object Also known as: exponentialRampToValueAtTime



82
83
84
85
86
87
88
89
90
# File 'lib/deftones/core/signal.rb', line 82

def exponential_ramp_to_value_at_time(target_value, end_time)
  resolved_end = resolve_time(end_time)
  schedule_automation(
    :exponential,
    bounded_value(coerce_value(target_value)),
    start_time: resolve_automation_start_time(resolved_end),
    end_time: resolved_end
  )
end

#get(*keys, strict: false) ⇒ Object

Raises:

  • (ArgumentError)


209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/deftones/core/signal.rb', line 209

def get(*keys, strict: false)
  unknown = []
  values = keys.flatten.each_with_object({}) do |key, collected|
    reader = key.to_sym
    if respond_to?(reader)
      collected[reader] = public_send(reader)
    else
      unknown << reader
    end
  end
  raise ArgumentError, "Unknown parameter(s): #{unknown.join(', ')}" if strict && unknown.any?

  values
end

#get_defaultsObject Also known as: getDefaults



224
225
226
227
228
229
# File 'lib/deftones/core/signal.rb', line 224

def get_defaults
  {
    value: default_value,
    units: units
  }
end

#get_value_at_time(time) ⇒ Object Also known as: getValueAtTime



151
152
153
# File 'lib/deftones/core/signal.rb', line 151

def get_value_at_time(time)
  value_at(resolve_time(time))
end

#immediateObject



235
236
237
# File 'lib/deftones/core/signal.rb', line 235

def immediate
  now
end

#insert_event(event) ⇒ Object (private)



359
360
361
362
363
364
365
# File 'lib/deftones/core/signal.rb', line 359

def insert_event(event)
  key = event_sort_key(event)
  index = @events.bsearch_index { |existing| (event_sort_key(existing) <=> key).positive? }
  return @events << event unless index

  @events.insert(index, event)
end

#interpolate(event, progress) ⇒ Object (private)



389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'lib/deftones/core/signal.rb', line 389

def interpolate(event, progress)
  return event[:to] if event[:duration].zero?

  bounded_progress = progress.clamp(0.0, 1.0)
  case event[:type]
  when :linear
    event[:from] + ((event[:to] - event[:from]) * bounded_progress)
  when :exponential
    from = safe_exponential_value(event[:from])
    to = safe_exponential_value(event[:to])
    from * ((to / from)**bounded_progress)
  else
    event[:to]
  end
end

#linear_ramp_to(target_value, duration) ⇒ Object



52
53
54
55
56
57
58
59
60
# File 'lib/deftones/core/signal.rb', line 52

def linear_ramp_to(target_value, duration)
  resolved_end = context.current_time + Deftones::Music::Time.parse(duration)
  schedule_automation(
    :linear,
    bounded_value(coerce_value(target_value)),
    start_time: resolve_automation_start_time(resolved_end),
    end_time: resolved_end
  )
end

#linear_ramp_to_value_at_time(target_value, end_time) ⇒ Object Also known as: linearRampToValueAtTime



62
63
64
65
66
67
68
69
70
# File 'lib/deftones/core/signal.rb', line 62

def linear_ramp_to_value_at_time(target_value, end_time)
  resolved_end = resolve_time(end_time)
  schedule_automation(
    :linear,
    bounded_value(coerce_value(target_value)),
    start_time: resolve_automation_start_time(resolved_end),
    end_time: resolved_end
  )
end

#nameObject



255
256
257
# File 'lib/deftones/core/signal.rb', line 255

def name
  self.class.name.split("::").last
end

#nowObject



231
232
233
# File 'lib/deftones/core/signal.rb', line 231

def now
  context.current_time
end

#overridden?Boolean

Returns:

  • (Boolean)


251
252
253
# File 'lib/deftones/core/signal.rb', line 251

def overridden?
  false
end

#process(num_frames, start_frame = 0) ⇒ Object



281
282
283
284
285
286
287
# File 'lib/deftones/core/signal.rb', line 281

def process(num_frames, start_frame = 0)
  return Array.new(num_frames, bounded_value(@base_value)) if @events.empty?

  Array.new(num_frames) do |offset|
    value_at(sample_time(start_frame + offset))
  end
end

#ramp_to(target_value, duration) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/deftones/core/signal.rb', line 44

def ramp_to(target_value, duration)
  if EXPONENTIAL_UNITS.include?(@units)
    exponential_ramp_to(target_value, duration)
  else
    linear_ramp_to(target_value, duration)
  end
end

#resolve_automation_start_time(end_time) ⇒ Object (private)



371
372
373
374
375
376
377
378
# File 'lib/deftones/core/signal.rb', line 371

def resolve_automation_start_time(end_time)
  anchors = @events.filter_map do |event|
    anchor = automation_anchor_time(event)
    anchor if anchor <= end_time
  end

  anchors.max || context.current_time
end

#resolve_time(time) ⇒ Object (private)



433
434
435
436
437
# File 'lib/deftones/core/signal.rb', line 433

def resolve_time(time)
  return context.current_time if time.nil?

  Deftones::Music::Time.parse(time)
end

#safe_exponential_value(value) ⇒ Object (private)



424
425
426
427
# File 'lib/deftones/core/signal.rb', line 424

def safe_exponential_value(value)
  magnitude = value.abs < 1.0e-6 ? 1.0e-6 : value.abs
  value.negative? ? -magnitude : magnitude
end

#sample_time(frame_index) ⇒ Object (private)



429
430
431
# File 'lib/deftones/core/signal.rb', line 429

def sample_time(frame_index)
  frame_index.to_f / context.sample_rate
end

#schedule_automation(type, target_value, start_time:, end_time:) ⇒ Object (private)



339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/deftones/core/signal.rb', line 339

def schedule_automation(type, target_value, start_time:, end_time:)
  duration_in_seconds = [end_time.to_f - start_time.to_f, 0.0].max
  add_event(
    type: type,
    start_time: start_time,
    end_time: end_time,
    time: start_time,
    duration: duration_in_seconds,
    from: value_at(start_time),
    to: target_value
  )
  self
end

#set(strict: false, **params) ⇒ Object

Raises:

  • (ArgumentError)


198
199
200
201
202
203
204
205
206
207
# File 'lib/deftones/core/signal.rb', line 198

def set(strict: false, **params)
  unknown = params.keys.reject { |key| respond_to?(:"#{key}=") }
  raise ArgumentError, "Unknown parameter(s): #{unknown.join(', ')}" if strict && unknown.any?

  params.each do |key, entry|
    writer = :"#{key}="
    public_send(writer, entry) if respond_to?(writer)
  end
  self
end

#set_ramp_point(time = context.current_time) ⇒ Object Also known as: setRampPoint



147
148
149
# File 'lib/deftones/core/signal.rb', line 147

def set_ramp_point(time = context.current_time)
  cancel_and_hold_at_time(time)
end

#set_target_at_time(target_value, start_time, time_constant) ⇒ Object Also known as: setTargetAtTime



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/deftones/core/signal.rb', line 112

def set_target_at_time(target_value, start_time, time_constant)
  resolved_start = resolve_time(start_time)
  add_event(
    type: :target,
    time: resolved_start,
    start_time: resolved_start,
    time_constant: [Deftones::Music::Time.parse(time_constant), 1.0e-6].max,
    from: value_at(resolved_start),
    to: bounded_value(coerce_value(target_value))
  )
  self
end

#set_value_at_time(target_value, time) ⇒ Object Also known as: setValueAtTime



92
93
94
95
# File 'lib/deftones/core/signal.rb', line 92

def set_value_at_time(target_value, time)
  add_event(type: :set, time: resolve_time(time), value: bounded_value(coerce_value(target_value)))
  self
end

#set_value_curve_at_time(values, start_time, duration) ⇒ Object Also known as: setValueCurveAtTime



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/deftones/core/signal.rb', line 97

def set_value_curve_at_time(values, start_time, duration)
  curve = Array(values).map { |value| bounded_value(coerce_value(value)) }
  resolved_start = resolve_time(start_time)
  resolved_duration = Deftones::Music::Time.parse(duration)
  add_event(
    type: :curve,
    time: resolved_start,
    start_time: resolved_start,
    end_time: resolved_start + resolved_duration,
    duration: resolved_duration,
    values: curve
  )
  self
end

#target_ramp_to(target_value, duration, start_time = context.current_time) ⇒ Object Also known as: targetRampTo



125
126
127
128
129
130
# File 'lib/deftones/core/signal.rb', line 125

def target_ramp_to(target_value, duration, start_time = context.current_time)
  resolved_start = resolve_time(start_time)
  resolved_duration = Deftones::Music::Time.parse(duration)
  set_target_at_time(target_value, resolved_start, [resolved_duration / 5.0, 1.0e-6].max)
  set_value_at_time(target_value, resolved_start + resolved_duration)
end

#target_value(event, time) ⇒ Object (private)



419
420
421
422
# File 'lib/deftones/core/signal.rb', line 419

def target_value(event, time)
  elapsed = [time - event[:start_time], 0.0].max
  event[:to] + ((event[:from] - event[:to]) * Math.exp(-(elapsed / event[:time_constant])))
end

#to_frequency(entry = value) ⇒ Object Also known as: toFrequency



247
248
249
# File 'lib/deftones/core/signal.rb', line 247

def to_frequency(entry = value)
  Deftones::Music::Frequency.parse(entry)
end

#to_sObject Also known as: toString



259
260
261
# File 'lib/deftones/core/signal.rb', line 259

def to_s
  name
end

#to_seconds(time = value) ⇒ Object Also known as: toSeconds



239
240
241
# File 'lib/deftones/core/signal.rb', line 239

def to_seconds(time = value)
  Deftones::Music::Time.parse(time)
end

#to_ticks(time = value) ⇒ Object Also known as: toTicks



243
244
245
# File 'lib/deftones/core/signal.rb', line 243

def to_ticks(time = value)
  Deftones.transport.seconds_to_ticks(to_seconds(time))
end

#valueObject



30
31
32
# File 'lib/deftones/core/signal.rb', line 30

def value
  @base_value
end

#value=(new_value) ⇒ Object



34
35
36
37
# File 'lib/deftones/core/signal.rb', line 34

def value=(new_value)
  @base_value = bounded_value(coerce_value(new_value))
  @events.clear
end

#value_at(time) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/deftones/core/signal.rb', line 300

def value_at(time)
  current_value = @base_value

  @events.each do |event|
    case event[:type]
    when :set
      break if time < event[:time]

      current_value = event[:value]
    when :linear, :exponential
      break if time < event[:start_time]

      if time >= event[:end_time]
        current_value = event[:to]
        next
      end

      current_value = interpolate(event, (time - event[:start_time]) / event[:duration])
    when :curve
      break if time < event[:start_time]

      if time >= event[:end_time]
        current_value = event[:values].last || current_value
        next
      end

      current_value = curve_value(event, time)
    when :target
      break if time < event[:start_time]

      current_value = target_value(event, time)
    end
  end

  bounded_value(current_value)
end