Beta Testers

All developers participating in the HISE Betatest Program

Private

Posts

  • RE: Drawing gradient along a path? Thermometer style gradient?

    @Chazrox I think a radial gradient would work. There should be a video about gradients on my YouTube channel I think, or maybe Patreon

    Edit: actually a linear gradient might be better

    posted in Scripting
  • RE: AAX Build on MacOS

    @David-Healey Well so an SDK downgrade would be the best thing to test, at least to see if the process is the good one

    posted in General Questions
  • RE: AAX Build on MacOS

    @ustk said in AAX Build on MacOS:

    Can your mac support a more recent OS?

    I'm still on Ventura, so not sure a newer OS will help Lindon here.

    posted in General Questions
  • RE: AAX Build on MacOS

    @Lindon I guess you meant 2.8.0...

    Can your mac support a more recent OS?

    Can you try downgrading the SDK?

    posted in General Questions
  • RE: Show default preset name on first launch

    @dannytaurus Oh nice one! 👍

    posted in General Questions
  • RE: AAX Build on MacOS

    @ustk sigh well nope I compile that and I get the same problems...

    Im using Sonoma 14.6.1 and XCode 15.4 and AAX SDK V2.0.8

    ..anyone think this is wrong? got a better suggestion?

    posted in General Questions
  • RE: Bank headers in flat-list preset browser?

    @dannytaurus No the column just lists the files. A one column browser is pretty easy to create with a viewport or panel though.

    posted in Scripting
  • RE: New Release - RustEQ le bleu

    @ustk very sexy!

    posted in General Questions
  • RE: C++ Global Cables // How to add multiple cables to an existing compiled network.

    @Chazrox

    I don't have a "clean" example.
    This is old code too, so not a good example of good c++ node practices or anything like that.

    But here is a c++ node with a global cable(s) set up.

    I've had no problems before, with updating existing c++ headers when adding more global cables.

    If you make sure that the generated global cable code is up to date.
    When you create more cables, you need to re-generate the global cable c++ code in Hise and update those parts in the c++

    This generated stuff needs to be kept up to date with the cable names and IDs in your Hise project:

    enum class GlobalCables { grainpos = 0 };
        using cable_manager_t = routing::global_cable_cpp_manager<SN_GLOBAL_CABLE(94428153)>;
    

    ^ Here, only one global cable exists, however when you add more global cables to your Hise project, this code will need regenerating / updating with the new names and IDs of the cables in your Hise project.

    C++ node example (using one global cable):

    // FILE: Griffin_GrainOsc.h
    /*
        Granular OSC node: per-voice state & note-reactive pitch.
        Each voice owns a Granulator instance via snex::PolyData so reset/prepare
        are scoped to the active voice. Pitch maps so that MIDI note 60 plays the
        sample at its original pitch / speed
    */
    
    #pragma once
    #include <JuceHeader.h>
    #include <array>
    #include <vector>
    #include <deque>
    #include <cmath>
    #include <atomic>
    #include "src/GriffinGrainOsc/GGO_GranularEngine.h"
    
    namespace project
    {
        using namespace juce;  using namespace hise;  using namespace scriptnode;
    
        enum ParamID
        {
            kGrainSizeMs, kDensityHz, kMainPos, kSpray, kPitchSt, kRandPitch, kRandSize,
            kStereoSpread, kReverseChance, kEnvMode, kMaxGrains, kNumParams
        };
    
        enum class GlobalCables { grainpos = 0 };
        using cable_manager_t = routing::global_cable_cpp_manager<SN_GLOBAL_CABLE(94428153)>;
    
        template<int NV>
        struct Griffin_GrainOsc : public data::base, public cable_manager_t
        {
            SNEX_NODE(Griffin_GrainOsc);
            struct MetadataClass { SN_NODE_ID("Griffin_GrainOsc"); };
    
            static constexpr bool isModNode() { return false; }
            static constexpr bool isPolyphonic() { return NV > 1; }
            static constexpr bool hasTail() { return false; }
            static constexpr bool isSuspendedOnSilence() { return false; }
            static constexpr int  getFixChannelAmount() { return 2; }
    
            static constexpr int NumTables = 0, NumSliderPacks = 0,
                NumAudioFiles = 1, NumFilters = 0,
                NumDisplayBuffers = 0;
    
            struct Voice
            {
                ggrain::Granulator<float> gran;
                uint32_t lastGrainSm{ 0 };
                double   lastDensityHz{ -1.0 };
                int      note{ 60 };
                double   sr{ 44100.0 };
    
                void prepare(double sampleRate)
                {
                    sr = sampleRate;
                    gran.prepare(sr);
                    lastGrainSm = 0; lastDensityHz = -1.0;
                }
    
                void reset()
                {
                    gran.prepare(sr);                   // keeps grains cleared, parameters intact
                    lastGrainSm = 0; lastDensityHz = -1.0;
                }
    
                void setSample(const float* mono, uint32_t num, double fileSr)
                {
                    gran.loadSample(mono, num, fileSr);
                }
            };
    
            template<typename PD>
            void process(PD& d)
            {
                auto& fix = d.template as<snex::Types::ProcessData<2>>();
                auto  blk = fix.toAudioBlock();
                float* L = blk.getChannelPointer(0);
                float* R = blk.getChannelPointer(1);
    
                auto& v = voices.get();
                v.gran.process(L, R, d.getNumSamples());
    
                flushSpawnEvents();
            }
    
            void prepare(snex::Types::PrepareSpecs s)
            {
                sr = s.sampleRate;
    
                voices.prepare(s);
                for (auto& v : voices)
                {
                    v.prepare(sr);
                    v.gran.setSpawnCallback(&Griffin_GrainOsc::onSpawnThunk, this);
                    v.gran.setSpawnInfoCallback(&Griffin_GrainOsc::onSpawnInfoThunk, this);
                }
    
                // Leave existing param cache alone in runtime use; these are only defaults for first-time init.
                params[kGrainSizeMs] = (params[kGrainSizeMs] == 0.0 ? 100.0 : params[kGrainSizeMs]);
                params[kDensityHz] = (params[kDensityHz] == 0.0 ? 5.0 : params[kDensityHz]);
                params[kMainPos] = (params[kMainPos] == 0.0 ? 0.5 : params[kMainPos]);
                params[kSpray] = (params[kSpray] == 0.0 ? 0.0 : params[kSpray]);
                params[kPitchSt] = params[kPitchSt];   // keep
                params[kRandPitch] = params[kRandPitch];
                params[kRandSize] = params[kRandSize];
                params[kStereoSpread] = params[kStereoSpread];
                params[kReverseChance] = params[kReverseChance];
                params[kEnvMode] = (params[kEnvMode] == 0.0 ? 2.0 : params[kEnvMode]);
    
                for (auto& v : voices)
                    params[kMaxGrains] = (double)v.gran.capacity();
    
                applyParamsAll();
    
                qCount.store(0, std::memory_order_relaxed);
                seq.store(0, std::memory_order_relaxed);
            }
    
            template<int P>
            void setParameter(double v)
            {
                static_assert(P < kNumParams);
                params[P] = v;
                for (auto& voice : voices)
                    applyParamsForVoice(voice);
            }
    
            void createParameters(ParameterDataList& data)
            {
                using PD = parameter::data;
    
                PD pSize("GrainSizeMs", { 1.0, 2000.0, 0.01 }); pSize.setDefaultValue(100.0); pSize.setSkewForCentre(100.0);
                registerCallback<kGrainSizeMs>(pSize); data.add(pSize);
    
                PD pDen("DensityHz", { 0.1, 200.0, 0.0001 }); pDen.setDefaultValue(5.0); pDen.setSkewForCentre(10.0);
                registerCallback<kDensityHz>(pDen); data.add(pDen);
    
                PD pPos("Position", { 0.0, 1.0, 0.0001 }); pPos.setDefaultValue(0.0);
                registerCallback<kMainPos>(pPos); data.add(pPos);
    
                PD pSpr("Spray", { 0.0, 1.0, 0.0 }); pSpr.setDefaultValue(0.0); pSpr.setSkewForCentre(0.2);
                registerCallback<kSpray>(pSpr); data.add(pSpr);
    
                PD pPst("PitchSt", { -36.0, 36.0, 0.001 }); pPst.setDefaultValue(0.0);
                registerCallback<kPitchSt>(pPst); data.add(pPst);
    
                PD pRP("RandPitch", { 0.0, 1.0, 0.0 }); pRP.setDefaultValue(0.0); pRP.setSkewForCentre(0.08);
                registerCallback<kRandPitch>(pRP); data.add(pRP);
    
                PD pRS("RandSize", { 0.0, 1.0, 0.0001 }); pRS.setDefaultValue(0.0); pRS.setSkewForCentre(0.4);
                registerCallback<kRandSize>(pRS); data.add(pRS);
    
                PD pSpread("RandPan", { 0.0, 1.0, 0.0001 }); pSpread.setDefaultValue(0.0); pSpread.setSkewForCentre(0.35);
                registerCallback<kStereoSpread>(pSpread); data.add(pSpread);
    
                PD pRev("ReverseChance", { 0.0, 1.0, 0.0001 }); pRev.setDefaultValue(0.0); pRev.setSkewForCentre(0.4);
                registerCallback<kReverseChance>(pRev); data.add(pRev);
    
                PD pEnv("EnvelopeShape", { 0.0, 2.0, 1.0 }); pEnv.setDefaultValue(2.0);
                registerCallback<kEnvMode>(pEnv); data.add(pEnv);
    
                PD pCap("MaxVoiceGrains", { 1.0, (double)128.0, 1.0 }); pCap.setDefaultValue((double)128.0);
                registerCallback<kMaxGrains>(pCap); data.add(pCap);
            }
    
            /*
               Safe sample load:
                 - Push newest buffer.
                 - Point all voices at newest buffer (Granulator snapshots pointer/size).
                 - Compact pool keeping newest and any buffers still used by active grains.
            */
            void setExternalData(const snex::ExternalData& ed, int) override
            {
                using DT = snex::ExternalData::DataType;
    
                if (ed.dataType != DT::AudioFile) return;
                if (ed.isXYZ()) return;
                if (ed.isEmpty()) return;
                if (ed.numChannels <= 0 || ed.numChannels > 2) return;
                if (ed.numSamples <= 0 || ed.sampleRate <= 0.0) return;
    
                auto buf = ed.toAudioSampleBuffer();
                const int nc = buf.getNumChannels();
                const int ns = buf.getNumSamples();
                if (ns <= 0 || (nc != 1 && nc != 2)) return;
    
                const double maxSec = 600.0;
                const int cap = juce::jmin(ns, (int)juce::roundToInt(ed.sampleRate * maxSec));
    
                SampleBuf newest;
                newest.data.resize((size_t)cap);
    
                if (nc == 1)
                {
                    const float* s0 = buf.getReadPointer(0);
                    for (int i = 0; i < cap; ++i)
                    {
                        float x = s0[i];
                        if (!std::isfinite(x)) x = 0.0f;
                        newest.data[(size_t)i] = juce::jlimit(-1.0f, 1.0f, x);
                    }
                }
                else
                {
                    const float* L = buf.getReadPointer(0);
                    const float* R = buf.getReadPointer(1);
                    for (int i = 0; i < cap; ++i)
                    {
                        float x = 0.5f * (L[i] + R[i]);
                        if (!std::isfinite(x)) x = 0.0f;
                        newest.data[(size_t)i] = juce::jlimit(-1.0f, 1.0f, x);
                    }
                }
                newest.srcRate = ed.sampleRate;
    
                pool.push_back(std::move(newest));
                const size_t newestIdx = pool.size() - 1;
                const float* newestPtr = pool[newestIdx].data.data();
                const uint32_t newestSz = (uint32_t)pool[newestIdx].data.size();
                const double newestSR = pool[newestIdx].srcRate;
    
                for (auto& v : voices)
                    v.setSample(newestPtr, newestSz, newestSR);
    
                if (pool.size() <= 1)
                    return;
    
                size_t write = 0;
                for (size_t read = 0; read < pool.size(); ++read)
                {
                    const bool keepNewest = (read == newestIdx);
                    bool keep = keepNewest;
    
                    if (!keepNewest)
                    {
                        const float* p = pool[read].data.data();
                        for (auto& v : voices)
                        {
                            if (v.gran.isSamplePointerInUse(p)) { keep = true; break; }
                        }
                    }
    
                    if (keep)
                    {
                        if (write != read)
                            pool[write] = std::move(pool[read]);
                        ++write;
                    }
                }
                pool.resize(write);
            }
    
            void reset()
            {
                for (auto& v : voices)
                {
                    v.reset();                 // clear grains; keep parameters/seeds/buffers
                    applyParamsForVoice(v);
                }
                qCount.store(0, std::memory_order_relaxed);
            }
    
            void handleHiseEvent(HiseEvent& e)
            {
                if (e.isNoteOn())
                {
                    auto& v = voices.get();
                    v.gran.flush(true);
                    v.note = e.getNoteNumberIncludingTransposeAmount();
                    applyParamsForVoice(v);
                }
            }
    
            SN_EMPTY_PROCESS_FRAME;
    
            void connectToRuntimeTarget(bool addConnection, const runtime_target::connection& c) override
            {
                cable_manager_t::connectToRuntimeTarget(addConnection, c);
            }
    
        private:
            // GUI spawn-event sender
    
            juce::Array<juce::var> packed;
    
            void flushSpawnEvents()
            {
                const int N = qCount.exchange(0, std::memory_order_acq_rel);
                if (N <= 0)
                {
                    packed.clearQuick();
                    return;
                }
    
                packed.clearQuick();
                packed.ensureStorageAllocated(N * 3);
    
                for (int i = 0; i < N; ++i)
                {
                    packed.add(juce::var((double)qP0[(size_t)i]));
                    packed.add(juce::var((double)qVel[(size_t)i]));
                    packed.add(juce::var((double)qDurMs[(size_t)i]));
                }
    
                this->sendDataToGlobalCable<GlobalCables::grainpos>(juce::var(packed));
            }
    
            static void onSpawnThunk(void*, float) {}
    
            static void onSpawnInfoThunk(void* user, float p0, float v01ps, float durMs)
            {
                auto* self = static_cast<Griffin_GrainOsc*>(user);
                if (!self) return;
    
                const int idx = self->qCount.load(std::memory_order_relaxed);
                if ((unsigned)idx >= (unsigned)kQueueCap) return;
    
                self->qP0[(size_t)idx] = p0;
                self->qVel[(size_t)idx] = v01ps;
                self->qDurMs[(size_t)idx] = durMs;
                self->qCount.store(idx + 1, std::memory_order_release);
            }
    
            void applyParamsAll()
            {
                for (auto& v : voices)
                    applyParamsForVoice(v);
            }
    
            void applyParamsForVoice(Voice& v)
            {
                const double grainMs = juce::jlimit(1.0, 2000.0, params[kGrainSizeMs]);
                const double densityHz = juce::jlimit(0.1, 200.0, params[kDensityHz]);
    
                uint32_t grainSm = (uint32_t)juce::roundToInt((grainMs * 0.001) * v.sr);
                if (grainSm == 0) grainSm = 1;
    
                const bool lenChanged = (grainSm != v.lastGrainSm);
                const bool denChanged = (std::abs(densityHz - v.lastDensityHz) > 1e-12);
    
                if (lenChanged || denChanged)
                {
                    v.gran.setParameters(grainSm, densityHz, 0.0);
                    v.lastGrainSm = grainSm;
                    v.lastDensityHz = densityHz;
                }
    
                ggrain::SpawnParams sp{};
                sp.mainPos01 = juce::jlimit(0.0, 1.0, params[kMainPos]);
                sp.spray01 = juce::jlimit(0.0, 1.0, params[kSpray]);
                sp.sprayMode = ggrain::SprayMode::Gaussian;
    
                sp.baseLenSm = grainSm;
                sp.sizeRand01 = juce::jlimit(0.0, 1.0, params[kRandSize]);
    
                // Honor requested pitch exactly (no limiting). UI control is still clamped, but note+UI sum is not.
                const double uiPitchSt = juce::jlimit(-36.0, 36.0, params[kPitchSt]);
                const double noteSemis = (double)v.note - 60.0;
                sp.pitchSemitones = uiPitchSt + noteSemis;
                sp.pitchRand01 = juce::jlimit(0.0, 1.0, params[kRandPitch]);
    
                sp.reverseChance01 = juce::jlimit(0.0, 1.0, params[kReverseChance]);
                sp.stereoSpread01 = juce::jlimit(0.0, 1.0, params[kStereoSpread]);
    
                const int envIdx = (int)juce::jlimit(0.0, 2.0, params[kEnvMode]);
                sp.envMode = (envIdx == 0 ? ggrain::EnvelopeMode::RectRaisedCos
                    : envIdx == 1 ? ggrain::EnvelopeMode::Triangle
                    : ggrain::EnvelopeMode::Hanning);
    
                v.gran.setSpawnParams(sp);
    
                const size_t cap = v.gran.capacity();
                const size_t want = (size_t)juce::roundToInt(juce::jlimit(1.0, (double)cap, params[kMaxGrains]));
                v.gran.setMaxActiveGrains(want);
            }
    
            struct SampleBuf { std::vector<float> data; double srcRate{ 44100.0 }; };
    
            static constexpr int kQueueCap = 512;
    
            double                         sr{ 44100.0 };
            std::array<double, kNumParams> params{};
            snex::PolyData<Voice, NV>      voices;
    
            std::vector<SampleBuf>         pool;
    
            std::array<float, kQueueCap>   qP0{};
            std::array<float, kQueueCap>   qVel{};
            std::array<float, kQueueCap>   qDurMs{};
            std::atomic<int>               qCount{ 0 };
            std::atomic<int>               seq{ 0 };
        };
    }
    
    
    posted in Scripting
  • RE: HISE Update broke my project.

    @Chazrox said in HISE Update broke my project.:

    If its not an xml problem whats my next move?

    If it was me I'd crack open a debugger and see what it says, not a simple thing to explain how to here though.

    posted in Scripting