| @@ -36,11 +36,6 @@ struct HostAudio : TerminalModule { | |||||
| dsp::RCFilter dcFilters[numIO]; | dsp::RCFilter dcFilters[numIO]; | ||||
| bool dcFilterEnabled = (numIO == 2); | bool dcFilterEnabled = (numIO == 2); | ||||
| // for stereo meter | |||||
| volatile bool resetMeters = true; | |||||
| float gainMeterL = 0.0f; | |||||
| float gainMeterR = 0.0f; | |||||
| HostAudio() | HostAudio() | ||||
| : pcontext(static_cast<CardinalPluginContext*>(APP)), | : pcontext(static_cast<CardinalPluginContext*>(APP)), | ||||
| numParams(numIO == 2 ? 1 : 0), | numParams(numIO == 2 ? 1 : 0), | ||||
| @@ -63,13 +58,10 @@ struct HostAudio : TerminalModule { | |||||
| void onReset() override | void onReset() override | ||||
| { | { | ||||
| dcFilterEnabled = (numIO == 2); | dcFilterEnabled = (numIO == 2); | ||||
| resetMeters = true; | |||||
| } | } | ||||
| void onSampleRateChange(const SampleRateChangeEvent& e) override | void onSampleRateChange(const SampleRateChangeEvent& e) override | ||||
| { | { | ||||
| resetMeters = true; | |||||
| for (int i=0; i<numIO; ++i) | for (int i=0; i<numIO; ++i) | ||||
| dcFilters[i].setCutoffFreq(10.f * e.sampleTime); | dcFilters[i].setCutoffFreq(10.f * e.sampleTime); | ||||
| } | } | ||||
| @@ -103,7 +95,51 @@ struct HostAudio : TerminalModule { | |||||
| } | } | ||||
| } | } | ||||
| void processTerminalOutput(const ProcessArgs&) override | |||||
| json_t* dataToJson() override | |||||
| { | |||||
| json_t* const rootJ = json_object(); | |||||
| DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr); | |||||
| json_object_set_new(rootJ, "dcFilter", json_boolean(dcFilterEnabled)); | |||||
| return rootJ; | |||||
| } | |||||
| void dataFromJson(json_t* const rootJ) override | |||||
| { | |||||
| json_t* const dcFilterJ = json_object_get(rootJ, "dcFilter"); | |||||
| DISTRHO_SAFE_ASSERT_RETURN(dcFilterJ != nullptr,); | |||||
| dcFilterEnabled = json_boolean_value(dcFilterJ); | |||||
| } | |||||
| }; | |||||
| struct HostAudio2 : HostAudio<2> { | |||||
| // for stereo meter | |||||
| int internalDataFrame = 0; | |||||
| float internalDataBuffer[2][128]; | |||||
| volatile bool resetMeters = true; | |||||
| float gainMeterL = 0.0f; | |||||
| float gainMeterR = 0.0f; | |||||
| HostAudio2() | |||||
| : HostAudio<2>() | |||||
| { | |||||
| std::memset(internalDataBuffer, 0, sizeof(internalDataBuffer)); | |||||
| } | |||||
| void onReset() override | |||||
| { | |||||
| HostAudio<2>::onReset(); | |||||
| resetMeters = true; | |||||
| } | |||||
| void onSampleRateChange(const SampleRateChangeEvent& e) override | |||||
| { | |||||
| HostAudio<2>::onSampleRateChange(e); | |||||
| resetMeters = true; | |||||
| } | |||||
| void processTerminalOutput(const ProcessArgs&) | |||||
| { | { | ||||
| const int blockFrames = pcontext->engine->getBlockFrames(); | const int blockFrames = pcontext->engine->getBlockFrames(); | ||||
| @@ -116,11 +152,15 @@ struct HostAudio : TerminalModule { | |||||
| float** const dataOuts = pcontext->dataOuts; | float** const dataOuts = pcontext->dataOuts; | ||||
| // stereo version gain | |||||
| const float gain = numParams != 0 ? std::pow(params[0].getValue(), 2.f) : 1.0f; | |||||
| // gain (stereo variant only) | |||||
| const float gain = std::pow(params[0].getValue(), 2.f); | |||||
| // left/mono check | |||||
| const bool in2connected = inputs[1].isConnected(); | |||||
| // read first value, special case for mono mode | |||||
| // read stereo values | |||||
| float valueL = inputs[0].getVoltageSum() * 0.1f; | float valueL = inputs[0].getVoltageSum() * 0.1f; | ||||
| float valueR = inputs[1].getVoltageSum() * 0.1f; | |||||
| // Apply DC filter | // Apply DC filter | ||||
| if (dcFilterEnabled) | if (dcFilterEnabled) | ||||
| @@ -132,68 +172,111 @@ struct HostAudio : TerminalModule { | |||||
| valueL = clamp(valueL * gain, -1.0f, 1.0f); | valueL = clamp(valueL * gain, -1.0f, 1.0f); | ||||
| dataOuts[0][k] += valueL; | dataOuts[0][k] += valueL; | ||||
| // read everything else | |||||
| for (int i=1; i<numInputs; ++i) | |||||
| if (in2connected) | |||||
| { | { | ||||
| float v = inputs[i].getVoltageSum() * 0.1f; | |||||
| // Apply DC filter | |||||
| if (dcFilterEnabled) | if (dcFilterEnabled) | ||||
| { | { | ||||
| dcFilters[i].process(v); | |||||
| v = dcFilters[i].highpass(); | |||||
| dcFilters[1].process(valueR); | |||||
| valueR = dcFilters[1].highpass(); | |||||
| } | } | ||||
| dataOuts[i][k] += clamp(v * gain, -1.0f, 1.0f); | |||||
| valueR = clamp(valueR * gain, -1.0f, 1.0f); | |||||
| dataOuts[1][k] += valueR; | |||||
| } | } | ||||
| if (numInputs == 2) | |||||
| else | |||||
| { | { | ||||
| const bool connected = inputs[1].isConnected(); | |||||
| valueR = valueL; | |||||
| dataOuts[1][k] += valueL; | |||||
| } | |||||
| if (! connected) | |||||
| dataOuts[1][k] += valueL; | |||||
| const int j = internalDataFrame++; | |||||
| internalDataBuffer[0][j] = valueL; | |||||
| internalDataBuffer[1][j] = valueR; | |||||
| if (dataFrame == blockFrames) | |||||
| { | |||||
| if (resetMeters) | |||||
| gainMeterL = gainMeterR = 0.0f; | |||||
| if (internalDataFrame == 128) | |||||
| { | |||||
| internalDataFrame = 0; | |||||
| gainMeterL = std::max(gainMeterL, d_findMaxNormalizedFloat(dataOuts[0], blockFrames)); | |||||
| if (resetMeters) | |||||
| gainMeterL = gainMeterR = 0.0f; | |||||
| if (connected) | |||||
| gainMeterR = std::max(gainMeterR, d_findMaxNormalizedFloat(dataOuts[1], blockFrames)); | |||||
| else | |||||
| gainMeterR = gainMeterL; | |||||
| gainMeterL = std::max(gainMeterL, d_findMaxNormalizedFloat(internalDataBuffer[0], 128)); | |||||
| resetMeters = false; | |||||
| } | |||||
| if (in2connected) | |||||
| gainMeterR = std::max(gainMeterR, d_findMaxNormalizedFloat(internalDataBuffer[1], 128)); | |||||
| else | |||||
| gainMeterR = gainMeterL; | |||||
| resetMeters = false; | |||||
| } | } | ||||
| } | } | ||||
| }; | |||||
| json_t* dataToJson() override | |||||
| struct HostAudio8 : HostAudio<8> { | |||||
| // no meters in this variant | |||||
| void processTerminalOutput(const ProcessArgs&) override | |||||
| { | { | ||||
| json_t* const rootJ = json_object(); | |||||
| DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr); | |||||
| const int blockFrames = pcontext->engine->getBlockFrames(); | |||||
| json_object_set_new(rootJ, "dcFilter", json_boolean(dcFilterEnabled)); | |||||
| return rootJ; | |||||
| // only incremented on output | |||||
| const int k = dataFrame++; | |||||
| DISTRHO_SAFE_ASSERT_INT2_RETURN(k < blockFrames, k, blockFrames,); | |||||
| if (isBypassed()) | |||||
| return; | |||||
| float** const dataOuts = pcontext->dataOuts; | |||||
| for (int i=0; i<numInputs; ++i) | |||||
| { | |||||
| float v = inputs[i].getVoltageSum() * 0.1f; | |||||
| if (dcFilterEnabled) | |||||
| { | |||||
| dcFilters[i].process(v); | |||||
| v = dcFilters[i].highpass(); | |||||
| } | |||||
| dataOuts[i][k] += clamp(v, -1.0f, 1.0f); | |||||
| } | |||||
| } | } | ||||
| void dataFromJson(json_t* const rootJ) override | |||||
| }; | |||||
| // -------------------------------------------------------------------------------------------------------------------- | |||||
| template<int numIO> | |||||
| struct HostAudioWidget : ModuleWidgetWith8HP { | |||||
| HostAudio<numIO>* const module; | |||||
| HostAudioWidget(HostAudio<numIO>* const m) | |||||
| : module(m) | |||||
| { | { | ||||
| json_t* const dcFilterJ = json_object_get(rootJ, "dcFilter"); | |||||
| DISTRHO_SAFE_ASSERT_RETURN(dcFilterJ != nullptr,); | |||||
| setModule(m); | |||||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HostAudio.svg"))); | |||||
| dcFilterEnabled = json_boolean_value(dcFilterJ); | |||||
| createAndAddScrews(); | |||||
| for (uint i=0; i<numIO; ++i) | |||||
| { | |||||
| createAndAddInput(i); | |||||
| createAndAddOutput(i); | |||||
| } | |||||
| } | |||||
| void appendContextMenu(Menu* const menu) override { | |||||
| menu->addChild(new MenuSeparator); | |||||
| menu->addChild(createBoolPtrMenuItem("DC blocker", "", &module->dcFilterEnabled)); | |||||
| } | } | ||||
| }; | }; | ||||
| template<int numIO> | |||||
| // -------------------------------------------------------------------------------------------------------------------- | |||||
| struct HostAudioNanoMeter : NanoMeter { | struct HostAudioNanoMeter : NanoMeter { | ||||
| HostAudio<numIO>* const module; | |||||
| HostAudio2* const module; | |||||
| HostAudioNanoMeter(HostAudio<numIO>* const m) | |||||
| HostAudioNanoMeter(HostAudio2* const m) | |||||
| : module(m) | : module(m) | ||||
| { | { | ||||
| hasGainKnob = true; | hasGainKnob = true; | ||||
| @@ -211,69 +294,58 @@ struct HostAudioNanoMeter : NanoMeter { | |||||
| } | } | ||||
| }; | }; | ||||
| template<int numIO> | |||||
| struct HostAudioWidget : ModuleWidgetWith8HP { | |||||
| HostAudio<numIO>* const module; | |||||
| // -------------------------------------------------------------------------------------------------------------------- | |||||
| HostAudioWidget(HostAudio<numIO>* const m) | |||||
| : module(m) | |||||
| struct HostAudioWidget2 : HostAudioWidget<2> { | |||||
| HostAudioWidget2(HostAudio2* const m) | |||||
| : HostAudioWidget<2>(m) | |||||
| { | { | ||||
| setModule(m); | |||||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/HostAudio.svg"))); | |||||
| // FIXME | |||||
| const float middleX = box.size.x * 0.5f; | |||||
| addParam(createParamCentered<NanoKnob>(Vec(middleX, 310.0f), m, 0)); | |||||
| HostAudioNanoMeter* const meter = new HostAudioNanoMeter(m); | |||||
| meter->box.pos = Vec(middleX - padding + 2.75f, startY + padding * 2); | |||||
| meter->box.size = Vec(padding * 2.0f - 4.0f, 136.0f); | |||||
| addChild(meter); | |||||
| } | |||||
| createAndAddScrews(); | |||||
| void draw(const DrawArgs& args) override | |||||
| { | |||||
| drawBackground(args.vg); | |||||
| drawOutputJacksArea(args.vg, 2); | |||||
| setupTextLines(args.vg); | |||||
| for (uint i=0; i<numIO; ++i) | |||||
| { | |||||
| createAndAddInput(i); | |||||
| createAndAddOutput(i); | |||||
| } | |||||
| drawTextLine(args.vg, 0, "Left/M"); | |||||
| drawTextLine(args.vg, 1, "Right"); | |||||
| if (numIO == 2) | |||||
| { | |||||
| // FIXME | |||||
| const float middleX = box.size.x * 0.5f; | |||||
| addParam(createParamCentered<NanoKnob>(Vec(middleX, 310.0f), m, 0)); | |||||
| HostAudioNanoMeter<numIO>* const meter = new HostAudioNanoMeter<numIO>(m); | |||||
| meter->box.pos = Vec(middleX - padding + 2.75f, startY + padding * 2); | |||||
| meter->box.size = Vec(padding * 2.0f - 4.0f, 136.0f); | |||||
| addChild(meter); | |||||
| } | |||||
| ModuleWidgetWith8HP::draw(args); | |||||
| } | } | ||||
| }; | |||||
| struct HostAudioWidget8 : HostAudioWidget<8> { | |||||
| HostAudioWidget8(HostAudio8* const m) | |||||
| : HostAudioWidget<8>(m) {} | |||||
| void draw(const DrawArgs& args) override | void draw(const DrawArgs& args) override | ||||
| { | { | ||||
| drawBackground(args.vg); | drawBackground(args.vg); | ||||
| drawOutputJacksArea(args.vg, numIO); | |||||
| drawOutputJacksArea(args.vg, 8); | |||||
| setupTextLines(args.vg); | setupTextLines(args.vg); | ||||
| if (numIO == 2) | |||||
| { | |||||
| drawTextLine(args.vg, 0, "Left/M"); | |||||
| drawTextLine(args.vg, 1, "Right"); | |||||
| } | |||||
| else | |||||
| for (int i=0; i<8; ++i) | |||||
| { | { | ||||
| for (int i=0; i<numIO; ++i) | |||||
| { | |||||
| char text[] = {'A','u','d','i','o',' ',static_cast<char>('0'+i+1),'\0'}; | |||||
| drawTextLine(args.vg, i, text); | |||||
| } | |||||
| char text[] = {'A','u','d','i','o',' ',static_cast<char>('0'+i+1),'\0'}; | |||||
| drawTextLine(args.vg, i, text); | |||||
| } | } | ||||
| ModuleWidgetWith8HP::draw(args); | ModuleWidgetWith8HP::draw(args); | ||||
| } | } | ||||
| void appendContextMenu(Menu* const menu) override { | |||||
| menu->addChild(new MenuSeparator); | |||||
| menu->addChild(createBoolPtrMenuItem("DC blocker", "", &module->dcFilterEnabled)); | |||||
| } | |||||
| }; | }; | ||||
| // -------------------------------------------------------------------------------------------------------------------- | // -------------------------------------------------------------------------------------------------------------------- | ||||
| Model* modelHostAudio2 = createModel<HostAudio<2>, HostAudioWidget<2>>("HostAudio2"); | |||||
| Model* modelHostAudio8 = createModel<HostAudio<8>, HostAudioWidget<8>>("HostAudio8"); | |||||
| Model* modelHostAudio2 = createModel<HostAudio2, HostAudioWidget2>("HostAudio2"); | |||||
| Model* modelHostAudio8 = createModel<HostAudio8, HostAudioWidget8>("HostAudio8"); | |||||
| // -------------------------------------------------------------------------------------------------------------------- | // -------------------------------------------------------------------------------------------------------------------- | ||||