From 927c40be448e6e1f7782817d01ebe7ee8d354435 Mon Sep 17 00:00:00 2001 From: Xavier Riley Date: Mon, 23 May 2022 18:29:46 +0100 Subject: [PATCH] Add FFT based vocoder effect This works by using the left channel as the modulator and the right channel as the carrier. A parameter for adding white noise helps to improve intelligibility for spoken/sung texts. Another parameter for gating the carrier helps reduce low level signals causing continuous output. ``` live_loop :foo do with_fx :vocoder, gate_threshold: 0.9, noise_ratio: 0.25 do pth = :loop_amen sample pth, pan: -1 x = synth :dsaw, pan: 1, note: :a3, sustain: sample_duration(pth), note_slide: 0.1 32.times do control(x, note: [scale(:a3, :minor_pentatonic, num_octaves: 1).choose]) sleep sample_duration(pth) / 32 end end end ``` --- .../ruby/lib/sonicpi/synths/synthinfo.rb | 48 ++++++++++++++++++ .../compiled/sonic-pi-fx_vocoder.scsyndef | Bin 0 -> 3356 bytes .../designs/supercollider/vocoder_fft.scd | 33 ++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 etc/synthdefs/compiled/sonic-pi-fx_vocoder.scsyndef create mode 100644 etc/synthdefs/designs/supercollider/vocoder_fft.scd 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 0000000000000000000000000000000000000000..df86d858cf3b08fd69c54646195d061edaa75722 GIT binary patch literal 3356 zcmbtXSx?(Q5T3O|LJ}b5=zZTOZK3zUEohNSL87!(Um`;cSPF5J1JSqsvr2vF6a7u? zeCr((JHbJ8q}^|K=bM?G^-k=p&TVZHkwt2_?pM5x(W*DPxmVovH~f-YV@)qNn)*TX z>o?IfkAL!bo^guX?0fErMs&{|c#z}biCr@q*_cz%?#xgDG{)ln3C9<%;gPNPwMFz6w<=m9{xkbT!9#tto3ge6i?o zjJp09`wy!@B@UT%jT;>|VR^~kfD@KMbe$8v36;b(*fs};B^~3nCam?E#qVH)atPVu z=5fwAx^3_@uyY=Fpo;CUZ4M5L>|4%T=D4d6LH&B+xh@gLp6G)E4Nd9(JdYrE(OHp~ zR+M-T1eWXQl8DzpV9^FiM1)59bPYWUcPvK^tqrXBhk!*xDtZ)%9vp?7DOMhHQ!4jm z+iSQV@qGabgq2D8Xgj8jgKL@J2iP9`iammgWh?-C0<`*;oV^eJhEtwxZtAZ=?1G!Q z7dG{Z7Ifn+c4_cQ@C<6Q7ZyIj0loo%M9+0k^Z;n=q$p%A2S6&n^0~O=Y|X6uyRARc zZ2H3ZL87Gob5pO3v!}qWo5}{8;@@1P6g)E+zDCFL5}nEgEcv!OpuK_G1j(q>A|Fxo z!MtrD2$3xP9*Pq0gUB)wK!8^I>w;5q$3aEa1;;KGGij)o4c-EF^8L^dj^JBLV%nHQ VecQ{;hHTpq7_ck+rGd-@@DCNDVtxPs literal 0 HcmV?d00001 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