Skip to content

Commit

Permalink
GuitarML sample rate correction filter (#267)
Browse files Browse the repository at this point in the history
* GuitarML: Adding optional sample rate correction filter

* Try to fix file chooser bug on Linux

* Adjust makeup filter tuning

* Apply clang-format

* Fix ambiguous chowdsp::Version

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
jatinchowdhury18 and github-actions[bot] authored Mar 29, 2023
1 parent 920c4c2 commit c6104e2
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 3 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "Minimum OS X deployment ta
if(WIN32)
set(CMAKE_SYSTEM_VERSION 7.1 CACHE STRING INTERNAL FORCE) # Windows SDK for Windows 7 and up
endif()
project(BYOD VERSION 1.1.3)
project(BYOD VERSION 1.1.4)

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CMAKE_CXX_STANDARD 17)
Expand Down
39 changes: 39 additions & 0 deletions sim/GuitarML/filter_design.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import numpy as np
from scipy.io import wavfile
import scipy.signal as signal
import matplotlib.pyplot as plt
import audio_dspy as adsp

def freqSmooth(x, sm=1.0/24.0):
s = sm if sm > 1.0 else np.sqrt(2.0**sm)
N = len(x)
y = np.zeros_like(x)
for i in range(N):
i1 = max(int(np.floor(i/s)), 0)
i2 = min(int(np.floor(i*s)+1), N-1)
if i2 > i1:
y[i] = np.mean(x[i1:i2])
return y

def plot_fft(sig, fs, gain=1):
b,a = adsp.design_highshelf(1000, 0.7071, gain, fs)
xx = signal.lfilter(b, a, sig)

freqs = np.fft.rfftfreq(len(xx), 1.0 / fs)
fft = freqSmooth(np.fft.rfft(xx), 1.0)
plt.semilogx(freqs, 20 * np.log10(np.abs(fft)))

fs, x = wavfile.read("noise_48k.wav")
print(fs)
plot_fft(x, fs)

fs, x = wavfile.read("noise_96k.wav")
print(fs)
plot_fft(x, fs) #, 1 / 2)

fs, x = wavfile.read("noise_192k.wav")
print(fs)
plot_fft(x, fs) #, 1 / 4)

