• NEW: Online Knob Builder for HISE!! by Me :)

    Scripting
    1
    2 Votes
    1 Posts
    8 Views
    No one has replied
  • 0 Votes
    31 Posts
    533 Views
    ustkU

    @Oli-Ullmann May the --force be with you light-saber.png

  • Compiling VST files on Windows for macOS!!

    General Questions
    3
    0 Votes
    3 Posts
    15 Views
    David HealeyD

    @Oli-Ullmann Yes a vm is the way to go if you don't have a mac

  • 0 Votes
    10 Posts
    44 Views
    J

    @David-Healey ill try it

  • 0 Votes
    3 Posts
    21 Views
    Y

    @David-Healey payload is good.

    Oh ok, I didn't know. I do it right now.

  • Latency problem on FX plugins...

    Bug Reports
    26
    0 Votes
    26 Posts
    6k Views
    J

    i had to set eh latency upto 80ms, for the sample to sync. inside flstudio and reaper

  • compiled DSP network wont work after vst export

    General Questions
    3
    0 Votes
    3 Posts
    35 Views
    J

    @David-Healey Thank you. It compiled and worked just fine.

  • Drawing gradient along a path? Thermometer style gradient?

    Scripting
    17
    0 Votes
    17 Posts
    82 Views
    dannytaurusD

    @Chazrox Never used them, sorry.

  • AAX Build on MacOS

    General Questions
    10
    0 Votes
    10 Posts
    126 Views
    ustkU

    @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

  • Show default preset name on first launch

    General Questions
    8
    0 Votes
    8 Posts
    161 Views
    ustkU

    @dannytaurus Oh nice one! 👍

  • 0 Votes
    1 Posts
    39 Views
    No one has replied
  • Bank headers in flat-list preset browser?

    Scripting
    2
    0 Votes
    2 Posts
    42 Views
    David HealeyD

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

  • New Release - RustEQ le bleu

    General Questions
    4
    9 Votes
    4 Posts
    136 Views
    DanHD

    @ustk very sexy!

  • 0 Votes
    2 Posts
    59 Views
    griffinboyG

    @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 }; }; }
  • HISE Update broke my project.

    Scripting
    29
    0 Votes
    29 Posts
    284 Views
    David HealeyD

    @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.

  • I'm having trouble exporting my project in HISE.

    General Questions
    3
    0 Votes
    3 Posts
    83 Views
    E

    @ulrik Thanks for clarifying that detail, I was able to export my practice projects.

  • Is the HISE REST server ready for use?

    AI discussion
    20
    0 Votes
    20 Posts
    227 Views
    trillbillyT

    I havent used Claude but Codex works better than expected as well.

    Ive used it with VS Code and linked it directly to my HISE projects.

    No GUI creation or anything but with effects, scriptnode, debugging, etc it has been a huge time saver.

  • Is the CSS debugger gone?

    Bug Reports
    3
    0 Votes
    3 Posts
    54 Views
    ustkU

    @David-Healey That's where I use it (the blank part above it the bottom of my property editor)
    But it's now back, I just needed to remove it and re-insert, probably a compromised window XML...

  • Mask does not scale properly on HiDPI or Retina display

    Bug Reports
    23
    0 Votes
    23 Posts
    4k Views
    ustkU

    @Chazrox I thought I fixed it about 2 weeks ago. It was a line that was already there so I simply uncommented it and it was working nicely in Hise. But I suspected it made my plugin unstable/causing crash, so I just went back a step.

    I'm not 100% sure it was the real cause, but since the line was commented, I think @Christoph-Hart had a good reason...

  • Automatic license file placement, like user presets

    General Questions
    11
    0 Votes
    11 Posts
    386 Views
    dannytaurusD

    @David-Healey No, because this is about persisting the file to the user's disk.

    Although as I said earlier, I think the final best answer is to display the license directly in the plugin somewhere.

    Then nobody could ever accuse the developer of not making the license easily available.

    Website goes down - no problem
    User moves the plugin to another machine - no problem
    User wipes/moves/modifies their AppData folder - no problem

    If the license is in the plugin itself, it can't be accidentally moved, deleted or overwritten, and it can't become unavailable because of server outage.

    Probably going this route now...