1 #ifndef AL_POLYSYNTH_HPP
2 #define AL_POLYSYNTH_HPP
51 #include "al/graphics/al_Graphics.hpp"
52 #include "al/io/al_AudioIOData.hpp"
53 #include "al/io/al_File.hpp"
54 #include "al/types/al_SingleRWRingBuffer.hpp"
55 #include "al/ui/al_Parameter.hpp"
59 int asciiToIndex(
int asciiKey,
int offset = 0);
61 int asciiToMIDI(
int asciiKey,
int offset = 0);
94 if (numFields < (
int)mTriggerParams.size()) {
98 for (
auto ¶m : mTriggerParams) {
99 param->fromFloat(*pFields++);
113 bool noCalls =
false) {
114 if (pFields.size() < mTriggerParams.size()) {
118 auto it = pFields.begin();
120 for (
auto ¶m : mTriggerParams) {
121 static_cast<Parameter *
>(param)->setNoCalls(*it++);
124 for (
auto ¶m : mTriggerParams) {
125 static_cast<Parameter *
>(param)->set(*it++);
137 bool noCalls =
false);
186 virtual void update(
double dt = 0) { (void)dt; }
244 void id(
int idValue) { mId = idValue; }
250 int id() {
return mId; }
263 int getEndOffsetFrames(
unsigned int framesPerBuffer);
265 void userData(
void *ud) { mUserData = ud; }
267 void *userData() {
return mUserData; }
289 std::shared_ptr<Parameter>
291 float minValue = -9999.0,
292 float maxValue = 9999.0);
329 mTriggerParams.push_back(¶m);
336 template <
class... Args>
338 std::vector<ParameterMeta *> params{¶msArgs...};
339 for (
auto *param : params) {
349 std::vector<ParameterMeta *> triggerParameters() {
return mTriggerParams; }
364 mContinuousParameters.push_back(¶m);
371 template <
class... Args>
373 std::vector<ParameterMeta *> params{¶msArgs...};
374 for (
auto *param : params) {
384 std::vector<ParameterMeta *>
parameters() {
return mContinuousParameters; }
397 void free() { mActive =
false; }
411 mNumOutChannels = numOutputs;
414 std::vector<ParameterMeta *> mTriggerParams;
416 std::vector<ParameterMeta *> mContinuousParameters;
418 std::vector<std::shared_ptr<Parameter>> mInternalParameters;
423 int mOnOffsetFrames{0};
424 int mOffOffsetFrames{0};
426 unsigned int mNumOutChannels{1};
438 PolySynth(TimeMasterMode masterMode = TimeMasterMode::TIME_MASTER_AUDIO);
452 void *userData =
nullptr);
472 template <
class TSynthVoice> TSynthVoice *
getVoice(
bool forceAlloc =
false);
519 template <
class TSynthVoice>
void disableAllocation();
521 void disableAllocation(std::string name);
641 void print(std::ostream &stream = std::cout);
647 void gain(
float gainValue) { mAudioGain = gainValue; }
653 float gain() {
return mAudioGain; }
659 std::function<
bool(
SynthVoice *voice,
int offsetFrames,
int id,
662 void *userData =
nullptr);
669 void *userData =
nullptr);
675 void *userData =
nullptr);
682 void *userData =
nullptr);
689 template <
class TSynthVoice>
691 bool allowAutoAllocation =
true) {
692 if (name.size() == 0) {
693 name = demangle(
typeid(TSynthVoice).name());
695 if (mCreators.find(name) != mCreators.end()) {
697 std::cout <<
"Warning: Overriding registration of SynthVoice: " << name
701 if (!allowAutoAllocation) {
702 if (std::find(mNoAllocationList.begin(), mNoAllocationList.end(), name) ==
703 mNoAllocationList.end()) {
704 mNoAllocationList.push_back(name);
707 mCreators[name] = [&]() {
708 TSynthVoice *voice = allocateVoice<TSynthVoice>();
715 template <
class TSynthVoice> TSynthVoice *allocateVoice() {
716 TSynthVoice *voice =
new TSynthVoice;
717 voice->next =
nullptr;
718 if (mDefaultUserData) {
719 voice->userData(mDefaultUserData);
722 for (
auto allocCb : mAllocationCallbacks) {
723 allocCb.first(voice, allocCb.second);
728 bool verbose() {
return mVerbose; }
729 void verbose(
bool verbose) { mVerbose = verbose; }
763 typedef const std::function<void(
AudioIOData &internalVoiceIO,
795 mCpuGranularitySec = timeSecs;
805 if (mVoiceToInsertLock.try_lock()) {
810 while (voice->next) {
825 mVoiceToInsertLock.unlock();
828 if (mFreeVoiceLock.try_lock()) {
829 mAllNotesOff =
false;
833 previousVoice->
id(-1);
836 previousVoice = voice;
839 previousVoice->next =
844 mFreeVoiceLock.unlock();
856 int voicesToTurnOff[16];
857 size_t numVoicesToTurnOff;
858 while ((numVoicesToTurnOff = mVoiceIdsToTurnOff.
read(
859 (
char *)voicesToTurnOff, 16 *
sizeof(
int)))) {
860 for (
size_t i = 0; i < numVoicesToTurnOff / int(
sizeof(
int)); i++) {
863 if (voice->id() == voicesToTurnOff[i]) {
865 std::cout <<
"Voice trigger off " << voice->
id() << std::endl;
873 size_t numVoicesToFree;
874 int voicesToFree[16];
875 while ((numVoicesToFree =
876 mVoiceIdsToFree.
read((
char *)voicesToFree, 16 *
sizeof(
int)))) {
877 for (
size_t i = 0; i < numVoicesToFree / int(
sizeof(
int)); i++) {
879 std::cout <<
"Voice free " << voicesToFree[i] << std::endl;
883 if (voice->id() == voicesToFree[i]) {
884 voice->mActive =
false;
900 if (mFreeVoiceLock.try_lock()) {
905 if (!voice->active()) {
906 int id = voice->
id();
910 previousVoice->next = voice->next;
915 voice = previousVoice;
917 auto *nextVoice = voice->next;
925 for (
auto cbNode : mFreeCallbacks) {
926 cbNode.first(
id, cbNode.second);
929 previousVoice = voice;
934 mFreeVoiceLock.unlock();
939 void startCpuClockThread();
943 if (mAudioGain != 1.0f) {
944 for (
unsigned int i = 0; i < io.
channelsOut(); i++) {
948 *buffer++ *= mAudioGain;
954 virtual void prepare(AudioIOData &io);
964 std::mutex mVoiceToInsertLock;
965 std::mutex mFreeVoiceLock;
966 std::mutex mGraphicsLock;
968 bool m_useInternalAudioIO =
true;
969 bool m_internalAudioConfigured =
false;
971 uint16_t mVoiceMaxOutputChannels = 2;
972 uint16_t mVoiceMaxInputChannels = 0;
973 uint16_t mVoiceBusChannels = 0;
974 std::shared_ptr<BusRoutingCallback> mBusRoutingCallback;
978 SingleRWRingBuffer mVoiceIdsToFree{64 *
sizeof(int)};
980 TimeMasterMode mMasterMode;
982 std::vector<AudioCallback *> mPostProcessing;
985 std::function<bool(SynthVoice *voice,
int offsetFrames,
int id,
void *)>,
988 std::vector<TriggerOnCallback> mTriggerOnCallbacks;
990 typedef std::pair<std::function<bool(
int id,
void *)>,
void *>
992 std::vector<TriggerOffCallback> mTriggerOffCallbacks;
994 typedef std::pair<std::function<bool(
int id,
void *)>,
void *> FreeCallback;
995 std::vector<FreeCallback> mFreeCallbacks;
997 typedef std::pair<std::function<void(SynthVoice *voice,
void *)>,
void *>
999 std::vector<AllocationCallback> mAllocationCallbacks;
1001 float mAudioGain{1.0f};
1003 int mIdCounter{1000};
1006 bool mAllNotesOff{
false};
1008 typedef std::function<SynthVoice *()> VoiceCreatorFunc;
1009 typedef std::map<std::string, VoiceCreatorFunc> Creators;
1011 void *mDefaultUserData{
nullptr};
1015 std::vector<std::string> mNoAllocationList;
1016 std::vector<size_t> mChannelMap;
1018 bool mRunCPUClock{
true};
1019 double mCpuGranularitySec = 0.001;
1020 std::unique_ptr<std::thread> mCpuClockThread;
1022 bool mVerbose{
false};
1025 template <
class TSynthVoice>
void PolySynth::disableAllocation() {
1026 std::string name = demangle(
typeid(TSynthVoice).name());
1027 disableAllocation(name);
1031 std::unique_lock<std::mutex> lk(
1036 freeVoice =
nullptr;
1039 if (std::type_index(
typeid(*freeVoice)) ==
1040 std::type_index(
typeid(TSynthVoice))) {
1041 if (previousVoice) {
1042 previousVoice->next = freeVoice->next;
1048 previousVoice = freeVoice;
1049 freeVoice = freeVoice->next;
1056 std::string name = demangle(
typeid(TSynthVoice).name());
1057 if (std::find(mNoAllocationList.begin(), mNoAllocationList.end(), name) ==
1058 mNoAllocationList.end()) {
1060 freeVoice = allocateVoice<TSynthVoice>();
1062 std::cout <<
"Allocating voice of type " <<
typeid(TSynthVoice).name()
1063 <<
"." << std::endl;
1066 std::cout <<
"Automatic allocation disabled for voice:" << name
1070 return static_cast<TSynthVoice *
>(freeVoice);
1074 std::unique_lock<std::mutex> lk(mFreeVoiceLock);
1077 while (lastVoice->next) {
1078 lastVoice = lastVoice->next;
1081 lastVoice =
mFreeVoices = allocateVoice<TSynthVoice>();
1084 for (
int i = 0; i < number; i++) {
1085 lastVoice->next = allocateVoice<TSynthVoice>();
1086 lastVoice = lastVoice->next;
uint64_t framesPerBuffer() const
Get frames/buffer of audio I/O stream.
float * outBuffer(unsigned int chan=0) const
Get non-interleaved output buffer on specified channel.
unsigned int channelsOut() const
Get effective number of output channels.
unsigned int frame() const
Get current frame number.
Interface for loading fonts and rendering text.
A PolySynth manages polyphony and rendering of SynthVoice instances.
TSynthVoice * getVoice(bool forceAlloc=false)
Get a reference to a voice.
virtual void allNotesOff()
Turn off all notes immediately (without calling triggerOff() )
void gain(float gainValue)
Set audio output gain.
void setVoiceBusChannels(uint16_t channels)
Determines the number of buses for the internal AudioIOData objects.
void allocatePolyphony(int number)
void registerTriggerOnCallback(std::function< bool(SynthVoice *voice, int offsetFrames, int id, void *userData)> cb, void *userData=nullptr)
register a callback to be notified of a trigger on event
void registerFreeCallback(std::function< bool(int id, void *userData)> cb, void *userData=nullptr)
register a callback to be notified of a note is moved to the free pool from the active list
void setChannelMap(std::vector< size_t > channelMap)
Set channel map for output.
virtual void render(AudioIOData &io)
render all the active voices into the audio buffers
void registerTriggerOffCallback(std::function< bool(int id, void *userData)> cb, void *userData=nullptr)
register a callback to be notified of a trigger off event
void setTimeMaster(TimeMasterMode masterMode)
Set time master context.
void allocatePolyphony(std::string name, int number)
void processVoiceTurnOff()
Check for voices that need trigger off and execute.
void triggerOff(int id)
trigger release of voice with id
void setDefaultUserData(void *userData)
Set default user data to set to voices before the are returned by getVoice()
void setBusRoutingCallback(BusRoutingCallback cb)
setBusRoutingCallback
SynthVoice * getFreeVoice()
Get the first available voice with minimal checks.
PolySynth & insertAfter(AudioCallback &v, AudioCallback &afterThis)
Insert an AudioCallback object after another in the queue.
virtual void update(double dt=0)
update internal state for all voices.
void registerAllocateCallback(std::function< void(SynthVoice *voice, void *)> cb, void *userData=nullptr)
register a callback to be notified of allocation of a voice.
SynthVoice * mFreeVoices
Allocated voices available for reuse.
void registerSynthClass(std::string name="", bool allowAutoAllocation=true)
PolySynth & insertBefore(AudioCallback &v, AudioCallback &beforeThis)
Insert an AudioCallback object before another in the queue.
void insertFreeVoice(SynthVoice *voice)
Use this function to insert a voice allocated externally into the free voice pool.
PolySynth & remove(AudioCallback &v)
Remove an AudioCallback object from the queue.
SynthVoice * getVoice(std::string name, bool forceAlloc=false)
Get a reference to a free voice by voice type name.
int triggerOn(SynthVoice *voice, int offsetFrames=0, int id=-1, void *userData=nullptr)
trigger Puts voice in active voice lit and calls triggerOn() for it
virtual void render(Graphics &g)
render graphics for all active voices
bool popFreeVoice(SynthVoice *voice)
Remove voice from the free voice pool.
SynthVoice * mActiveVoices
float gain()
get current audio gain
void processInactiveVoices()
Check for voices marked as free and move them to the free voice pool.
void setCpuClockGranularity(double timeSecs)
Set the time in seconds to wait between sequencer updates when time master is CPU.
void setVoiceMaxOutputChannels(uint16_t channels)
Determines the number of output channels allocated for the internal AudioIOData objects.
void processVoices()
Add new voices to the chain.
PolySynth & append(AudioCallback &v)
Insert an AudioCallback object at the end of the callback queue.
SynthVoice * mVoicesToInsert
PolySynth & prepend(AudioCallback &v)
Insert an AudioCallback object at the head of the callback queue.
SynthVoice * getFreeVoices()
getFreeVoices
void print(std::ostream &stream=std::cout)
prints details of the allocated voices (free, active and queued)
A local coordinate frame.
size_t read(char *dst, size_t sz)
Event Sequencer triggering audio visual "notes".
virtual void onFree()
This function gets called after the voice is taken out of the processing chain.
float getInternalParameterValue(std::string name)
Get value for internal trigger parameter.
bool active()
Returns true if voice is currently active.
virtual bool setTriggerParams(std::vector< float > &pFields, bool noCalls=false)
Set trigger parameter values.
virtual std::vector< ParameterField > getTriggerParams()
Get this instance's parameter fields.
virtual void init()
Override this function to initialize internal data.
void id(int idValue)
Set the id for this voice.
void setInternalParameterValue(std::string name, float value)
Set value for internal trigger parameter.
void setNumOutChannels(unsigned int numOutputs)
Set the number of outputs this SynthVoice generates.
int id()
Get the id for this voice.
virtual void onProcess(Graphics &)
Override this function to define graphics for this synth.
int getStartOffsetFrames(unsigned int framesPerBuffer)
returns the offset frames framesPerSecondand sets them to 0.
virtual bool setTriggerParams(std::vector< ParameterField > pFields, bool noCalls=false)
Set parameter values.
SynthVoice & registerParameters(Args &... paramsArgs)
virtual void update(double dt=0)
Override this function to update internal state, e.g. from an asynchronous simulator.
void free()
Mark this voice as done.
void triggerOn(int offsetFrames=0)
Trigger a note by calling onTriggerOn() and setting voice as active.
virtual SynthVoice & registerParameter(ParameterMeta ¶m)
registerParameter
std::vector< ParameterMeta * > parameters()
Get the Voice's continuous (i.e. not "trigger") parameters.
Parameter & getInternalParameter(std::string name)
Get reference to internal Parameter.
SynthVoice & registerTriggerParameters(Args &... paramsArgs)
std::shared_ptr< Parameter > createInternalTriggerParameter(std::string name, float defaultValue=0.0, float minValue=-9999.0, float maxValue=9999.0)
A convenience function for quick creation of a managed parameter.
virtual void onTriggerOff()
determine what needs to be done when note/event ends Define this function to determine what needs to ...
void triggerOff(int offsetFrames=0)
Call the voice's onTriggerOff() function to begin note's deactivation.
virtual bool setTriggerParams(float *pFields, int numFields=-1)
virtual void onTriggerOn()
Override this function to determine what needs to be done when note/event starts.
virtual void onProcess(AudioIOData &)
Override this function to define audio processing.
unsigned int numOutChannels()
Query the number of channels this voice generates.
virtual SynthVoice & registerTriggerParameter(ParameterMeta ¶m)
Register a parameter as a "trigger" parameter.
virtual int getTriggerParams(float *pFields, int maxParams=-1)
Get this instance's parameter fields.