plt.xlim(20, 28000)
plt.show()
1 change: 1 addition & 0 deletions src/headless/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ target_sources(BYOD_headless PRIVATE
PresetResaver.cpp
PresetSaveLoadTime.cpp
ScreenshotGenerator.cpp
GuitarMLFilterDesigner.cpp

tests/ParameterSmoothTest.cpp
tests/PreBufferTest.cpp
Expand Down
54 changes: 54 additions & 0 deletions src/headless/GuitarMLFilterDesigner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "GuitarMLFilterDesigner.h"
#include "../BYOD.h"

GuitarMLFilterDesigner::GuitarMLFilterDesigner()
{
this->commandOption = "--guitarml-filter";
this->argumentDescription = "--guitarml-filter";
this->shortDescription = "Generates files for GuitarML filter design";
this->longDescription = "";
this->command = [=] (const ArgumentList& args)
{ generateFiles (args); };
}

void GuitarMLFilterDesigner::generateFiles ([[maybe_unused]] const ArgumentList& args)
{
static constexpr int bufferSize = 8192;
chowdsp::AudioFileSaveLoadHelper saveLoadHelper;

chowdsp::Noise<float> noiseGenerator;
noiseGenerator.setGainDecibels (0.0f);
noiseGenerator.setSeed (123);
noiseGenerator.setNoiseType (chowdsp::Noise<float>::NoiseType::Normal);

const auto factory = ProcessorStore::getStoreMap().at ("GuitarML");
const auto processFile = [&factory,
&saveLoadHelper,
&noiseGenerator] (const juce::String& filePath, double sampleRate)
{
const auto file = File { filePath };
std::cout << "Rendering file " << file.getFullPathName() << " at sample rate " << sampleRate << std::endl;

noiseGenerator.prepare ({ sampleRate, (uint32_t) bufferSize, 1 });

auto processor = factory (nullptr);
processor->getParameters()[3]->setValueNotifyingHost (1.0f);
processor->prepareProcessing (sampleRate, bufferSize);

AudioBuffer<float> buffer { 1, bufferSize };
buffer.clear();
auto&& block = dsp::AudioBlock<float> { buffer };

noiseGenerator.process (dsp::ProcessContextReplacing<float> { block });
buffer.clear();
noiseGenerator.process (dsp::ProcessContextReplacing<float> { block });
processor->processAudioBlock (buffer);

file.deleteFile();
saveLoadHelper.saveBufferToFile (filePath, buffer, sampleRate);
};

processFile ("noise_48k.wav", 48000.0);
processFile ("noise_96k.wav", 96000.0);
processFile ("noise_192k.wav", 192000.0);
}
15 changes: 15 additions & 0 deletions src/headless/GuitarMLFilterDesigner.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include "../pch.h"

class GuitarMLFilterDesigner : public ConsoleApplication::Command
{
public:
GuitarMLFilterDesigner();

private:
/** Generates files for plotting GuitarML frequency content */
static void generateFiles (const ArgumentList& args);

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GuitarMLFilterDesigner)
};
2 changes: 2 additions & 0 deletions src/headless/main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "GuitarMLFilterDesigner.h"
#include "PresetResaver.h"
#include "PresetSaveLoadTime.h"
#include "ScreenshotGenerator.h"
Expand Down Expand Up @@ -29,6 +30,7 @@ int main (int argc, char* argv[])
app.addCommand (ScreenshotGenerator());
app.addCommand (PresetResaver());
app.addCommand (PresetSaveLoadTime());
app.addCommand (GuitarMLFilterDesigner());
app.addCommand (UnitTests());

// ArgumentList args { "--unit-tests", "--all" };
Expand Down
27 changes: 26 additions & 1 deletion src/processors/drive/GuitarMLAmp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const auto numBuiltInModels = (int) guitarMLModelResources.size();
const String modelTag = "model";
const String gainTag = "gain";
const String conditionTag = "condition";
const String sampleRateCorrFilterTag = "sample_rate_corr_filter";
const String customModelTag = "custom_model";
constexpr std::string_view modelNameTag = "byod_guitarml_model_name";

Expand All @@ -46,6 +47,8 @@ GuitarMLAmp::GuitarMLAmp (UndoManager* um) : BaseProcessor ("GuitarML", createPa
using namespace ParameterHelpers;
loadParameterPointer (gainParam, vts, gainTag);
conditionParam.setParameterHandle (getParameterPointer<chowdsp::FloatParameter*> (vts, conditionTag));
loadParameterPointer (sampleRateCorrectionFilterParam, vts, sampleRateCorrFilterTag);
addPopupMenuParameter (sampleRateCorrFilterTag);

loadModel (0); // load Blues Jr. model by default

Expand All @@ -65,6 +68,7 @@ ParamLayout GuitarMLAmp::createParameterLayout()

createGainDBParameter (params, gainTag, "Gain", -18.0f, 18.0f, 0.0f);
createPercentParameter (params, conditionTag, "Condition", 0.5f);
emplace_param<chowdsp::BoolParameter> (params, sampleRateCorrFilterTag, "Sample Rate Correction Filter", true);

return { params.begin(), params.end() };
}
Expand Down Expand Up @@ -101,6 +105,12 @@ void GuitarMLAmp::loadModelFromJson (const chowdsp::json& modelJson, const Strin
const auto modelSampleRate = modelDataJson.value ("sample_rate", 44100.0);
const auto rnnDelaySamples = jmax (1.0, processSampleRate / modelSampleRate);

sampleRateCorrectionFilter.reset();
sampleRateCorrectionFilter.calcCoefs (8100.0f,
chowdsp::CoefficientCalculators::butterworthQ<float>,
(processSampleRate < modelSampleRate * 1.1) ? 1.0f : 0.25f,
(float) processSampleRate);

if (numInputs == 1 && hiddenSize == 40) // non-conditioned LSMT40
{
SpinLock::ScopedLockType modelChangingLock { modelChangingMutex };
Expand Down Expand Up @@ -220,6 +230,8 @@ void GuitarMLAmp::prepare (double sampleRate, int samplesPerBlock)
inGain.prepare (spec);
inGain.setRampDurationSeconds (0.1);

sampleRateCorrectionFilter.prepare (2);

conditionParam.prepare (sampleRate, samplesPerBlock);
conditionParam.setRampLength (0.05);

Expand Down Expand Up @@ -279,6 +291,11 @@ void GuitarMLAmp::processAudio (AudioBuffer<float>& buffer)
}
}

