• What's the deal with this assert? isMonophonicOrInsideVoiceRendering()

    1
    0 Votes
    1 Posts
    6 Views
    No one has replied
  • c++ callback for voice stop?

    4
    0 Votes
    4 Posts
    48 Views
    griffinboyG

    @Christoph-Hart said in c++ callback for voice stop?:

    once it stops rendering it will not be incremented further, so you should be able to pick that up at any time.

    Where is this dealt with?
    I don't really understand where voices are dealt with in Hise. If it's something to do with polydata, or a different script that manages voices.

    All of my scriptnode synths have been based on snex examples and the like, and so when it comes to actually interacting with the voice system, I don't understand what is really controlling it behind the scenes.

    The node is pretty beefy but if it helps, you can take a look, there is a high chance that my usage of the voice system is incorrect or stupid ๐Ÿ˜†

    // Griffin_WT.h (2-May-2025) #pragma once #include <JuceHeader.h> #include <array> #include <vector> #include <memory> #include <atomic> #include <mutex> #include "src/griffinwave5/BaseVoiceState.cpp" #include "src/griffinwave5/BaseVoiceState.h" #include "src/griffinwave5/rspl.hpp" #include "src/griffinwave5/InterpPack.cpp" #include "src/griffinwave5/InterpPack.h" #include "src/griffinwave5/MipMapFlt.hpp" #include "src/griffinwave5/ResamplerFlt.cpp" #include "src/griffinwave5/ResamplerFlt.h" #include "src/griffinwave5/Wave.h" #include "src/griffinwave5/AsyncMipBuilder.h" namespace project { using namespace juce; using namespace hise; using namespace scriptnode; /* --------------------------------------------------------------------- */ /* CONSTANTS */ /* --------------------------------------------------------------------- */ template <int NV> struct Griffin_WT; static constexpr float kVoiceDetuneLUT[24] = { 0.0f, 0.3f, -0.2f, 3.119f, 2.5f, 0.1f, -0.1f, 0.0f, 4.119f, 1.5f, 2.119f, 3.119f, 1.5f, 0.0f, 0.2f, 0.1f, 1.5f, 0.0f, 0.0f, 1.0f, 3.119f, 0.5f, 0.0f, 1.5f }; inline float getVoiceDetune(int idx) noexcept { return kVoiceDetuneLUT[idx % 24]; } /* --------------------------------------------------------------------- */ /* SHARED DEFAULT WAVE โ€“ ONE PER PROCESS */ /* --------------------------------------------------------------------- */ namespace { inline const std::shared_ptr<const gw5::MipMapFlt>& builtinMip() { static std::shared_ptr<const gw5::MipMapFlt> s = [] { constexpr int FRAME_SIZE = 2048; constexpr int MAX_FRAMES = 256; constexpr int PADDED = FRAME_SIZE * 3; constexpr int TOTAL_LEN = MAX_FRAMES * PADDED; auto mp = std::make_shared<gw5::MipMapFlt>(); mp->init_sample(TOTAL_LEN, gw5::InterpPack::get_len_pre(), gw5::InterpPack::get_len_post(), 12, gw5::ResamplerFlt::_fir_mip_map_coef_arr, gw5::ResamplerFlt::MIP_MAP_FIR_LEN); mp->fill_sample(wavetable, TOTAL_LEN); return mp; }(); return s; } } /* --------------------------------------------------------------------- */ /* ONE RESAMPLER LANE */ /* --------------------------------------------------------------------- */ struct Lane { gw5::ResamplerFlt res; int frameIdx = -1; bool active = false; }; /* --------------------------------------------------------------------- */ /* MAIN NODE */ /* --------------------------------------------------------------------- */ template <int NV> struct Griffin_WT : public data::base { SNEX_NODE(Griffin_WT); struct MetadataClass { SN_NODE_ID("Griffin_WT"); }; static constexpr int MAX_FRAMES = 256; static constexpr int FRAME_SIZE = 2048; static constexpr int PADDED = FRAME_SIZE * 3; static constexpr int SLICE = 8; static constexpr int FADE_LEN = gw5::BaseVoiceState::FADE_LEN; static constexpr int BITS_OCT = gw5::BaseVoiceState::NBR_BITS_PER_OCT; static constexpr double TARGET_ROOT_HZ = 32.703195; static constexpr double SEMI2BITS = double(1 << BITS_OCT) / 12.0; 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; static constexpr int NumSliderPacks = 0; static constexpr int NumAudioFiles = 0; static constexpr int NumFilters = 0; static constexpr int NumDisplayBuffers = 0; /* ----------------------------------------------------------------- */ /* VOICE PACK */ /* ----------------------------------------------------------------- */ struct VoicePack { Lane A, B; int pitchBits = 0; double semiOff = 0.0; double multOff = 1.0; bool toggle = false; bool fading = false; float fadeAlpha = 1.f; int frameParam = 0; int pendFrame = 0; bool pendFlag = false; int midi = -1; float vel = 1.f; bool active = false; static constexpr float fadeDelta() { return 1.f / float(FADE_LEN); } void clear() { A.active = B.active = active = false; fading = toggle = pendFlag = false; fadeAlpha = 1.f; } void reset(int note, float v, int gFrame, double semi, double mult) { clear(); midi = note; vel = v; frameParam = pendFrame = gFrame; semiOff = semi; multOff = mult; active = true; } }; PolyData<VoicePack, NV> voices; /* ----------------------------------------------------------------- */ /* PUBLIC API */ /* ----------------------------------------------------------------- */ void reset() { for (auto& v : voices) v.clear(); } Griffin_WT() : globalVolume(0.8f), paramSemi(0.0), paramMult(1.0), _activeMip(builtinMip()) { } void prepare(PrepareSpecs spec) { sr = spec.sampleRate; lastSpecs = spec; haveSpecs = true; rootOffSemis = 12.0 * std::log2(TARGET_ROOT_HZ / (sr / double(FRAME_SIZE))); for (int f = 0; f < MAX_FRAMES; ++f) frameStart[f] = gw5::Int64(f) * PADDED + FRAME_SIZE; voices.prepare(lastSpecs); for (auto& v : voices) initVoice(v); ready = true; } /* ===== process ===== */ template <typename PD> void process(PD& d) { /* 1) fetch newest ready mip */ if (auto mp = gw5::AsyncMipBuilder::instance().current(); mp && mp->is_ready() && mp.get() != _activeMip.get()) { _activeMip = mp; // shared_ptr copy /* update every lane with new table */ for (auto& v : voices) { v.A.res.set_sample_sp(_activeMip); v.A.res.clear_buffers(); v.B.res.set_sample_sp(_activeMip); v.B.res.clear_buffers(); if (v.active) { v.A.res.set_pitch(v.pitchBits); v.B.res.set_pitch(v.pitchBits); } } } if (!ready) return; /* 2) render voices ------------------------------------------------ */ auto blk = d.template as<ProcessData<2>>().toAudioBlock(); float* L = blk.getChannelPointer(0); float* R = blk.getChannelPointer(1); const int N = d.getNumSamples(); std::fill(L, L + N, 0.f); for (int base = 0; base < N; base += SLICE) { const int len = jmin(SLICE, N - base); std::fill(mixBuf, mixBuf + len, 0.f); for (auto& vp : voices) { if (!vp.active) continue; if (vp.pendFlag) switchFrame(vp); Lane& cur = vp.toggle ? vp.B : vp.A; if (!cur.active) continue; cur.res.set_playback_pos(wrap(vp.frameParam, cur.res.get_playback_pos())); cur.res.interpolate_block(laneBuf, len); if (vp.fading) { Lane& prev = vp.toggle ? vp.A : vp.B; if (prev.active) { prev.res.set_playback_pos( wrap(vp.frameParam, prev.res.get_playback_pos())); prev.res.interpolate_block(prevBuf, len); float a = vp.fadeAlpha; FloatVectorOperations::multiply(laneBuf, a, len); FloatVectorOperations::addWithMultiply( laneBuf, prevBuf, 1.f - a, len); } } FloatVectorOperations::add(mixBuf, laneBuf, len); if (vp.fading) { vp.fadeAlpha += VoicePack::fadeDelta() * len; if (vp.fadeAlpha >= 1.f) { vp.fading = false; (vp.toggle ? vp.A : vp.B).active = false; } } } FloatVectorOperations::add(L + base, mixBuf, len); } FloatVectorOperations::multiply(L, globalVolume, N); FloatVectorOperations::copy(R, L, N); } /* ===== event handler ===== */ void handleHiseEvent(HiseEvent& e) { if (!ready) return; if (e.isNoteOn()) { auto& vp = voices.get(); vp.reset(e.getNoteNumber(), e.getFloatVelocity(), globalFrame, paramSemi, paramMult); vp.A.res.set_sample_sp(_activeMip); vp.A.res.clear_buffers(); vp.B.res.set_sample_sp(_activeMip); vp.B.res.clear_buffers(); updatePitch(vp); uint32 rand32 = Random::getSystemRandom().nextInt(); float noteFrac = float(e.getNoteNumber()) / 127.0f; float phase = 17.0f + noteFrac * (60.0f - 17.0f); gw5::Int64 maxR = (cycle * gw5::Int64(phase)) / 100; gw5::Int64 randIp = gw5::Int64(rand32) % maxR; gw5::Int64 pos = ((frameStart[vp.frameParam] + randIp) << 32) | gw5::Int64(rand32); vp.A.res.set_playback_pos(pos); vp.A.frameIdx = vp.frameParam; vp.A.active = true; } } /* ===== parameters ===== */ template <int P> void setParameter(double v) { if constexpr (P == 1) // frame select { globalFrame = jlimit(0, MAX_FRAMES - 1, int(v)); for (auto& vp : voices) if (vp.active && globalFrame != vp.frameParam) { vp.pendFrame = globalFrame; vp.pendFlag = true; } } else if constexpr (P == 2) { globalVolume = float(v); } else if constexpr (P == 3) { paramSemi = v; for (auto& vp : voices) { vp.semiOff = v; if (vp.active) updatePitch(vp); } } else if constexpr (P == 4) { paramMult = v <= 0.0 ? 1.0 : v; for (auto& vp : voices) { vp.multOff = paramMult; if (vp.active) updatePitch(vp); } } } void createParameters(ParameterDataList& ps) { { parameter::data p("Frame", { 0.0, MAX_FRAMES - 1.0, 1.0 }); p.setDefaultValue(0); registerCallback<1>(p); ps.add(std::move(p)); } { parameter::data p("Volume", { 0.0, 1.0, 0.001 }); p.setDefaultValue(0.8); registerCallback<2>(p); ps.add(std::move(p)); } { parameter::data p("Semitone", { -72.0, 36.0, 0.1 }); p.setDefaultValue(-12); registerCallback<3>(p); ps.add(std::move(p)); } { parameter::data p("Pitch-Mult", { 0.25, 4.0, 0.001 }); p.setDefaultValue(1.0); registerCallback<4>(p); ps.add(std::move(p)); } } SN_EMPTY_PROCESS_FRAME; /* ----------------------------------------------------------------- */ /* INTERNAL */ /* ----------------------------------------------------------------- */ private: float globalVolume = 0.8f; int globalFrame = 0; double paramSemi = 0.0; double paramMult = 1.0; int cycle = FRAME_SIZE; double sr = 0.0; double rootOffSemis = 0.0; std::array<gw5::Int64, MAX_FRAMES> frameStart; gw5::InterpPack interp; bool ready = false; bool haveSpecs = false; PrepareSpecs lastSpecs; float laneBuf[SLICE]{}; float prevBuf[SLICE]{}; float mixBuf[SLICE]{}; std::shared_ptr<const gw5::MipMapFlt> _activeMip; /* ----- helpers ----- */ void initLane(Lane& l) { l.res.set_interp(interp); l.res.set_sample_sp(_activeMip); l.res.clear_buffers(); l.active = false; l.frameIdx = -1; } void initVoice(VoicePack& vp) { initLane(vp.A); initLane(vp.B); vp.clear(); int startF = globalFrame; vp.frameParam = vp.pendFrame = startF; vp.A.res.set_playback_pos(frameStart[startF] << 32); vp.A.frameIdx = startF; vp.semiOff = paramSemi; vp.multOff = paramMult; } static double centsToSemis(double c) noexcept { return c / 100.0; } void updatePitch(VoicePack& vp) { int vIdx = voices.getVoiceIndexForData(vp); double semMul = std::log2(vp.multOff) * 12.0; double sem = rootOffSemis + vp.semiOff + semMul + (vp.midi - 24) + centsToSemis(getVoiceDetune(vIdx)); vp.pitchBits = int(std::lround(sem * SEMI2BITS)); vp.A.res.set_pitch(vp.pitchBits); vp.B.res.set_pitch(vp.pitchBits); } gw5::Int64 wrap(int idx, gw5::Int64 p) const noexcept { gw5::Int64 ip = p >> 32; gw5::Int64 frac = p & 0xffffffff; gw5::Int64 st = frameStart[idx]; return (((ip - st) & (cycle - 1)) + st) << 32 | frac; } void switchFrame(VoicePack& vp) { Lane& src = vp.toggle ? vp.B : vp.A; Lane& dst = vp.toggle ? vp.A : vp.B; gw5::Int64 p = src.res.get_playback_pos(); gw5::Int64 ip = p >> 32; gw5::Int64 frac = p & 0xffffffff; gw5::Int64 rel = (ip - frameStart[vp.frameParam]) & (cycle - 1); initLane(dst); dst.res.set_playback_pos(((frameStart[vp.pendFrame] + rel) << 32) | frac); dst.res.set_pitch(vp.pitchBits); dst.frameIdx = vp.pendFrame; dst.active = true; vp.fading = true; vp.toggle = !vp.toggle; vp.fadeAlpha = 0.f; vp.frameParam = vp.pendFrame; vp.pendFlag = false; } }; } // namespace project

    The whole thing I'm actually looking to implement into my node is free-running phase for the voices.
    Each one currently has it's own stateful resampler, and I'm basically wanting to extend it such that when a voice ends, we keep track of what the phase and time was, so that when the voice gets used again, it can figure out what the phase should've been if it had always been running.

    Upon reflection, I don't really need to know when the voice ends for this... I could just store the data on note off and it would still work as long as the global clock functions properly.

    I guess a question I'd like to ask you then is if there is a clock I can tap into? because I don't know if Process() always runs and can be used for a clock. I have the feeling it gets optimized and doesn't always run, from behaviour that I've seen. I might be wrong. But if that's the case, it's probably what's been tripping up my efforts.

  • 1 Votes
    20 Posts
    544 Views
    d.healeyD

    @bendurso Not that I'm aware of

  • 0 Votes
    34 Posts
    581 Views
    Christoph HartC

    @Morphoice hmm, weird, anyways, I've added a safe check as well as the option to edit the flags set by the node_properties.json file in the DLL exporter. This should fix most issues related to this subject.

  • C++ API: SliderBase cannot find Constant Modulator (SIGSEV)

    3
    0 Votes
    3 Posts
    180 Views
    F

    @fellhouseaudio said in C++ API: SliderBase cannot find Constant Modulator (SIGSEV):

    reach a SIGSEV in RootObject.h:61

    I opened a github issue

  • UI Thread vs Audio Thread (c++ nodes)

    Solved
    3
    0 Votes
    3 Posts
    113 Views
    griffinboyG

    @Christoph-Hart

    Thanks I'll look into it!

  • What is the process for writing my own module (not scriptnode)

    50
    0 Votes
    50 Posts
    1k Views
    Christoph HartC

    @HISEnberg empty room problem, but as soon as there starts to be demand for it I'll think about a good solution.

  • [Free Dsp] Unfinished Self Oscillating Filter

    1
    7 Votes
    1 Posts
    57 Views
    No one has replied
  • [Free Dsp] Lookahead Limiter (true peak)

    15
    10 Votes
    15 Posts
    272 Views
    ustkU

    @griffinboy For what I understand, it's not Hise to support latency, be it variable or not. Hise just reports what you want, so if you want it to be variable, technically, you should be able to do it.
    But the DAW might not like it... It doesn't seem to be something "normal" to report a continuously changing value, but I can't refer to anything else than my own perfectible thoughts...

  • [Free Dsp] Oberheim-8 Analog Filter

    6
    12 Votes
    6 Posts
    201 Views
    T

    Thank you!!!

  • [Free dsp] C++ FFT

    28
    11 Votes
    28 Posts
    1k Views
    clevername27C

    @mmprod Yep! We haven't announced it yet, soโ€ฆwell, if you know who I am, you probably know who it is. HISE will be on it! ๐Ÿ‘ป

  • FFT in Third Party C++

    12
    2 Votes
    12 Posts
    398 Views
    griffinboyG

    @ustk

    I did have it working wonderfully. But I'm going to have to get back to you.
    My copy of hise has gone completely insane - it cannot see any of my thirdparty nodes now, not even in old projects. I've really messed something up badly

    edit: After doing some more tests, I think this is a bug

  • Can I export the plugin as source code, such as C++?

    3
    0 Votes
    3 Posts
    142 Views
    TinyHustlrT

    @Christoph-Hart This is super helpful and more than enough to get me started. Much appreciated.

  • ExternalData SliderPack seems to be limited to 128

    13
    0 Votes
    13 Posts
    260 Views
    d.healeyD

    @ustk Yeah it's giving me super weird behaviour too, and it seems like the data objects have a "memory" so even when you change the number of sliders it shows a previous number that was being used.

  • Custom nested modulators

    1
    0 Votes
    1 Posts
    64 Views
    No one has replied
  • [ThirdPartyNode] How do I get ExternalData update to call a function?

    Unsolved
    2
    0 Votes
    2 Posts
    85 Views
    ustkU

    @Christoph-Hart Alright so making it inline does the job, but then the data somehow refuses to ReadLock. This Mean my update function cannot account for it... What's the wizardry here? The WriteLock is still active? But then what do I do?

    ExternalData data; void setExternalData(const ExternalData& ed, int index) { data = ed; ed.referBlockTo(tableValues, 0); accountForUpdate(); // crashes here } inline void accountForUpdate() { DataReadLock rl(data); if (rl) { // never goes there form setExternalData, // but works from other calling points } }
  • data block operations (aka simd)

    3
    0 Votes
    3 Posts
    83 Views
    ustkU

    @Christoph-Hart Ok I let it go then! Thanks!

  • C++ pass data[i] and xcode breakpoint misunderstanding

    2
    0 Votes
    2 Posts
    78 Views
    ustkU

    @ustk So I tried to set the Hise executable as debug, the node as debug, and any combination, I get only crashes...

    But I still think it is a scheme configuration vs debug executable/node thingy:

    Screenshot 2024-12-03 at 17.40.03.png

  • 0 Votes
    1 Posts
    81 Views
    No one has replied
  • Beginner: External cpp to Hise

    7
    0 Votes
    7 Posts
    360 Views
    B

    @Christoph-Hart

    You are right, so i doubt it tbh :)
    Anyways i will stick to my roots and work on my synthesizer which goes pretty well, including faust dsp

    Thx so much haha..

42

Online

1.7k

Users

11.6k

Topics

101.4k

Posts