JSFX Dojo

JSFX experiments, learnings or bedazzlements go here.

1 Like

I tidied the MultiFreaq code up so it’s a better base to start from:

desc: MultiFreaq
//################################################
// MULTIFREAQ                                    # 
// Muitichannel mixing spectroscope              #
// version 1.3                                   #
//                                               # 
// author: Sergey Grechin   (U.F.O scientific)   #
// Plug's homepage:                              #
// http://www.ufo-scientific.com/js_multifreaq   #
// contact me: support@ufo-scientific.com        #
// Original:  JS-multifreaq v1.1:multichannel mixing spectroscope #
//################################################


slider1:-90<-140,-60,10>Floor
slider2:0<0,5,1>--CPU release
slider3:4<0,5,1{512,1024,2048,4096,8192,16384}>FFT size
slider4:2<0,5,0.1>Smoothing factor
 

@init
  max_ch = 14;
  fftSize = 8192;
  maxFftSize = 16384;
  pcmBufferSize = 200000;
  pcmBuffer = 0;
  nextRecPosition = 0;
  lastBatchOfSamples = pcmBuffer;
  windowType = 0;
  fftWorkspace = pcmBuffer + pcmBufferSize;
  window = fftWorkspace + maxFftSize * 2;
  barWorkspace = window + maxFftSize;
  barAutoReductionSpeed = 7;
  ampThreshold = -90;

  //------------------------
  // Grid-related constants
  marginLeft = 5;
  marginRight = 30;
  hspacingFactor = 8;
  vspacingFactor = 2;
  marginTop = vspacingFactor * 13;
  marginBottom = vspacingFactor * 13;
  ampStep = 10;
  //------------------------

  numBars = 120;
  f0 = 16;
  k = 1.062088578; // Taken from Maple sheet
  bars = barWorkspace + numBars + 1;

  dWindowPos = $pi * 2 / (fftSize - 1);
  windowCreated = 0;
  gfx_flag = 0;

  cpuReleaseCounter = 0;


@slider
  ampThreshold = slider1;
  cpuReleaseCounter = 0;
  fftSize = 2 ^ (slider3 + 9);
  slider4 == 0 ? (barAutoReductionSpeed=0) : (barAutoReductionSpeed = 5.01 - slider4;);


@block
  num_ch > max_ch ? my_num_ch = max_ch : my_num_ch = num_ch;


@sample
  is = 0;
  loop(my_num_ch / 2,
    pcmBuffer[nextRecPosition] = (spl(is + is + 1) + spl(is + is));
    is += 1;
    nextRecPosition += 1;
    nextRecPosition == pcmBufferSize ? nextRecPosition = 0;
  );
  lastBatchOfSamples = nextRecPosition - my_num_ch / 2;


