99
1010#define PROPERTY_NAME " source"
1111#define CHILD_NAME " SelectedSource"
12+ #define FOLLOW_VOLUME_PROPERTY " followVolume"
13+ #define FOLLOW_VOLUME_CHILD " FollowVolumeSettings"
14+ #define FOLLOW_MUTE_PROPERTY " followMute"
15+ #define FOLLOW_MUTE_CHILD " FollowMuteSettings"
1216
1317static void drawTextLayout (
1418 juce::Graphics& g,
@@ -32,25 +36,25 @@ static void drawTextLayout(
3236 textLayout.draw (g, textBounds.toFloat ());
3337}
3438
35- static juce::StringArray GetObsAudioSources (obs_source_t * parentSource = nullptr )
39+ static std::vector<std::string> GetObsAudioSources (obs_source_t * parentSource = nullptr )
3640{
37- juce::StringArray sourceNames;
41+ std::vector<std::string> sourceNames;
3842
3943 obs_enum_sources (
4044 [](void * param, obs_source_t * src)
4145 {
42- auto * names = static_cast <juce::StringArray *>(param);
46+ auto * names = static_cast <std::vector<std::string> *>(param);
4347 const char * name = obs_source_get_name (src);
4448 uint32_t caps = obs_source_get_output_flags (src);
4549
4650 if ((caps & OBS_SOURCE_AUDIO) == 0 )
4751 return true ;
4852
49- if (name && juce::String (name).containsIgnoreCase (" ph2out" ))
53+ if (name && std::string (name).find (" ph2out" ) != std::string::npos )
5054 return true ;
5155
5256 if (name)
53- names->add ( juce::String (name));
57+ names->push_back ( std::string (name));
5458 return true ;
5559 },
5660 &sourceNames
@@ -152,14 +156,19 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
152156 return apvts;
153157 }
154158
159+ obs_source_t * getCurrentObsSource () const
160+ {
161+ return currentObsSource;
162+ }
163+
155164 void removeObsAudioCaptureCallback ()
156165 {
157- auto sourceName =
158- apvts.state .getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString ();
159- if (sourceName. isEmpty () )
166+ auto sourceNameRaw =
167+ apvts.state .getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString (). toRawUTF8 () ;
168+ if (strlen (sourceNameRaw) == 0 )
160169 return ;
161170
162- obs_source_t * source = obs_get_source_by_name (sourceName. toRawUTF8 () );
171+ obs_source_t * source = obs_get_source_by_name (sourceNameRaw );
163172 if (source)
164173 {
165174 obs_source_remove_audio_capture_callback (source, obs_capture_callback, this );
@@ -175,14 +184,21 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
175184 void addObsAudioCaptureCallback ()
176185 {
177186 removeObsAudioCaptureCallback ();
178- auto sourceName =
179- apvts.state .getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString ();
187+ auto sourceNameRaw = apvts.state .getOrCreateChildWithName (CHILD_NAME, nullptr )
188+ .getProperty (PROPERTY_NAME)
189+ .toString ()
190+ .toStdString ();
180191
181- obs_source_t * source = obs_get_source_by_name (sourceName. toRawUTF8 ());
192+ obs_source_t * source = obs_get_source_by_name (sourceNameRaw. c_str ());
182193 if (source)
183194 {
184195 obs_source_add_audio_capture_callback (source, obs_capture_callback, this );
185- obs_source_set_muted (source, true );
196+
197+ auto followVolume = apvts.state .getOrCreateChildWithName (FOLLOW_VOLUME_CHILD, nullptr )
198+ .getProperty (FOLLOW_VOLUME_PROPERTY, false );
199+ if (!followVolume)
200+ obs_source_set_muted (source, true );
201+
186202 currentObsSource = source;
187203 }
188204 }
@@ -195,6 +211,26 @@ class ObsSourceAudioProcessor : public juce::AudioProcessor
195211 buffer.getNumSamples (),
196212 getSampleRate ()
197213 );
214+
215+ auto followVolume = apvts.state .getOrCreateChildWithName (FOLLOW_VOLUME_CHILD, nullptr )
216+ .getProperty (FOLLOW_VOLUME_PROPERTY, false );
217+
218+ if (followVolume && currentObsSource)
219+ {
220+ float obsVolume = obs_source_get_volume (currentObsSource);
221+ if (obsVolume != 1 .0f )
222+ buffer.applyGain (obsVolume);
223+ }
224+
225+ auto followMute =
226+ apvts.state .getOrCreateChildWithName (FOLLOW_MUTE_CHILD, nullptr ).getProperty (FOLLOW_MUTE_PROPERTY, false );
227+
228+ if (followMute && currentObsSource)
229+ {
230+ bool obsMuted = obs_source_muted (currentObsSource);
231+ if (obsMuted)
232+ buffer.clear ();
233+ }
198234 }
199235
200236private:
@@ -237,13 +273,67 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
237273 : juce::AudioProcessorEditor(&p)
238274 , processor(p)
239275 , listBox(p)
276+ , followVolumeToggle(" Follow Source Volume" )
277+ , followMuteToggle(" Follow Source Mute" )
240278 {
241279 auto sources = GetObsAudioSources ();
242- auto savedSource =
243- processor.getApvts ().state .getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME);
280+ auto savedSourceRaw = processor.getApvts ()
281+ .state .getOrCreateChildWithName (CHILD_NAME, nullptr )
282+ .getProperty (PROPERTY_NAME)
283+ .toString ()
284+ .toRawUTF8 ();
285+
286+ followVolumeToggle.setButtonText (" Follow Source Volume" );
287+ auto followVolumeState = processor.getApvts ()
288+ .state .getOrCreateChildWithName (FOLLOW_VOLUME_CHILD, nullptr )
289+ .getProperty (FOLLOW_VOLUME_PROPERTY, false );
290+ followVolumeToggle.setToggleState (followVolumeState, juce::dontSendNotification);
291+
292+ followVolumeToggle.onClick = [this ]()
293+ {
294+ auto & state = processor.getApvts ().state ;
295+ bool newState = followVolumeToggle.getToggleState ();
296+ state.getOrCreateChildWithName (FOLLOW_VOLUME_CHILD, nullptr )
297+ .setProperty (FOLLOW_VOLUME_PROPERTY, newState, nullptr );
298+
299+ obs_source_t * currentSource = processor.getCurrentObsSource ();
300+ if (currentSource)
301+ {
302+ if (newState)
303+ obs_source_set_muted (currentSource, false );
304+ else
305+ obs_source_set_muted (currentSource, true );
306+ }
307+ };
308+
309+ // Setup follow mute toggle checkbox
310+ followMuteToggle.setButtonText (" Follow Source Mute" );
311+ auto followMuteState = processor.getApvts ()
312+ .state .getOrCreateChildWithName (FOLLOW_MUTE_CHILD, nullptr )
313+ .getProperty (FOLLOW_MUTE_PROPERTY, false );
314+ followMuteToggle.setToggleState (followMuteState, juce::dontSendNotification);
315+
316+ followMuteToggle.onClick = [this ]()
317+ {
318+ auto & state = processor.getApvts ().state ;
319+ bool newState = followMuteToggle.getToggleState ();
320+ state.getOrCreateChildWithName (FOLLOW_MUTE_CHILD, nullptr )
321+ .setProperty (FOLLOW_MUTE_PROPERTY, newState, nullptr );
322+ };
323+
324+ addAndMakeVisible (followVolumeToggle);
325+ addAndMakeVisible (followMuteToggle);
244326 addAndMakeVisible (listBox);
245327
246- auto selectedIdx = sources.indexOf (savedSource.toString (), false );
328+ auto selectedIdx = -1 ;
329+ for (size_t i = 0 ; i < sources.size (); ++i)
330+ {
331+ if (sources[i] == savedSourceRaw)
332+ {
333+ selectedIdx = static_cast <int >(i);
334+ break ;
335+ }
336+ }
247337
248338 setSize (300 , 200 );
249339 setResizable (true , true );
@@ -254,6 +344,15 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
254344 {
255345 auto area = getLocalBounds ().reduced (8 );
256346
347+ auto checkboxHeight = 24 ;
348+ followVolumeToggle.setBounds (area.removeFromTop (checkboxHeight));
349+
350+ area.removeFromTop (4 );
351+
352+ followMuteToggle.setBounds (area.removeFromTop (checkboxHeight));
353+
354+ area.removeFromTop (8 );
355+
257356 listBox.setBounds (area);
258357 }
259358
@@ -269,28 +368,28 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
269368 , processor(p)
270369 {
271370 for (const auto & item : GetObsAudioSources ())
272- items.add (item);
371+ items.push_back (item);
273372 setModel (this );
274373 setOutlineThickness (1 );
275374 }
276375
277376 int getNumRows () override
278377 {
279- return items.size ();
378+ return static_cast < int >( items.size () );
280379 }
281380
282381 void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override
283382 {
284383 auto & state = processor.getApvts ().state ;
285- auto selectedSource =
286- state.getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString ();
287- if (isPositiveAndBelow (row, items.size ()))
384+ auto selectedSourceRaw =
385+ state.getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString (). toRawUTF8 () ;
386+ if (isPositiveAndBelow (row, static_cast < int >( items.size () )))
288387 {
289388 if (rowIsSelected)
290389 g.fillAll (findColour (TextEditor::highlightColourId).withMultipliedAlpha (0 .3f ));
291390
292- auto item = items[row];
293- bool enabled = (item == selectedSource );
391+ auto itemRaw = items[row];
392+ bool enabled = (itemRaw == selectedSourceRaw );
294393
295394 auto x = getTickX ();
296395 auto tickW = (float )height * 0 .75f ;
@@ -308,7 +407,8 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
308407 false
309408 );
310409
311- drawTextLayout (g, *this , item, {x + 5 , 0 , width - x - 5 , height}, enabled);
410+ juce::String displayText = juce::String::fromUTF8 (itemRaw.c_str ());
411+ drawTextLayout (g, *this , displayText, {x + 5 , 0 , width - x - 5 , height}, enabled);
312412 }
313413 }
314414
@@ -334,7 +434,7 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
334434 {
335435 ListBox::paint (g);
336436
337- if (items.isEmpty ())
437+ if (items.empty ())
338438 {
339439 g.setColour (Colours::grey);
340440 g.setFont (0 .5f * (float )getRowHeight ());
@@ -351,28 +451,30 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
351451
352452 private:
353453 ObsSourceAudioProcessor& processor;
354- juce::Array<juce::String > items;
454+ std::vector<std::string > items;
355455
356456 void flipEnablement (const int row)
357457 {
358- if (isPositiveAndBelow (row, items.size ()))
458+ if (isPositiveAndBelow (row, static_cast < int >( items.size () )))
359459 {
360- auto sourceName = items[row];
460+ auto sourceNameRaw = items[row];
361461 auto & state = processor.getApvts ().state ;
362- auto currentSelected =
363- state.getOrCreateChildWithName (CHILD_NAME, nullptr ).getProperty (PROPERTY_NAME).toString ();
462+ auto currentSelectedRaw = state.getOrCreateChildWithName (CHILD_NAME, nullptr )
463+ .getProperty (PROPERTY_NAME)
464+ .toString ()
465+ .toRawUTF8 ();
364466
365- if (sourceName. isNotEmpty ())
467+ if (!sourceNameRaw. empty ())
366468 {
367- if (currentSelected == sourceName )
469+ if (currentSelectedRaw == sourceNameRaw )
368470 {
369471 processor.removeObsAudioCaptureCallback ();
370472 state.getOrCreateChildWithName (CHILD_NAME, nullptr ).removeProperty (PROPERTY_NAME, nullptr );
371473 }
372474 else
373475 {
374476 state.getOrCreateChildWithName (CHILD_NAME, nullptr )
375- .setProperty (PROPERTY_NAME, sourceName , nullptr );
477+ .setProperty (PROPERTY_NAME, juce::String (sourceNameRaw) , nullptr );
376478 processor.addObsAudioCaptureCallback ();
377479 }
378480 }
@@ -390,9 +492,10 @@ class ObsSourceAudioProcessorEditor : public juce::AudioProcessorEditor
390492
391493 ObsSourceAudioProcessor& processor;
392494 MidiInputSelectorComponentListBox listBox;
495+ juce::ToggleButton followVolumeToggle;
496+ juce::ToggleButton followMuteToggle;
393497};
394498
395- // Implementation of createEditor
396499inline juce::AudioProcessorEditor* ObsSourceAudioProcessor::createEditor ()
397500{
398501 return new ObsSourceAudioProcessorEditor (*this );
0 commit comments