Skip to content
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@
*.app

.vscode/

docs/_build/
*.DS_Store
43 changes: 27 additions & 16 deletions NAM/get_dsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,8 @@

namespace nam
{
struct Version
{
int major;
int minor;
int patch;
};

Version ParseVersion(const std::string& versionStr)
{
Version version;

// Split the version string into major, minor, and patch components
std::stringstream ss(versionStr);
std::string majorStr, minorStr, patchStr;
Expand All @@ -33,11 +24,14 @@ Version ParseVersion(const std::string& versionStr)
std::getline(ss, patchStr);

// Parse the components as integers and assign them to the version struct
int major;
int minor;
int patch;
try
{
version.major = std::stoi(majorStr);
version.minor = std::stoi(minorStr);
version.patch = std::stoi(patchStr);
major = std::stoi(majorStr);
minor = std::stoi(minorStr);
patch = std::stoi(patchStr);
}
catch (const std::invalid_argument&)
{
Expand All @@ -49,24 +43,41 @@ Version ParseVersion(const std::string& versionStr)
}

// Validate the semver components
if (version.major < 0 || version.minor < 0 || version.patch < 0)
if (major < 0 || minor < 0 || patch < 0)
{
throw std::invalid_argument("Negative version component: " + versionStr);
}
return version;
return Version(major, minor, patch);
}

void verify_config_version(const std::string versionStr)
{
Version version = ParseVersion(versionStr);
if (version.major != 0 || version.minor != 5)
Version currentVersion = ParseVersion(LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION);
Version earliestSupportedVersion = ParseVersion(EARLIEST_SUPPORTED_NAM_FILE_VERSION);

if (version < earliestSupportedVersion)
{
std::stringstream ss;
ss << "Model config is an unsupported version " << versionStr
ss << "Model config is an unsupported version " << versionStr << ". The earliest supported version is "
<< earliestSupportedVersion.toString()
<< ". Try either converting the model to a more recent version, or "
"update your version of the NAM plugin.";
throw std::runtime_error(ss.str());
}
if (version.major > currentVersion.major || version.minor > currentVersion.minor)
{
std::stringstream ss;
ss << "Model config is an unsupported version " << versionStr << ". The latest fully-supported version is "
<< currentVersion.toString();
throw std::runtime_error(ss.str());
}
else if (version.major == 0 && version.minor == 6 && version.patch > 0)
{
std::cerr << "Model config is a partially-supported version " << versionStr
<< ". The latest fully-supported version is " << currentVersion.toString()
<< ". Continuing with partial support." << std::endl;
}
}

std::vector<float> GetWeights(nlohmann::json const& j)
Expand Down
39 changes: 39 additions & 0 deletions NAM/get_dsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,45 @@

namespace nam
{
class Version
{
public:
Version(int major, int minor, int patch)
: major(major)
, minor(minor)
, patch(patch)
{
}

std::string toString() const
{
return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch);
}

bool operator>(const Version& other) const
{
return major > other.major
|| (major == other.major && (minor > other.minor || (minor == other.minor && patch > other.patch)));
}

bool operator<(const Version& other) const
{
return major < other.major
|| (major == other.major && (minor < other.minor || (minor == other.minor && patch < other.patch)));
}

int major;
int minor;
int patch;
};

Version ParseVersion(const std::string& versionStr);

void verify_config_version(const std::string versionStr);

const std::string LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION = "0.6.0";
const std::string EARLIEST_SUPPORTED_NAM_FILE_VERSION = "0.5.0";

/// \brief Get NAM from a .nam file at the provided location
/// \param config_filename Path to the .nam model file
/// \return Unique pointer to a DSP object
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Welcome to the NeuralAmpModelerCore documentation. This library provides a core
:caption: Contents:

wavenet_walkthrough
nam_file_version
api/index

Overview
Expand Down
33 changes: 33 additions & 0 deletions docs/nam_file_version.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
``.nam`` file versions
======================

As more features are added to NAM, the version of the ``.nam`` file format will be incremented.
The general rules try to follow semantic versioning.
That means that any changes to file contents where an older version of
NeuralAmpModelerCore is either not able to understand the contents or might
misunderstand them (e.g. new fields that old code is not looking for) will trigger a
version bump communicating a breaking change (e.g. minor version while pre-v1.0.0;
later, a major version bump).
Improvements where the model will be loaded correctly, but possibly with some incomplete
functionality will trigger a version bump communicating a non-breaking change
(e.g. minor version or patch pre-v1.0.0).

Version history
---------------

The following table shows which versions of NeuralAmpModelerCore support which model file versions:

.. list-table:: Core Version Support Matrix
:header-rows: 1
:widths: 30 70

* - Core Version
- Latest fully-supported ``.nam`` file version
* - 0.0.0
- 0.5.1
* - 0.2.0
- 0.5.2
* - 0.3.0
- 0.5.3
* - 0.4.0
- 0.6.0
2 changes: 1 addition & 1 deletion example_models/wavenet_a2_max.nam
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"This model is meant as a 'test case' to contain all of the new features that are being considered for A2.",
"It doesn't have slimmability."
],
"version": "0.5.4",
"version": "0.6.0",
"metadata": {
"date": {
"year": 2026,
Expand Down
4 changes: 2 additions & 2 deletions example_models/wavenet_condition_dsp.nam
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.5.4",
"version": "0.6.0",
"metadata": {
"date": {
"year": 2026,
Expand All @@ -17,7 +17,7 @@
"architecture": "WaveNet",
"config": {
"condition_dsp": {
"version": "0.5.4",
"version": "0.6.0",
"metadata": {
"date": {
"year": 2026,
Expand Down
2 changes: 1 addition & 1 deletion tools/create_wavenet.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def generate_weights(weight_count: int, seed: int = None) -> List[float]:

def create_wavenet_nam(config: Dict[str, Any], output_path: Path,
seed: int = None, sample_rate: int = 48000,
version: str = "0.5.4") -> None:
version: str = "0.6.0") -> None:
"""
Create a WaveNet .nam file with the given configuration and random weights.

Expand Down
3 changes: 3 additions & 0 deletions tools/run_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ int main()
test_get_dsp::test_gets_output_level();
test_get_dsp::test_null_input_level();
test_get_dsp::test_null_output_level();
test_get_dsp::test_version_patch_one_beyond_supported();
test_get_dsp::test_version_minor_one_beyond_supported();
test_get_dsp::test_version_too_early();

// Finally, some end-to-end tests.
test_get_dsp::test_load_and_process_nam_files();
Expand Down
74 changes: 74 additions & 0 deletions tools/test/test_get_dsp.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include <cassert>
#include <cmath>
#include <filesystem>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>

Expand Down Expand Up @@ -158,4 +160,76 @@ void test_load_and_process_nam_files()
process_buffers(dsp.get(), num_buffers, buffer_size);
}
}

// Helper function to create a config string with a specific version
std::string createConfigWithVersion(const std::string& version)
{
nlohmann::json j = nlohmann::json::parse(basicConfigStr);
j["version"] = version;
return j.dump();
}

void test_version_patch_one_beyond_supported()
{
// Test that a .nam file with version one patch beyond the latest fully supported
// can still be loaded
nam::Version latestVersion = nam::ParseVersion(nam::LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION);
latestVersion.patch++;
const std::string configStr = createConfigWithVersion(latestVersion.toString());
nam::dspData config = _GetConfig(configStr);

// Suppress the warning message that gets printed to std::cerr
std::streambuf* originalCerr = std::cerr.rdbuf();
std::ostringstream nullStream;
std::cerr.rdbuf(nullStream.rdbuf());

// Should succeed (with a warning, but we suppress it)
std::unique_ptr<nam::DSP> dsp = get_dsp(config);
assert(dsp != nullptr);

// Restore original cerr
std::cerr.rdbuf(originalCerr);
}

void test_version_minor_one_beyond_supported()
{
// Test that a .nam file with version one minor beyond the latest fully supported
// cannot be loaded
nam::Version latestVersion = nam::ParseVersion(nam::LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION);
latestVersion.minor++;
latestVersion.patch = 0; // Reset patch when incrementing minor
const std::string configStr = createConfigWithVersion(latestVersion.toString());
nam::dspData config = _GetConfig(configStr);

bool threw = false;
try
{
std::unique_ptr<nam::DSP> dsp = get_dsp(config);
}
catch (const std::runtime_error&)
{
threw = true;
}
assert(threw);
}

void test_version_too_early()
{
// Test that a .nam file with version too early (before earliest supported) cannot be loaded
nam::Version earliestVersion = nam::ParseVersion(nam::EARLIEST_SUPPORTED_NAM_FILE_VERSION);
earliestVersion.minor--; // Decrement minor to get a version before earliest supported
const std::string configStr = createConfigWithVersion(earliestVersion.toString());
nam::dspData config = _GetConfig(configStr);

bool threw = false;
try
{
std::unique_ptr<nam::DSP> dsp = get_dsp(config);
}
catch (const std::runtime_error&)
{
threw = true;
}
assert(threw);
}
}; // namespace test_get_dsp