@gfx 515 300
  cpuReleaseCounter == slider2 ? (
    cpuReleaseCounter = 0;

    windowCreated == 0 ? (
      loop(fftSize,
        window[i] = (
          windowType == 0 ? 0.35875 - 0.48829 * cos(windowPos) + 0.14128 
            * cos(2 * windowPos) - 0.01168 * cos(6 * windowPos) : 1) 
          * 1.5; // *0.5;
        windowPos += dWindowPos;
        i += 1;
      );
      windowCreated = 1;
    );

    gfx_flag < 1 ? (
      gfx_flag += 1;
    ) : (
      num_ch <= max_ch ? (

        my_num_ch = num_ch;
        my_num_ch > max_ch ? my_num_ch = max_ch;

        i = 0;
        gfx_r = 1;
        gfx_a = 1;
        i = lastBatchOfSamples;

        // Draw grid
        i = 0;
        spacingFlag = 0;
        vspacingFlag = 0;
        f = 0;

        gfx_r = 1;
        gfx_g = 1;
        gfx_b = 1;  

        barWidth = floor((gfx_w - marginLeft - marginRight) / numBars);

        // Drawing border
        gfx_a = 0.9;
        gfx_x = marginLeft - 1;
        gfx_y = marginTop;
        gfx_lineto(gfx_x, gfx_h - marginBottom, 1);

        gfx_x = marginLeft - 1;
        gfx_y = marginTop;
        gfx_lineto(gfx_x + numBars * barWidth, gfx_y, 1);

        gfx_x = marginLeft - 1;
        gfx_y = gfx_h - marginBottom;
        gfx_lineto(gfx_x + numBars * barWidth, gfx_y, 1);

        gfx_x = marginLeft + barWidth * numBars;
        gfx_y = marginTop;
        gfx_lineto(gfx_x, gfx_h - marginBottom, 1);

        // Drawing decibel figures and lines
        amp = 0;
        i = 0;
        gfx_y = marginTop;
        loop(my_num_ch / 2,
          amp = 0;
          gfx_y = marginTop + (gfx_h - marginTop - marginBottom) / (my_num_ch / 2) * i;
          loop(-ampThreshold / ampStep,
            gfx_a = 0.8;
            gfx_x = marginLeft + numBars * barWidth + 2;
            gfx_y -= gfx_texth / 2;
            gfx_drawnumber(amp, 0);
            gfx_y += (gfx_h - marginTop - marginBottom) / (my_num_ch) * 2 / (-ampThreshold / ampStep);
            gfx_y += gfx_texth / 2;

            gfx_x = marginLeft;
            gfx_a = 0.1;
            gfx_lineto(gfx_x + numBars * barWidth, gfx_y, 1);
            amp -= ampStep;
          );
          i += 1;
        );

        c = 1;
        gfx_a = 0.5;
        loop(my_num_ch / 2 - 1,
          gfx_x = marginLeft;
          gfx_y = ceil(marginTop + ((gfx_h - marginTop - marginBottom) / (my_num_ch / 2)) * c);
          gfx_lineto(gfx_x + numBars * barWidth, gfx_y, 1);
          c += 1;
        );

        // Drawing frequency grid
        loop(numBars,
          f != 0 ? f *= k : f = f0;
          spacingFlag == 0 ? (
            gfx_a = 0.8;
            gfx_x = barWidth * (i + 1) + 2 + marginLeft;
            gfx_y = 5 + gfx_texth * 1.3 * vspacingFlag;
            f < 1000 ? gfx_drawnumber(f, 0) : gfx_drawnumber(f / 1000, 1);
            vspacingFlag += 1;
            vspacingFlag == vspacingFactor ? vspacingFlag = 0;
            gfx_a = 0.1;
            gfx_x = barWidth * (i + 1) + marginLeft;
            gfx_y = marginTop;
            gfx_lineto(gfx_x, gfx_h - marginBottom, 0);
            gfx_a = 0.8;
            gfx_y = gfx_h - gfx_texth * 1.3 * (vspacingFlag + 1);
            f < 1000 ? gfx_drawnumber(f, 0) : gfx_drawnumber(f / 1000, 1);
          );
          i += 1;
          spacingFlag += 1;
          spacingFlag == hspacingFactor ? spacingFlag = 0;
        );
        c = 0;
        lbs = lastBatchOfSamples;
        gfx_a = 1; 
        my_num_ch_div_2 = my_num_ch / 2;
        loop (my_num_ch_div_2,

          // Silence detection algorithm
          i = lbs + c - fftSize * my_num_ch / 2;
          i < 0 ? i += pcmBufferSize;
          j = 0; 
          sum1 = 0;
          silenceDetectionLength = 5;
          j = 0; 
          loop (silenceDetectionLength,
            sum1 += abs(pcmBuffer[i]);
            i += my_num_ch_div_2 * floor(fftSize / silenceDetectionLength);
            i >= pcmBufferSize ? i -= pcmBufferSize;
          );
          sum1 >= 0.001 ? ( // if NOT silence
            i = lbs + c - fftSize * my_num_ch / 2;
            i < 0 ? i += pcmBufferSize;
            j = 0; 
            j = 0; 
            loop(fftSize,
              fftWorkspace[j + j] = pcmBuffer[i] * window[j];
              fftWorkspace[j + j + 1] = 0;
              i += my_num_ch_div_2;
              i >= pcmBufferSize ? i -= pcmBufferSize;
              sum1 += abs(pcmBuffer[i]);
              j += 1;
            );

            fft(fftWorkspace,fftSize);
            fft_permute(fftWorkspace,fftSize);

            i = 0;
            loop(numBars,
              barWorkspace[i] = 0;
              i += 1;
            );

            fftWorkspace[fftSize] = 100000;
            g = (fftSize) / srate;
            i = 0;
            f2 = f0; // the high frequency for the bar zero
            f1 = 0;  // the low frequency
            loop(numBars,
              j = floor(g * f1); // calculating corresponding element from fft results
              sum = 0;
              num = ceil(g * f2) - floor(g * f1);
              loop(ceil(g * f2) - floor(g * f1),
                aa = fftWorkspace[j * 2 + 2];
                bb = fftWorkspace[j * 2 + 3];
                cc = sqrt(aa * aa + bb * bb);
                sum += cc;
                j += 1;
              );
              num ? ( 
                // the last /2 because we do not devide PCM data in sample block
                barWorkspace[i] = sum / num / (fftSize / 2) / 2; 
                barWorkspace[i + 1] = barWorkspace[i];
              );
              f1 = f2;
              f2 = f2 * k;
              i += 1;
            ); // loop

            // applying vertical transform
            i = 0;
            loop(numBars,
              barWorkspace[i] = 20 * log(barWorkspace[i]) / log(10) - ampThreshold;
              barWorkspace[i] < 0 ? barWorkspace[i] = 0;
              i += 1;
            );

            i = 0;
            loop(numBars,
              barAutoReductionSpeed ? bars[c*numBars + i] -= barAutoReductionSpeed * (slider2 + 1) : bars[c * numBars + i] = 0;
              bars[c * numBars + i] < barWorkspace[i] ? bars[c * numBars + i] = barWorkspace[i];
              i += 1;
            );

            i = 0;
            gfx_r = 1;
            gfx_g = 0;
            gfx_b = 0;
            x = marginLeft;
            barWidth = floor((gfx_w - marginLeft - marginRight) / numBars);
            barWidth > 2 ? barSpacing = 1 : barSpacing = 0;

            loop(numBars,
              gfx_r -= 1 / numBars;
              gfx_b += 1 / numBars;
              gfx_g += 1 / numBars / 1.5;

              gfx_x = x;
              gfx_y = marginTop + ceil((gfx_h - marginTop - marginBottom) / my_num_ch * 2 * (c + 1) + 1);

              h = (gfx_h - marginTop - marginBottom) / my_num_ch * 2 * bars[c * numBars + i] / abs(ampThreshold);
              h > gfx_h / my_num_ch * 2 ? h = gfx_h / my_num_ch * 2;
              gfx_rectto(gfx_x + barWidth - barSpacing, gfx_y - h);

              gfx_x = x;
              x += barWidth;
              i += 1;
            ); 
          ); // end of silence detection condition
          c+=1;
        ); 

      ) : ( // num channels > max
        gfx_a = 1;
        gfx_r = 1;
        gfx_x = 10;
        gfx_y = 10;
        gfx_drawstr("too many channels");
      ); // num channels > max
    ); // gfx_flag
  ) : ( // crc
    cpuReleaseCounter+=1;
  ); // crc

