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.

487 lines
16KB

  1. /*
  2. * Swanky Amp tube amplifier simulation
  3. * Copyright (C) 2020 Garrin McGoldrick
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. #include <algorithm>
  19. #include <cmath>
  20. #include <unordered_map>
  21. #include <JuceHeader.h>
  22. #include "PluginEditor.h"
  23. #include "Utils.h"
  24. #include "PluginProcessor.h"
  25. // add parameter to the VTS with default range -1 to +1
  26. #define MAKE_PARAMETER_UNIT(n) \
  27. std::make_unique<AudioParameterFloat>( \
  28. "id" #n, #n, NormalisableRange<float>(-1.0f, 1.0f, 2.0f / 1e3f), 0.0f)
  29. // add parameter to the VTS with custom range
  30. #define MAKE_PARAMETER(n, l, h, d) \
  31. std::make_unique<AudioParameterFloat>( \
  32. "id" #n, #n, NormalisableRange<float>(l, h, fabs(h - l) / 1e3f), d)
  33. // assign a VTS parameter to an object member of the same name
  34. #define ASSIGN_PARAMETER(n) par##n = parameters.getRawParameterValue("id" #n);
  35. SwankyAmpAudioProcessor::SwankyAmpAudioProcessor() :
  36. #ifndef JucePlugin_PreferredChannelConfigurations
  37. AudioProcessor(BusesProperties()
  38. #if !JucePlugin_IsMidiEffect
  39. #if !JucePlugin_IsSynth
  40. .withInput("Input", AudioChannelSet::stereo(), true)
  41. #endif
  42. .withOutput("Output", AudioChannelSet::stereo(), true)
  43. #endif
  44. ),
  45. #endif
  46. parameters(
  47. *this,
  48. nullptr,
  49. Identifier("APVTSSwankyAmp"),
  50. {
  51. MAKE_PARAMETER_UNIT(InputLevel),
  52. MAKE_PARAMETER_UNIT(OutputLevel),
  53. MAKE_PARAMETER_UNIT(TsLow),
  54. MAKE_PARAMETER_UNIT(TsMid),
  55. MAKE_PARAMETER_UNIT(TsHigh),
  56. MAKE_PARAMETER_UNIT(TsPresence),
  57. MAKE_PARAMETER(TsSelection, 0.0f, 2.0f, 0.0f),
  58. MAKE_PARAMETER(GainStages, 1.0f, 5.0f, 3.0f),
  59. MAKE_PARAMETER_UNIT(GainOverhead),
  60. MAKE_PARAMETER_UNIT(LowCut),
  61. std::make_unique<AudioParameterBool>(
  62. "idCabOnOff", "CabOnOff", true),
  63. MAKE_PARAMETER_UNIT(CabBrightness),
  64. MAKE_PARAMETER(CabDistance, 0.0f, 1.0f, 0.5f),
  65. MAKE_PARAMETER(CabDynamic, -1.0f, 1.0f, -0.3f),
  66. MAKE_PARAMETER(PreAmpDrive, -1.0f, 1.0f, -0.4f),
  67. MAKE_PARAMETER_UNIT(PreAmpTight),
  68. MAKE_PARAMETER_UNIT(PreAmpGrit),
  69. MAKE_PARAMETER(PowerAmpDrive, -1.0f, 1.0f, -0.2f),
  70. MAKE_PARAMETER_UNIT(PowerAmpTight),
  71. MAKE_PARAMETER_UNIT(PowerAmpGrit),
  72. MAKE_PARAMETER(PowerAmpSag, -1.0f, 1.0f, -0.6f),
  73. MAKE_PARAMETER_UNIT(PowerAmpSagRatio),
  74. })
  75. {
  76. ASSIGN_PARAMETER(InputLevel)
  77. ASSIGN_PARAMETER(OutputLevel)
  78. ASSIGN_PARAMETER(TsLow)
  79. ASSIGN_PARAMETER(TsMid)
  80. ASSIGN_PARAMETER(TsHigh)
  81. ASSIGN_PARAMETER(TsPresence)
  82. ASSIGN_PARAMETER(TsSelection)
  83. ASSIGN_PARAMETER(GainStages)
  84. ASSIGN_PARAMETER(GainOverhead)
  85. ASSIGN_PARAMETER(LowCut)
  86. ASSIGN_PARAMETER(CabOnOff)
  87. ASSIGN_PARAMETER(CabBrightness)
  88. ASSIGN_PARAMETER(CabDistance)
  89. ASSIGN_PARAMETER(CabDynamic)
  90. ASSIGN_PARAMETER(PreAmpDrive)
  91. ASSIGN_PARAMETER(PreAmpTight)
  92. ASSIGN_PARAMETER(PreAmpGrit)
  93. ASSIGN_PARAMETER(PowerAmpDrive)
  94. ASSIGN_PARAMETER(PowerAmpTight)
  95. ASSIGN_PARAMETER(PowerAmpGrit)
  96. ASSIGN_PARAMETER(PowerAmpSag)
  97. ASSIGN_PARAMETER(PowerAmpSag)
  98. ASSIGN_PARAMETER(PowerAmpSagRatio)
  99. }
  100. #undef MAKE_PARAMETER_UNIT
  101. #undef MAKE_PARAMETER
  102. #undef ASSIGN_PARAMETER
  103. SwankyAmpAudioProcessor::~SwankyAmpAudioProcessor() {}
  104. // set the amp object user parameters from the VTS values
  105. void SwankyAmpAudioProcessor::setAmpParameters()
  106. {
  107. for (int i = 0; i < 2; i++)
  108. {
  109. const float preAmpDriveMap = remapSinh(*parPreAmpDrive, 0.5f, 1.0f);
  110. const float powerAmpDriveMap = remapSinh(*parPowerAmpDrive, -0.2f, 1.0f);
  111. const float powerAmpSagMap = remapSinh(*parPowerAmpSag, 0.0f, 1.0f);
  112. const float lowCutMap =
  113. remapSinh(remapXY(*parLowCut, -1.0f, 1.0f, 0.0f, 1.23f), 0.5f, 1.0f);
  114. amp_channel[i].set_input_level(*parInputLevel);
  115. amp_channel[i].set_output_level(
  116. *parOutputLevel
  117. + (10.0f + remapXY(preAmpDriveMap, 0.0f, 1.0f, 0.0f, -3.0f)) / 35.0f);
  118. amp_channel[i].set_triode_drive(preAmpDriveMap);
  119. amp_channel[i].set_tetrode_drive(powerAmpDriveMap);
  120. amp_channel[i].set_tonestack_bass(*parTsLow);
  121. amp_channel[i].set_tonestack_mids(*parTsMid);
  122. amp_channel[i].set_tonestack_treble(*parTsHigh);
  123. amp_channel[i].set_tonestack_presence(*parTsPresence);
  124. amp_channel[i].set_tonestack_selection(*parTsSelection);
  125. amp_channel[i].set_triode_num_stages(*parGainStages);
  126. amp_channel[i].set_triode_overhead(*parGainOverhead);
  127. amp_channel[i].set_cabinet_on((*parCabOnOff > 0.5f) ? true : false);
  128. amp_channel[i].set_cabinet_brightness(
  129. remapSided(*parCabBrightness, -0.6f, +0.6f));
  130. amp_channel[i].set_cabinet_distance(*parCabDistance);
  131. // full dynamic when knob is at 0.0
  132. amp_channel[i].set_cabinet_dynamic(
  133. remapXY(*parCabDynamic, -1.0f, 0.0f, -1.0f, +1.0f));
  134. // move the dynamic level down over the dynamic knob range
  135. amp_channel[i].set_cabinet_dynamic_level(-1.0f * *parCabDynamic);
  136. amp_channel[i].set_triode_hp_freq(remapSided(lowCutMap, -1.0f, +0.75f));
  137. // amp_channel[i].set_tetrode_hp_freq(remapSided(lowCutMap, -1.0f, +0.75f));
  138. const float minPreAmpTight =
  139. remapXY(preAmpDriveMap, -0.5f, +1.0f, -1.0f, 0.0f);
  140. const float adjPreAmpTight =
  141. remapRange(*parPreAmpTight, minPreAmpTight, +1.0f);
  142. amp_channel[i].set_triode_grid_tau(
  143. remapSided(adjPreAmpTight * -1.0f, -0.5f, +0.1f));
  144. amp_channel[i].set_triode_grid_ratio(
  145. remapSided(adjPreAmpTight * -1.0f, -1.0f, +0.1f));
  146. amp_channel[i].set_triode_plate_bias(
  147. remapSided(adjPreAmpTight, -1.0f, +0.5f));
  148. amp_channel[i].set_triode_plate_comp_ratio(
  149. remapSided(adjPreAmpTight, -1.0f, +0.0f));
  150. amp_channel[i].set_triode_grid_level(
  151. remapSided(*parPreAmpGrit * -1.0f, -0.2f, +3.0f));
  152. amp_channel[i].set_triode_grid_clip(
  153. remapSided(*parPreAmpGrit * -1.0f, -1.0f, +4.0f));
  154. amp_channel[i].set_triode_plate_comp_level(
  155. remapSided(*parPreAmpGrit * +1.0f, -0.0f, +1.0f));
  156. amp_channel[i].set_triode_plate_comp_offset(
  157. remapSided(*parPreAmpGrit * -1.0f, -0.0f, +5.0f));
  158. amp_channel[i].set_tetrode_grid_tau(
  159. remapSided(*parPowerAmpTight * -1.0f, -1.0f, +1.0f));
  160. amp_channel[i].set_tetrode_grid_ratio(
  161. remapSided(*parPowerAmpTight * -1.0f, -1.0f, +0.1f));
  162. amp_channel[i].set_tetrode_plate_comp_depth(
  163. remapSided(*parPowerAmpTight * -1.0f, -0.5f, +0.0f));
  164. amp_channel[i].set_tetrode_plate_sag_tau(
  165. remapSided(*parPowerAmpTight * -1.0f, -1.0f, +1.0f));
  166. amp_channel[i].set_tetrode_plate_sag_depth(
  167. powerAmpSagMap +
  168. // shift the depth higher at low drive to get some audible effect when
  169. // not much of signal is over clip, and lower at high drive to avoid
  170. // just hacking away the signal with a constant db offset
  171. remapXY(powerAmpDriveMap, -1.0f, 1.0f, 1.0f, -1.0f));
  172. amp_channel[i].set_tetrode_plate_sag_ratio(*parPowerAmpSagRatio);
  173. amp_channel[i].set_tetrode_plate_sag_onset(powerAmpSagMap);
  174. amp_channel[i].set_tetrode_plate_sag_factor(
  175. amp_channel[i].get_tetrode_drive());
  176. amp_channel[i].set_tetrode_plate_sag_toggle(
  177. powerAmpSagMap < -0.99f ? -1.0f : 1.0f);
  178. }
  179. }
  180. const String SwankyAmpAudioProcessor::getName() const
  181. {
  182. return JucePlugin_Name;
  183. }
  184. bool SwankyAmpAudioProcessor::acceptsMidi() const
  185. {
  186. #if JucePlugin_WantsMidiInput
  187. return true;
  188. #else
  189. return false;
  190. #endif
  191. }
  192. bool SwankyAmpAudioProcessor::producesMidi() const
  193. {
  194. #if JucePlugin_ProducesMidiOutput
  195. return true;
  196. #else
  197. return false;
  198. #endif
  199. }
  200. bool SwankyAmpAudioProcessor::isMidiEffect() const
  201. {
  202. #if JucePlugin_IsMidiEffect
  203. return true;
  204. #else
  205. return false;
  206. #endif
  207. }
  208. double SwankyAmpAudioProcessor::getTailLengthSeconds() const { return 0.0; }
  209. int SwankyAmpAudioProcessor::getNumPrograms() { return 1; }
  210. int SwankyAmpAudioProcessor::getCurrentProgram() { return 0; }
  211. void SwankyAmpAudioProcessor::setCurrentProgram(int index)
  212. {
  213. ignoreUnused(index);
  214. }
  215. const String SwankyAmpAudioProcessor::getProgramName(int index)
  216. {
  217. ignoreUnused(index);
  218. return {};
  219. }
  220. void SwankyAmpAudioProcessor::changeProgramName(
  221. int index, const String& newName)
  222. {
  223. ignoreUnused(index, newName);
  224. }
  225. void SwankyAmpAudioProcessor::prepareToPlay(
  226. double sampleRate, int samplesPerBlock)
  227. {
  228. ignoreUnused(samplesPerBlock);
  229. // Use this method as the place to do any pre-playback
  230. // initialisation that you need..
  231. for (int i = 0; i < 2; i++) amp_channel[i].prepare(jmax(1, (int)sampleRate));
  232. }
  233. void SwankyAmpAudioProcessor::releaseResources()
  234. {
  235. // When playback stops, you can use this as an opportunity to free up any
  236. // spare memory, etc.
  237. for (int i = 0; i < 2; i++) amp_channel[i].reset();
  238. }
  239. #ifndef JucePlugin_PreferredChannelConfigurations
  240. bool SwankyAmpAudioProcessor::isBusesLayoutSupported(
  241. const BusesLayout& layouts) const
  242. {
  243. #if JucePlugin_IsMidiEffect
  244. ignoreUnused(layouts);
  245. return true;
  246. #else
  247. // outputs must be mono or stereo
  248. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
  249. && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
  250. return false;
  251. // inputs must be mono or stereo
  252. if (layouts.getMainInputChannelSet() != AudioChannelSet::mono()
  253. && layouts.getMainInputChannelSet() != AudioChannelSet::stereo())
  254. return false;
  255. // if input is stereo, output must be stereo
  256. if (layouts.getMainInputChannelSet() == AudioChannelSet::stereo()
  257. && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
  258. return false;
  259. // This checks if the input layout matches the output layout
  260. #if !JucePlugin_IsSynth
  261. if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
  262. return false;
  263. #endif
  264. return true;
  265. #endif
  266. }
  267. #endif
  268. void SwankyAmpAudioProcessor::processBlock(
  269. AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
  270. {
  271. ignoreUnused(midiMessages);
  272. if (buffer.getNumSamples() <= 0) return;
  273. ScopedNoDenormals noDenormals;
  274. auto totalNumInputChannels = getTotalNumInputChannels();
  275. auto totalNumOutputChannels = getTotalNumOutputChannels();
  276. // copy plugin parameter values into the amp object
  277. setAmpParameters();
  278. // In case we have more outputs than inputs, this code clears any output
  279. // channels that didn't contain input data, (because these aren't
  280. // guaranteed to be empty - they may contain garbage).
  281. // This is here to avoid people getting screaming feedback
  282. // when they first compile a plugin, but obviously you don't need to keep
  283. // this code if your algorithm always overwrites all the output channels.
  284. for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
  285. buffer.clear(i, 0, buffer.getNumSamples());
  286. const auto numSamples = buffer.getNumSamples();
  287. for (int ichannel = 0; ichannel < jmin(totalNumInputChannels, 2); ichannel++)
  288. {
  289. auto inLevel = buffer.getMagnitude(ichannel, 0, numSamples);
  290. // convert to decibels and add the input level which ranges from -35 to +35
  291. inLevel = 20 * log10f(inLevel) + (*parInputLevel * 35);
  292. if (meterListenersIn[ichannel] != nullptr)
  293. meterListenersIn[ichannel]->update(inLevel);
  294. if (ichannel == 0 && totalNumInputChannels < 2
  295. && meterListenersIn[1] != nullptr)
  296. meterListenersIn[1]->update(inLevel);
  297. }
  298. // mono to mono: run the amp once
  299. if (totalNumInputChannels == 1 && totalNumOutputChannels == 1)
  300. {
  301. float* amp_buffer = buffer.getWritePointer(0);
  302. amp_channel[0].process(buffer.getNumSamples(), &amp_buffer);
  303. }
  304. // mono to stereo: run the amp once, copy the result
  305. else if (totalNumInputChannels == 1 && totalNumOutputChannels == 2)
  306. {
  307. float* amp_buffer = buffer.getWritePointer(0);
  308. amp_channel[0].process(buffer.getNumSamples(), &amp_buffer);
  309. float* amp_buffer_other = buffer.getWritePointer(1);
  310. std::memcpy(
  311. (void*)amp_buffer_other, (void*)amp_buffer, numSamples * sizeof(float));
  312. }
  313. // stereo to stereo: run the amp twice
  314. else if (totalNumInputChannels == 2 && totalNumOutputChannels == 2)
  315. {
  316. for (int i = 0; i < 2; i++)
  317. {
  318. float* amp_buffer = buffer.getWritePointer(i);
  319. amp_channel[i].process(buffer.getNumSamples(), &amp_buffer);
  320. }
  321. }
  322. for (int ichannel = 0; ichannel < jmin(totalNumOutputChannels, 2); ichannel++)
  323. {
  324. auto outLevel = buffer.getMagnitude(ichannel, 0, numSamples);
  325. // note: the output level parameter is already factored into the buffer
  326. outLevel = 20 * log10f(outLevel);
  327. if (meterListenersOut[ichannel] != nullptr)
  328. meterListenersOut[ichannel]->update(outLevel);
  329. if (ichannel == 0 && totalNumOutputChannels < 2
  330. && meterListenersOut[1] != nullptr)
  331. meterListenersOut[1]->update(outLevel);
  332. }
  333. if (numBurnIn > 0)
  334. {
  335. const int numBurnInUsed = jmin(numBurnIn.load(), buffer.getNumSamples());
  336. buffer.applyGain(0, numBurnInUsed, 0.0f);
  337. numBurnIn -= numBurnInUsed;
  338. }
  339. }
  340. #if ! JUCE_AUDIOPROCESSOR_NO_GUI
  341. bool SwankyAmpAudioProcessor::hasEditor() const
  342. {
  343. return true; // (change this to false if you choose to not supply an editor)
  344. }
  345. AudioProcessorEditor* SwankyAmpAudioProcessor::createEditor()
  346. {
  347. return new SwankyAmpAudioProcessorEditor(*this, parameters);
  348. }
  349. #endif
  350. void SwankyAmpAudioProcessor::setPresetText(const String& text)
  351. {
  352. storedPresetText = text;
  353. }
  354. const String& SwankyAmpAudioProcessor::getPresetText() const
  355. {
  356. return storedPresetText;
  357. }
  358. void SwankyAmpAudioProcessor::getStateInformation(MemoryBlock& destData)
  359. {
  360. auto state = parameters.copyState();
  361. std::unique_ptr<XmlElement> xml(state.createXml());
  362. const String presetText = getPresetText();
  363. if (presetText.isNotEmpty()) { xml->setAttribute("presetName", presetText); }
  364. copyXmlToBinary(*xml, destData);
  365. }
  366. void SwankyAmpAudioProcessor::setStateInformation(
  367. const void* data, int sizeInBytes)
  368. {
  369. std::unique_ptr<XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
  370. if (xmlState.get() != nullptr)
  371. {
  372. if (xmlState->hasTagName(parameters.state.getType()))
  373. {
  374. if (xmlState->hasAttribute("presetName"))
  375. {
  376. setStateInformation(
  377. xmlState, xmlState->getStringAttribute("presetName"), true);
  378. }
  379. else
  380. {
  381. setStateInformation(xmlState, "", true);
  382. }
  383. }
  384. }
  385. }
  386. void SwankyAmpAudioProcessor::setStateInformation(
  387. const std::unique_ptr<XmlElement>& state,
  388. const String& presetText,
  389. bool useAll)
  390. {
  391. setPresetText(presetText);
  392. std::unordered_map<String, double> values;
  393. if (state != nullptr) values = mapParameterValues(state);
  394. for (const auto& id : parameterIds)
  395. {
  396. if (!useAll && (id == "idInputLevel" || id == "idCabOnOff")) continue;
  397. auto parameter = parameters.getParameter(id);
  398. if (parameter == nullptr) continue;
  399. if (values.find(id) == values.end())
  400. parameter->setValueNotifyingHost(parameter->getDefaultValue());
  401. else
  402. parameter->setValueNotifyingHost(
  403. parameter->convertTo0to1((float)values[id]));
  404. }
  405. // clear the amp state so that buffered values don't decay too slowly with new
  406. // parameters
  407. for (int i = 0; i < 2; i++) amp_channel[i].reset();
  408. notifyStateChanged = true;
  409. numBurnIn = burnInLength;
  410. }
  411. AudioProcessor* JUCE_CALLTYPE createPluginFilter()
  412. {
  413. return new SwankyAmpAudioProcessor();
  414. }