Class: Wavify::DSP::Oscillator

Inherits:
Object
  • Object
show all
Defined in:
lib/wavify/dsp/oscillator.rb,
sig/dsp.rbs

Overview

Oscillator and noise source generator.

Constant Summary collapse

WAVEFORMS =

Supported waveform symbols.

%i[sine square sawtooth triangle pulse white_noise pink_noise].freeze
SINE_TABLE_SIZE =

Sample count in the interpolated sine wavetable.

2_048
SINE_WAVETABLE =

Shared sine cycle used by the eager generation hot path.

Array.new(SINE_TABLE_SIZE) do |index|
  Math.sin(2.0 * Math::PI * index / SINE_TABLE_SIZE)
end.freeze
TRIANGLE_TABLE_SIZE =

Sample count in each band-limited triangle wavetable.

2_048
TRIANGLE_MAX_HARMONIC =

Highest odd harmonic considered when building triangle tables.

255
TRIANGLE_TABLE_CACHE_LIMIT =

Maximum number of sample-rate/frequency-specific tables retained.

8
MAX_DURATION_SECONDS =

Maximum duration accepted by one eager generation call.

3_600.0
MAX_GENERATE_FRAMES =

Maximum number of frames allocated by one eager generation call.

50_000_000
MAX_FREQUENCY =

Absolute input guard before output-format Nyquist validation.

1_000_000.0
MAX_UNISON =

Maximum number of detuned oscillator voices.

64

Instance Method Summary collapse

Constructor Details

#initialize(waveform:, frequency:, amplitude: 1.0, phase: 0.0, pulse_width: 0.5, detune: 0.0, unison: 1, random: Random.new) ⇒ Oscillator

Returns a new instance of Oscillator.

Parameters:

  • phase (Numeric) (defaults to: 0.0)

    initial phase in cycles (0.0..1.0 wraps)

  • waveform: (Symbol)
  • frequency: (Numeric)
  • amplitude: (Numeric) (defaults to: 1.0)
  • phase: (Numeric) (defaults to: 0.0)
  • pulse_width: (Numeric) (defaults to: 0.5)
  • detune: (Numeric) (defaults to: 0.0)
  • unison: (Integer) (defaults to: 1)
  • random: (Random) (defaults to: Random.new)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/wavify/dsp/oscillator.rb', line 31

def initialize(waveform:, frequency:, amplitude: 1.0, phase: 0.0, pulse_width: 0.5, detune: 0.0, unison: 1,
               random: Random.new)
  @waveform = validate_waveform!(waveform)
  @frequency = validate_frequency!(frequency)
  @amplitude = validate_amplitude!(amplitude)
  @initial_phase = validate_phase!(phase)
  @voice_phases = nil
  @voice_frequencies = nil
  @sample_rate = nil
  @pulse_width = validate_pulse_width!(pulse_width)
  @detune = validate_detune!(detune)
  @unison = validate_unison!(unison)
  @random = random
  @triangle_tables = {}
  reset_pink_noise!
end

Instance Method Details

#each_sample(format:) ⇒ Enumerator<Float>

Returns a stateful infinite enumerator of mono sample values.

Parameters:

Returns:

  • (Enumerator<Float>)


91
92
93
94
95
96
97
98
99
100
101
# File 'lib/wavify/dsp/oscillator.rb', line 91

def each_sample(format:)
  validate_format!(format)
  prepare_sample_rate!(format.sample_rate)

  Enumerator.new do |yielder|
    loop do
      value = noise_waveform? ? scaled_noise_sample : next_periodic_sample(format.sample_rate)
      yielder << value
    end
  end
end

#generate(duration_seconds, format:) ⇒ Wavify::Core::SampleBuffer

Generates the next finite sample buffer in the requested format. Calls are stateful and continue oscillator phase. Call #reset_phase before changing sample rate or when repeatable output is required.

Parameters:

Returns:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/wavify/dsp/oscillator.rb', line 64

def generate(duration_seconds, format:)
  validate_format!(format)
  prepare_sample_rate!(format.sample_rate)
  unless duration_seconds.is_a?(Numeric) && duration_seconds.respond_to?(:finite?) && duration_seconds.finite? &&
         duration_seconds.between?(0.0, MAX_DURATION_SECONDS)
    raise InvalidParameterError, "duration_seconds must be a finite Numeric in 0.0..#{MAX_DURATION_SECONDS}"
  end

  sample_frames = (duration_seconds.to_f * format.sample_rate).round
  if sample_frames > MAX_GENERATE_FRAMES
    raise InvalidParameterError, "generation is limited to #{MAX_GENERATE_FRAMES} sample frames"
  end
  samples = Array.new(sample_frames * format.channels)

  if @waveform == :sine
    generate_sine_samples!(samples, sample_frames, format.channels, format.sample_rate)
  else
    generate_samples!(samples, sample_frames, format.channels, format.sample_rate)
  end

  Core::SampleBuffer.new(samples, format)
end

#reset_phase(phase = @initial_phase) ⇒ Oscillator

Parameters:

  • phase (Numeric) (defaults to: @initial_phase)

Returns:



48
49
50
51
52
53
54
55
# File 'lib/wavify/dsp/oscillator.rb', line 48

def reset_phase(phase = @initial_phase)
  @initial_phase = validate_phase!(phase)
  @voice_phases = nil
  @voice_frequencies = nil
  @sample_rate = nil
  reset_pink_noise!
  self
end