/*
* Klang – a node+text-based synthesizer library
*
* This file is part of the *wellen* library (https://github.com/dennisppaul/wellen).
* Copyright (c) 2022 Dennis P Paul.
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef Controller_hpp
#define Controller_hpp
#include "Connection.hpp"
#include "ConnectionPool.hpp"
#include "KlangCommands.hpp"
#include "KlangNode.hpp"
//#define ENABLE_RESET_CMD
namespace klang {
class Controller {
public:
std::vector<Node*> nodes;
// @TODO("add a sequential reset sequence ( e.g 6× `0x00` )")
void command(KLANG_CMD_TYPE pData) {
// @TODO (this might not necessary)
#ifdef ENABLE_RESET_CMD
if (pData == KLANG_CMD_RESET_CMD) {
mCommandResetCounter++;
/* reset */
if (mCommandResetCounter >= CMD_RESET_COUNTER) {
reset_cmd_collection();
return;
}
}
#endif
/* -- AUTO GENERATED CODE (BEGIN) -- */
if (mCommandCollectorCounter == NO_CMD) {
mCommandPayloadID = 0;
switch (pData) {
case KLANG_CMD_SYNTH_START:
case KLANG_CMD_RESERVED_01:
case KLANG_CMD_RESET_CMD:
case KLANG_CMD_RESET:
case KLANG_CMD_SYNTH_STOP:
mCommandCollectorCounter = 0;
break;
case KLANG_CMD_OUTPUT_NODE_I8:
case KLANG_CMD_CREATE_NODE_I8:
case KLANG_SET_START:
case KLANG_SET_STOP:
case KLANG_CMD_INPUT_NODE_I8:
case KLANG_CMD_DELETE_NODE_I8:
case KLANG_SET_CLEAR_STAGES:
mCommandCollectorCounter = 1;
break;
case KLANG_SET_TYPE_I8:
case KLANG_SET_MUTE_I8:
case KLANG_SET_WAVEFORM_I8:
case KLANG_SET_STEREO_I8:
mCommandCollectorCounter = 2;
break;
case KLANG_CMD_CONNECT_NODES_I8_I8_I8_I8:
mCommandCollectorCounter = 4;
break;
case KLANG_SET_CLIP_F32:
case KLANG_SET_RANGE_MAX_F32:
case KLANG_SET_DECAY_F32:
case KLANG_SET_RELEASE_F32:
case KLANG_SET_AMPLIFICATION_F32:
case KLANG_SET_SPEED_F32:
case KLANG_SET_ATTACK_F32:
case KLANG_SET_FEEDBACK_F32:
case KLANG_SET_FILTER_F32:
case KLANG_SET_RATE_F32:
case KLANG_SET_STRIDE_F32:
case KLANG_SET_MIN_F32:
case KLANG_SET_SWEEPRATE_F32:
case KLANG_SET_RESONANCE_F32:
case KLANG_SET_AMPLITUDE_F32:
case KLANG_SET_IN_MIN_F32:
case KLANG_SET_CUTOFF_F32:
case KLANG_SET_VALUE_F32:
case KLANG_SET_DELAY_F32:
case KLANG_SET_FREQUENCY_F32:
case KLANG_SET_OUT_MAX_F32:
case KLANG_SET_SUSTAIN_F32:
case KLANG_SET_SWEEP_F32:
case KLANG_SET_DURATION_F32:
case KLANG_SET_OUT_MIN_F32:
case KLANG_SET_IN_MAX_F32:
case KLANG_SET_MAX_F32:
case KLANG_SET_RANGE_MIN_F32:
case KLANG_SET_TIME_SCALE_F32:
case KLANG_SET_OFFSET_F32:
case KLANG_SET_WET_F32:
case KLANG_SET_MIX_F32:
mCommandCollectorCounter = 5;
break;
case KLANG_SET_PAN_I8_F32:
case KLANG_SET_MIX_I8_F32:
mCommandCollectorCounter = 6;
break;
case KLANG_SET_ADD_STAGE_F32_F32:
mCommandCollectorCounter = 9;
break;
}
}
/* -- AUTO GENERATED CODE (END) -- */
// KLANG_LOG("data: 0x%02X (payload_counter: %i)", pData, mCommandCollectorCounter);
mCommandPayloadCollector[mCommandPayloadID++] = pData;
if (mCommandCollectorCounter <= 0) {
// KLANG_LOG("cmd_data");
command_package(mCommandPayloadCollector);
reset_cmd_collection();
} else {
mCommandCollectorCounter--;
}
}
void commands(uint8_t* data, uint8_t length) {
for (uint8_t i = 0; i < length; i++) {
command(data[i]);
}
}
/**
*
* @deprecated("use serial cmd collector only")
*/
void command_sequence(std::initializer_list<KLANG_CMD_TYPE> pData) {
for (KLANG_CMD_TYPE mDatum : pData) {
command(mDatum);
}
}
// @TODO("find a better way to parse the payload. maybe inscribe it in the constant name e.g `KLANG_SET_AMPLITUDE_1F32`, `KLANG_CMD_DELETE_NODE_1I8`, or `KLANG_CMD_CONNECT_NODES_4I8` ")
void command_package(KLANG_CMD_TYPE* pData) {
try {
const KLANG_CMD_TYPE mCommand = pData[CMD_LOCATION];
switch (mCommand) {
case KLANG_CMD_RESET_CMD:
reset_cmd_collection();
break;
case KLANG_CMD_RESET:
KLANG_LOG("@Controller :: KLANG_CMD_RESET");
cmd_reset();
break;
case KLANG_CMD_RESERVED_01:
break;
case KLANG_CMD_SYNTH_START:
KLANG_LOG("@Controller :: KLANG_CMD_SYNTH_START");
Klang::unlock();
break;
case KLANG_CMD_SYNTH_STOP:
KLANG_LOG("@Controller :: KLANG_CMD_SYNTH_STOP");
Klang::lock();
break;
case KLANG_CMD_CREATE_NODE_I8:
KLANG_LOG("@Controller :: KLANG_CMD_CREATE_NODE_I8");
cmd_create_node(pData[CMD_NODE_TYPE_LOCATION]);
break;
case KLANG_CMD_DELETE_NODE_I8:
KLANG_LOG("@Controller :: KLANG_CMD_DELETE_NODE_I8 @TODO");
cmd_delete_node(pData[CMD_NODE_TYPE_LOCATION]);
break;
case KLANG_CMD_CONNECT_NODES_I8_I8_I8_I8:
KLANG_LOG("@Controller :: KLANG_CMD_CONNECT_NODES_I8_I8_I8_I8");
cmd_connect_nodes(pData);
break;
case KLANG_CMD_OUTPUT_NODE_I8:
KLANG_LOG("@Controller :: KLANG_CMD_OUTPUT_NODE_I8");
cmd_output_node(pData);
break;
case KLANG_CMD_INPUT_NODE_I8:
KLANG_LOG("@Controller :: KLANG_CMD_INPUT_NODE_I8");
cmd_input_node(pData);
break;
// @TODO("automate the set branch")
case KLANG_SET_CLIP_F32:
case KLANG_SET_RANGE_MAX_F32:
case KLANG_SET_DECAY_F32:
case KLANG_SET_RELEASE_F32:
case KLANG_SET_AMPLIFICATION_F32:
case KLANG_SET_SPEED_F32:
case KLANG_SET_ATTACK_F32:
case KLANG_SET_FEEDBACK_F32:
case KLANG_SET_FILTER_F32:
case KLANG_SET_RATE_F32:
case KLANG_SET_STRIDE_F32:
case KLANG_SET_MIN_F32:
case KLANG_SET_SWEEPRATE_F32:
case KLANG_SET_RESONANCE_F32:
case KLANG_SET_AMPLITUDE_F32:
case KLANG_SET_IN_MIN_F32:
case KLANG_SET_CUTOFF_F32:
case KLANG_SET_VALUE_F32:
case KLANG_SET_DELAY_F32:
case KLANG_SET_FREQUENCY_F32:
case KLANG_SET_OUT_MAX_F32:
case KLANG_SET_SUSTAIN_F32:
case KLANG_SET_SWEEP_F32:
case KLANG_SET_DURATION_F32:
case KLANG_SET_OUT_MIN_F32:
case KLANG_SET_IN_MAX_F32:
case KLANG_SET_MAX_F32:
case KLANG_SET_RANGE_MIN_F32:
case KLANG_SET_TIME_SCALE_F32:
case KLANG_SET_OFFSET_F32:
case KLANG_SET_WET_F32:
case KLANG_SET_MIX_F32:
KLANG_LOG("@Controller :: KLANG_SET_*_F32");
cmd_set_value_f32(pData);
break;
case KLANG_SET_TYPE_I8:
case KLANG_SET_MUTE_I8:
case KLANG_SET_WAVEFORM_I8:
case KLANG_SET_STEREO_I8:
KLANG_LOG("@Controller :: KLANG_SET_*_I8");
cmd_set_value_i8(pData);
break;
case KLANG_SET_START:
case KLANG_SET_STOP:
case KLANG_SET_CLEAR_STAGES:
case KLANG_SET_PAN_I8_F32:
case KLANG_SET_MIX_I8_F32:
case KLANG_SET_ADD_STAGE_F32_F32:
KLANG_LOG("@Controller :: KLANG_SET_*_* @TODO(might crash)");
cmd_set_value(pData);
break;
default:
KLANG_LOG_ERR("@Controller :: could not parse command (0x%02X)", pData[CMD_LOCATION]);
}
} catch (const std::exception& e) {
KLANG_LOG_ERR("@Controller :: command failed with exception: %s", e.what());
}
}
void process_frame_output(float* pLeft, float* pRight) {
if (!Klang::islocked() && mOutputNode != nullptr) {
mOutputNode->process_frame(pLeft, pRight);
} else {
for (uint16_t i = 0; i < KLANG_SAMPLES_PER_AUDIO_BLOCK; i++) {
pLeft[i] = 0.0;
pRight[i] = 0.0;
}
}
}
void process_frame_input(float* pLeft, float* pRight) {
// KLANG_LOG("Klang::islocked(): %i", Klang::islocked());
if (!Klang::islocked() && mInputNode != nullptr) {
mInputNode->process_frame(pLeft, pRight);
}
}
private:
static const KLANG_CMD_TYPE CMD_LOCATION = 0x00;
static const KLANG_CMD_TYPE CMD_CONNECT_DATA_LOCATION = 0x01;
static const KLANG_CMD_TYPE CMD_OUTPUT_NODE_LOCATION = 0x01;
static const KLANG_CMD_TYPE CMD_SET_NODE_ID_LOCATION = 0x01;
static const KLANG_CMD_TYPE CMD_SET_NODE_VALUE_LOCATION = 0x02;
static const KLANG_CMD_TYPE CMD_NODE_TYPE_LOCATION = 0x01;
static const int8_t NO_CMD = -1;
int8_t mCommandCollectorCounter = NO_CMD;
uint8_t mCommandPayloadID = 0;
uint8_t mCommandResetCounter = 0;
static const uint8_t CMD_RESET_COUNTER = 5;
static const uint8_t PAYLOAD_COLLECTOR_SIZE = 8;
KLANG_CMD_TYPE mCommandPayloadCollector[PAYLOAD_COLLECTOR_SIZE] = {0};
uint8_t mOutputNodeID = 255;
uint8_t mInputNodeID = 255;
NodeDAC* mOutputNode = nullptr;
NodeADC* mInputNode = nullptr;
void reset_payload_collector() {
for (uint8_t i = 0; i < PAYLOAD_COLLECTOR_SIZE; i++) {
mCommandPayloadCollector[i] = 0;
}
}
void reset_cmd_collection() {
mCommandPayloadID = 0;
mCommandResetCounter = 0;
mCommandCollectorCounter = NO_CMD;
reset_payload_collector();
}
void cmd_set_value_i8(KLANG_CMD_TYPE* pData) {
const uint8_t mID = pData[CMD_SET_NODE_ID_LOCATION];
if (mID >= nodes.size()) {
return;
}
Node* mNode = nodes[mID];
if (mNode == nullptr) {
return;
}
uint8_t pPayload[] = {
pData[CMD_SET_NODE_ID_LOCATION + 1]};
KLANG_LOG("@Controller :: NODE_%02i > set_value_i8 (0x%02X) > %i ", mID, pData[CMD_LOCATION], pPayload[0]);
mNode->set_command(pData[CMD_LOCATION], pPayload);
}
void cmd_set_value_f32(KLANG_CMD_TYPE* pData) {
const uint8_t mID = pData[CMD_SET_NODE_ID_LOCATION];
if (mID >= nodes.size()) {
return;
}
Node* mNode = nodes[mID];
if (mNode == nullptr) {
return;
}
uint8_t pPayload[] = {
pData[CMD_SET_NODE_VALUE_LOCATION + 0],
pData[CMD_SET_NODE_VALUE_LOCATION + 1],
pData[CMD_SET_NODE_VALUE_LOCATION + 2],
pData[CMD_SET_NODE_VALUE_LOCATION + 3]};
KLANG_LOG("@Controller :: NODE_%02i > set_value_f32 (0x%02X) > %f ", mID, pData[CMD_LOCATION], KlangMath::FLOAT_32(pPayload));
mNode->set_command(pData[CMD_LOCATION], pPayload);
}
void cmd_set_value(KLANG_CMD_TYPE* pData) {
// @TODO("`cmd_set_value` could be unified")
const uint8_t mID = pData[CMD_SET_NODE_ID_LOCATION];
if (mID >= nodes.size()) {
return;
}
Node* mNode = nodes[mID];
if (mNode == nullptr) {
return;
}
uint8_t pPayload[] = {
pData[CMD_SET_NODE_VALUE_LOCATION + 0],
pData[CMD_SET_NODE_VALUE_LOCATION + 1],
pData[CMD_SET_NODE_VALUE_LOCATION + 2],
pData[CMD_SET_NODE_VALUE_LOCATION + 3],
pData[CMD_SET_NODE_VALUE_LOCATION + 4],
pData[CMD_SET_NODE_VALUE_LOCATION + 5],
pData[CMD_SET_NODE_VALUE_LOCATION + 6],
pData[CMD_SET_NODE_VALUE_LOCATION + 7]};
KLANG_LOG("@Controller :: NODE_%02i > set_value_* (0x%02X)", mID, pData[CMD_LOCATION]);
mNode->set_command(pData[CMD_LOCATION], pPayload);
}
void cmd_connect_nodes(KLANG_CMD_TYPE* pData) {
KLANG_LOG("@Controller :: connecting NODE_%02i(OUT%02i) with NODE_%02i(IN%02i)",
pData[CMD_CONNECT_DATA_LOCATION + 0],
pData[CMD_CONNECT_DATA_LOCATION + 1],
pData[CMD_CONNECT_DATA_LOCATION + 2],
pData[CMD_CONNECT_DATA_LOCATION + 3]);
Klang::connect(*nodes[pData[CMD_CONNECT_DATA_LOCATION + 0]],
pData[CMD_CONNECT_DATA_LOCATION + 1],
*nodes[pData[CMD_CONNECT_DATA_LOCATION + 2]],
pData[CMD_CONNECT_DATA_LOCATION + 3]);
}
void cmd_output_node(KLANG_CMD_TYPE* pData) {
mOutputNodeID = pData[CMD_OUTPUT_NODE_LOCATION];
KLANG_LOG("@Controller :: set NODE_%02i as output_node", mOutputNodeID);
if (mOutputNodeID < nodes.size() &&
nodes[mOutputNodeID] != nullptr) {
mOutputNode = dynamic_cast<NodeDAC*>(nodes[mOutputNodeID]);
if (!mOutputNode) {
KLANG_LOG("@Controller :: setting output node failed! node id or node is not valid ( make sure node is of type `NodeDAC` )");
}
}
}
void cmd_input_node(KLANG_CMD_TYPE* pData) {
mInputNodeID = pData[CMD_OUTPUT_NODE_LOCATION];
KLANG_LOG("@Controller :: set NODE_%02i as input_node", mInputNodeID);
if (mInputNodeID < nodes.size() &&
nodes[mInputNodeID] != nullptr) {
mInputNode = dynamic_cast<NodeADC*>(nodes[mInputNodeID]);
if (!mInputNode) {
KLANG_LOG("@Controller :: setting input node failed! node id or node is not valid ( make sure node is of type `NodeADC` )");
}
}
}
void cmd_delete_node(const uint8_t pNodeID) {
// @TODO("this corrupts all subsequent IDs")
KLANG_LOG("@Controller :: delete NODE_%02lu", nodes.size());
KLANG_LOG("@TODO("this corrupts all subsequent IDs")");
nodes.erase(nodes.begin() + pNodeID);
}
void cmd_reset() {
// @TODO("delete all nodes and connections, reset synth, reset controller")
reset_cmd_collection();
Klang::instance().reset_frame_index();
ConnectionPool::instance().reset();
reset();
}
void reset() {
mInputNode = nullptr;
mOutputNode = nullptr;
for (uint16_t i = 0; i < nodes.size(); i++) {
delete nodes[i];
}
nodes.clear();
Node::reset();
}
/* -- AUTO GENERATED CODE (BEGIN) -- */
void cmd_create_node(const KLANG_CMD_TYPE pNodeType) {
switch (pNodeType) {
case KLANG_NODE_ADC:
nodes.push_back(new NodeADC());
break;
case KLANG_NODE_ADD:
nodes.push_back(new NodeAdd());
break;
case KLANG_NODE_ADSR:
nodes.push_back(new NodeADSR());
break;
case KLANG_NODE_ALIEN_WAH:
nodes.push_back(new NodeAlienWah());
break;
case KLANG_NODE_BUFFER:
nodes.push_back(new NodeBuffer());
break;
case KLANG_NODE_CHORUS:
nodes.push_back(new NodeChorus());
break;
case KLANG_NODE_CLIP:
nodes.push_back(new NodeClip());
break;
case KLANG_NODE_DAC:
nodes.push_back(new NodeDAC());
break;
case KLANG_NODE_DELAY:
nodes.push_back(new NodeDelay());
break;
case KLANG_NODE_DISTORTION:
nodes.push_back(new NodeDistortion());
break;
case KLANG_NODE_ENVELOPE:
nodes.push_back(new NodeEnvelope());
break;
case KLANG_NODE_MAP:
nodes.push_back(new NodeMap());
break;
case KLANG_NODE_MIXER2:
nodes.push_back(new NodeMixer2());
break;
case KLANG_NODE_MIXER4:
nodes.push_back(new NodeMixer4());
break;
case KLANG_NODE_MIXER4_STEREO:
nodes.push_back(new NodeMixer4Stereo());
break;
case KLANG_NODE_MUTE:
nodes.push_back(new NodeMute());
break;
case KLANG_NODE_NOISE:
nodes.push_back(new NodeNoise());
break;
case KLANG_NODE_PASSTHROUGH:
nodes.push_back(new NodePassthrough());
break;
case KLANG_NODE_PATCH:
nodes.push_back(new NodePatch());
break;
case KLANG_NODE_PATCH16:
nodes.push_back(new NodePatch16());
break;
case KLANG_NODE_PHASER:
nodes.push_back(new NodePhaser());
break;
case KLANG_NODE_PORTAMENTO:
nodes.push_back(new NodePortamento());
break;
case KLANG_NODE_SAMPLER:
nodes.push_back(new NodeSampler());
break;
case KLANG_NODE_SPLITTER:
nodes.push_back(new NodeSplitter());
break;
case KLANG_NODE_VALUE:
nodes.push_back(new NodeValue());
break;
case KLANG_NODE_VCA:
nodes.push_back(new NodeVCA());
break;
case KLANG_NODE_VCF_LP:
nodes.push_back(new NodeVCFLowPass());
break;
case KLANG_NODE_VCF_MOOG_LHBP:
nodes.push_back(new NodeVCFMoogLHBP());
break;
case KLANG_NODE_VCF_MOOG_LP:
nodes.push_back(new NodeVCFMoogLP());
break;
case KLANG_NODE_VCO_FUNC:
nodes.push_back(new NodeVCOFunction());
break;
case KLANG_NODE_VCO_WAVETABLE:
nodes.push_back(new NodeVCOWavetable());
break;
}
}
/* -- AUTO GENERATED CODE (END) -- */
};
} // namespace klang
/*
* a set of exemplary commands that create and connect a sinewave oscillator to a DAC:
*
* | COMMANDS | HEX REPRESENTATION | EXPLANATION |
* |-----------------------------------------|----------------------------------------|-------------------------------------------------------------------------|
* | KLANG_CMD_CREATE_NODE, KLANG_NODE_DAC | ( 0x05, 0x03 ) | create NODE_DAC with ID(0) |
* | KLANG_CMD_CREATE_NODE, KLANG_NODE_WAVETABLE | ( 0x05, 0x04 ) | create NODE_WAVETABLE with ID(1) |
* | KLANG_CMD_CONNECT_NODES, 1, 0, 0, 0 | ( 0x07, 0x01, 0x00, 0x00, 0x00 ) | connect OUPUT(0) of NODE_WAVETABLE ID(1) to INPUT(1)) of NODE_DAC ID(0) |
* | KLANG_SET_WAVEFORM, 1, 1 | ( 0x83, 0x01, 0x01 ) | set WAVEFORM of NODE_WAVETABLE ID(1) to SINE |
* | KLANG_SET_FREQUENCY_F32, 1, 110.0 | ( 0x81, 0x01, 0x00, 0x00, 0xDC, 0x36 ) | set FREQUENCY of NODE_WAVETABLE ID(1) to 110.0 |
*
*/
#endif /* Controller_hpp */