#pragma once
#include <JuceHeader.h>
namespace project
{
using namespace juce;
using namespace hise;
using namespace scriptnode;
using namespace snex;
/**
Smallest possible BPM listener example.
Demonstrates:
- TempoListener registration
- tempoChanged() callback
- BPM flowing into the audio graph
*/
struct MinimalBPMListener : public data::base,
public hise::TempoListener
{
SNEX_NODE(MinimalBPMListener);
struct MetadataClass { SN_NODE_ID("MinimalBPMListener"); };
static constexpr bool isModNode() { return true; }
static constexpr bool isPolyphonic() { return false; }
static constexpr bool hasTail() { return false; }
static constexpr bool isSuspendedOnSilence() { return false; }
static constexpr int getFixChannelAmount() { return 1; }
// --- Tempo sync ---
hise::DllBoundaryTempoSyncer* tempoSyncer = nullptr;
double bpm = 120.0;
// Exposed modulation value
double lastOut = 120.0;
// --- TempoListener ---
void tempoChanged(double newTempo) override
{
bpm = newTempo;
lastOut = bpm; // make it observable
}
// --- Lifecycle ---
void prepare(PrepareSpecs specs)
{
if (tempoSyncer == nullptr && specs.voiceIndex != nullptr)
{
tempoSyncer = specs.voiceIndex->getTempoSyncer();
if (tempoSyncer != nullptr)
tempoSyncer->registerItem(this);
}
// Initialize output
lastOut = bpm;
}
void reset() {}
~MinimalBPMListener() override
{
if (tempoSyncer != nullptr)
{
tempoSyncer->deregisterItem(this);
tempoSyncer = nullptr;
}
}
// --- Processing ---
template <typename T>
void process(T& data)
{
static constexpr int NumChannels = getFixChannelAmount();
auto& fixData = data.template as<ProcessData<NumChannels>>();
auto fd = fixData.toFrameData();
while (fd.next())
fd.toSpan()[0] = (float)lastOut;
}
int handleModulation(double& value)
{
value = lastOut;
return 1;
}
void setExternalData(const ExternalData&, int) {}
};
}
This is a minimal example of how to get your custom C++ node to listen to the host BPM. Code above doesn't actually DO anything with the BPM information. But it proves the concept.