Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GuitarML sample rate correction filter #267

Merged
merged 6 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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