From ac875eef46d6e583bc52db24568ec2e0218eff2e Mon Sep 17 00:00:00 2001 From: FelipeZanabria <84168997+FelipeZanabria@users.noreply.github.com> Date: Wed, 1 Jun 2022 09:09:57 -0300 Subject: [PATCH] STFT release incorporate changes from develop, leaving old changes in experiments --- .gitignore | 362 ++++++++++++++++++++++++++++ README.md | 5 +- vocalrediso.jsfx | 614 ++++++++++++++++++----------------------------- 3 files changed, 603 insertions(+), 378 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ee5385 --- /dev/null +++ b/.gitignore @@ -0,0 +1,362 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd diff --git a/README.md b/README.md index 45da385..dc3d635 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,11 @@ Adjust stregth and phase width (how close are the two signals to the same phase) "Phase (degrees)" controls what pan in the stereo track the centre signal will be constructed from. 0 Means exaclty the centre, 45 means panned hard left, -45 means panned hard right. sometimes setting the phase to 90 or -90 (and setting dry mix to 0) can isolate things that are not centre panned and can give a better seperation (e.g. for karaoke wen centre subtraction does not give good results), however it will also only give a mono signal in that case. If you set the phase to anything but 0, if dry mix is 0, to have a balanced output it is recommended to enable "undo rotation due to phase", unless you plan to combine the output of the plugin with the original input (compensating for the introduced delay) +Phase2 controls the phase offset between both channels, controlled by rotating the left phase by that many degrees. It usually doesn't lead to much good results, but it is left there in case it might be wanted. ### A word of caution Some tracks are mixed in such a way that the results from this plugin will not be so sucessful. -In that case it is best to go find a pre mixed version. You might get away with making an acapella version by aligning and subtracting an instrumental version with the original mix, or vice versa if you have the wrong version. +In that case it is best to go find a pre mixed version. You might get away with making an acapella version by aligning and subtracting an instrumental version with the original mix, or vice versa if you have the wrong version. There is a plugin that I've made to get better results with this, but with more artifacts: https://github.com/micsthepick/JSFFTDenoise ### Credits -Robert Haenggi for making the original vocalrediso.ny for Audacity, and Neil Bickford for making the Noise Reduction [effect](https://github.com/Nbickford/REAPERDenoiser) which I was able to base my code from to use the algorithm in realtime. +Robert Haenggi for making the original vocalrediso.ny for Audacity, and Neil Bickford for making the Noise Reduction [effect](https://github.com/Nbickford/REAPERDenoiser) which helped me first make the effect, and finally Geraint Luff for the STFT template that I currently use: https://forum.cockos.com/showthread.php?t=225955 diff --git a/vocalrediso.jsfx b/vocalrediso.jsfx index bc315ce..4224f94 100644 --- a/vocalrediso.jsfx +++ b/vocalrediso.jsfx @@ -1,405 +1,267 @@ -// based on vocalrediso.ny, a built in nyquist filter for audacity: Released under terms of the GNU General Public License version 2 -// uses some code from https://github.com/Nbickford/REAPERDenoiser +// Based on Vocalrediso.ny, a nyquist filter for audacity, currently released under the GNU GPL V3 +// see https://www.gnu.org/licenses/gpl-3.0.en.html, https://www.audacityteam.org/ +// credits to Neil Bickford for his denoiser, https://github.com/Nbickford/REAPERDenoiser, +// which was used as a starting point for this script +// which now uses the STFT template code by geraintluff: https://forum.cockos.com/showthread.php?t=225955 desc: vocal removal/isolation -//tags: processing vocals stereo -//author: Michael Pannekoek +//slider1:fft_size_index=2<0,7,1{256,512,1024,2048,4096,8192,16384,32768}>FFT size slider1:0<-100,100,0.1>dry mix slider2:0<-100,100,0.1>C mix (Vocals) slider3:0<-5,5,0.001>strength at Low Cut slider4:0<-5,5,0.001>strength at High Cut -slider5:80<0,24000>Low Cut (Vocals) -slider6:24000<0,24000>High Cut (Vocals) -slider7:0<-90,90,1>Phase (Degrees) +slider5:80<0,24000,10>Low Cut (Vocals) +slider6:24000<0,24000,10>High Cut (Vocals) +slider7:0<-90,90,0.1>Phase (Degrees) slider8:90<1,180,0.1>Phase width at Low Cut (Degrees) slider9:90<1,180,0.1>Phase width at High Cut (Degrees) slider10:1<0,1,0.05>Attenuate if different volume -slider11:1<0,1,1{No,Yes}>undo rotation due to phase -slider12:0<0,5,1{Hann,Triangular,Lanczos (sinc),Blackman-Harris,Blackman-Nutall,Nutall}>pre FFT window -slider13:2<0,5,1{None,Hann,Triangular,Lanczos (sinc),Blackman-Harris,Blackman-Nutall,Nutall}>post FFT window -slider14:0<-180,180,1>Phase2 (Degrees) -slider15:0<0,8192,1>Input buffering (delay, reduce clicks) - -in_pin:left input -in_pin:right input -out_pin:left output -out_pin:right output +slider11:1<0,1,1{No,Yes}>undo input corrections +slider12:0<-180,180,0.05>Phase2 (Degrees) +@init -@slider -// calculate sinc, handling 0 as the limiting value -function sinc(x) -( - sincIn = $pi * x; - sincIn == 0 ? 1 : sin(sincIn) / sincIn; +function process_stft_segment(fft_buffer, fft_size) local(fft_bin, left_real, left_imag, right_real, right_imag) ( + fft_bin = 0; // FFT bin number + loop(fft_size/2, + fft_bin2 = fft_bin ? (fft_size - fft_bin) : 0; + + // Unfold complex spectrum into two real spectra + left_real = fft_buffer[2*fft_bin] + fft_buffer[2*fft_bin2]; + left_imag = fft_buffer[2*fft_bin + 1] - fft_buffer[2*fft_bin2 + 1]; + right_real = fft_buffer[2*fft_bin + 1] + fft_buffer[2*fft_bin2 + 1]; + right_imag = -fft_buffer[2*fft_bin] + fft_buffer[2*fft_bin2]; + + // input corrections + // first, change the phase of L based on phase2: + l_real_twisted = left_real*cosine2 + left_imag*sine2; + l_imag_twisted = left_imag*cosine2 - left_real*sine2; + + // now mix L&R together based on phase + r_real_rotated = right_real*cosine + l_real_twisted*sine; + r_imag_rotated = right_imag*cosine + l_imag_twisted*sine; + l_real_rotated = l_real_twisted*cosine - right_real*sine; + l_imag_rotated = l_imag_twisted*cosine - right_imag*sine; + + //////////////////////// Main STFT block + // The 'meat' of the algorithm, the code in this block will most resemble the code from vocalrediso.ny + + // first, apply vocal reduction algorithm only in the right bands + fft_bin >= low_bin && fft_bin < high_bin ? ( + // get constants for this bin + strength = strength_buffer[fft_bin]; + phase_width = phase_width_buffer[fft_bin]; + + // cacluate energy for this bin + norm_left = sqrt(sqr(l_real_rotated) + sqr(l_imag_rotated)); + norm_right = sqrt(sqr(r_real_rotated) + sqr(r_imag_rotated)); + + // calculate phase difference between left & right, divide by phase_width + w1 = acos((l_real_rotated * r_real_rotated + l_imag_rotated * r_imag_rotated) / (norm_left * norm_right)) / phase_width; + // calculate weight: truncate w1 to [0, 1] and apply strength, then take 1 - the result, and multiply + // by 1 - the square of the difference between the two norm values divided by the sum of the two, moderated by strength * slider10 + weight = (1 - min(1, max(0, w1)) ^ strength) * ( + 1 - (sqr(norm_left - norm_right)/sqr(norm_left + norm_right)) + ) ^ (strength * slider10) / 2; + + // find the c channel, the sum of the two complex numbers + c_real = l_real_rotated + r_real_rotated; + c_imag = l_imag_rotated + r_imag_rotated; + + // apply weight to c channel + c_real *= weight; + c_imag *= weight; + ) : + ( + c_real = 0; + c_imag = 0; + ); + //////////////////////// END MAIN STFT block + + // apply wet dry mix + out_l_real = l_real_rotated * dry_mix + c_real * wet_mix; + out_l_imag = l_imag_rotated * dry_mix + c_imag * wet_mix; + out_r_real = r_real_rotated * dry_mix + c_real * wet_mix; + out_r_imag = r_imag_rotated * dry_mix + c_imag * wet_mix; + + // output corrections + slider11 > 0.5 ? ( + // if requested, undo input corrections + l_real_twisted = out_l_real*cosine - out_l_imag*sine; + l_imag_twisted = out_l_imag*cosine + out_l_imag*sine; + + right_real = out_r_real*cosine - l_real_twisted*sine; + right_imag = out_r_imag*cosine - l_imag_twisted*sine; + + left_real = l_real_twisted * cosine2 - l_imag_twisted * sine2; + left_imag = l_real_twisted * cosine2 + l_imag_twisted * sine2; + ) : + ( + // else, just copy the values + left_real = out_l_real; + left_imag = out_l_imag; + right_real = out_r_real; + right_imag = out_r_imag; + ); + + // Re-fold back into complex spectrum + fft_buffer[2*fft_bin] = (left_real - right_imag)*0.5; + fft_buffer[2*fft_bin + 1] = (left_imag + right_real)*0.5; + fft_buffer[2*fft_bin2] = (left_real + right_imag)*0.5; + fft_buffer[2*fft_bin2 + 1] = (-left_imag + right_real)*0.5; + + fft_bin += 1; + ); ); -// convert low cut and high cut to bins every time a slider is changed -lowBin = min(slider5, slider6) / srate * SIZE; -highBin = max(slider6, slider5) / srate * SIZE; -// convert to radians -rotation = slider7*$pi/180; -// convert percentage to raw scale factor -dryMix = slider1/100; -centreMix = slider2/100; -lowStrength = slider3; -highstrength = slider4; -phaseWlow = slider8*$pi/180; -phaseWhigh = slider9*$pi/180; -cosine = cos(rotation); -sine = sin(rotation); -cosine2 = cos(slider14*$pi/180); -sine2 = sin(slider14*$pi/180); -// fill strengthBuffer and phaseWBuffer -bandIndex = 0; -loop(SIZE, - bandIndex >= lowBin && bandIndex < highBin ? - ( - // only set values for the appropriate frequency range - frac = (bandIndex - lowBin)/(highBin - lowBin - 1); - // fraction of progress through range [lowBin, highBin) - strength = lowStrength* (1 - frac) + highStrength * frac; - strengthBuffer[bandIndex] = 10^strength; - // precaculate strength (actual value should be positive, so it makes - // sense to take the power of ten, but only after the - // linear mapping over the spectrum is done. - phaseW = phaseWlow * (1 - frac) + phaseWhigh * frac; - phaseWBuffer[bandIndex] = phaseW; - // precalculate phase width - ); - - slider12 == 0 ? - ( - windowBuffer1[bandIndex] = 0.5 - 0.5 * cos(2*$pi*bandIndex/SIZE); - ); - slider12 == 1 ? - ( - windowBuffer1[bandIndex] = bandIndex < SIZE/2 ? ( - bandIndex / (SIZE/2-1) - ) : ( - (SIZE-bandIndex-1) / (SIZE/2-1) - ); - ); - slider12 == 2 ? - ( - windowBuffer1[bandIndex] = sinc(2 * bandIndex / SIZE - 1); - ); - slider12 == 3 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer1[bandIndex] = 0.35875 - 0.48829 * cos(2 * cosIn) + 0.14128 * cos(4 * cosIn) - 0.01168 * cos(6 * cosIn); - ); - slider12 == 4 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer1[bandIndex] = 0.3635819 - 0.4891775 * cos(2 * cosIn) + 0.1365995 * cos(4 * cosIn) - 0.0106411 * cos(6 * cosIn); - ); - slider12 == 5 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer1[bandIndex] = 0.355768 - 0.487396 * cos(2 * cosIn) + 0.144232 * cos(4 * cosIn) - 0.012604 * cos(6 * cosIn); - ); - - slider13 == 0 ? - ( - windowBuffer2[bandIndex] = 1; - ); - - slider13 == 1 ? - ( - windowBuffer2[bandIndex] = 0.5 - 0.5 * cos(2*$pi*bandIndex/SIZE); - ); - slider13 == 2 ? ( - windowBuffer2[bandIndex] = bandIndex < SIZE/2 ? ( - bandIndex / (SIZE/2-1) - ) : ( - (SIZE-bandIndex-1) / (SIZE/2-1) - ); - ); - slider13 == 3 ? - ( - windowBuffer2[bandIndex] = sinc(2 * bandIndex / SIZE - 1); - ); - slider13 == 4 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer2[bandIndex] = 0.35875 - 0.48829 * cos(2 * cosIn) + 0.14128 * cos(4 * cosIn) - 0.01168 * cos(6 * cosIn); - ); - slider13 == 5 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer2[bandIndex] = 0.3635819 - 0.4891775 * cos(2 * cosIn) + 0.1365995 * cos(4 * cosIn) - 0.0106411 * cos(6 * cosIn); - ); - slider13 == 6 ? - ( - cosIn = $pi * bandIndex / SIZE; - windowBuffer2[bandIndex] = 0.355768 - 0.487396 * cos(2 * cosIn) + 0.144232 * cos(4 * cosIn) - 0.012604 * cos(6 * cosIn); - ); - - bandIndex += 1; - // next index +function setup_stft_state(fft_size, first_time) ( + //////////////////////// Setup block + // This is called when playback starts, or when the FFT size is changed + 0; + //////////////////////// ); +MAX_FFT_SIZE = 32768; +fft_size = 8192; +freemem = 0; +freemem = (fft_buffer = freemem) + MAX_FFT_SIZE*2; +freemem = (window_buffer = freemem) + MAX_FFT_SIZE; -@init -// init variables -// FFT - fft window size (will be constant) -SIZE = 8192; -// track how many half frames of delay from the start we are -// to mute the first inital buffers -silence = 2; - - -// fft has real and complex values thus is twice as large -bufferFFTIL = SIZE*0; -bufferFFTIR = SIZE*2; -bufferFFTO = SIZE*4; -bufferI1L = SIZE*6; -bufferI2L = SIZE*7; -bufferDryL = SIZE*8; -bufferI1R = SIZE*9; -bufferI2R = SIZE*10; -bufferDryR = SIZE*11; -bufferO1C = SIZE*12; -bufferO2C = SIZE*13; -strengthBuffer = SIZE*14; -phaseWBuffer = SIZE*15; -windowBuffer1 = SIZE*16; -windowBuffer2 = SIZE*17; -inputbufL = SIZE*18; -inputbufR = SIZE*18+8192; -freembuf(SIZE*18+16384); - -// samplesCollected tracks the position in the last of the two tiles -// ranges from 0 to (SIZE/2)-1 -samplesCollected = 0; -// offset ranges from 0 to SIZE-1 -offset = 0; - -// position in input buffer -inPos = 0; - -// tell reaper what delay this plugin will introduce, -// so that it can be compensated for -pdc_delay = SIZE + slider15; -// delay is one full buffer -pdc_bot_ch = 0; pdc_top_ch = 2; -// which channels are delayed -// (channel number must be greater than or equal to 0, -// but less than 2, so 0 and 1 - LR output). +buffer_length = srate; +buffer_index = 0; +freemem = (input_buffer = freemem) + buffer_length*2; +freemem = (output_buffer = freemem) + buffer_length*2; +freemem = (strength_buffer = freemem) + buffer_length*2; +freemem = (phase_width_buffer = freemem) + buffer_length*2; - -@sample -// store raw samples for later storing in input buffer -firstL = spl0; -firstR = spl1; - -// input corrections: -// rotate stereo field to isloate different phases -// and read input from buffer -slider15 == 0 ? -( - sampleLeft = cosine * spl0 + sine * spl1; - sampleRight = - sine * spl0 + cosine * spl1; -) : ( - sampleLeft = cosine * inputbufL[inPos] + sine * inputbufR[inPos]; - sampleRight = - sine * inputbufL[inPos] + cosine * inputbufR[inPos]; +function window(r) local(s, s2, gaussian_width, x) ( + // When squared, the Hann window adds up perfectly for overlap >= 4, so it's suitable for perfect reconstruction + (0.5 - 0.5*cos(r*2*$pi))/sqrt(0.375); ); -// that's a lowercase L in spl, not the number 1. -// spl0 corresponds to L, spl1 corresponds to R. - -// find positions in tile 1 and 2 -tilePos1 = samplesCollected + SIZE/2; -tilePos2 = samplesCollected; - -// apply the selected envelope while writing the sample into the tile buffer -// See https://en.wikipedia.org/wiki/Window_function -windowTile1 = windowBuffer1[tilePos1]; -windowTile2 = windowBuffer1[tilePos2]; -// windowTile2 has a relationship to the other window tile, so -// it is epressed in this way for accuracy and speed - -// subtract or output the center channel from L and R based on slider1 -// if slider1 is less than two, the Left channel is present in the output -// if slider1 is equal to 2, then the center channel is present in the output, -// otherwise it is subtracted -dryL = bufferDryL[offset]; -dryR = bufferDryR[offset]; -outPart1 = bufferO1C[tilePos1] * windowBuffer2[tilePos1]; -outPart2 = bufferO2C[tilePos2] * windowBuffer2[tilePos2]; -outScale = windowBuffer1[tilePos1] * windowBuffer2[tilePos1] + windowBuffer1[tilePos2] * windowBuffer2[tilePos2]; -outC = (outPart1 + outPart2) / outScale; -// account for how the two overlap positions -// in the window don't add to 1 - -centrePart = outC * centreMix; -leftPart = dryL * dryMix; -rightPart = dryR * dryMix; -outL = leftPart + centrePart; -outR = rightPart + centrePart; - -// write to dry buffers -bufferDryL[offset] = sampleLeft; -bufferDryR[offset] = sampleRight; - -// output audio, undoing rotation -silence == 0 ? ( - slider11 == 0 ? - ( - spl0 = outL; - spl1 = outR; - ) : ( - spl0 = outL * cosine - outR * sine; - spl1 = outR * cosine + outL * sine; - ); -) : spl0 = spl1 = 0; - -inputbufL[inPos] = firstL; -inputbufR[inPos] = firstR; - -// write to buffers: -// Left + Right input buffers -bufferI1L[tilePos1] = sampleLeft * windowTile1; -bufferI2L[tilePos2] = sampleLeft * windowTile2; -bufferI1R[tilePos1] = sampleRight * windowTile1; -bufferI2R[tilePos2] = sampleRight * windowTile2; - -// increment positions -samplesCollected += 1; - -offset += 1; -offset == SIZE ? -( - offset = 0; +overlap_factor = 8; +fft_interval = fft_size/overlap_factor; +fft_scaling_factor = 1/overlap_factor/fft_size; + +fft_size != prev_fft_size ? ( + setup_stft_state(fft_size, prev_fft_size == 0); + prev_fft_size = fft_size; + // Fill window buffer + i = 0; + loop(fft_size, + r = (i + 0.5)/fft_size; + window_buffer[i] = window(r); + i += 1; + ); ); -inPos += 1; -inPos >= slider15 ? -( - inPos = 0; +pdc_delay = fft_size; +pdc_bot_ch = 0; +pdc_top_ch = 2; + +freembuf(freemem); + + +@sample + +input_buffer[buffer_index*2] = spl0; +input_buffer[buffer_index*2 + 1] = spl1; + +fft_counter += 1; +fft_counter >= fft_interval ? ( + fft_counter = 0; + + // Copy input to buffer + bi = buffer_index - fft_size + 1; + i = 0; + loop(fft_size, + i2 = bi + i; + i2 < 0 ? i2 += buffer_length; + + fft_buffer[2*i] = input_buffer[2*i2]*window_buffer[i]; + fft_buffer[2*i + 1] = input_buffer[2*i2 + 1]*window_buffer[i]; + + i += 1; + ); + + // Process buffer + fft(fft_buffer, fft_size); + fft_permute(fft_buffer, fft_size); + + process_stft_segment(fft_buffer, fft_size); + + fft_ipermute(fft_buffer, fft_size); + ifft(fft_buffer, fft_size); + + // Add to output + bi = buffer_index - fft_size + 1; + i = 0; + loop(fft_size, + i2 = bi + i; + (i2 < 0) ? i2 += buffer_length; + + output_buffer[2*i2] += fft_buffer[2*i]*fft_scaling_factor*window_buffer[i]; + output_buffer[2*i2 + 1] += fft_buffer[2*i + 1]*fft_scaling_factor*window_buffer[i]; + + i += 1; + ); ); -// once we reach the end of a tile: -samplesCollected == SIZE/2 ? -( - // make silence go to 0 - silence > 0 ? silence -= 1; - samplesCollected = 0; - // wrap back to 0 on the tile - - // calculate fft for left channel - // Loop over each of the audio samples, from index = 0 to SIZE - 1. - index = 0; - loop(SIZE, - bufferFFTIL[2 * index + 0] = bufferI1L[index]; - // Real part - bufferFFTIL[2 * index + 1] = 0.0; - // Imaginary part - index += 1; - // Next index - ); - // calculate fft in place - // size specifies the number of bins - // (one complex number for each bin) - fft(bufferFFTIL, SIZE); - // need to permute the frequency bins to get them in the right order - // See https://www.reaper.fm/sdk/js/advfunc.php#js_advanced for more info. - fft_permute(bufferFFTIL, SIZE); - - // calculate fft for right channel - // Loop over each of the audio samples, from index = 0 to SIZE - 1. - index = 0; - loop(SIZE, - bufferFFTIR[2 * index + 0] = bufferI1R[index]; - // Real part - bufferFFTIR[2 * index + 1] = 0.0; - // Imaginary part - index += 1; - // Next index - ); - // calculate fft in place - // size specifies the number of bins - // (one complex number for each bin) - fft(bufferFFTIR, SIZE); - // need to permute the frequency bins to get them in the right order - // See https://www.reaper.fm/sdk/js/advfunc.php#js_advanced for more info. - fft_permute(bufferFFTIR, SIZE); - - - // Compute center: - // Make a weighted center (mono with respect to phase) - // that can be substracted from L&R - // we start off with the fft of the mid channel in the fftBuffer - bandIndex = 0; - loop(SIZE, - bandIndex >= lowBin && bandIndex < highBin ? - ( - Lreal = bufferFFTIL[bandIndex * 2]; - Limag = bufferFFTIL[bandIndex * 2 + 1]; - Rreal = bufferFFTIR[bandIndex * 2]; - Rimag = bufferFFTIR[bandIndex * 2 + 1]; - strength = strengthBuffer[bandIndex]; - // get strength - phaseW = phaseWBuffer[bandIndex]; - // get phase width - normL = sqrt(sqr(Lreal) + sqr(Limag)); - normR = sqrt(sqr(Rreal) + sqr(Rimag)); - normprod = normL * normR; - //sinval = (Lreal * Rimag - Limag * Rreal); - cosineval = (Lreal * Rreal + Limag * Rimag) * cosine2 + (Limag * Rreal + Lreal * Rimag) * sine2; - cosnorm = cosineval / normprod; - uncapped = acos(cosnorm) / phaseW; - weight1 = min(1, uncapped); - weight2 = 1 - (weight1 ^ strength); - // attenuate if relative norms are very different, and option enabled - atten = 1 - (sqr(normL - normR)/sqr(normL + normR)); - weight = weight2 * atten ^ (strength * slider10); - - fftOutReal = Lreal + Rreal; - fftOutImag = Limag + Rimag; - // isolate the mid fft (can just sum real and imaginary component - // since fft is a linear operator) - fftOutReal *= weight / SIZE; - fftOutImag *= weight / SIZE; - // attenuate by weight to do the actual isolation, and divide by SIZE - // to normalize the fft (since SIZE entries in fft buffer) - bufferFFTO[bandIndex * 2] = fftOutReal; - bufferFFTO[bandIndex * 2 + 1] = fftOutImag; - ) : ( - bufferFFTO[bandIndex * 2] = 0; - bufferFFTO[bandIndex * 2 + 1] = 0; - ); - bandIndex += 1; - ); - - // apply ifft to calculate center channel, which should now be present in fftBuffer - fft_ipermute(bufferFFTO, SIZE); - ifft(bufferFFTO, SIZE); - - // Copy from the complex numbers in fftBuffer to the center tile: - index = 0; - loop(SIZE, - bufferO1C[index] = bufferFFTO[2 * index + 0]; - index += 1; - ); - - temp = bufferI1L; - bufferI1L = bufferI2L; - bufferI2L = temp; - - temp = bufferI1R; - bufferI1R = bufferI2R; - bufferI2R = temp; - - temp = bufferO1C; - bufferO1C = bufferO2C; - bufferO2C = temp; +output_index = buffer_index - fft_size; +output_index < 0 ? output_index += buffer_length; +spl0 = output_buffer[output_index*2]; +spl1 = output_buffer[output_index*2 + 1]; +output_buffer[output_index*2] = 0; // clear the sample we just read +output_buffer[output_index*2 + 1] = 0; + +buffer_index = (buffer_index + 1)%buffer_length; + + +@slider +// convert low cut and high cut to bins every time a slider is changed +low_bin = min(slider5, slider6) / srate * fft_size; +high_bin = max(slider6, slider5) / srate * fft_size; +// convert to radians +rotation = slider7*$pi/180; +// convert percentage to raw scale factor +dry_mix = slider1/100; +wet_mix = slider2/100; +low_strength = slider3; +high_strength = slider4; +phase_width_low = slider8*$pi/180; +phase_width_high = slider9*$pi/180; +cosine = cos(rotation); +sine = sin(rotation); +cosine2 = cos(slider12*$pi/180); +sine2 = sin(slider12*$pi/180); +// fill strength_buffer and phase_width_buffer +band_index = 0; +loop(fft_size, + band_index >= low_bin && band_index < high_bin ? + ( + // only set values for the appropriate frequency range + frac = (band_index - low_bin)/(high_bin - low_bin - 1); + frac = max(0, min(1, frac)); + // fraction of progress through range [low_bin, high_bin) + strength = low_strength* (1 - frac) + high_strength * frac; + strength_buffer[band_index] = 10^strength; + // precaculate strength (actual value should be positive, so it makes + // sense to take the power of ten, but only after the + // linear mapping over the spectrum is done. + // precalculate phase width + phase_width_buffer[band_index] = phase_width_low * (1 - frac) + phase_width_high * frac; + ); + + band_index += 1; + // next index ); -// sliders are serialized automatically -// thus nothing to serialize, as nothing else makes sense to store +@serialize +serial_version = 1.00; +file_var(0, serial_version); +// nothing serialized yet, but keep track of the serial_version +// for the preset state of the original plugin, serial_version would now be euqal to 0.