HISE Logo Forum
    • Categories
    • Register
    • Login
    1. HISE
    2. HISEnberg
    3. Posts
    • Profile
    • Following 0
    • Followers 4
    • Topics 53
    • Posts 589
    • Groups 1

    Posts

    Recent Best Controversial
    • Oscilloscope Script

      I am trying to optimize my oscilloscope script while still keeping it visually interesting. I recall having a discussion a few months ago about this but I can't find the original post.

      Anyways here is an oscilloscope script for drawing the analyzer to a path in a panel. There are a lot of adjustable variables for optimizing it (buffer size, frame rate, frame count, etc.) There is also optional opacity settings (glow effect) and ghost trails (though these consume a lot more CPU). Hope you guys like it and let me know if you can think of any optimizations or better visual performance for it!

      Here is an A/B comparison of it with the floating tile display

      osc.gif

      HiseSnippet 3084.3oc2as0baabEFTRH0Vw4Vmz59HFMsy.YISSRcupNQzTjRbh3kRRY0LdznYIvJx0BDfE.zxJdzL8WRm9SnO1eF8w9X+Ij+AsmcWbYW.PKZEeIN7AEuWNm8rmy24xtKRaWGCrmmiqRtE6c0XrRt6o18Ja+gUFhH1J02WI2mp53Y7PSb+ICTdxUiQddXSkb4l+.5Dxc2ETX+9wu8IHKjsANtKEkm5PLvGQFQ7i6s8deGwxpFxD2iLRX1quWcCG6JNVNS.gYd0BJiQFWfFfahnSaNUkCQdCUx8.09EMJfWquAB0GsCd6s2n+5au0NHbQyhaswl6rQgc5u4FasogRtOopIw2wsqOxG6ojagm3XdU2gNWZyWfmR7H8svzFEU5BqLu6ZNVlzsHsWkJCIVlsC0RdJ.SaGqylmqy9Z0FDSRT+w5tujMfVLEhJvbyIKdyKIdEEEuBBhWFhTNAQZAtH8UpcMbIi8iGgaKqa6icOGA1IQQgOWk4962WshCLCa+7iPWfq4BMhnPe8BEVUC9yx6tnMXT7.CDVqkmAXOAHhyX7huZQM3GXG870dAxUqrMx5JOraQsGqwfU4Gf8qd94XCe8khFbIfgxz8jiqUqZGQh1m3M1Bc0Sl.D61EvHf7bCb3rVcq.rfyqT7PuPJZFaacFrc.ZBUB.QUbFM1wFZnuTv3YrZ9.T1Enqp8.hMNugKFLoT7saq9OmtYSKeS78crKN00JX7vkRfZ5N6npMOn2g.wqWXmM2US6QORqC1bhA1TqOa2o4Q9Ar14NtZ8w9f8SqR6iiYjKd.iMs6zpc0N8pWsKvpWsDWwbD1df+vk9iBKzpZK0bxH.Vaais7fgJdMWrBzx48v9cH1C3L.fbiwt9Drmt7hrbJhJa3SdAV22cBN8FsW0FsOKBIv4cfpUOV1VdWIh1uS4SlIhRtZ05TtQ0ypz53l8.BWa2HE04t.VmyHOXjmcZ7PFSbcAqUM5LpaaheILdAg8AXVpfLFBVkwH+gZNLrfWD4ifvDsoCDCB3RJsyPHCch9tHh0MNS1enlbcBSNzHZ+IwsEzwJqrLaVb+T17E1c4GOwan9T0YAKy0x3nQnA1D+IlX5ZleCQbdixGzrdui2u5YcazpECuVH+NRyndyyhlEa3BaHq+1GaPFg7IN1hF4pUp2nbupmUqbkdsnF50JsaJTP0mVsy2eVyyXp.JBuXBiZEmIzXaILZAdYkq7cGzATa6CJuiZcLcQJ7xBA+D2CmT9oUq0pSCw4UtPsZkqNk408vx625D171Dl21Sie85Tt9QroslD6BUMG4bIH7u.YMA6I5pCNePqQzjwBr8fiZApDvunBEeS83jLDs5UuUS9R1MiwqzpSypchsiqkWRlaWt2gm06v5U9tlU6RouT9DBaiHXxjwl.vh0Ow1BBWpc9DaCpENXnnopmDrZ4XfrzrwWBSAVDAecZzSA51MhDQ3Y7+9AowlqDx2GnoWLeAsGlZJSgqM.WPHa4K0k.yqFOmz9Mf9fEyPyCR+Ck3.Klis0Ufxb7UZlbDOMngCwNHbQRMEkPbEgvOozUICDD6HSauxiS5DsbDkICK7rTg4N8YjSk0+PGwZmn+QVwG0S24JZEWV6OHEpJCM19tnKgbLQ5fL0KlvjDqHQevpPrWHqEydjTGMH+4vLKaYomxce4L1Nb32kDSVfXFao3tSncHh43yaHlLXnu3DOj0S5YNhX98v7BH3QZkl5Z6A+AadXHmCn3AAXPhME4x2oIWCphgtHr05gRL5QkRN49PfQSVxNfaLJWkusWUhvrL4AFJdJKNLxSibtF1FA0xZFMOnKcofNw3uWIiDo3XFepaxyupE0JQ5sv9CyxkMCCW8Xd93z.0kSQRPvNehcXjQweo5fqHo91YB4eX7lXEwMQl9Ao4qCT5Mw+JVFys.loSWnGIynG.is4LHoQEWj2vBibEgmI.enQisjh38PFbSF7IE4HbSd5yJb5xYv3301yG452DeY2I8Y0zPgwAPU1xt7LrShB4kHz1sI.X13lXkAKTFAz4BEHApblWxtYR2OI0GIS0mrJjFIrmi9KuYM20uInBWZvfJNt1fDoKUGvrXSFPKymepdc9+wK+kD+gksFODoKWqypQ3ZJ1cir1wCxSCGwPHQR3pAwqVENBSugDiKrgC7BGRIQQILddcBdFqItd5wxn0oypfWH+Oux8ztLbzgnCOXwugroSOb+LAYDYXB+rH4bFbuV7ssajr6yapaysXum1IIZ+ey9FYX+indlbADgMGX4boF+dNjx3ITD9zzSynCC+PDqlQl+j5.AWlvMzL6w.kwe8qWGIJtINIj.g2ZY3QktNc4yA2CCckaCbyuiyDHsLVOrJP8AKq8pXQLqZB8GR7jJQmO8qWV9TKrqvQqVX0kAmMlUmNTJ5xYVBpiMipJHKq9HiKRUXd30ePYRGLxL35nDtsir.U+zJmWtRcfKBqV1ktm5nXwyIiydjsWP2KHiCKAz2QiLZrqyKvoNeZz4MBNO9Jqr6hoJTK7r5eyiy5v8SyYJDo3hGSAJIqsI6aAHFscs.ZfcEeTHmr0Mg0VD+jFXDbodzqvw0wR2H759VkeJ9jHE5lmM.sD0hu1Bjuwa7Y50zvn+4b5edBj0ymo5jRcfQxoO64mxuNmoktMUwBA5WZdJlFUesBYF4Aa4gyjPmwb5BiWHXIBz67PULcuf8S1n.TesfEqoiOtkM3C+pEu6hfPjbnyOOywBXlEUbxX3.Ac5DpaOYTeraDtHXhJ4VP9ICTm9SFH9hFF76LTXhN10AG6Viw1S6cNTBtnQ5yKDHUvT8YOtvmG73BbUmBwTI2cUCziJLQV7ggTNt99HeTHi.dFeCwPe6ieAw.ye0h6ptO16BvTBKUzkgqj6S3K6mE9lFzCTyV0EUCuSdkWJ9vSWI1fUbQbGWsG+Xyw8P1SQXwtmeva5vWrZVNHH6xfdDPuRWyuPUrqouvj8lgENPgv28egZ4IlDmvW0Po+fvmC6dpqWZ6har9FaTZMEhOdjz.aUbss1oz1BCTJXjRat45EJrthO9k9hOsFydj6HUZzfkhr50Mg7uBOpxpzQYknRuxeVqZ.n14xSbbuf85OP+minNjWqHpB+Uy.LYVsW+s+bJ012rmI9bzDK+mxgZL.b5m.6KUa3XNwB4K+hbz2sLX.vmS5YvnO0ksGb.DQ36asmoaVEwuRsMw2XX1x3bYHifm06BYL3wM+LUdgrwB3Bp09KuidIy4DV+6D8fzDZs5GDt72SsAxCxXy5X5uHshx9XKjjZ5DYGxueuLdy551u.BM01wB4lTIO+LuKK952kKLyHA5VR6mBZcgYyT75ey6YE49qUYp7OlDXFj3iGA9KUgje+LPb4G4Z.b5SWBDIWs4jQ7GkO7Qhg9xMGsJBd6Bz1TApK11j03+A+BFrHsctfAKFNnnJQkqRVTMJ2H+qYHJWkjdH3iDf7CBN1+y8Z6BEZfuLnPiftuydu07peeqS9b9N4Sgvi1X12IASo7aYs0NAAmz5.rM1kBfJNs3j68u9r+yr9k6Ldl+xcZY3CKeOWjs2XGOIF2EOhzCJSvSryi8v0bw+0NTHsX+UbPtYNzO9s0fMYlzzE4Owk4aTdD8jcutH3KLa15a3CBZg2UePPumir7AqvHoueo6FJi7r9UgzwVvoEhxuvJ7KrWYbcCGamwCcrIFhF8NXvebv.rqnrm4FpruObjvnd9ww60AagQB.3l+i8NB.dHWPOguk5hhuweKWYZu9cpbwkcGUe7j.60Vb67yTwsu0k2O.E599NYw8CSVPyKDmr32vZSuMtojqXN4Zp+2uyyUTLyjER8lfhRYRQI4CB3OwVl07tjlEUUTCTEEEKOHryRhcB0fIwLnsDmZPDNc6+8aqxd66tXXmaxuHZpdVzkr8D3PzrRQKJRXb2kD69PjqIXCMdKjZSNluv7+n3Ce8Wz41J8tL2lxWmJ2lxu+Wf419vdYMy+K5KqY189f3g+71JdeUHtbIMloT6Cis78QAAuOViQHCWmyL3uX.0o+Nrdf8sM6+QNtqZCZasTOTfhxHHkwYFFxrJEgktsDt1skv0usDtwskvMusDt0skvsuYBoYzKOw2g+4XSKyocU9CIjiWgCyMQ4+iYkPuv
      
      Content.makeFrontInterface(400, 400);
      namespace Oscilloscope
      {
          const var Analyser1 = Synth.getEffect("Analyser1");
          const var BUFFER = Synth.getDisplayBufferSource("Analyser1");
          const var BUF_OSC = BUFFER.getDisplayBuffer(0);
          const var pnl_Osc = Content.getComponent("pnl_Osc1");
          const var timer = Engine.createTimerObject();
          const var Button1 = Content.getComponent("Button1");
          
          const BUF_LENGTH = 4096;  // Reduced buffer size for better CPU
          
          reg BUF_PROPERTIES = {"BufferLength": BUF_LENGTH, "NumChannels": 1};
          BUF_OSC.setRingBufferProperties(BUF_PROPERTIES);
          BUF_OSC.setActive(true);
          
          const TEMP_BUFFER = Buffer.create(BUF_LENGTH);  
          const DRAW_BUFFER = Buffer.create(BUF_LENGTH); 
          
          const FRAME_COUNT = 3;
          reg frameBuffers = [];
          reg currentFrameIndex = 0;
          
          // Cached path objects
          reg mainPath = Content.createPath();
          reg trailPath = Content.createPath();
          
          for (i = 0; i < FRAME_COUNT; i++)
          {
              frameBuffers.push(Buffer.create(BUF_LENGTH));
          }
          
          reg magnitude = 0.5;
          const MAGNITUDE_SMOOTH = 0.9;
          const MIN_MAGNITUDE = 0.05;
          
          // Decimation
          const DECIMATE_FACTOR = 32; 
          const DRAW_EVERY_N_FRAMES = 1;
          reg frameCounter = 0;
          
          const BACKGROUND_COLOUR = 0x00000000;
          const WAVEFORM_COLOUR = 0xA0FFAE00;
          const WAVEFORM_SHADOW = 0x60FF8000;
          const WAVEFORM_TRAIL = 0x30FFAE00;
          
          // Lower values for better performance
          const GLOW_EFFECT = true;
          const MOTION_TRAILS = true;
          const CORNER_SMOOTH = 3.0;
          const PATH_THICKNESS = 2.0;
          
          // Magnitude update
          inline function updateMagnitude()
          {
              local newMag = DRAW_BUFFER.getMagnitude();
              magnitude = magnitude * MAGNITUDE_SMOOTH + newMag * (1.0 - MAGNITUDE_SMOOTH);
              magnitude = Math.max(MIN_MAGNITUDE, magnitude);
          }
          
          // Frame storage - only copy decimated points
          inline function storeCurrentFrame()
          {
              for (i = 0; i < BUF_LENGTH; i += DECIMATE_FACTOR)
                  frameBuffers[currentFrameIndex][i] = DRAW_BUFFER[i];
              
              currentFrameIndex = (currentFrameIndex + 1) % FRAME_COUNT;
          }
          
          // Drawing function
          inline function drawOscilloscope(g, panel, mag)
          {
              g.fillAll(BACKGROUND_COLOUR);
              
              local width = panel.getWidth();
              local height = panel.getHeight();
              local midY = height / 2;
              
              local scaledHeight = height * Math.min(1.0, mag);
              local drawY = midY - scaledHeight/2;
              local bounds = [0, drawY, width, scaledHeight];
              
              // Draw trail frames if enabled
              if (MOTION_TRAILS)
              {
                  for (frameIdx = 0; frameIdx < FRAME_COUNT; frameIdx++)
                  {
                      if (frameIdx == currentFrameIndex)
                          continue;
                      
                      local age = (currentFrameIndex - frameIdx + FRAME_COUNT) % FRAME_COUNT;
                      local opacity = 0.7 - (age / FRAME_COUNT) * 0.6;
                      
                      trailPath.clear();
                      local sample = Math.max(-1.0, Math.min(1.0, frameBuffers[frameIdx][0]));
                      trailPath.startNewSubPath(0, midY - sample);
                      
                      for (i = DECIMATE_FACTOR; i < BUF_LENGTH; i += DECIMATE_FACTOR)
                      {
                          local x = (i / BUF_LENGTH) * width;
                          sample = Math.max(-1.0, Math.min(1.0, frameBuffers[frameIdx][i]));
                          trailPath.lineTo(x, midY - sample);
                      }
                      
                      trailPath.roundCorners(CORNER_SMOOTH);
                      
                      g.setColour(Colours.withAlpha(WAVEFORM_TRAIL, opacity * 0.5));
                      g.drawPath(trailPath, bounds, {"Thickness": PATH_THICKNESS * 0.5});
                  }
              }
              
              // Draw main path
              mainPath.clear();
              local currentFrame = frameBuffers[currentFrameIndex];
              
              local sample = Math.max(-1.0, Math.min(1.0, currentFrame[0]));
              mainPath.startNewSubPath(0, midY - sample);
      
              for (i = DECIMATE_FACTOR; i < BUF_LENGTH; i += DECIMATE_FACTOR)
              {
                  local x = (i / BUF_LENGTH) * width;
                  sample = Math.max(-1.0, Math.min(1.0, currentFrame[i]));
                  mainPath.lineTo(x, midY - sample);
              }
              
              mainPath.roundCorners(CORNER_SMOOTH);
              
              // Glow Effect
              if (GLOW_EFFECT)
              {
                  g.setColour(Colours.withAlpha(WAVEFORM_SHADOW, Math.min(1.0, mag)));
                  g.drawPath(mainPath, bounds, {"Thickness": PATH_THICKNESS * 2.0});
              }
              
              g.setColour(WAVEFORM_COLOUR);
              g.drawPath(mainPath, bounds, {"Thickness": PATH_THICKNESS/2});
          }
          
          pnl_Osc.setPaintRoutine(function(g) { 
              drawOscilloscope(g, this, magnitude); 
          });
          
          // Timer Function (Buffer copying)
          inline function onTimerCallback()
          {
              BUF_OSC.copyReadBuffer(TEMP_BUFFER);
              
              for (i = 0; i < BUF_LENGTH; i += DECIMATE_FACTOR)
                  DRAW_BUFFER[i] = TEMP_BUFFER[i];
              
              updateMagnitude();
              storeCurrentFrame();
              
              // Skip frames to improve performance
              frameCounter++;
              if (frameCounter >= DRAW_EVERY_N_FRAMES)
              {
                  pnl_Osc.repaint();
                  frameCounter = 0;
              }
          };
          
          timer.setTimerCallback(onTimerCallback);
          
          inline function onButton1Control(component, value)
          {
              if (value == 1)
              {
                  for (i = 0; i < FRAME_COUNT; i++)
                  {
                      for (j = 0; j < BUF_LENGTH; j += DECIMATE_FACTOR)
                          frameBuffers[i][j] = 0.0;
                  }
                  
                  timer.startTimer(30);
              }
              else
                  timer.stopTimer();
          };
          
          Button1.setControlCallback(onButton1Control);
      }
      
      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Multichannel MIDI FX - Is it possible in HISE?

      @VirtualVirgin Awesome I am glad to hear that!
      My setup would be more or less static. I intend to have either 8 or 10 drum channels. By default it will only be 5 channels active, and the user can enable more channels if they want, but it shouldn't effect the MIDI routing.

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: Which AAX SDK version works for HISE 4.1.0?

      @Sawatakashi Lol not really I only set this up last week but I had similar issues.

      For the first issue on Windows you have basically solved it. When you build the AAX in Visual Studio (so the one that is in HISE's SDK folder), set it to static release. Each time you build your HISE project it will build against this version and you will be good to go. There is a post about this on the forum somewhere.

      When building the AAX SDK on Xcode it was a bit more trial and error for me but I think what happens is HISE is only designed to build for arm64 or Intel slice  x86_64 (depending on what you set in your Projucer). I ended up asking chat gpt for the solution to the universal binary and it gave me the right response. I found the terminal command (option 2) to be more efficient:

      Option 1 – Re‑build in Xcode (GUI)

      Open the project again
      Libs/AAXLibrary/MacBuild/AAXLibrary.xcodeproj

      Target & Build Settings

      Select the AAXLibrary_libcpp target.

      In Build Settings

      Architectures → Standard Architectures (arm64, x86_64)
      (don’t choose “arm64 only”).

      Build Active Architecture Only → No (Debug and Release).

      Excluded Architectures → (leave empty)

      Choose a universal build destination
      In the scheme selector (the drop‑down next to the ▶︎ button) pick
      Any Mac (My Mac) or Any Mac (Mac Catalyst) – not “My Mac (Apple Silicon)”.
      That generic destination tells Xcode to build all valid slices.

      Build (⌘B)
      When it finishes, run:

      lipo -info "$HOME/HISE/tools/SDK/AAX/Libs/Release/libAAXLibrary_libcpp.a" 
      

      Expected output:

      Architectures in the fat file: libAAXLibrary_libcpp.a are: arm64 x86_64
      

      Option 2 – One‑liner with xcodebuild (CLI)

      From Terminal inside Libs/AAXLibrary/MacBuild/:

      xcodebuild -project AAXLibrary.xcodeproj \
                 -target AAXLibrary_libcpp \
                 -configuration Release \
                 -sdk macosx \
                 ARCHS="arm64 x86_64" \
                 BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
                 BUILD_ACTIVE_ARCH_ONLY=NO
      

      This explicitly asks Xcode to build both slices and drops the fat libAAXLibrary_libcpp.a in:

      Libs/Release/libAAXLibrary_libcpp.a
      Check it with lipo -info as above.

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: Table curvy shape issue

      @d-healey said in Table curvy shape issue:

      HISE_Definitions

      It would be really great to have this added to the HISE documentation!

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: FFT Analyser Path - Need help drawing the magnitude to height

      @ustk Ah great tips thank you!

      My OG script is using the createPath method. It performs well (generally better than the HISE stock display with the floatingTile).

      The interface script deferred, does that just mean the Synth.deferCallbacks(true), or are you referring to the specific script?

      Interesting suggestion about copying the buffers. I would have thought there was more overhead this way but I could see how this is more safe for threading.

      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Flangers...

      @DanH You mean more or less like this correct?

      Screenshot 2025-05-12 at 4.37.10 PM.png

      You could accomplish it with a simple delay and modulating the delay time. @griffinboy is correct though, reading papers and checking some repos is the way to go here. My example is more or less just a theoretic flanger but doesn't match the performance of what you may find from people who have done actual research on the topic.

      But you can try this:

      import("stdfaust.lib");
      
      // UI
      maxdelay    = 0.5; 
      delaytime   = hslider("delay [ms]", 5.0, 0.0, 20.0, 0.01); //Add si.smooth(0.99) for interploation here if you want
      feedback    = hslider("feedback", 0.5, 0, 0.99, 0.01);
      mix         = hslider("mix",      0.5, 0, 1.00, 0.01);
      
      // State variables
      SR          = ma.SR;
      maxdlsamps  = int(maxdelay * SR);
      delaysamps  = int(delaytime / 1000  * SR); 
      
      // Dleay
      delayedSignal(x) = fb ~ de.fdelay(maxdlsamps, delaysamps)
        with { fb(y) = x + y * feedback; };
      
      modDelay(x) = (1-mix)*x + mix*delayedSignal(x);
      
      // Stereo
      process = modDelay, modDelay;
      
      
      
      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: FFT Analyser Path - Need help drawing the magnitude to height

      @ustk Precisely what I am using, it's been super helpful and I really appreciate Christoph's work on this! Unfortunatley it's a really strange issue. Generally (90% of cases) the plugin runs completley fine, and there really is nothing super complex about it. However for about 10% of users there is some serious lag and even crashes their DAW and I can't put my finger on whats causing this (I haven't been able to recreate the issue). So I am trying to minimize the imapct of any UI/Script callbacks and paint routines

      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Flangers...

      @Lindon Couldn't you just create one in Scriptnode or Faust with a modulated delay?

      *Edit here is one I wrote a long time ago when I was learning Faust

      declare name "Flanger";
      declare version "1.0.0";
      declare author "HISENBERG";
      declare license "IDGAF-DWYW"; // I don't care do what you want with it
      
      import("stdfaust.lib");
      
      // Mono/Stereo
      flanger = _,_ : flanger_stereo : _,_;
      flanger_stereo(l,r) = flanger_mono(l), flanger_mono(r);
      
      flanger_mono = effect
      with {
      
          // UI controls
          maxdelay = 0.02;
          rate = hslider("rate[unit:Hz]", 0.5, 0.01, 10.0, 0.01);
          depth = hslider("depth", 0.7, 0.0, 1.0, 0.01);
          feedback = hslider("feedback", 0.5, 0.0, 0.99, 0.01);
          mix = hslider("mix", 0.5, 0.0, 1.0, 0.01);
          
          lfo = os.oscrs(rate) * 0.5 + 0.5; 
          delayTime = 0.0001 + lfo * depth * maxdelay;
          
          // Delay line with feedback
          delayedSignal(x) = fb ~ de.fdelay(maxdlsamps, delaysamples) 
          with {
              SR = ma.SR;                  // Sample rate
              maxdlsamps = int(maxdelay * SR);
              delaysamples = delayTime * SR;
              fb(y) = x + y * feedback;    // Feedback loop
          };
          
          // Mix dry and wet signals
          effect(x) = dry * x + wet * delayedSignal(x)
          with {
              wet = mix;
              dry = 1 - mix;
          };
      };
      
      process = flanger;
      
      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: FFT Analyser Path - Need help drawing the magnitude to height

      @ustk Thanks for taking a look! In this particular case I am attempting to create a subpath so that I can ignore painting/drawing any values that are already displayed. I wasn't able to do this with DisplayBuffer.createPath, thus the subPath.

      Long story short is I have one project with a very large FFT display and users are reporting a lot of UI lag, so I suspect this is the culprit. I just cracked the magnitude issue however! This is the current draft of the code (though still a mess from working with AI on this, I am in the process of cleaning it up):

      To be honest I don't actually see much performance benefit to this method so far so this might be an effort in vain. The only other real benefits of this script is it will allow you to define the bins (I know you can do this already), but also assign different colour gradients, etc.

      Content.makeFrontInterface(400, 400);
       
      namespace MinimalFftAnalyser
      {
          // Core components
          const var Analyser1 = Synth.getEffect("Analyser1");
          const var source = Synth.getDisplayBufferSource("Analyser1");
          const var fftTimer = Engine.createTimerObject();
          const var pnl_Fft = Content.getComponent("pnl_Fft0");
          
          // Display buffer
          pnl_Fft.data.buffer = source.getDisplayBuffer(0);
          pnl_Fft.data.buffer.setActive(true);
          pnl_Fft.data.path = Content.createPath();
          
          // Processing buffers
          const buff = Buffer.create(pnl_Fft.data.buffer.getReadBuffer().length);
          reg lastPoints = [];
      
          // Configuration constants
          const THRESHOLD = 1.0;   
          const SIGNAL_DECAY = 0.1; 
          const SILENCE_THRESHOLD = 0.05; 
          const DISPLAY_BINS = 4098;  // Increase/Decrease for more detail
          const HEIGHT_SCALE = 0.5;  
          const CURVE = 0.99;
          
          // Frequency mapping parameters
          const MIN_FREQ = 20;       // Hz - lowest frequency to display
          const MAX_FREQ = 20000;    // Hz - highest frequency to display
          const SAMPLE_RATE = Engine.getSampleRate(); // Hz - adjust to match your system
          Console.print(SAMPLE_RATE);
          
          // Amplitude response settings
          const AMPLITUDE_DECAY = 0.95;     // Higher values = slower decay
          const AMPLITUDE_ATTACK = 0.3;     // Higher values = faster attack
          const MAX_MAGNITUDE = 0.1;        // Lowered to make visualization more sensitive
          const MIN_DISPLAY_DB = -60;       // Minimum dB level to display (lower = more sensitive)
          
          const button = Content.getComponent("Button1");
          
          // Fixed scaling factor - adjust for desired sensitivity
          const scaleFactor = 4.0;   // Increased for better visibility
          
          // State variables
          reg signalLevel = 0.0;
          reg smoothedMagnitudes = [];      // Array to store smoothed magnitude values
          
          // Button callback for enabling/disabling the FFT
          inline function onButton1Control(component, value)
          {
              if (value == 1)
                  fftTimer.startTimer(30);
              else
                  fftTimer.stopTimer();
          }
          
          button.setControlCallback(onButton1Control);
          
          // Initialize the FFT system
          inline function initialize()
          {
              pnl_Fft.setPaintRoutine(fftPaintRoutine);
              updateFFT();
              fftTimer.setTimerCallback(updateFFT);
              fftTimer.startTimer(30);
          }
          
          // Main paint routine for the FFT panel
          inline function fftPaintRoutine(g)
          {
              local bounds = this.getLocalBounds(0);
              local w = bounds[2];
              local h = bounds[3];
              local path = this.data.path;
              
              g.setColour(Colours.withAlpha(0xFFFFFFFF, signalLevel));
              g.fillPath(path, [0, 0, w, h]);
          }
          
          // Converts linear magnitude to decibels with a minimum threshold
          inline function linearToDb(linearValue)
          {
              if (linearValue < 0.000001) 
                  return MIN_DISPLAY_DB;
                  
              local dbValue = 20.0 * Math.log10(linearValue);
              return Math.max(dbValue, MIN_DISPLAY_DB);
          }
          
          // Maps decibels to display height with a better curve
          inline function dbToDisplayHeight(db, height)
          {
              local normalized = (db - MIN_DISPLAY_DB) / (-MIN_DISPLAY_DB);
              normalized = Math.max(0, Math.min(1, normalized));
              normalized = Math.pow(normalized, CURVE);
              
              return height/2 - (normalized * height/2 * HEIGHT_SCALE);
          }
          
          // Detects signal level from buffer data with improved decaying
          inline function detectSignalLevel()
          {
              local magnitude = buff.getMagnitude(0, buff.length);
              
              // Use slower decay for a more natural fade out
              signalLevel = Math.max(magnitude * scaleFactor, signalLevel * SIGNAL_DECAY);
              signalLevel = Math.min(1.0, signalLevel);
              
              return signalLevel;
          }
          
          // Handles silence or very low signal
          inline function handleSilence(path, w, h)
          {
              // Reset smoothed magnitudes during silence
              smoothedMagnitudes = [];
              
              for (i = 0; i < lastPoints.length; i++)
                  lastPoints[i] = h/2;
              
              path.lineTo(w, h/2);
              path.lineTo(w, h/2);
              path.lineTo(0, h/2);
              path.closeSubPath();
              
              return path;
          }
          
          // Creates the FFT path with optimized points and improved frequency distribution
          inline function createFilteredPath()
          {
              local bounds = pnl_Fft.getLocalBounds(0);
              local w = bounds[2];
              local h = bounds[3];
              
              local path = Content.createPath();
              path.startNewSubPath(0, h/2);
              
              local actualBins = Math.min(DISPLAY_BINS, buff.length);
              
              detectSignalLevel();
              
              if (lastPoints.length != actualBins)
              {
                  lastPoints = [];
                  for (i = 0; i < actualBins; i++)
                      lastPoints[i] = h/2;
              }
              
              // Initialize smoothedMagnitudes if needed
              if (smoothedMagnitudes.length != actualBins)
              {
                  smoothedMagnitudes = [];
                  for (i = 0; i < actualBins; i++)
                      smoothedMagnitudes[i] = 0.0;
              }
              
              if (signalLevel < SILENCE_THRESHOLD)
                  return handleSilence(path, w, h);
              
              // Calculate the proper bin-to-frequency mapping
              local totalBins = buff.length;
              local binFrequencies = [];
              
              // Pre-calculate bin frequencies
              for (i = 0; i < actualBins; i++)
              {
                  local freq = (i / totalBins) * (SAMPLE_RATE / 2);
                  binFrequencies[i] = freq;
              }
              
              // Now draw the path using the frequency mapping
              for (i = 0; i < actualBins; i++)
              {
                  local freq = binFrequencies[i];
                  
                  // Skip if frequency is out of our visible range
                  if (freq < MIN_FREQ || freq > MAX_FREQ)
                      continue;
                      
                  // Logarithmic mapping of frequency to x position
                  local logPos = Math.log(freq / MIN_FREQ) / Math.log(MAX_FREQ / MIN_FREQ);
                  local x = logPos * w;
                  
                  // Get current bin magnitude
                  local magnitude = Math.abs(buff[i]);
                  
                  // Apply envelope smoothing for each bin
                  // Fast attack, slow decay
                  if (magnitude > smoothedMagnitudes[i])
                      smoothedMagnitudes[i] = smoothedMagnitudes[i] * (1 - AMPLITUDE_ATTACK) + magnitude * AMPLITUDE_ATTACK;
                  else
                      smoothedMagnitudes[i] = smoothedMagnitudes[i] * AMPLITUDE_DECAY;
                  
                  // Convert to dB for better visualization
                  local dbValue = linearToDb(smoothedMagnitudes[i]);
                  
                  // Map dB to display height
                  local y = dbToDisplayHeight(dbValue, h);
                  
                  // Ensure valid range
                  y = Math.max(0, Math.min(h, y));
                  
                  if (Math.abs(y - lastPoints[i]) >= THRESHOLD)
                  {
                      path.lineTo(x, y);
                      lastPoints[i] = y;
                  }
                  else
                  {
                      path.lineTo(x, lastPoints[i]);
                  }
              }
              
              path.lineTo(w, lastPoints[actualBins-1]);
              path.lineTo(w, h/2);
              path.lineTo(0, h/2);
              path.closeSubPath();
              
              return path;
          }
          
          // Main update function called by the timer
          inline function updateFFT()
          {
              pnl_Fft.data.buffer.copyReadBuffer(buff);
              pnl_Fft.data.path = createFilteredPath();
              pnl_Fft.repaint();
          }
          
          // Initialize everything
          initialize();
      }
      
      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Multichannel MIDI FX - Is it possible in HISE?

      @VirtualVirgin That would be awesome. It looks like it would serve a very similar purpose. I want to create a MIDI drum rack and route the MIDI outputs to individual tracks in the DAW. If in a couple days you are still struggling with this I would be happy to contribute (currently working on something else), so just keep me updated and thanks for your input!

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: Multiple Eq nodes to the FilterDisplay FloatingTile

      @JulesV I've also posted a hack to creating a "draggable filer", I think it's in the HISE snippet browser. I would probably approach this differently now but it could be of use in your case.

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: Persistent Data Recommendation

      @d-healey Wonderful I'll see if I can rewrite with just the File (System) APIs in that case! Luckily for me these are "universal settings" so they should be applied cross each instance of the plugin.

      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Persistent Data Recommendation

      @d-healey For each instance when the plugin is loaded (initiated)*

      posted in Scripting
      HISEnbergH
      HISEnberg
    • Persistent Data Recommendation

      I have a few buttons on my UI and I want their data to persist before and after the UI is constructed/deconstructed. Just wondering what the best folder to dump JSON Data into would be? I imagine putting it in the AppData folder that is created would be the best but I am open to recommendations. Currently I am just saving it to the Expansions folder for no particular reason. Also any recommendations on best practices here is welcome!

      	const var STATE_FILENAME = "buttonStates.json";
      	const var btns= Content.getAllComponents("Button");
      	const var stateFilePath = FileSystem.getFolder(FileSystem.Expansions).getChildFile(STATE_FILENAME);
      // Presumably just change this to FileSystem.AppData
      	
          /** Save button states to file */
          inline function saveStates(states)
          {
              Engine.dumpAsJSON(states, stateFilePath.toString(0));
              return true;
          }
          
          /** Load button states */
          inline function loadButtonStates()
          {
              if (stateFilePath.isFile())
              {
                  return Engine.loadFromJSON(stateFilePath.toString(0));
              }
              
              return {};
          }
          
          /** Save state for a specific button */
          inline function saveButtonState(buttonId, state)
          {
              local states = loadButtonStates();
              states[buttonId] = state;
              return saveStates(states);
          }
          
          /** Get state for a specific button */
          inline function getButtonState(buttonId, defaultValue)
          {
              local states = loadButtonStates();
             
              if (isDefined(states[buttonId]))
              {
                  return states[buttonId];
              }
              
              return defaultValue;
          }
          
          /** Button callback function */
          inline function onButtonControl(component, value)
          {
              // Use component's ID as the button ID
              local id = component.getId();
              saveButtonState(id, value);
          }
          
          /** Initialize buttons on startup */
          inline function initializeButtons()
          {
              for (i = 0; i < btns.length; i++)
              {
                  local id = btns[i].getId();
                  local savedState = getButtonState(id, 0);
                  btns[i].setValue(savedState);
              }
          }
          
          // Set up callbacks for all buttons
          for (i = 0; i < btns.length; i++)
          {
              btns[i].setControlCallback(onButtonControl);
          }
          
          // Initialize buttons
          initializeButtons();
      
           
      }
      
      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: Multichannel MIDI FX - Is it possible in HISE?

      @VirtualVirgin Oh thank you so much for replying, I thought this was a lost cause! By any chance do you have an example/template to work from? If not no worries it would just save me a few brain cells 🤠

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: Adding a load button for Convolution reverb?

      @Sawatakashi I think this is what you will want to refer to:

      posted in ScriptNode
      HISEnbergH
      HISEnberg
    • RE: Which AAX SDK version works for HISE 4.1.0?

      @Sawatakashi Yes I am on the recent branch. What stage are you experiencing issues with? Are you able to build the AAX with HISE? Is the issue that you cannot load the AAX in ProTools? The signing process is quite different, Avid will forward your case to PACE and they will provide all the details on how to do this.

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: FFT Analyser Path - Need help drawing the magnitude to height

      Just a bump on this if anyone has any suggestions! 🤞

      @ustk apologies for just throwing you onto this (ignore me if its too forward) but I recall you sharing a solution for mapping the magnitude (or peak value) onto an oscilloscope or path.

      posted in Scripting
      HISEnbergH
      HISEnberg
    • RE: FFt Spectrum2D

      @hisefilo Woah that is awesome! 😊
      Thanks for sharing!

      posted in General Questions
      HISEnbergH
      HISEnberg
    • RE: FFt Spectrum2D

      @hisefilo could you share a snippet? I’m curious what this entails(spectral processing, etc.)

      posted in General Questions
      HISEnbergH
      HISEnberg