Skip to content

Commit 3c2d16e

Browse files
committed
pickle MIDI events and PluginProcessor
1 parent 7772cd2 commit 3c2d16e

File tree

6 files changed

+1102
-2
lines changed

6 files changed

+1102
-2
lines changed

Source/FaustProcessor.h

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,80 @@ class FaustProcessor : public ProcessorBase
302302
}
303303
state["parameters"] = params;
304304

305+
// Serialize MIDI buffers
306+
state["midi_qn"] = serializeMidiBuffer(myMidiBufferQN);
307+
state["midi_sec"] = serializeMidiBuffer(myMidiBufferSec);
308+
305309
return state;
306310
}
307311

312+
// Helper method to serialize a MidiBuffer to bytes
313+
nb::bytes serializeMidiBuffer(const juce::MidiBuffer& buffer)
314+
{
315+
std::vector<uint8_t> data;
316+
317+
// Iterate through all MIDI messages in the buffer
318+
for (const auto metadata : buffer)
319+
{
320+
auto message = metadata.getMessage();
321+
int samplePosition = metadata.samplePosition;
322+
323+
// Store sample position (4 bytes, big-endian)
324+
data.push_back((samplePosition >> 24) & 0xFF);
325+
data.push_back((samplePosition >> 16) & 0xFF);
326+
data.push_back((samplePosition >> 8) & 0xFF);
327+
data.push_back(samplePosition & 0xFF);
328+
329+
// Store message size (2 bytes for safety, though MIDI messages are small)
330+
int numBytes = message.getRawDataSize();
331+
data.push_back((numBytes >> 8) & 0xFF);
332+
data.push_back(numBytes & 0xFF);
333+
334+
// Store message data
335+
const uint8_t* rawData = message.getRawData();
336+
for (int i = 0; i < numBytes; i++)
337+
{
338+
data.push_back(rawData[i]);
339+
}
340+
}
341+
342+
return nb::bytes((const char*)data.data(), data.size());
343+
}
344+
345+
// Helper method to deserialize bytes to a MidiBuffer
346+
void deserializeMidiBuffer(juce::MidiBuffer& buffer, nb::bytes data)
347+
{
348+
buffer.clear();
349+
350+
const uint8_t* bytes = (const uint8_t*)data.c_str();
351+
size_t size = data.size();
352+
size_t pos = 0;
353+
354+
while (pos + 6 <= size) // Need at least 6 bytes (4 for position + 2 for size)
355+
{
356+
// Read sample position (4 bytes, big-endian)
357+
int samplePosition = (bytes[pos] << 24) | (bytes[pos + 1] << 16) |
358+
(bytes[pos + 2] << 8) | bytes[pos + 3];
359+
pos += 4;
360+
361+
// Read message size (2 bytes)
362+
int numBytes = (bytes[pos] << 8) | bytes[pos + 1];
363+
pos += 2;
364+
365+
// Read message data
366+
if (pos + numBytes <= size)
367+
{
368+
juce::MidiMessage message(bytes + pos, numBytes, samplePosition);
369+
buffer.addEvent(message, samplePosition);
370+
pos += numBytes;
371+
}
372+
else
373+
{
374+
break; // Corrupted data
375+
}
376+
}
377+
}
378+
308379
void setPickleState(nb::dict state)
309380
{
310381
std::string name = nb::cast<std::string>(state["unique_name"]);
@@ -374,6 +445,19 @@ class FaustProcessor : public ProcessorBase
374445
setAutomationVal(param_name.c_str(), param_value);
375446
}
376447
}
448+
449+
// Restore MIDI buffers
450+
if (state.contains("midi_qn"))
451+
{
452+
nb::bytes midi_qn_data = nb::cast<nb::bytes>(state["midi_qn"]);
453+
deserializeMidiBuffer(myMidiBufferQN, midi_qn_data);
454+
}
455+
456+
if (state.contains("midi_sec"))
457+
{
458+
nb::bytes midi_sec_data = nb::cast<nb::bytes>(state["midi_sec"]);
459+
deserializeMidiBuffer(myMidiBufferSec, midi_sec_data);
460+
}
377461
}
378462