(I replaced all of the gfx_drawchar stuff at the end with the gfx_drawstr function that wouldn’t have been in JSFX at the time)

I have my plate full right now with the c++ learning, but I’m looking forward to seeing anything that lands in here, especially anything beginner’ish. It’s been a long minute since I tinkered with jsfx, and last I left off I had learned the basics of MIDI (then found out that Admiral Bumblebee had a nice tutorial for that - doh!), got some signals graphing, and got some super basic gui elements together. I remember last banging my head on making draggable buttons (with the aim for a modular routing gui) and getting stuck with the buttons losing track of the mouse pointer. I wanted to look next at basic filtering (a single band parametric eq to begin). I also do remember having thought that I needed to patch up my C learning quite a bit before coming back to jsfx. The end of the current book I’m working with covers some C for comparison to C++, and when I get through that stuff I’ll likely head back into a book on C (hopefully a good one).

I’m lost in the woods when it comes to DSP stuff but will be able to help with the programming. EEL2 has functions and other fairly advanced doo dahs that it didn’t have in the early days so producing nice code is easier in it. I’ll need to blow the dust off that part of my brain myself though!

I imagine you at least know to how to use the FFT functions. I remember not having a clue and left it alone for the time.

Bevo recently dropped some youtube links in another thread on beginner jsfx stuff. The first one was very long winded but covered using an averaging of two consecutive samples to start with filtering in the time domain. The beginning of the second video cleared up the previous video with a better explanation of what is going on, but I haven’t gotten further into the second one yet.

