You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

573 lines
27KB

  1. #include <mutex>
  2. #include <chrono>
  3. #include <thread>
  4. #include <condition_variable>
  5. #include "plugin.hpp"
  6. #include <audio.hpp>
  7. #include <context.hpp>
  8. namespace rack {
  9. namespace core {
  10. template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS>
  11. struct AudioInterfacePort : audio::Port {
  12. Module* module;
  13. dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 32768> engineInputBuffer;
  14. dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 32768> engineOutputBuffer;
  15. dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc;
  16. dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc;
  17. // Port variable caches
  18. int deviceNumInputs = 0;
  19. int deviceNumOutputs = 0;
  20. float deviceSampleRate = 0.f;
  21. int requestedEngineFrames = 0;
  22. AudioInterfacePort(Module* module) {
  23. this->module = module;
  24. maxOutputs = NUM_AUDIO_INPUTS;
  25. maxInputs = NUM_AUDIO_OUTPUTS;
  26. inputSrc.setQuality(6);
  27. outputSrc.setQuality(6);
  28. }
  29. void setPrimary() {
  30. APP->engine->setPrimaryModule(module);
  31. }
  32. bool isPrimary() {
  33. return APP->engine->getPrimaryModule() == module;
  34. }
  35. void processInput(const float* input, int inputStride, int frames) override {
  36. // DEBUG("%p: new device block ____________________________", this);
  37. // Claim primary module if there is none
  38. if (!APP->engine->getPrimaryModule()) {
  39. setPrimary();
  40. }
  41. bool isPrimaryCached = isPrimary();
  42. // Set sample rate of engine if engine sample rate is "auto".
  43. if (isPrimaryCached) {
  44. APP->engine->setSuggestedSampleRate(deviceSampleRate);
  45. }
  46. float engineSampleRate = APP->engine->getSampleRate();
  47. float sampleRateRatio = engineSampleRate / deviceSampleRate;
  48. // DEBUG("%p: %d block, engineOutputBuffer still has %d", this, frames, (int) engineOutputBuffer.size());
  49. // Consider engine buffers "too full" if they contain a bit more than the audio device's number of frames, converted to engine sample rate.
  50. int maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 2.0) - 1;
  51. // If the engine output buffer is too full, clear it to keep latency low. No need to clear if primary because it's always cleared below.
  52. if (!isPrimaryCached && (int) engineOutputBuffer.size() > maxEngineFrames) {
  53. engineOutputBuffer.clear();
  54. // DEBUG("%p: clearing engine output", this);
  55. }
  56. if (deviceNumInputs > 0) {
  57. // Always clear engine output if primary
  58. if (isPrimaryCached) {
  59. engineOutputBuffer.clear();
  60. }
  61. // Set up sample rate converter
  62. outputSrc.setRates(deviceSampleRate, engineSampleRate);
  63. outputSrc.setChannels(deviceNumInputs);
  64. // Convert audio input -> engine output
  65. dsp::Frame<NUM_AUDIO_OUTPUTS> audioInputBuffer[frames];
  66. std::memset(audioInputBuffer, 0, sizeof(audioInputBuffer));
  67. for (int i = 0; i < frames; i++) {
  68. for (int j = 0; j < deviceNumInputs; j++) {
  69. float v = input[i * inputStride + j];
  70. audioInputBuffer[i].samples[j] = v;
  71. }
  72. }
  73. int audioInputFrames = frames;
  74. int outputFrames = engineOutputBuffer.capacity();
  75. outputSrc.process(audioInputBuffer, &audioInputFrames, engineOutputBuffer.endData(), &outputFrames);
  76. engineOutputBuffer.endIncr(outputFrames);
  77. // Request exactly as many frames as we have in the engine output buffer.
  78. requestedEngineFrames = engineOutputBuffer.size();
  79. }
  80. else {
  81. // Upper bound on number of frames so that `audioOutputFrames >= frames` when processOutput() is called.
  82. requestedEngineFrames = std::max((int) std::ceil(frames * sampleRateRatio) - (int) engineInputBuffer.size(), 0);
  83. }
  84. }
  85. void processBuffer(const float* input, int inputStride, float* output, int outputStride, int frames) override {
  86. // Step engine
  87. if (isPrimary() && requestedEngineFrames > 0) {
  88. // DEBUG("%p: %d block, stepping %d", this, frames, requestedEngineFrames);
  89. APP->engine->stepBlock(requestedEngineFrames);
  90. }
  91. }
  92. void processOutput(float* output, int outputStride, int frames) override {
  93. // bool isPrimaryCached = isPrimary();
  94. float engineSampleRate = APP->engine->getSampleRate();
  95. float sampleRateRatio = engineSampleRate / deviceSampleRate;
  96. if (deviceNumOutputs > 0) {
  97. // Set up sample rate converter
  98. inputSrc.setRates(engineSampleRate, deviceSampleRate);
  99. inputSrc.setChannels(deviceNumOutputs);
  100. // Convert engine input -> audio output
  101. dsp::Frame<NUM_AUDIO_OUTPUTS> audioOutputBuffer[frames];
  102. int inputFrames = engineInputBuffer.size();
  103. int audioOutputFrames = frames;
  104. inputSrc.process(engineInputBuffer.startData(), &inputFrames, audioOutputBuffer, &audioOutputFrames);
  105. engineInputBuffer.startIncr(inputFrames);
  106. // Copy the audio output buffer
  107. for (int i = 0; i < audioOutputFrames; i++) {
  108. for (int j = 0; j < deviceNumOutputs; j++) {
  109. float v = audioOutputBuffer[i].samples[j];
  110. v = clamp(v, -1.f, 1.f);
  111. output[i * outputStride + j] = v;
  112. }
  113. }
  114. // Fill the rest of the audio output buffer with zeros
  115. for (int i = audioOutputFrames; i < frames; i++) {
  116. for (int j = 0; j < deviceNumOutputs; j++) {
  117. output[i * outputStride + j] = 0.f;
  118. }
  119. }
  120. }
  121. // DEBUG("%p: %d block, engineInputBuffer left %d", this, frames, (int) engineInputBuffer.size());
  122. // If the engine input buffer is too full, clear it to keep latency low.
  123. int maxEngineFrames = (int) std::ceil(frames * sampleRateRatio * 2.0) - 1;
  124. if ((int) engineInputBuffer.size() > maxEngineFrames) {
  125. engineInputBuffer.clear();
  126. // DEBUG("%p: clearing engine input", this);
  127. }
  128. // DEBUG("%p %s:\tframes %d requestedEngineFrames %d\toutputBuffer %d engineInputBuffer %d\t", this, isPrimaryCached ? "primary" : "secondary", frames, requestedEngineFrames, engineOutputBuffer.size(), engineInputBuffer.size());
  129. }
  130. void onStartStream() override {
  131. deviceNumInputs = std::min(getNumInputs(), NUM_AUDIO_OUTPUTS);
  132. deviceNumOutputs = std::min(getNumOutputs(), NUM_AUDIO_INPUTS);
  133. deviceSampleRate = getSampleRate();
  134. engineInputBuffer.clear();
  135. engineOutputBuffer.clear();
  136. // DEBUG("onStartStream %d %d %f", deviceNumInputs, deviceNumOutputs, deviceSampleRate);
  137. }
  138. void onStopStream() override {
  139. deviceNumInputs = 0;
  140. deviceNumOutputs = 0;
  141. deviceSampleRate = 0.f;
  142. engineInputBuffer.clear();
  143. engineOutputBuffer.clear();
  144. // DEBUG("onStopStream");
  145. }
  146. };
  147. template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS>
  148. struct AudioInterface : Module {
  149. static constexpr int NUM_INPUT_LIGHTS = (NUM_AUDIO_INPUTS > 2) ? (NUM_AUDIO_INPUTS / 2) : 0;
  150. static constexpr int NUM_OUTPUT_LIGHTS = (NUM_AUDIO_OUTPUTS > 2) ? (NUM_AUDIO_OUTPUTS / 2) : 0;
  151. enum ParamIds {
  152. ENUMS(GAIN_PARAM, NUM_AUDIO_INPUTS == 2),
  153. NUM_PARAMS
  154. };
  155. enum InputIds {
  156. ENUMS(AUDIO_INPUTS, NUM_AUDIO_INPUTS),
  157. NUM_INPUTS
  158. };
  159. enum OutputIds {
  160. ENUMS(AUDIO_OUTPUTS, NUM_AUDIO_OUTPUTS),
  161. NUM_OUTPUTS
  162. };
  163. enum LightIds {
  164. ENUMS(INPUT_LIGHTS, NUM_INPUT_LIGHTS * 2),
  165. ENUMS(OUTPUT_LIGHTS, NUM_OUTPUT_LIGHTS * 2),
  166. ENUMS(VU_LIGHTS, (NUM_AUDIO_INPUTS == 2) ? (2 * 6) : 0),
  167. NUM_LIGHTS
  168. };
  169. AudioInterfacePort<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> port;
  170. dsp::RCFilter dcFilters[NUM_AUDIO_INPUTS];
  171. bool dcFilterEnabled = false;
  172. dsp::ClockDivider lightDivider;
  173. // For each pair of inputs/outputs
  174. float inputClipTimers[(NUM_AUDIO_INPUTS > 0) ? NUM_INPUT_LIGHTS : 0] = {};
  175. float outputClipTimers[(NUM_AUDIO_INPUTS > 0) ? NUM_OUTPUT_LIGHTS : 0] = {};
  176. dsp::VuMeter2 vuMeter[(NUM_AUDIO_INPUTS == 2) ? 2 : 0];
  177. AudioInterface() : port(this) {
  178. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  179. if (NUM_AUDIO_INPUTS == 2)
  180. configParam(GAIN_PARAM, 0.f, 2.f, 1.f, "Level", " dB", -10, 40);
  181. for (int i = 0; i < NUM_AUDIO_INPUTS; i++)
  182. configInput(AUDIO_INPUTS + i, string::f("To \"device output %d\"", i + 1));
  183. for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++)
  184. configOutput(AUDIO_OUTPUTS + i, string::f("From \"device input %d\"", i + 1));
  185. for (int i = 0; i < NUM_INPUT_LIGHTS; i++)
  186. configLight(INPUT_LIGHTS + 2 * i, string::f("Device output %d/%d status", 2 * i + 1, 2 * i + 2));
  187. for (int i = 0; i < NUM_OUTPUT_LIGHTS; i++)
  188. configLight(OUTPUT_LIGHTS + 2 * i, string::f("Device input %d/%d status", 2 * i + 1, 2 * i + 2));
  189. lightDivider.setDivision(512);
  190. float sampleTime = APP->engine->getSampleTime();
  191. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  192. dcFilters[i].setCutoffFreq(10.f * sampleTime);
  193. }
  194. onReset();
  195. }
  196. ~AudioInterface() {
  197. // Close stream here before destructing AudioInterfacePort, so processBuffer() etc are not called on another thread while destructing.
  198. port.setDriverId(-1);
  199. }
  200. void onReset() override {
  201. port.setDriverId(-1);
  202. if (NUM_AUDIO_INPUTS == 2)
  203. dcFilterEnabled = true;
  204. else
  205. dcFilterEnabled = false;
  206. }
  207. void onSampleRateChange(const SampleRateChangeEvent& e) override {
  208. port.engineInputBuffer.clear();
  209. port.engineOutputBuffer.clear();
  210. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  211. dcFilters[i].setCutoffFreq(10.f * e.sampleTime);
  212. }
  213. }
  214. void process(const ProcessArgs& args) override {
  215. const float clipTime = 0.25f;
  216. // Push inputs to buffer
  217. if (port.deviceNumOutputs > 0) {
  218. dsp::Frame<NUM_AUDIO_INPUTS> inputFrame = {};
  219. for (int i = 0; i < port.deviceNumOutputs; i++) {
  220. // Get input
  221. float v = 0.f;
  222. if (inputs[AUDIO_INPUTS + i].isConnected())
  223. v = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f;
  224. // Normalize right input to left on Audio-2
  225. else if (i == 1 && NUM_AUDIO_INPUTS == 2)
  226. v = inputFrame.samples[0];
  227. // Apply DC filter
  228. if (dcFilterEnabled) {
  229. dcFilters[i].process(v);
  230. v = dcFilters[i].highpass();
  231. }
  232. // Detect clipping
  233. if (NUM_AUDIO_INPUTS > 2) {
  234. if (std::fabs(v) >= 1.f)
  235. inputClipTimers[i / 2] = clipTime;
  236. }
  237. inputFrame.samples[i] = v;
  238. }
  239. // Audio-2: Apply gain from knob
  240. if (NUM_AUDIO_INPUTS == 2) {
  241. float gain = std::pow(params[GAIN_PARAM].getValue(), 2.f);
  242. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  243. inputFrame.samples[i] *= gain;
  244. }
  245. }
  246. if (!port.engineInputBuffer.full()) {
  247. port.engineInputBuffer.push(inputFrame);
  248. }
  249. // Audio-2: VU meter process
  250. if (NUM_AUDIO_INPUTS == 2) {
  251. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  252. vuMeter[i].process(args.sampleTime, inputFrame.samples[i]);
  253. }
  254. }
  255. }
  256. else {
  257. // Audio-2: Clear VU meter
  258. if (NUM_AUDIO_INPUTS == 2) {
  259. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  260. vuMeter[i].reset();
  261. }
  262. }
  263. }
  264. // Pull outputs from buffer
  265. if (!port.engineOutputBuffer.empty()) {
  266. dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame = port.engineOutputBuffer.shift();
  267. for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) {
  268. float v = outputFrame.samples[i];
  269. outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v);
  270. // Detect clipping
  271. if (NUM_AUDIO_OUTPUTS > 2) {
  272. if (std::fabs(v) >= 1.f)
  273. outputClipTimers[i / 2] = clipTime;
  274. }
  275. }
  276. }
  277. else {
  278. // Zero outputs
  279. for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) {
  280. outputs[AUDIO_OUTPUTS + i].setVoltage(0.f);
  281. }
  282. }
  283. // Lights
  284. if (lightDivider.process()) {
  285. float lightTime = args.sampleTime * lightDivider.getDivision();
  286. // Audio-2: VU meter
  287. if (NUM_AUDIO_INPUTS == 2) {
  288. for (int i = 0; i < NUM_AUDIO_INPUTS; i++) {
  289. lights[VU_LIGHTS + i * 6 + 0].setBrightness(vuMeter[i].getBrightness(0, 0));
  290. lights[VU_LIGHTS + i * 6 + 1].setBrightness(vuMeter[i].getBrightness(-3, 0));
  291. lights[VU_LIGHTS + i * 6 + 2].setBrightness(vuMeter[i].getBrightness(-6, -3));
  292. lights[VU_LIGHTS + i * 6 + 3].setBrightness(vuMeter[i].getBrightness(-12, -6));
  293. lights[VU_LIGHTS + i * 6 + 4].setBrightness(vuMeter[i].getBrightness(-24, -12));
  294. lights[VU_LIGHTS + i * 6 + 5].setBrightness(vuMeter[i].getBrightness(-36, -24));
  295. }
  296. }
  297. // Audio-8 and Audio-16: pair state lights
  298. else {
  299. // Turn on light if at least one port is enabled in the nearby pair.
  300. for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) {
  301. bool active = port.deviceNumOutputs >= 2 * i + 1;
  302. bool clip = inputClipTimers[i] > 0.f;
  303. if (clip)
  304. inputClipTimers[i] -= lightTime;
  305. lights[INPUT_LIGHTS + i * 2 + 0].setBrightness(active && !clip);
  306. lights[INPUT_LIGHTS + i * 2 + 1].setBrightness(active && clip);
  307. }
  308. for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) {
  309. bool active = port.deviceNumInputs >= 2 * i + 1;
  310. bool clip = outputClipTimers[i] > 0.f;
  311. if (clip)
  312. outputClipTimers[i] -= lightTime;
  313. lights[OUTPUT_LIGHTS + i * 2 + 0].setBrightness(active & !clip);
  314. lights[OUTPUT_LIGHTS + i * 2 + 1].setBrightness(active & clip);
  315. }
  316. }
  317. }
  318. }
  319. json_t* dataToJson() override {
  320. json_t* rootJ = json_object();
  321. json_object_set_new(rootJ, "audio", port.toJson());
  322. json_object_set_new(rootJ, "dcFilter", json_boolean(dcFilterEnabled));
  323. return rootJ;
  324. }
  325. void dataFromJson(json_t* rootJ) override {
  326. json_t* audioJ = json_object_get(rootJ, "audio");
  327. if (audioJ)
  328. port.fromJson(audioJ);
  329. json_t* dcFilterJ = json_object_get(rootJ, "dcFilter");
  330. if (dcFilterJ)
  331. dcFilterEnabled = json_boolean_value(dcFilterJ);
  332. }
  333. /** Must be called when the Engine mutex is unlocked.
  334. */
  335. void setPrimary() {
  336. APP->engine->setPrimaryModule(this);
  337. }
  338. bool isPrimary() {
  339. return APP->engine->getPrimaryModule() == this;
  340. }
  341. };
  342. template <int NUM_AUDIO_INPUTS, int NUM_AUDIO_OUTPUTS>
  343. struct AudioInterfaceWidget : ModuleWidget {
  344. typedef AudioInterface<NUM_AUDIO_INPUTS, NUM_AUDIO_OUTPUTS> TAudioInterface;
  345. AudioInterfaceWidget(TAudioInterface* module) {
  346. setModule(module);
  347. if (NUM_AUDIO_INPUTS == 8 && NUM_AUDIO_OUTPUTS == 8) {
  348. setPanel(Svg::load(asset::system("res/Core/AudioInterface.svg")));
  349. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  350. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  351. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  352. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  353. addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069211, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 0));
  354. addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 1));
  355. addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 2));
  356. addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 3));
  357. addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069209, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 4));
  358. addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 5));
  359. addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 6));
  360. addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 7));
  361. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 0));
  362. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 1));
  363. addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 2));
  364. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506519, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 3));
  365. addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 4));
  366. addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 5));
  367. addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 6));
  368. addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506523, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 7));
  369. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(12.524985, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 0 * 2));
  370. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(35.725647, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 1 * 2));
  371. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(12.524985, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 2 * 2));
  372. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(35.725647, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 3 * 2));
  373. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(12.524985, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 0 * 2));
  374. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(35.725647, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 1 * 2));
  375. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(12.524985, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 2 * 2));
  376. addChild(createLight<SmallLight<GreenRedLight>>(mm2px(Vec(35.725647, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 3 * 2));
  377. AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339)));
  378. audioWidget->box.size = mm2px(Vec(44, 28));
  379. audioWidget->setAudioPort(module ? &module->port : NULL);
  380. addChild(audioWidget);
  381. }
  382. else if (NUM_AUDIO_INPUTS == 16 && NUM_AUDIO_OUTPUTS == 16) {
  383. setPanel(Svg::load(asset::system("res/Core/AudioInterface16.svg")));
  384. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  385. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  386. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  387. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  388. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 0));
  389. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 1));
  390. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 2));
  391. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 3));
  392. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 4));
  393. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 5));
  394. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 6));
  395. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 7));
  396. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 8));
  397. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 9));
  398. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 10));
  399. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 11));
  400. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 12));
  401. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 13));
  402. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 14));
  403. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 15));
  404. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 0));
  405. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 1));
  406. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 2));
  407. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 3));
  408. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 4));
  409. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 5));
  410. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 6));
  411. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 7));
  412. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 8));
  413. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 9));
  414. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 10));
  415. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 11));
  416. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 12));
  417. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 13));
  418. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 14));
  419. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 15));
  420. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(13.46, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 0 * 2));
  421. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(36.661, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 1 * 2));
  422. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(59.861, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 2 * 2));
  423. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(83.061, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 3 * 2));
  424. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(13.46, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 4 * 2));
  425. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(36.661, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 5 * 2));
  426. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(59.861, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 6 * 2));
  427. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(83.061, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 7 * 2));
  428. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(13.46, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 0 * 2));
  429. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(36.661, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 1 * 2));
  430. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(59.861, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 2 * 2));
  431. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(83.061, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 3 * 2));
  432. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(13.46, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 4 * 2));
  433. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(36.661, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 5 * 2));
  434. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(59.861, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 6 * 2));
  435. addChild(createLightCentered<SmallLight<GreenRedLight>>(mm2px(Vec(83.061, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 7 * 2));
  436. AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839)));
  437. audioWidget->box.size = mm2px(Vec(91.382, 28.0));
  438. audioWidget->setAudioPort(module ? &module->port : NULL);
  439. addChild(audioWidget);
  440. }
  441. else if (NUM_AUDIO_INPUTS == 2 && NUM_AUDIO_OUTPUTS == 2) {
  442. setPanel(Svg::load(asset::system("res/Core/AudioInterface2.svg")));
  443. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  444. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  445. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  446. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  447. addParam(createParamCentered<RoundBigBlackKnob>(mm2px(Vec(12.7, 74.019)), module, TAudioInterface::GAIN_PARAM));
  448. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(6.697, 94.253)), module, TAudioInterface::AUDIO_INPUTS + 0));
  449. addInput(createInputCentered<PJ301MPort>(mm2px(Vec(18.703, 94.253)), module, TAudioInterface::AUDIO_INPUTS + 1));
  450. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(6.699, 112.254)), module, TAudioInterface::AUDIO_OUTPUTS + 0));
  451. addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(18.7, 112.254)), module, TAudioInterface::AUDIO_OUTPUTS + 1));
  452. addChild(createLightCentered<MediumLight<RedLight>>(mm2px(Vec(6.7, 29.759)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 0));
  453. addChild(createLightCentered<MediumLight<RedLight>>(mm2px(Vec(18.7, 29.759)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 0));
  454. addChild(createLightCentered<MediumLight<YellowLight>>(mm2px(Vec(6.7, 34.753)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 1));
  455. addChild(createLightCentered<MediumLight<YellowLight>>(mm2px(Vec(18.7, 34.753)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 1));
  456. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(6.7, 39.749)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 2));
  457. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(18.7, 39.749)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 2));
  458. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(6.7, 44.744)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 3));
  459. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(18.7, 44.744)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 3));
  460. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(6.7, 49.744)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 4));
  461. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(18.7, 49.744)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 4));
  462. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(6.7, 54.745)), module, TAudioInterface::VU_LIGHTS + 0 * 6 + 5));
  463. addChild(createLightCentered<MediumLight<GreenLight>>(mm2px(Vec(18.7, 54.745)), module, TAudioInterface::VU_LIGHTS + 1 * 6 + 5));
  464. AudioDeviceWidget* audioWidget = createWidget<AudioDeviceWidget>(mm2px(Vec(2.135, 14.259)));
  465. audioWidget->box.size = mm2px(Vec(21.128, 6.725));
  466. audioWidget->setAudioPort(module ? &module->port : NULL);
  467. // Adjust deviceChoice position
  468. audioWidget->deviceChoice->textOffset = Vec(6, 14);
  469. addChild(audioWidget);
  470. }
  471. }
  472. void appendContextMenu(Menu* menu) override {
  473. TAudioInterface* module = dynamic_cast<TAudioInterface*>(this->module);
  474. menu->addChild(new MenuSeparator);
  475. menu->addChild(createCheckMenuItem("Primary audio module",
  476. [=]() {return module->isPrimary();},
  477. [=]() {module->setPrimary();}
  478. ));
  479. menu->addChild(createBoolPtrMenuItem("DC blocker", &module->dcFilterEnabled));
  480. }
  481. };
  482. Model* modelAudioInterface2 = createModel<AudioInterface<2, 2>, AudioInterfaceWidget<2, 2>>("AudioInterface2");
  483. // Legacy name for Audio-8
  484. Model* modelAudioInterface = createModel<AudioInterface<8, 8>, AudioInterfaceWidget<8, 8>>("AudioInterface");
  485. Model* modelAudioInterface16 = createModel<AudioInterface<16, 16>, AudioInterfaceWidget<16, 16>>("AudioInterface16");
  486. } // namespace core
  487. } // namespace rack