Module: Wavify::DSP::Processor

Defined in:
lib/wavify/dsp/processor.rb,
sig/dsp.rbs

Overview

Shared invocation and result contract for stateful audio processors.

Constant Summary collapse

TAIL_CHUNK_FRAMES =

Maximum number of frames requested from a processor tail at once.

4_096

Class Method Summary collapse

Class Method Details

.build_runtime(processor) ⇒ Object

Parameters:

  • processor (Object)

Returns:

  • (Object)


49
50
51
52
53
54
55
56
57
# File 'lib/wavify/dsp/processor.rb', line 49

def build_runtime(processor)
  return processor.build_runtime if processor.respond_to?(:build_runtime)

  runtime = processor.dup
  runtime.reset if runtime.respond_to?(:reset)
  runtime
rescue TypeError
  raise InvalidParameterError, "stateful processor must implement #build_runtime or support #dup"
end

.coerce_buffer(result, context = "processor") ⇒ Object

Raises:



42
43
44
45
46
47
# File 'lib/wavify/dsp/processor.rb', line 42

def coerce_buffer(result, context = "processor")
  return result.buffer if defined?(Wavify::Audio) && result.is_a?(Wavify::Audio)
  return result if result.is_a?(Core::SampleBuffer)

  raise ProcessingError, "#{context} must return Core::SampleBuffer or Audio"
end

.duration(processor, method_name) ⇒ Float

Parameters:

  • processor (Object)
  • method_name (Symbol)

Returns:

  • (Float)


59
60
61
62
63
64
65
66
67
68
69
# File 'lib/wavify/dsp/processor.rb', line 59

def duration(processor, method_name)
  return 0.0 unless processor.respond_to?(method_name)

  value = processor.public_send(method_name)
  return 0.0 if value.nil?
  unless value.is_a?(Numeric) && value.respond_to?(:finite?) && value.finite? && value >= 0.0
    raise InvalidParameterError, "#{method_name} must return a non-negative finite Numeric"
  end

  value.to_f
end

.each_buffer(result, context = "processor") ⇒ Object



32
33
34
35
36
37
38
39
40
# File 'lib/wavify/dsp/processor.rb', line 32

def each_buffer(result, context = "processor")
  return [].each if result.nil?
  return [coerce_buffer(result, context)].each if buffer?(result)
  unless result.respond_to?(:each)
    raise ProcessingError, "#{context} must return Core::SampleBuffer, Audio, an Enumerable of them, or nil"
  end

  result.lazy.map { |item| coerce_buffer(item, context) }
end

.flush(processor, format: nil) ⇒ Enumerator[Core::SampleBuffer, nil]

Parameters:

  • processor (Object)
  • format: (Core::Format, nil) (defaults to: nil)

Returns:



25
26
27
28
29
30
# File 'lib/wavify/dsp/processor.rb', line 25

def flush(processor, format: nil)
  return [].each unless processor.respond_to?(:flush)

  result = processor.flush(format: format)
  each_buffer(result, "processor flush")
end

.process(processor, buffer) ⇒ Core::SampleBuffer

Parameters:

Returns:



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/wavify/dsp/processor.rb', line 12

def process(processor, buffer)
  result = if processor.respond_to?(:process)
             processor.process(buffer)
           elsif processor.respond_to?(:call)
             processor.call(buffer)
           elsif processor.respond_to?(:apply)
             processor.apply(buffer)
           else
             raise InvalidParameterError, "processor must respond to :process, :call, or :apply"
           end
  coerce_buffer(result, "processor")
end

.render(processor, buffer) ⇒ Core::SampleBuffer

Parameters:

Returns:



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/wavify/dsp/processor.rb', line 71

def render(processor, buffer)
  runtime = build_runtime(processor)
  processed = process(runtime, buffer)
  chunks = [processed]
  chunks.concat(flush(runtime, format: processed.format).to_a)
  combined = chunks.reduce { |left, right| left.concat(right) }
  latency_frames = (duration(runtime, :latency) * combined.format.sample_rate).round
  return combined if latency_frames.zero?

  combined.slice(latency_frames, [combined.sample_frame_count - latency_frames, 0].max)
end