https://www.youtube.com/c/IDDQDMusic/search?query=beginner%20jsfx%20scripting

Nope, never used them. I understand that the steeper the slope the higher the frequency which can be used for filtering, and obviously the basics of compression/limiting but with zero experience of that or doing lookaheads, looking out for intersample peaks etc.

The goal with this one to help @Bevo with converting to a TCP GUI. The plugin uses FFT functions, but there’s no need to know them to change code around it and refactor.

I think the FFT functions aren’t needed for most things any way (anything that doesn’t require a samples window to do it’s thing). Interesting on the averaging of samples for filtering is that it is essentially the digital equivalent of analog RC filtering. Add two samples and take the average: (sample1 + sample2) / 2 = lowpass; (sample1 - sample2) / 2 = highpass. These are subtle filters, but it’s the beginnings on the concept. Basic analog RC filters use a capacitor and a resistor, and just flipping their positions in the circuit changes from a lowpass to highpass (like flipping the operator in the digital equivalent). Justin has said a number of times that most of the dsp in Reaper is just basic math. But I never saw any beginner tutorials approach dsp that way with basic math, outside of some very beginnings in Loser’s tutorial and now the videos with Leandro on youtube. Justin did also mention a number of times this book: https://www.dspguide.com/ I have read some from it here and there, and it was mostly understandable. Seems like I also remember him mentioning the RBJ Audio Eq Cookbook, but it was too concise for me to make any sense of it: RBJ Audio-EQ-Cookbook — Musicdsp.org documentation

It would be nice working through something like dspguide, I’ve had a wee peek in the past, but at the same time it’s good to draw lines under some things, being happy with a rough overview and the occasional dip of a toe. I’ve read enough tos and fros between actual ninjas to know that it would take a long time to get to ninja status as well. I suppose learning DSP is like planting trees, the best times to do them are today, or 10 years ago. :slight_smile:

Too bad jsfx weren’t written in lua originally
then scripting and fx would use the same language

I dont suppose anyone made a jsfx extension for vscode, did they?
Apparently there’s one for sublime already

There is one already!

I’ll check it out when I get home
Lua and EEL are covered

I get the drift that the bulk of what we might be interested in boils down to arithmetic and a little algebra and trig and complex numbers (from mentions in past discussions). Surely there are old books and discussions from old school game devs, etc. where this sort of stuff is talked about. I did spot a book that looked like a good place for someone who is already a programmer to start: Designing Audio Effect Plugins in C++: For AAX, AU, and VST3 with DSP Theory. No experience with it though.

jsfx is very C’ish. If you know C, jsfx is very familiar with some minor syntax differences.

Yeah thats what I thought too from a first look

Someone mentioned it’s similar to JS
also kinda C-esque

I wonder when a book for the new open-source CLAP format will appear
That might end up the future of plugins

The syntax isn’t difficult at all. It just requires that you know your C programming stuffs, such as using bitwise operators. I didn’t when I started tinkering with it, but filled in some holes pretty quick and spotted more that I should fill in.

Who knows, but I think it will likely be very similar (programming wise) to other formats, where the hardest part will be knowing general programming and dsp.

Im not saying it looks difficult

Just that it’d be nice to use Lua for both reascript and jsfx
But Lua got included long after EEL, so…

Yea, I suppose that would be nice for anyone who already knows Lua. Jsfx is so small of a language and environment though that I don’t know if it would matter. The only smaller language I have had an encounter with is the pseudo assembly in the game Human Resource Machine (fun game!). I really like jsfx for how compact it is. I only wish that Justin would converge it with eel2 for a more general programming environment. I guess that is what WDL is about though.

One EEL2 thing that has caught many people out over the years is that everything’s an expression and between parens () the final expression is the value that is returned. So:

a = (127; 94; 42;);
// a == 42

a = (1 < 2; 2 < 1;);
// a == 0

a = (1 > 2; 2 > 1;);
// a == 1

It’s not unusual: Lisp, Rust and other languages do this too.

What is a practical use case for that?