if (sampleRateCorrectionFilterParam->get())
{
sampleRateCorrectionFilter.processBlock (buffer);
}

buffer.applyGain (normalizationGain);

dcBlocker.processAudio (buffer);
Expand Down Expand Up @@ -306,6 +323,13 @@ void GuitarMLAmp::fromXML (XmlElement* xml, const chowdsp::Version& version, boo
}

BaseProcessor::fromXML (xml, version, loadPosition);

using namespace std::string_view_literals;
if (version < chowdsp::Version { "1.1.4"sv })
{
// Sample rate correction filters were only added in version 1.1.4
sampleRateCorrectionFilterParam->setValueNotifyingHost (0.0f);
}
}

bool GuitarMLAmp::getCustomComponents (OwnedArray<Component>& customComps, chowdsp::HostContextProvider& hcp)
Expand Down Expand Up @@ -404,7 +428,7 @@ bool GuitarMLAmp::getCustomComponents (OwnedArray<Component>& customComps, chowd

onChange = [this, &processor]
{
processor.loadModel (getSelectedItemIndex(), getParentComponent());
processor.loadModel (getSelectedItemIndex(), getTopLevelComponent());
};

this->setName (modelTag + "__box");
Expand All @@ -429,6 +453,7 @@ bool GuitarMLAmp::getCustomComponents (OwnedArray<Component>& customComps, chowd

void GuitarMLAmp::addToPopupMenu (PopupMenu& menu)
{
BaseProcessor::addToPopupMenu (menu);
menu.addItem ("Download more models", []
{ URL { "https://guitarml.com/tonelibrary/tonelib-pro.html" }.launchInDefaultBrowser(); });
menu.addSeparator();
Expand Down
2 changes: 2 additions & 0 deletions src/processors/drive/GuitarMLAmp.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class GuitarMLAmp : public BaseProcessor

chowdsp::FloatParameter* gainParam = nullptr;
chowdsp::SmoothedBufferValue<float> conditionParam;
chowdsp::BoolParameter* sampleRateCorrectionFilterParam = nullptr;
chowdsp::Gain<float> inGain;

SpinLock modelChangingMutex;
Expand All @@ -46,6 +47,7 @@ class GuitarMLAmp : public BaseProcessor

std::array<LSTM40Cond, 2> lstm40CondModels;
std::array<LSTM40NoCond, 2> lstm40NoCondModels;
chowdsp::HighShelfFilter<float> sampleRateCorrectionFilter;

enum class ModelArch
{
Expand Down
2 changes: 1 addition & 1 deletion src/processors/tone/AmpIRs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ bool AmpIRs::getCustomComponents (OwnedArray<Component>& customComps, chowdsp::H
fileItem.isTicked = file == ampIRs.curFile;
fileItem.action = [this, file]
{
ampIRs.loadIRFromStream (file.createInputStream(), getParentComponent());
ampIRs.loadIRFromStream (file.createInputStream(), getTopLevelComponent());
};
menu->addItem (fileItem);

Expand Down

0 comments on commit c6104e2

Please sign in to comment.