Skip to content

Add FFT based vocoder effect #3091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions app/server/ruby/lib/sonicpi/synths/synthinfo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8012,6 +8012,53 @@ def specific_arg_info
end
end

class FXVocoder < FXInfo
def name
"Vocoder"
end

def introduced
Version.new(4,0,0)
end

def synth_name
"fx_vocoder"
end

def doc
"Vocoder/talkbox/robot voice effect. This is unusual in Sonic Pi in that it treats the left channel as the modulator (usually a voice) and the right channel as the carrier (usually a bright synth sound like saw or pulse). It uses the modulator to filter the carrier and reproduce some of the formants (higher harmonics) from the original modulator signal. As an extra step, it also add in some white noise (controlled via the noise_ratio parameter) to make the speech sounds a bit easier to understand.

To get the most out of this effect, use a bright or noisy sounding synth for the carrier - saw, dsaw, pulse or hoover all work well. Play this with a long sustain and pan hard right (pan: 1). For your modulator, try using a sample with some good clear speech - anything will do! Pan this hard left (pan: -1). Make sure they are both inside the with_fx block."
end

def arg_defaults
super.merge({
:mix => 1,
:pre_mix => 1,
:noise_ratio => 0.1,
:gate_threshold => 0.5
})
end

def specific_arg_info
{
:noise_ratio =>
{
:doc => "The amount of white noise added to the carrier signal. Higher values make the text of spoken works easier to understand, but it can make it harder to hear the musical notes.",
:validations => [v_between_inclusive(:noise_ratio, 0, 1)],
:modulatable => true,
},

:gate_threshold =>
{
:doc => "Threshold to gate the signal at - this helps to make sure that the synth sound (the carrier) stops when the voice (the modulator) is silent.",
:validations => [v_between_inclusive(:gate_threshold, 0, 1)],
:modulatable => true,
}
}
end
end

class FXPingPong < FXInfo
def name
"Ping Pong Echo"
Expand Down Expand Up @@ -8469,6 +8516,7 @@ class BaseInfo
:fx_record => FXRecord.new,
:fx_sound_out => FXSoundOut.new,
:fx_sound_out_stereo => FXSoundOutStereo.new,
:fx_vocoder => FXVocoder.new,
:fx_ping_pong => FXPingPong.new
}

Expand Down
Binary file not shown.
33 changes: 33 additions & 0 deletions etc/synthdefs/designs/supercollider/vocoder_fft.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"./makeFX.scd".loadRelative;

~makeFX.value("sonic-pi-fx_vocoder", {
|dry_l, dry_r|

var voicedCarrier;
var replacementSound;
var snd, numBands, bandFreqs, bandMuls, carrier;
var perc, chainMod, chainCar, chain, sig;

numBands = 20;
bandFreqs = (0..numBands - 1).linexp(0, numBands - 1, 50, 8000);
bandMuls = [0.6,0.6,1.5,1.5,1.5,1.5,1,1,1,1,1,1,1.5,1.5,1.2,1.2,1.2,1.2,1.2,1.2];

// modulator (i.e. voice) is taken from the right channel of input
snd = dry_l;

// carrier (i.e. a synth) is taken from the left channel of input
voicedCarrier = dry_r;
replacementSound = LPF.ar(HPF.ar(WhiteNoise.ar(mul: 1), 3000), 8000);

// FFT based voiced/unvoiced detection
chainMod = FFT(LocalBuf(1024), snd);
// the following line gates the modulator signal
chainMod = PV_MagAbove(chainMod, 0.2);
chainCar = FFT(LocalBuf(1024), voicedCarrier);
chain = PV_MagMul(chainCar, chainMod);
// perc = SpecPcile.kr(chain, 0.9).explin(2000, 8000, 0, 1);
chain = PV_MagClip(chain, 50);

sig = IFFT(chain);
Mix.new([[sig, sig], [snd, snd]]);
}, [], 2);