diff --git a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb index e89c7be940..b78d877b29 100644 --- a/app/server/ruby/lib/sonicpi/synths/synthinfo.rb +++ b/app/server/ruby/lib/sonicpi/synths/synthinfo.rb @@ -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" @@ -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 } diff --git a/etc/synthdefs/compiled/sonic-pi-fx_vocoder.scsyndef b/etc/synthdefs/compiled/sonic-pi-fx_vocoder.scsyndef new file mode 100644 index 0000000000..df86d858cf Binary files /dev/null and b/etc/synthdefs/compiled/sonic-pi-fx_vocoder.scsyndef differ diff --git a/etc/synthdefs/designs/supercollider/vocoder_fft.scd b/etc/synthdefs/designs/supercollider/vocoder_fft.scd new file mode 100644 index 0000000000..e8aa46c5bd --- /dev/null +++ b/etc/synthdefs/designs/supercollider/vocoder_fft.scd @@ -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); \ No newline at end of file