@@ -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