@@ -7,10 +7,17 @@ use nih_plug_egui::{
77} ;
88use std:: sync:: Arc ;
99
10+ const MIN_WINDOW_WIDTH : u32 = 300 ;
11+ const MIN_WINDOW_HEIGHT : u32 = 220 ;
12+
1013/// The time it takes for the peak meter to decay by 12 dB after switching to complete silence.
1114const PEAK_METER_DECAY_MS : f64 = 150.0 ;
1215
13- /// This is mostly identical to the gain example, minus some fluff, and with a GUI.
16+ /// If you are using message channels, then allocate enough capacity for the expected worse case
17+ /// scenario for your plugin.
18+ const GUI_TO_AUDIO_MSG_CHANNEL_CAPACITY : usize = 512 ;
19+ const AUDIO_TO_GUI_MSG_CHANNEL_CAPACITY : usize = 128 ;
20+
1421pub struct Gain {
1522 params : Arc < GainParams > ,
1623
@@ -22,6 +29,25 @@ pub struct Gain {
2229 ///
2330 /// This is stored as voltage gain.
2431 peak_meter : Arc < AtomicF32 > ,
32+
33+ /// A message channel to send events between the GUI and the audio thread.
34+ ///
35+ /// This is optional. If you don't need to pass events, you can omit this field.
36+ msg_channel : AudioMsgChannel ,
37+ /// Used to demonstrate how to pass heap-allocated data from the GUI to the audio thread.
38+ heap_data_example : Vec < f32 > ,
39+
40+ /// State that is synced between the GUI and the audio thread using a triple buffer.
41+ /// This can be used as an alternative to the message channel approach. Note, the roles of which
42+ /// thread has the input and which has the output can be reversed.
43+ ///
44+ /// The downside to this approach is that it takes 3x the memory.
45+ ///
46+ /// This is optional. If you don't need this, you can omit it.
47+ triple_buffer_state : triple_buffer:: Output < TripleBufferState > ,
48+
49+ /// Temporarily hold on to the initial GUI state until the editor is first opened.
50+ initial_gui_state : Option < GuiState > ,
2551}
2652
2753#[ derive( Params ) ]
@@ -41,19 +67,43 @@ pub struct GainParams {
4167
4268impl Default for Gain {
4369 fn default ( ) -> Self {
70+ let ( to_audio_tx, from_gui_rx) = rtrb:: RingBuffer :: new ( GUI_TO_AUDIO_MSG_CHANNEL_CAPACITY ) ;
71+ let ( to_gui_tx, from_audio_rx) = rtrb:: RingBuffer :: new ( AUDIO_TO_GUI_MSG_CHANNEL_CAPACITY ) ;
72+
73+ let ( triple_buffer_input, triple_buffer_output) =
74+ triple_buffer:: triple_buffer ( & TripleBufferState :: default ( ) ) ;
75+
4476 Self {
4577 params : Arc :: new ( GainParams :: default ( ) ) ,
4678
4779 peak_meter_decay_weight : 1.0 ,
4880 peak_meter : Arc :: new ( AtomicF32 :: new ( util:: MINUS_INFINITY_DB ) ) ,
81+
82+ msg_channel : AudioMsgChannel {
83+ to_gui_tx,
84+ from_gui_rx,
85+ msg_sent : false ,
86+ } ,
87+ heap_data_example : Vec :: new ( ) ,
88+
89+ triple_buffer_state : triple_buffer_output,
90+
91+ initial_gui_state : Some ( GuiState {
92+ msg_channel : GuiMsgChannel {
93+ to_audio_tx,
94+ from_audio_rx,
95+ } ,
96+ triple_buffer_state : triple_buffer_input,
97+ next_value : 0 ,
98+ } ) ,
4999 }
50100 }
51101}
52102
53103impl Default for GainParams {
54104 fn default ( ) -> Self {
55105 Self {
56- editor_state : EguiState :: from_size ( 300 , 180 ) ,
106+ editor_state : EguiState :: from_size ( MIN_WINDOW_WIDTH , MIN_WINDOW_HEIGHT ) ,
57107
58108 // See the main gain example for more details
59109 gain : FloatParam :: new (
@@ -74,6 +124,71 @@ impl Default for GainParams {
74124 }
75125}
76126
127+ /// Here you can store any state you need for your GUI.
128+ ///
129+ /// This state persists across editor openings.
130+ pub struct GuiState {
131+ /// A message channel to send events between the GUI and the audio thread.
132+ ///
133+ /// This is optional. If you don't need to pass events, you can omit this field.
134+ msg_channel : GuiMsgChannel ,
135+
136+ /// State that is synced between the GUI and the audio thread using a triple buffer.
137+ /// This can be used as an alternative the message channel approach. Note, the roles of which
138+ /// thread has the input and which has the output can be reversed.
139+ ///
140+ /// The downside to this approach is that it takes 3x the memory.
141+ ///
142+ /// This is optional. If you don't need this, you can omit it.
143+ triple_buffer_state : triple_buffer:: Input < TripleBufferState > ,
144+ next_value : u64 ,
145+ }
146+
147+ /// A message channel to send events between the GUI and the audio thread.
148+ ///
149+ /// This is optional. If you don't need to pass events, you can omit this.
150+ pub struct GuiMsgChannel {
151+ /// A message channel to send events from the GUI to the audio thread.
152+ to_audio_tx : rtrb:: Producer < GuiToAudioMsg > ,
153+ /// A message channel to receive events from the audio thread.
154+ from_audio_rx : rtrb:: Consumer < AudioToGuiMsg > ,
155+ }
156+ /// A message channel to send events between the GUI and the audio thread.
157+ ///
158+ /// This is optional. If you don't need to pass events, you can omit this.
159+ pub struct AudioMsgChannel {
160+ /// A message channel to send events from the audio thread to the GUI thread.
161+ to_gui_tx : rtrb:: Producer < AudioToGuiMsg > ,
162+ /// A message channel to receive events from the GUI thread.
163+ from_gui_rx : rtrb:: Consumer < GuiToAudioMsg > ,
164+ msg_sent : bool ,
165+ }
166+
167+ #[ derive( Debug ) ]
168+ pub enum GuiToAudioMsg {
169+ MessageA ,
170+ MessageWithHeapData ( Vec < f32 > ) ,
171+ }
172+ #[ derive( Debug ) ]
173+ pub enum AudioToGuiMsg {
174+ MessageA ,
175+ DropOldHeapData ( Vec < f32 > ) ,
176+ }
177+
178+ /// State that is synced between the GUI and the audio thread using a triple buffer.
179+ /// This can be used as an alternative the message channel approach. Note, the roles
180+ /// of which thread has the input and which has the output can be reversed.
181+ ///
182+ /// The downside to this approach is that it takes 3x the memory.
183+ ///
184+ /// This is optional. If you don't need this, you can omit it.
185+ #[ derive( Debug , Default , Clone ) ]
186+ pub struct TripleBufferState {
187+ value_a : bool ,
188+ value_b : u64 ,
189+ some_data : Vec < u32 > ,
190+ }
191+
77192impl Plugin for Gain {
78193 const NAME : & ' static str = "Gain (nih_plug_egui)" ;
79194 const VENDOR : & ' static str = "Moist Plugins GmbH" ;
@@ -108,17 +223,16 @@ impl Plugin for Gain {
108223 let params = self . params . clone ( ) ;
109224 let peak_meter = self . peak_meter . clone ( ) ;
110225 let egui_state = params. editor_state . clone ( ) ;
226+
111227 create_egui_editor (
112228 self . params . editor_state . clone ( ) ,
113- ( ) ,
229+ self . initial_gui_state . take ( ) . unwrap ( ) ,
114230 Default :: default ( ) ,
115- |_ , _ , _ | { } ,
116- move |egui_ctx, setter, _queue, _state | {
231+ |_egui_ctx , _queue , _gui_state | { } ,
232+ move |egui_ctx, setter, _queue, gui_state | {
117233 ResizableWindow :: new ( "res-wind" )
118- . min_size ( Vec2 :: new ( 128.0 , 128.0 ) )
234+ . min_size ( Vec2 :: new ( MIN_WINDOW_WIDTH as f32 , MIN_WINDOW_HEIGHT as f32 ) )
119235 . show ( egui_ctx, egui_state. as_ref ( ) , |ui| {
120- // NOTE: See `plugins/diopser/src/editor.rs` for an example using the generic UI widget
121-
122236 // This is a fancy widget that can get all the information it needs to properly
123237 // display and modify the parameter from the parametr itself
124238 // It's not yet fully implemented, as the text is missing.
@@ -161,6 +275,25 @@ impl Plugin for Gain {
161275 egui:: widgets:: ProgressBar :: new ( peak_meter_normalized)
162276 . text ( peak_meter_text) ,
163277 ) ;
278+
279+ // Demonstrate sending a message to the audio thread.
280+ if ui. button ( "send message" ) . clicked ( ) {
281+ if let Err ( e) = gui_state. msg_channel . to_audio_tx . push ( GuiToAudioMsg :: MessageA ) {
282+ nih_error ! ( "Failed to send message to audio thread: {}" , e) ;
283+ }
284+ }
285+ // Demonstrate receiving messages from the audio thread.
286+ while let Ok ( msg) = gui_state. msg_channel . from_audio_rx . pop ( ) {
287+ nih_log ! ( "Got message from audio thread: {:?}" , & msg) ;
288+ }
289+
290+ // Demonstrate mutating synced triple buffer state.
291+ if ui. button ( "mutate synced state" ) . clicked ( ) {
292+ gui_state. next_value += 1 ;
293+ // Note, `triple_buffer_state.input_buffer_mut()` will not work for syncing state
294+ // this way. You must always completely overwrite the state with new data.
295+ gui_state. triple_buffer_state . write ( TripleBufferState { value_a : false , value_b : gui_state. next_value , some_data : Vec :: new ( ) } ) ;
296+ }
164297 } ) ;
165298 } ,
166299 )
@@ -187,6 +320,48 @@ impl Plugin for Gain {
187320 _aux : & mut AuxiliaryBuffers ,
188321 _context : & mut impl ProcessContext < Self > ,
189322 ) -> ProcessStatus {
323+ // Demonstrate receiving messages from the GUI thread.
324+ while let Ok ( msg) = self . msg_channel . from_gui_rx . pop ( ) {
325+ match msg {
326+ GuiToAudioMsg :: MessageA => {
327+ nih_dbg ! ( "Got MessageA from GUI" ) ;
328+ }
329+ GuiToAudioMsg :: MessageWithHeapData ( mut heap_data) => {
330+ nih_dbg ! ( "Got MessageWithHeapData from GUI" ) ;
331+
332+ // Replace the old heap data with the new data.
333+ std:: mem:: swap ( & mut self . heap_data_example , & mut heap_data) ;
334+
335+ // Note, you must be careful not to drop heap-allocated data on the audio
336+ // thread. Send the old data back to the GUI thread to be deallocated there.
337+ if let Err ( e) = self
338+ . msg_channel
339+ . to_gui_tx
340+ . push ( AudioToGuiMsg :: DropOldHeapData ( heap_data) )
341+ {
342+ nih_error ! ( "Failed to send message to GUI thread: {}" , e) ;
343+ }
344+ }
345+ }
346+ }
347+
348+ // Demonstrate sending messages to the GUI thread.
349+ if self . params . editor_state . is_open ( ) && !self . msg_channel . msg_sent {
350+ if let Err ( e) = self . msg_channel . to_gui_tx . push ( AudioToGuiMsg :: MessageA ) {
351+ nih_error ! ( "Failed to send message to GUI thread: {}" , e) ;
352+ }
353+
354+ // Only send the example message once to avoid spamming the GUI.
355+ self . msg_channel . msg_sent = true ;
356+ }
357+
358+ // Demonstrate triple buffer usage.
359+ let state = self . triple_buffer_state . read ( ) ;
360+ // Use the state somehow...
361+ let _ = & state. value_a ;
362+ let _ = & state. value_b ;
363+ let _ = & state. some_data ;
364+
190365 for channel_samples in buffer. iter_samples ( ) {
191366 let mut amplitude = 0.0 ;
192367 let num_samples = channel_samples. len ( ) ;
0 commit comments