379463
void setFaustLibrariesPath(std::string faustLibrariesPath)

Source/PluginProcessor.h

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,179 @@ class PluginProcessor : public ProcessorBase
6868

6969
void saveMIDI(std::string& savePath);
7070

71+
nb::dict getPickleState()
72+
{
73+
nb::dict state;
74+
state["unique_name"] = getUniqueName();
75+
state["plugin_path"] = myPluginPath;
76+
state["sample_rate"] = mySampleRate;
77+
78+
// Get plugin state as binary blob
79+
if (myPlugin)
80+
{
81+
juce::MemoryBlock stateData;
82+
myPlugin->getStateInformation(stateData);
83+
84+
// Convert MemoryBlock to bytes
85+
nb::bytes plugin_state((const char*)stateData.getData(), stateData.getSize());
86+
state["plugin_state"] = plugin_state;
87+
}
88+
else
89+
{
90+
state["plugin_state"] = nb::bytes("", 0);
91+
}
92+
93+
// Serialize MIDI buffers
94+
state["midi_qn"] = serializeMidiBuffer(myMidiBufferQN);
95+
state["midi_sec"] = serializeMidiBuffer(myMidiBufferSec);
96+
97+
return state;
98+
}
99+
100+
// Helper method to serialize a MidiBuffer to bytes
101+
nb::bytes serializeMidiBuffer(const juce::MidiBuffer& buffer)
102+
{
103+
std::vector<uint8_t> data;
104+
105+
// Iterate through all MIDI messages in the buffer
106+
for (const auto metadata : buffer)
107+
{
108+
auto message = metadata.getMessage();
109+
int samplePosition = metadata.samplePosition;
110+
111+
// Store sample position (4 bytes, big-endian)
112+
data.push_back((samplePosition >> 24) & 0xFF);
113+
data.push_back((samplePosition >> 16) & 0xFF);
114+
data.push_back((samplePosition >> 8) & 0xFF);
115+
data.push_back(samplePosition & 0xFF);
116+
117+
// Store message size (2 bytes for safety, though MIDI messages are small)
118+
int numBytes = message.getRawDataSize();
119+
data.push_back((numBytes >> 8) & 0xFF);
120+
data.push_back(numBytes & 0xFF);
121+
122+
// Store message data
123+
const uint8_t* rawData = message.getRawData();
124+
for (int i = 0; i < numBytes; i++)
125+
{
126+
data.push_back(rawData[i]);
127+
}
128+
}
129+
130+
return nb::bytes((const char*)data.data(), data.size());
131+
}
132+
133+
// Helper method to deserialize bytes to a MidiBuffer
134+
void deserializeMidiBuffer(juce::MidiBuffer& buffer, nb::bytes data)
135+
{
136+
buffer.clear();
137+
138+
const uint8_t* bytes = (const uint8_t*)data.c_str();
139+
size_t size = data.size();
140+
size_t pos = 0;
141+
142+
while (pos + 6 <= size) // Need at least 6 bytes (4 for position + 2 for size)
143+
{
144+
// Read sample position (4 bytes, big-endian)
145+
int samplePosition = (bytes[pos] << 24) | (bytes[pos + 1] << 16) |
146+
(bytes[pos + 2] << 8) | bytes[pos + 3];
147+
pos += 4;
148+
149+
// Read message size (2 bytes)
150+
int numBytes = (bytes[pos] << 8) | bytes[pos + 1];
151+
pos += 2;
152+
153+
// Read message data
154+
if (pos + numBytes <= size)
155+
{
156+
juce::MidiMessage message(bytes + pos, numBytes, samplePosition);
157+
buffer.addEvent(message, samplePosition);
158+
pos += numBytes;
159+
}
160+
else
161+
{
162+
break; // Corrupted data
163+
}
164+
}
165+
}
166+
167+
void setPickleState(nb::dict state)
168+
{
169+
std::string name = nb::cast<std::string>(state["unique_name"]);
170+
std::string plugin_path = nb::cast<std::string>(state["plugin_path"]);
171+
double sr = nb::cast<double>(state["sample_rate"]);
172+
173+
// Reconstruct using placement new
174+
new (this) PluginProcessor(name, sr, 512, plugin_path);
175+
176+
// Restore plugin state
177+
if (state.contains("plugin_state") && myPlugin)
178+
{
179+
nb::bytes plugin_state_bytes = nb::cast<nb::bytes>(state["plugin_state"]);
180+
if (plugin_state_bytes.size() > 0)
181+
{
182+
myPlugin->setStateInformation(plugin_state_bytes.c_str(),
183+
(int)plugin_state_bytes.size());
184+
185+
// Update automation values from plugin parameters
186+
int i = 0;
187+
for (auto* parameter : myPlugin->getParameters())
188+
{
189+
ProcessorBase::setAutomationValByIndex(i, parameter->getValue());
190+
i++;
191+
}
192+
}
193+
}
194+
195+
// Restore MIDI buffers
196+
if (state.contains("midi_qn"))
197+
{
198+
nb::bytes midi_qn_data = nb::cast<nb::bytes>(state["midi_qn"]);
199+
deserializeMidiBuffer(myMidiBufferQN, midi_qn_data);
200+
}
201+
202+
if (state.contains("midi_sec"))
203+
{
204+
nb::bytes midi_sec_data = nb::cast<nb::bytes>(state["midi_sec"]);
205+
deserializeMidiBuffer(myMidiBufferSec, midi_sec_data);
206+
}
207+
}
208+
209+
// Restore plugin state without placement new (for RenderEngine restoration)
210+
void restorePluginState(nb::dict state)
211+
{
212+
if (state.contains("plugin_state") && myPlugin)
213+
{
214+
nb::bytes plugin_state_bytes = nb::cast<nb::bytes>(state["plugin_state"]);
215+
if (plugin_state_bytes.size() > 0)
216+
{
217+
myPlugin->setStateInformation(plugin_state_bytes.c_str(),
218+
(int)plugin_state_bytes.size());
219+
220+
// Update automation values from plugin parameters
221+
int i = 0;
222+
for (auto* parameter : myPlugin->getParameters())
223+
{
224+
ProcessorBase::setAutomationValByIndex(i, parameter->getValue());
225+
i++;
226+
}
227+
}
228+
}
229+
230+
// Restore MIDI buffers
231+
if (state.contains("midi_qn"))
232+
{
233+
nb::bytes midi_qn_data = nb::cast<nb::bytes>(state["midi_qn"]);
234+
deserializeMidiBuffer(myMidiBufferQN, midi_qn_data);
235+
}
236+
237+
if (state.contains("midi_sec"))
238+
{
239+
nb::bytes midi_sec_data = nb::cast<nb::bytes>(state["midi_sec"]);
240+
deserializeMidiBuffer(myMidiBufferSec, midi_sec_data);
241+
}
242+
}
243+
71244
private:
72245
bool loadPlugin(double sampleRate, int samplesPerBlock);
73246

@@ -118,4 +291,8 @@ class PluginProcessorWrapper : public PluginProcessor
118291
int wrapperGetPluginParameterSize();
119292

120293
nb::list getPluginParametersDescription();
294+
295+
// Expose pickle methods from base class
296+
nb::dict getPickleState() { return PluginProcessor::getPickleState(); }
297+
void setPickleState(nb::dict state) { PluginProcessor::setPickleState(state); }
121298
};

0 commit comments

Comments
 (0)