One sound described as a waveform, pitch, envelope and filter, rendered to 16-bit PCM.
TuningFork.Voice.new(shape: :saw, freq: 220.0) |> TuningFork.Voice.render(44_100)
Summary
Functions
Hold each sample for every samples, carrying the hold in extra. nil or 1 changes
nothing.
Round the sample to bits of resolution. nil or 16 and up leaves it alone; bits may be
fractional.
Drive the sample through a soft clipper, amount deciding how hard. nil or 0.0 leaves
it alone; the gain is normalised out afterwards.
How long the voice sounds for, in seconds: the length of its envelope.
The curves actually moving on this voice, as %{freq:, cutoff:, gain:}.
A voice, with anything unset left at its default. An unknown key raises KeyError.
Render to mono signed 16-bit little-endian PCM at rate samples per second.
Render for channels channels: 2 applies the voice's :pan, 1 is render/2 unchanged.
Bend a sample over 0.7 softly towards full scale instead of clipping it. Samples within -0.7 to 0.7 are unchanged.
The vowels :vowel takes, each with its three formants as {hz, level} pairs.
Bend the sample by Strudel's shape curve, amount from 0.0 (none) towards 1.0 (hard).
nil or 0.0 leaves it alone; 1.0 and above is treated as just under 1.0.
Types
@type t() :: %TuningFork.Voice{ coarse: number() | nil, crush: float() | nil, curves: %{optional(:freq | :cutoff | :gain) => TuningFork.Curve.t()}, cutoff: float() | nil, distort: float() | nil, envelope: TuningFork.Envelope.t(), filter: TuningFork.Filter.t() | nil, fm: float() | nil, fmattack: float() | nil, fmh: float() | nil, freq: float(), gain: float(), highpass: float() | nil, pan: float(), phaser: float() | nil, phaserdepth: float() | nil, sample: TuningFork.Sample.t() | nil, seed: TuningFork.Wave.seed(), shape: TuningFork.Wave.shape() | :noise, sweep: float(), vib: float() | nil, vibmod: float() | nil, vowel: atom() | nil, waveshape: float() | nil }
Functions
Hold each sample for every samples, carrying the hold in extra. nil or 1 changes
nothing.
Round the sample to bits of resolution. nil or 16 and up leaves it alone; bits may be
fractional.
iex> TuningFork.Voice.crush(0.3, nil)
0.3
iex> TuningFork.Voice.crush(0.3, 1) in [-1.0, 0.0, 1.0]
true
Drive the sample through a soft clipper, amount deciding how hard. nil or 0.0 leaves
it alone; the gain is normalised out afterwards.
iex> TuningFork.Voice.distort(0.5, 0.0)
0.5
iex> TuningFork.Voice.distort(0.5, 4.0) > 0.5
true
How long the voice sounds for, in seconds: the length of its envelope.
@spec modulation(t()) :: %{ freq: TuningFork.Curve.t() | nil, cutoff: TuningFork.Curve.t() | nil, gain: TuningFork.Curve.t() | nil }
The curves actually moving on this voice, as %{freq:, cutoff:, gain:}.
A field whose curve holds one value is nil here, and so is :freq where :sweep is 1.0
and no freq curve is given.
A voice, with anything unset left at its default. An unknown key raises KeyError.
Options
:shape—:sine,:square,:saw,:triangleor:noise, default:sine:freq— starting pitch in Hz, default 440.0. Ignored by:noise:sweep— pitch multiplier reached by the end of the envelope, default 1.0. A:freqcurve supersedes it, even a flat one:envelope— aTuningFork.Envelope. Whennil, a voice with a:samplegets one spanning the recording at the voice's pitch with a 2 ms attack and 4 ms release, and any other voice getsTuningFork.Envelope.new/1's defaults:gain— 0.0 to 1.0, default 0.8:pan—-1.0hard left to1.0hard right, default0.0:filter— aTuningFork.Filter, ornilfor none:cutoff— one-pole lowpass coefficient, 0.0 to 1.0;nilfor none:highpass— one-pole highpass coefficient, 0.0 to 1.0;nilfor none:crush— round every sample to this many bits;nilor 16 for none:distort— drive into a soft clipper, 0.0 upwards;nilfor none:waveshape— Strudel'sshapewaveshaper, 0.0 to below 1.0;nilfor none:vowel—:a,:e,:i,:oor:u;nilfor none:fm,:fmh,:fmattack— modulation index of a second oscillator, its frequency as a multiple of:freq, and the fraction of the note over which the index arrives:vib,:vibmod— vibrato rate in hertz and depth in semitones:coarse— hold every sample for this many;nilor 1 for none:phaser,:phaserdepth— sweep rate in hertz and depth 0.0 to 1.0:curves—%{freq: curve, cutoff: curve, gain: curve}ofTuningFork.Curves, each a multiplier over its field across the note:sample— aTuningFork.Sampleto play instead of an oscillator, ornil:seed— the noise seed; the same seed renders the same samples
@spec render(t(), pos_integer()) :: binary()
Render to mono signed 16-bit little-endian PCM at rate samples per second.
The result is duration/1 seconds long, and at least one frame. :pan is not applied.
@spec render(t(), pos_integer(), pos_integer()) :: binary()
Render for channels channels: 2 applies the voice's :pan, 1 is render/2 unchanged.
Bend a sample over 0.7 softly towards full scale instead of clipping it. Samples within -0.7 to 0.7 are unchanged.
iex> TuningFork.Voice.saturate(0.5)
0.5
iex> TuningFork.Voice.saturate(4.0) < 1.0
true
The vowels :vowel takes, each with its three formants as {hz, level} pairs.
iex> TuningFork.Voice.vowels() |> Keyword.keys()
[:a, :e, :i, :o, :u]
Bend the sample by Strudel's shape curve, amount from 0.0 (none) towards 1.0 (hard).
nil or 0.0 leaves it alone; 1.0 and above is treated as just under 1.0.
iex> TuningFork.Voice.waveshape(0.5, 0.0)
0.5