Skip to content

Conversation

@damiangr
Copy link

@damiangr damiangr commented Nov 5, 2025

Description

This PR adds the ability to embed NAM model files (.nam) and impulse response files (.wav) directly into DAW session/project files, making sessions fully portable and self-contained.

Features

  • Automatic Embedding: When saving a session, NAM models and IR files are encoded and embedded in the plugin state
  • Automatic Extraction: When loading a session, embedded files are extracted to temporary files and loaded automatically
  • Backward Compatible: Sessions without embedded data continue to work with file paths as before
  • Cross-Platform: Designed to work on Windows, macOS, and Linux

Benefits

  • Share sessions without worrying about missing model/IR files
  • Old projects remain playable even if original files are moved/deleted
  • Team members can open sessions without manual file management

Implementation

  • Modified SerializeState() to embed file contents in session data
  • Modified UnserializeState() to detect and extract embedded files automatically
  • Added _StageModelFromData() and _StageIRFromData() helper methods for loading from memory
  • Maintain backward compatibility with file path references
  • Update AudioDSPTools submodule to include SIGFPE fix (depends on Fix SIGFPE crash - Initialize mRawAudioSampleRate and validate sample… AudioDSPTools#24)

PR Checklist

  • Did you format your code using format.bash?
  • Does the VST3 plugin pass all of the unit tests in the VST3PluginTestHost?
    • Windows - Validated with pluginval (strictness level 5) - SUCCESS
    • macOS - Unable to test: VMware Sonoma 14.7.2 + Xcode 16.2 environment has NanoVG/Metal incompatibility issues. Recommend testing via GitHub Actions CI which uses Xcode 15.x.
  • Does your PR add, remove, or rename any plugin parameters? No
  • Does your PR add or remove any graphical assets? No

Testing Notes

Windows (Validated ✅)

  • Build: Visual Studio 2022, x64 Release
  • Validation: pluginval v1.x, strictness level 5
  • Results: All tests passed (audio processing, automation, state save/restore, editor, bus configuration)

macOS (Limited Testing ⚠️)

Note: Local macOS testing was not possible due to environmental constraints:

  • Test environment: VMware Sonoma 14.7.2 + Xcode 16.2
  • Issue: NanoVG/Metal incompatibility causing image loading crashes (not related to this PR)
  • Official v0.7.13 release also crashes in this environment
  • Recommendation: Please test via GitHub Actions CI, which uses Xcode 15.x and should work correctly

The embedding code itself is platform-independent and uses standard C++ file I/O. The implementation should work on macOS, but comprehensive validation via CI is recommended.

Files Changed

NeuralAmpModeler.cpp: Embedding/extraction logic and helper methods (+260 lines)
NeuralAmpModeler.h: Added mNAMData and mIRData member variables (+8 lines)
Unserialization.cpp: Added embedded data extraction with versioned functions for 0.7.13 (+134 lines)

@damiangr damiangr force-pushed the feature/embed-clean-v2 branch from ba35a5e to 974ec7d Compare December 13, 2025 21:03
@damiangr
Copy link
Author

macOS version was tested with Fix missing path separator in prepare_resources-mac.py and it seems that it works.

@sdatkinson
Copy link
Owner

Thanks for your patience with my review.

High-level question: what happens when the required files are gone (so the model is loaded from the serialized state), then you try to use the arrows? It feels like this is a new dimension that has to be handled. I don't really like the idea of being responsible for it in this repo.

A high-level criticism of the benefits of this: It seems to me that this gives half of what needs to be a whole solution to make sense--all of the cited benefits may be solved for the model files, but they remain unsolved for the main part of a DAW session--the audio (track) files? I don't really agree that a DAW session can truly be called "portable" if these two different file inputs aren't both handled.

I'll look at the lines and provide detailed feedback there. [There are a considerable number of changes in this PR.]

Copy link
Owner

@sdatkinson sdatkinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My overall impression after reading the code is that this is too complicated for what it brings.

The changes below are probably good pieces of advice if you want to use them to improve your fork, but it's sort of surprising how many different places are touched in this PR. Unexpected things may be happening--for example, the sample rate validation.

If that's a problem in the existing code aside from the feature this is trying to introduce, then it'd be worth it to PR those fixes separately (i.e. validate inputs and demonstrate that those validations are failing). But if this is introducing invalid usage of those existing pieces of code, then it's not immediately clear to me how that's happening, and that feels risky for the stability of the plugin.

You definitely want to re-do the unserialization code to follow the existing pattern because that part definitely isn't following the pattern so far and risks breaking existing projects--which would be ironic, given the goal of this PR 🙂

{
const auto irData = mStagedIR->GetData();
mStagedIR = std::make_unique<dsp::ImpulseResponse>(irData, sampleRate);
// IR is in error state - discard it
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this wise? Feels wrong.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unnecessary safety checks

std::unique_ptr<nam::DSP> model = nam::get_dsp(dspPath);
std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move(model), GetSampleRate());
temp->Reset(GetSampleRate(), GetBlockSize());
std::unique_ptr<ResamplingNAM> temp = std::make_unique<ResamplingNAM>(std::move(model), sampleRate);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there an issue here? Or is this function being called from a different state from before?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, there was an issue - sample rate could be 0 during unserialization causing division by zero.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checked and removed - reverted to use [GetSampleRate()] directly like upstream. No issue, this was an unnecessary change.

mIRPath.Set(static_cast<std::string>(config["IRPath"]).c_str());

if (mNAMPath.GetLength())
// NEW: Prefer embedded data over file paths
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd like the opposite, but I'm willing to hear your rationale for this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. It will check for file first and revert to embedded data if not found.


mNAMPath.Set(static_cast<std::string>(config["NAMPath"]).c_str());
mIRPath.Set(static_cast<std::string>(config["IRPath"]).c_str());

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's going on here is being done wrong. Since you're defining new logic on how to deserialize for the next version, you should be adding new functions for v0.7.14 similar to how the ones for 0.7.12 were added.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created proper _GetConfigFrom_0_7_13() and _UpdateConfigFrom_0_7_13().

@damiangr damiangr requested a review from sdatkinson December 14, 2025 14:28
@damiangr damiangr marked this pull request as draft December 14, 2025 14:46
@damiangr damiangr force-pushed the feature/embed-clean-v2 branch 2 times, most recently from cac9298 to 1233630 Compare December 14, 2025 16:47
@damiangr
Copy link
Author

rows? It feel
Thank you for checking !
Regarding the arrows, they will now work as follows:
If the file is not found, but the directory exists, the arrows will work as usual to switch to the next file within the directory.
If the directory does not exist, the arrows do nothing, but the saved embedded files are loaded.

@damiangr damiangr force-pushed the feature/embed-clean-v2 branch from dc05b69 to 4996eaf Compare December 14, 2025 17:48
This enables DAW sessions to be fully portable by storing the actual
NAM model and IR file data in the session, not just file paths.

Changes:
- SerializeState: Embed NAM/IR file data in session chunk
- UnserializeState: Load from embedded data if file path not found
- New _StageModelFromData/_StageIRFromData functions
- Version bump to 0.7.13 with backward-compatible serialization

Behavior:
- Prefers loading from file path if available (for easy model updates)
- Falls back to embedded data if file is missing (for portability)
- Fully backward compatible with older session formats
@damiangr damiangr force-pushed the feature/embed-clean-v2 branch from 4996eaf to e3a830d Compare December 14, 2025 18:21
@damiangr damiangr marked this pull request as ready for review December 14, 2025 18:27
@damiangr
Copy link
Author

All review feedback addressed:

Removed test-release.yml and prepare_resources-mac.py changes
Removed unnecessary safety checks from _ResetModelAndIR
Reverted _StageModel to use GetSampleRate() directly
Removed mutable - files read at load time now
Created proper versioned functions (_GetConfigFrom_0_7_13, etc.)
Changed to prefer file path over embedded data
Using version 0.7.13

damiangr added a commit to damiangr/NeuralAmpModelerPlugin that referenced this pull request Dec 14, 2025
…- Store NAM model and IR file contents in plugin state for full preset recall - Add right-click context menu to save embedded data back to disk - Support loading embedded data from serialized state - Maintain backward compatibility with existing presets Changes in submodules: - AudioDSPTools: Add IR memory loading and WAV export - NeuralAmpModelerCore: Add string-based NAM model loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants