Skip to content

Commit fd909fd

Browse files
committed
Change to ensure libpd events are only ever dispatched from the main thread; added AudioSource Component dependency to LibPdInstance.
1 parent ff116c4 commit fd909fd

1 file changed

Lines changed: 67 additions & 13 deletions

File tree

Assets/Scripts/LibPdInstance.cs

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public class IntIntEvent : UnityEvent<int, int> {}
108108
/// change in future.
109109
///
110110
/// </remarks>
111+
[RequireComponent(typeof(AudioSource))]
111112
public class LibPdInstance : MonoBehaviour {
112113

113114
#region libpd imports
@@ -430,6 +431,13 @@ public delegate void LibPdMidiControlChangeHook(int channel,
430431

431432
/// Global variable used to ensure we don't initialise LibPd more than once.
432433
private static bool pdInitialised = false;
434+
435+
/// Holds any actions (events) sent from the audio thread.
436+
private List<System.Action> audioThreadActions = new List<System.Action>();
437+
/// Holds any actions (events) that have been copied over into the main thread to execute.
438+
private List<System.Action> mainThreadActions = new List<System.Action>();
439+
/// True if there are audioThreadActions pending.
440+
private volatile bool actionsPending = false;
433441
#endregion
434442

435443
#region events
@@ -638,6 +646,29 @@ void OnApplicationQuit ()
638646
}
639647
}
640648

649+
//--------------------------------------------------------------------------
650+
/// We use this to dispatch any events that we've been sent from libpd.
651+
/// Any send/MIDI events sent from libpd will be sent from the audio thread,
652+
/// so we have to queue them and send them from the main thread, or Unity
653+
/// will get very upset.
654+
public void Update() {
655+
if(actionsPending) {
656+
mainThreadActions.Clear();
657+
658+
//Copy events queued from audio thread.
659+
lock(audioThreadActions) {
660+
mainThreadActions.AddRange(audioThreadActions);
661+
662+
audioThreadActions.Clear();
663+
actionsPending = false;
664+
}
665+
666+
//Dispatch events on main thread.
667+
for(int i=0;i<mainThreadActions.Count;++i)
668+
mainThreadActions[i].Invoke();
669+
}
670+
}
671+
641672
//--------------------------------------------------------------------------
642673
/// This function updates our static pipePrintToConsole variable when the
643674
/// public one changes, and ensures all other active LibPdInstances are
@@ -659,7 +690,8 @@ private void OnValidate()
659690

660691
//We use this to store the name of our PD patch as a string, as we need
661692
//to feed the filename and directory into libpd.
662-
patchName = patch.name;
693+
if(patch != null)
694+
patchName = patch.name;
663695

664696
if(lastName != patchName) {
665697
patchDir = AssetDatabase.GetAssetPath(patch.GetInstanceID());
@@ -804,6 +836,7 @@ public void SendMidiCc(int channel, int controller, int value)
804836
//--------------------------------------------------------------------------
805837
/// Send a MIDI program change to the open patch.
806838
/// <param name="value">The program to change to (0-127).</param>
839+
[MethodImpl(MethodImplOptions.Synchronized)]
807840
public void SendMidiProgramChange(int channel, int value)
808841
{
809842
libpd_set_instance(instance);
@@ -815,6 +848,7 @@ public void SendMidiProgramChange(int channel, int value)
815848
//--------------------------------------------------------------------------
816849
/// Send a MIDI pitch bend to the open patch.
817850
/// <param name ="value">The bend value has a range -8192 -> +8192.</param>
851+
[MethodImpl(MethodImplOptions.Synchronized)]
818852
public void SendMidiPitchBend(int channel, int value)
819853
{
820854
libpd_set_instance(instance);
@@ -825,6 +859,7 @@ public void SendMidiPitchBend(int channel, int value)
825859

826860
//--------------------------------------------------------------------------
827861
/// Send a MIDI aftertouch message to the open patch.
862+
[MethodImpl(MethodImplOptions.Synchronized)]
828863
public void SendMidiAftertouch(int channel, int value)
829864
{
830865
libpd_set_instance(instance);
@@ -835,6 +870,7 @@ public void SendMidiAftertouch(int channel, int value)
835870

836871
//--------------------------------------------------------------------------
837872
/// Send a MIDI polyphonic aftertouch to the open patch.
873+
[MethodImpl(MethodImplOptions.Synchronized)]
838874
public void SendMidiPolyAftertouch(int channel, int pitch, int value)
839875
{
840876
libpd_set_instance(instance);
@@ -845,6 +881,7 @@ public void SendMidiPolyAftertouch(int channel, int pitch, int value)
845881

846882
//--------------------------------------------------------------------------
847883
/// Send a MIDI byte(?) to the open patch.
884+
[MethodImpl(MethodImplOptions.Synchronized)]
848885
public void SendMidiByte(int port, int value)
849886
{
850887
libpd_set_instance(instance);
@@ -855,6 +892,7 @@ public void SendMidiByte(int port, int value)
855892

856893
//--------------------------------------------------------------------------
857894
/// Send a MIDI sysex byte(?) to the open patch.
895+
[MethodImpl(MethodImplOptions.Synchronized)]
858896
public void SendMidiSysex(int port, int value)
859897
{
860898
libpd_set_instance(instance);
@@ -865,6 +903,7 @@ public void SendMidiSysex(int port, int value)
865903

866904
//--------------------------------------------------------------------------
867905
/// Send a MIDI sysrealtime byte(?) to the open patch.
906+
[MethodImpl(MethodImplOptions.Synchronized)]
868907
public void SendMidiSysRealtime(int port, int value)
869908
{
870909
libpd_set_instance(instance);
@@ -938,21 +977,21 @@ static void PrintOutput(string message)
938977
/// Receive bang messages.
939978
void BangOutput(string symbol)
940979
{
941-
pureDataEvents.Bang.Invoke(symbol);
980+
addAudioAction(() => {pureDataEvents.Bang.Invoke(symbol);});
942981
}
943982

944983
//--------------------------------------------------------------------------
945984
/// Receive float messages.
946985
void FloatOutput(string symbol, float val)
947986
{
948-
pureDataEvents.Float.Invoke(symbol, val);
987+
addAudioAction(() => {pureDataEvents.Float.Invoke(symbol, val);});
949988
}
950989

951990
//--------------------------------------------------------------------------
952991
/// Receive symbol messages.
953992
void SymbolOutput(string symbol, string val)
954993
{
955-
pureDataEvents.Symbol.Invoke(symbol, val);
994+
addAudioAction(() => {pureDataEvents.Symbol.Invoke(symbol, val);});
956995
}
957996

958997
//--------------------------------------------------------------------------
@@ -961,7 +1000,7 @@ void ListOutput(string source, int argc, IntPtr argv)
9611000
{
9621001
var args = ConvertList(argc, argv);
9631002

964-
pureDataEvents.List.Invoke(source, args);
1003+
addAudioAction(() => {pureDataEvents.List.Invoke(source, args);});
9651004
}
9661005

9671006
//--------------------------------------------------------------------------
@@ -970,56 +1009,56 @@ void MessageOutput(string source, string symbol, int argc, IntPtr argv)
9701009
{
9711010
var args = ConvertList(argc, argv);
9721011

973-
pureDataEvents.Message.Invoke(source, symbol, args);
1012+
addAudioAction(() => {pureDataEvents.Message.Invoke(source, symbol, args);});
9741013
}
9751014

9761015
//--------------------------------------------------------------------------
9771016
/// Receive MIDI note on messages.
9781017
void MidiNoteOnOutput(int channel, int pitch, int velocity)
9791018
{
980-
midiEvents.MidiNoteOn.Invoke(channel, pitch, velocity);
1019+
addAudioAction(() => {midiEvents.MidiNoteOn.Invoke(channel, pitch, velocity);});
9811020
}
9821021

9831022
//--------------------------------------------------------------------------
9841023
/// Receive MIDI control change messages.
9851024
void MidiControlChangeOutput(int channel, int controller, int value)
9861025
{
987-
midiEvents.MidiControlChange.Invoke(channel, controller, value);
1026+
addAudioAction(() => {midiEvents.MidiControlChange.Invoke(channel, controller, value);});
9881027
}
9891028

9901029
//--------------------------------------------------------------------------
9911030
/// Receive MIDI program change messages.
9921031
void MidiProgramChangeOutput(int channel, int program)
9931032
{
994-
midiEvents.MidiProgramChange.Invoke(channel, program);
1033+
addAudioAction(() => {midiEvents.MidiProgramChange.Invoke(channel, program);});
9951034
}
9961035

9971036
//--------------------------------------------------------------------------
9981037
/// Receive MIDI pitch bend messages.
9991038
void MidiPitchBendOutput(int channel, int value)
10001039
{
1001-
midiEvents.MidiPitchBend.Invoke(channel, value);
1040+
addAudioAction(() => {midiEvents.MidiPitchBend.Invoke(channel, value);});
10021041
}
10031042

10041043
//--------------------------------------------------------------------------
10051044
/// Receive MIDI aftertouch messages.
10061045
void MidiAftertouchOutput(int channel, int value)
10071046
{
1008-
midiEvents.MidiAftertouch.Invoke(channel, value);
1047+
addAudioAction(() => {midiEvents.MidiAftertouch.Invoke(channel, value);});
10091048
}
10101049

10111050
//--------------------------------------------------------------------------
10121051
/// Receive MIDI polyphonic aftertouch messages.
10131052
void MidiPolyAftertouchOutput(int channel, int pitch, int value)
10141053
{
1015-
midiEvents.MidiPolyAftertouch.Invoke(channel, pitch, value);
1054+
addAudioAction(() => {midiEvents.MidiPolyAftertouch.Invoke(channel, pitch, value);});
10161055
}
10171056

10181057
//--------------------------------------------------------------------------
10191058
/// Receive MIDI byte messages.
10201059
void MidiByteOutput(int channel, int value)
10211060
{
1022-
midiEvents.MidiByte.Invoke(channel, value);
1061+
addAudioAction(() => {midiEvents.MidiByte.Invoke(channel, value);});
10231062
}
10241063
#endregion
10251064

@@ -1072,5 +1111,20 @@ private object[] ConvertList(int argc, IntPtr argv)
10721111

10731112
return retval;
10741113
}
1114+
1115+
//--------------------------------------------------------------------------
1116+
/// Used to queue an event/action from the audio thread, to be dispatched in
1117+
/// the main thread.
1118+
private void addAudioAction(System.Action action) {
1119+
if (action == null)
1120+
throw new ArgumentNullException("action");
1121+
1122+
lock(audioThreadActions) {
1123+
audioThreadActions.Add(action);
1124+
1125+
actionsPending = true;
1126+
}
1127+
}
1128+
10751129
#endregion
10761130
}

0 commit comments

Comments
 (0)