DawDreamer supports pickling (serialization) of RenderEngine and all processor types using Python's pickle module. This allows you to save and restore complete audio processing graphs, including all processor state, parameters, automation curves, and MIDI events.
import pickle
import dawdreamer as daw
# Create and configure a RenderEngine
engine = daw.RenderEngine(44100, 512)
# ... configure processors and graph ...
engine.render(duration)
# Serialize to bytes
pickled_bytes = pickle.dumps(engine)
# Deserialize
restored_engine = pickle.loads(pickled_bytes)
# Or save/load from file
with open("my_session.pkl", "wb") as f:
pickle.dump(engine, f)
with open("my_session.pkl", "rb") as f:
restored_engine = pickle.load(f)All processor types support pickling:
- RenderEngine - Complete audio graph with all processors
- PlaybackProcessor - Audio data preserved as numpy arrays
- FaustProcessor - DSP code, parameters, polyphony settings, MIDI, automation
- PluginProcessor - Plugin path, VST state blob, MIDI events
- SamplerProcessor - Sample data, parameters, MIDI events
- OscillatorProcessor - Frequency setting
- FilterProcessor - Type, frequency, Q, gain
- CompressorProcessor - Threshold, ratio, attack, release
- ReverbProcessor - Room size, damping, wet/dry, width, freeze
- PannerProcessor - Panning rule, pan value
- DelayProcessor - Mode, delay time, feedback
- AddProcessor - Gain levels for each input
- Sample rate
- Buffer size
- BPM (single value or automation array)
- PPQN (pulses per quarter note)
- Audio processing graph structure
- All processor instances and their connections
- Unique name
- Sample rate
- Parameter values
- Parameter automation curves (numpy arrays)
- Complete audio data as numpy array (shape: [channels, samples])
- DSP source code string
- Faust library paths
- Compiled state (automatically recompiled on restore)
- Polyphony settings:
num_voices,group_voices,dynamic_voices,release_length - All parameter values and automation curves
- MIDI events (both beat-based and second-based)
- Plugin file path
- Plugin state blob (VST/AU internal state via
getStateInformation()) - Parameter values (restored from plugin state)
- MIDI events (both beat-based and second-based)
- Original sample data (non-upsampled)
- All sampler parameters (attack, decay, sustain, release, etc.)
- MIDI events (both beat-based and second-based)
MIDI events are serialized using a binary format for efficiency:
[Sample Position: 4 bytes, big-endian]
[Message Size: 2 bytes, big-endian]
[Message Data: variable length]
- Sample Position (int32): Sample-accurate timing position
- Message Size (uint16): Number of bytes in MIDI message
- Message Data: Raw MIDI bytes (typically 3 bytes for note on/off)
Each processor with MIDI support maintains two separate buffers:
- myMidiBufferQN: Beat-based events (quarter notes)
- myMidiBufferSec: Second-based events (absolute time)
Both buffers are preserved independently during pickling.
DawDreamer uses nanobind's placement new pattern for reconstruction:
void setPickleState(nb::dict state) {
// Extract parameters from state dictionary
std::string name = nb::cast<std::string>(state["unique_name"]);
// ... extract other parameters ...
// Reconstruct object in-place using placement new
new (this) ProcessorType(name, ...);
// Restore additional state
// ... restore parameters, MIDI, etc. ...
}This pattern is required by nanobind to properly reconstruct C++ objects within the Python object lifecycle.
Automation curves are restored after the audio graph is compiled:
- During pickle: Save automation arrays with processor name and parameter name
- During unpickle: Build processor map during graph restoration
- After graph compilation: Apply saved automation to processors by name
This deferred restoration is necessary because:
- Processors may be reconstructed during unpickling
- Graph compilation creates the final processor instances
- Automation must be applied to the compiled processor instances
Plugins use JUCE's binary state format:
getStateInformation()creates a binary blob of plugin statesetStateInformation()restores plugin from binary blob- This format is plugin-specific and opaque to DawDreamer
- Parameter values are automatically restored from plugin state
- Plugin paths must be valid on the restore system
- VST/AU plugins must be installed at the same paths
- Plugin state blobs are generally portable but plugin-specific
- Cross-platform compatibility depends on plugin implementation
- DSP code is preserved and recompiled on restore
- Faust libraries must be available at restore time
faust_libraries_pathsshould point to valid locations- Compilation may fail if Faust version differs significantly
- Sample rate is preserved and enforced
- Buffer size changes are supported but may affect timing
- Audio data is NOT resampled automatically
- PlaybackProcessor: Audio data is embedded (can be large)
- PluginProcessor: Only plugin path is stored (plugin must exist)
- SamplerProcessor: Sample data is embedded
- No automatic path resolution or file tracking
- Automation arrays are sample-accurate
- Changing sample rate after restore will affect timing
- BPM automation is preserved as-is (no time-stretching)
- MIDI events use sample positions (sample-accurate)
- Beat-based MIDI depends on BPM at restore time
- No MIDI time-stretching or quantization is applied
- Audio processing state (buffers, delay lines) is NOT preserved
- Processors are reset to initial state after restore
- No continuation of reverb tails, delay feedback, etc.
- Graph structure is preserved exactly
- Processor order matters (serialized in graph order)
- Cyclic graphs are not supported (limitation of DawDreamer, not pickle)
- Large audio data can create large pickle files
- PlaybackProcessor and SamplerProcessor embed full audio
- Consider external file storage for large audio datasets
- No compression is applied (use
pickle.HIGHEST_PROTOCOLand compress externally)
- Pickling is not thread-safe
- Do not pickle while rendering
- Ensure exclusive access during serialization
Every processor's state dictionary contains a pickle_version entry
(currently 1, defined as DAWDREAMER_PICKLE_VERSION in
Source/PickleVersion.h). Unpickling requires an exact version match and
raises a RuntimeError otherwise. Future versions may add backward
compatibility checks and migration code for format changes.
Recommendation: Store the DawDreamer version alongside pickled data:
import dawdreamer as daw
data = {
'version': daw.__version__,
'engine': pickle.dumps(engine)
}Always store the DawDreamer version used to create pickles:
metadata = {
'dawdreamer_version': daw.__version__,
'created_date': datetime.now().isoformat(),
'sample_rate': SAMPLE_RATE,
}Always verify restored state:
restored_engine = pickle.loads(pickled_bytes)
# Verify graph structure
assert len(restored_engine.get_audio().shape) == 2
# Test render
restored_engine.render(0.1) # Short test renderStore plugin paths separately for cross-platform support:
# Before pickle
plugin_paths = {
'effect': plugin.plugin_path,
'instrument': instrument.plugin_path
}
# After restore, verify paths exist
if not os.path.exists(plugin_paths['effect']):
# Handle missing pluginFor large projects, consider hybrid approach:
# Option 1: Separate audio storage
audio_data = playback.get_audio()
np.save('audio.npy', audio_data)
# ... pickle without audio data ...
# Option 2: Compressed pickle
import gzip
with gzip.open('session.pkl.gz', 'wb') as f:
pickle.dump(engine, f)Always wrap unpickling in try-except:
try:
engine = pickle.loads(pickled_bytes)
except Exception as e:
print(f"Failed to restore session: {e}")
# Handle error (use backup, notify user, etc.)The test suite (tests/test_pickle.py) includes:
- Round-trip tests for all processor types
- MIDI preservation tests (empty, normal, large buffers)
- Automation curve preservation
- Complex graph structures
- Edge cases and error conditions
Run tests:
pytest tests/test_pickle.py -v- Nanobind Documentation: https://nanobind.readthedocs.io/
- Python Pickle Protocol: https://docs.python.org/3/library/pickle.html
- JUCE VST State: Uses
AudioProcessor::getStateInformation() - MIDI Format: Based on JUCE
MidiBufferiteration
Potential improvements for future versions:
- Explicit versioning - Add version field to all pickle states
- Compression - Optional built-in compression for audio data
- Incremental updates - Patch format for parameter changes
- Validation - Checksum/hash verification of restored state
- Migration - Automatic format migration for older versions
- Streaming - Support for lazy loading of large audio data
- Metadata - Standardized metadata fields (author, date, description)
When adding new processors or extending pickle support:
- Implement
getPickleState()returningnb::dict - Implement
setPickleState(nb::dict)using placement new - Add comprehensive tests to
test_pickle.py - Update this documentation
- Consider backward compatibility impact
See existing processors for implementation patterns.