LibPdIntegrationExamples/Assets/Scripts/LibPdInstance.cs
2020-12-08 16:50:23 +00:00

1175 lines
43 KiB
C#

// LibPdInstance.cs - Unity integration of libpd, supporting multiple instances.
// -----------------------------------------------------------------------------
// Copyright (c) 2019 Niall Moody
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// -----------------------------------------------------------------------------
using System;
using UnityEngine;
using UnityEditor;
using UnityEngine.Events;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using AOT;
#region UnityEvent types
//------------------------------------------------------------------------------
/// Single string parameter event type (used for Bang events).
[System.Serializable]
public class StringEvent : UnityEvent<string> {}
/// String + float parameter event type (used for Float events).
[System.Serializable]
public class StringFloatEvent : UnityEvent<string, float> {}
/// String + string parameter event type (used for Symbol events).
[System.Serializable]
public class StringStringEvent : UnityEvent<string, string> {}
/// String + object array parameter event type (used for List events).
[System.Serializable]
public class StringObjArrEvent : UnityEvent<string, object[]> {}
/// String + object array parameter event type (used for Message events).
[System.Serializable]
public class StringStringObjArrEvent : UnityEvent<string, string, object[]> {}
/// Int + int + int parameter event type (used for various MIDI events).
[System.Serializable]
public class IntIntIntEvent : UnityEvent<int, int, int> {}
/// Int + int parameter event type (used for various MIDI events).
[System.Serializable]
public class IntIntEvent : UnityEvent<int, int> {}
#endregion
/// <summary>
/// Unity Component for running a Pure Data patch. Uses libpd's new multiple
/// instance support, so you can run multiple LibPdInstances in your scene.
///
/// <para>
/// Pd patches should be stored in Assets/StreamingAssets/PdAssets and assigned
/// to LibPdInstance in the inspector (type the patch name (minus .pd) into the
/// Patch text box).
/// </para>
/// </summary>
///
/// <remarks>
/// This uses the basic c version of libpd over the C# bindings, as I was unable
/// to get the C# bindings working with Unity. This is likely due to my own
/// inexperience with C# (I'm primarily a C++ programmer), rather than an issue
/// with the lipbd C# bindings themselves.
///
/// Along those lines, I modelled parts of this class after the C# bindings, so
/// you will likely see some duplicated code.
///
///
/// Note: LibPdInstance is all implemented as a single file because I find
/// single file libraries easier to integrate into my own projects. This may
/// change in future.
///
/// </remarks>
[RequireComponent(typeof(AudioSource))]
public class LibPdInstance : MonoBehaviour
{
#region libpd imports
#if UNITY_IOS
private const string DLL_NAME="__Internal";
//The following lines will be needed if we can get externals working on Windows.
//Just renaming libpd.dll to pd.dll does not seem to be enough though, so
//they're commented out for now.
//#elif UNITY_STANDALONE_WIN
// private const string DLL_NAME="pd";
#else
private const string DLL_NAME="libpd";
#endif
//--------------------------------------------------------------------------
/// libpd functions that we need to be able to call from C#.
[DllImport(DLL_NAME)]
private static extern int libpd_queued_init();
[DllImport(DLL_NAME)]
private static extern void libpd_queued_release();
[DllImport(DLL_NAME)]
private static extern void libpd_queued_receive_pd_messages();
[DllImport(DLL_NAME)]
private static extern void libpd_queued_receive_midi_messages();
[DllImport(DLL_NAME)]
private static extern void libpd_clear_search_path();
[DllImport(DLL_NAME)]
private static extern void libpd_add_to_search_path([In] [MarshalAs(UnmanagedType.LPStr)] string s);
[DllImport(DLL_NAME)]
private static extern IntPtr libpd_new_instance();
[DllImport(DLL_NAME)]
private static extern void libpd_set_instance(IntPtr instance);
[DllImport(DLL_NAME)]
private static extern void libpd_free_instance(IntPtr instance);
[DllImport(DLL_NAME)]
private static extern int libpd_init_audio(int inChans, int outChans, int sampleRate);
[DllImport(DLL_NAME)]
private static extern IntPtr libpd_openfile([In] [MarshalAs(UnmanagedType.LPStr)] string basename,
[In] [MarshalAs(UnmanagedType.LPStr)] string dirname);
[DllImport(DLL_NAME)]
private static extern void libpd_closefile(IntPtr p);
[DllImport(DLL_NAME)]
private static extern int libpd_getdollarzero(IntPtr p);
[DllImport(DLL_NAME)]
private static extern int libpd_process_float(int ticks,
[In] float[] inBuffer,
[Out] float[] outBuffer);
[DllImport(DLL_NAME)]
private static extern int libpd_blocksize();
[DllImport(DLL_NAME)]
private static extern int libpd_start_message(int max_length);
[DllImport(DLL_NAME)]
private static extern void libpd_add_float(float x);
[DllImport(DLL_NAME)]
private static extern void libpd_add_symbol([In] [MarshalAs(UnmanagedType.LPStr)] string sym);
[DllImport(DLL_NAME)]
private static extern int libpd_finish_list([In] [MarshalAs(UnmanagedType.LPStr)] string recv);
[DllImport(DLL_NAME)]
private static extern int libpd_finish_message([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
[In] [MarshalAs(UnmanagedType.LPStr)] string msg);
[DllImport(DLL_NAME)]
private static extern int libpd_bang([In] [MarshalAs(UnmanagedType.LPStr)] string recv);
[DllImport(DLL_NAME)]
private static extern int libpd_float([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
float x);
[DllImport(DLL_NAME)]
private static extern int libpd_symbol([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
[In] [MarshalAs(UnmanagedType.LPStr)] string sym);
[DllImport(DLL_NAME)]
private static extern int libpd_exists([In] [MarshalAs(UnmanagedType.LPStr)] string obj);
[DllImport(DLL_NAME)]
private static extern IntPtr libpd_bind([In] [MarshalAs(UnmanagedType.LPStr)] string symbol);
[DllImport(DLL_NAME)]
private static extern void libpd_unbind(IntPtr binding);
[DllImport(DLL_NAME)]
private static extern void libpd_set_verbose(int verbose);
[DllImport(DLL_NAME)]
private static extern int libpd_is_float(IntPtr atom);
[DllImport(DLL_NAME)]
private static extern int libpd_is_symbol(IntPtr atom);
[DllImport(DLL_NAME)]
private static extern float libpd_get_float(IntPtr atom);
[DllImport(DLL_NAME)]
private static extern IntPtr libpd_get_symbol(IntPtr atom);
[DllImport(DLL_NAME)]
private static extern IntPtr libpd_next_atom(IntPtr atom);
[DllImport(DLL_NAME)]
private static extern int libpd_noteon(int channel,
int pitch,
int velocity);
[DllImport(DLL_NAME)]
private static extern int libpd_controlchange(int channel,
int controller,
int value);
[DllImport(DLL_NAME)]
private static extern int libpd_programchange(int channel, int program);
[DllImport(DLL_NAME)]
private static extern int libpd_pitchbend(int channel, int value);
[DllImport(DLL_NAME)]
private static extern int libpd_aftertouch(int channel, int value);
[DllImport(DLL_NAME)]
private static extern int libpd_polyaftertouch(int channel,
int pitch,
int value);
[DllImport(DLL_NAME)]
private static extern int libpd_midibyte(int port, int value);
[DllImport(DLL_NAME)]
private static extern int libpd_sysex(int port, int value);
[DllImport(DLL_NAME)]
private static extern int libpd_sysrealtime(int port, int value);
[DllImport(DLL_NAME)]
private static extern int libpd_arraysize([In] [MarshalAs(UnmanagedType.LPStr)] string name);
[DllImport(DLL_NAME)]
private static extern int libpd_read_array([Out] float[] dest,
[In] [MarshalAs(UnmanagedType.LPStr)] string src,
int offset,
int n);
[DllImport(DLL_NAME)]
private static extern int libpd_write_array([In] [MarshalAs(UnmanagedType.LPStr)] string dest,
int offset,
[In] float[] src,
int n);
#endregion
#region libpd delegate/callback declarations
//--------------------------------------------------------------------------
//-Print hook---------------------------------------------------------------
// We don't make the print hook publicly available (for now), so it's just a
//single static delegate.
/// Delegate/function pointer type.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdPrintHook([In] [MarshalAs(UnmanagedType.LPStr)] string message);
/// libpd function for setting the hook.
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_printhook(LibPdPrintHook hook);
/// Instance of the print hook, kept to ensure it doesn't get garbage
/// collected.
private static LibPdPrintHook printHook;
//-Bang hook----------------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdBangHook([In] [MarshalAs(UnmanagedType.LPStr)] string symbol);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_banghook(LibPdBangHook hook);
private LibPdBangHook bangHook;
//-Float hook---------------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdFloatHook([In] [MarshalAs(UnmanagedType.LPStr)] string symbol,
float val);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_floathook(LibPdFloatHook hook);
private LibPdFloatHook floatHook;
//-Symbol hook--------------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdSymbolHook([In] [MarshalAs(UnmanagedType.LPStr)] string symbol,
[In] [MarshalAs(UnmanagedType.LPStr)] string val);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_symbolhook(LibPdSymbolHook hook);
private LibPdSymbolHook symbolHook;
//-List hook----------------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdListHook([In] [MarshalAs(UnmanagedType.LPStr)] string source,
int argc,
IntPtr argv);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_listhook(LibPdListHook hook);
private LibPdListHook listHook;
//-Message hook-------------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMessageHook([In] [MarshalAs(UnmanagedType.LPStr)] string source,
[In] [MarshalAs(UnmanagedType.LPStr)] string symbol,
int argc,
IntPtr argv);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_messagehook(LibPdMessageHook hook);
private LibPdMessageHook messageHook;
//-MIDI Note On hook--------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiNoteOnHook(int channel,
int pitch,
int velocity);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_noteonhook(LibPdMidiNoteOnHook hook);
private LibPdMidiNoteOnHook noteOnHook;
//-MIDI Control Change hook-------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiControlChangeHook(int channel,
int controller,
int value);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_controlchangehook(LibPdMidiControlChangeHook hook);
private LibPdMidiControlChangeHook controlChangeHook;
//-MIDI Program Change hook-------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiProgramChangeHook(int channel, int program);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_programchangehook(LibPdMidiProgramChangeHook hook);
private LibPdMidiProgramChangeHook programChangeHook;
//-MIDI Pitch Bend hook-----------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiPitchBendHook(int channel, int value);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_pitchbendhook(LibPdMidiPitchBendHook hook);
private LibPdMidiPitchBendHook pitchBendHook;
//-MIDI Aftertouch hook-----------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiAftertouchHook(int channel, int value);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_aftertouchhook(LibPdMidiAftertouchHook hook);
private LibPdMidiAftertouchHook aftertouchHook;
//-MIDI Polyphonic Aftertouch hook------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiPolyAftertouchHook(int channel, int pitch, int value);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_polyaftertouchhook(LibPdMidiPolyAftertouchHook hook);
private LibPdMidiPolyAftertouchHook polyAftertouchHook;
//-MIDI Byte hook-----------------------------------------------------------
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LibPdMidiByteHook(int channel, int value);
[DllImport(DLL_NAME)]
private static extern void libpd_set_queued_midibytehook(LibPdMidiByteHook hook);
private LibPdMidiByteHook midiByteHook;
#endregion
#region member variables
//--------------------------------------------------------------------------
/// The Pd patch this instance is running.
[HideInInspector]
public string patchName;
/// Path to the folder the patch is in.
[HideInInspector]
public string patchDir;
#if UNITY_EDITOR
/// This is a slightly tricky workaround we use so that we can drag and drop
/// PD patches into the Inspector. By default, Unity doesn't let you do that
/// with StreamingAssets, so we use the DefaultAsset type to get around that
/// limitation. It's not perfect, but it's nicer than the alternative
/// (typing the patch name by hand into a string box).
///
/// See also OnValidate().
///
/// (We have to store Pd patches in StreamingAssets because libpd requires
/// us to load patches from files on the filesystem)
public UnityEditor.DefaultAsset patch;
#endif
/// Hacky way of making pipePrintToConsoleStatic visible in the inspector.
public bool pipePrintToConsole = false;
/// Set to true to pipe any Pure Data *print* messages to Unity's console.
public static bool pipePrintToConsoleStatic = false;
/// Our pointer to the Pd patch this instance is running.
IntPtr patchPointer;
/// The Pd instance we're using.
private IntPtr instance;
/// The number of ticks to process at a time.
private int numTicks;
/// Any bindings we have for this patch.
private Dictionary<string, IntPtr> bindings;
/// We use this to ensure libpd -> Unity events get sent to all LibPdInstances.
/*!
It's also used to ensure libpd_queued_release() only gets called once
all LibPdInstances have been destroyed.
*/
private static List<LibPdInstance> activeInstances = new List<LibPdInstance>();
/// The WeakReference pointing to ourselves in activeInstances.
//private WeakReference instanceReference;
/// True if we were unable to intialise Pd's audio processing.
private bool pdFail = false;
/// True if we were unable to open our patch.
private bool patchFail = false;
/// Global variable used to ensure we don't initialise LibPd more than once.
private static bool pdInitialised = false;
#endregion
#region events
//--------------------------------------------------------------------------
/// Events placed in a struct so they don't clutter up the Inspector by default.
[System.Serializable]
public struct PureDataEvents
{
/// UnityEvent that will be invoked whenever we recieve a bang from the PD patch.
public StringEvent Bang;
/// UnityEvent that will be invoked whenever we recieve a float from the PD patch.
public StringFloatEvent Float;
/// UnityEvent that will be invoked whenever we recieve a symbol from the PD patch.
public StringStringEvent Symbol;
/// UnityEvent that will be invoked whenever we recieve a list from the PD patch.
public StringObjArrEvent List;
/// UnityEvent that will be invoked whenever we recieve a message from the PD patch.
public StringStringObjArrEvent Message;
};
[Header("libpd → Unity Events")]
public PureDataEvents pureDataEvents;
/// Events placed in a struct so they don't clutter up the Inspector by default.
[System.Serializable]
public struct MidiEvents
{
/// UnityEvent that will be invoked whenever we recieve a MIDI note on from the PD patch.
public IntIntIntEvent MidiNoteOn;
/// UnityEvent that will be invoked whenever we recieve a MIDI CC from the PD patch.
public IntIntIntEvent MidiControlChange;
/// UnityEvent that will be invoked whenever we recieve a MIDI program change from the PD patch.
public IntIntEvent MidiProgramChange;
/// UnityEvent that will be invoked whenever we recieve a MIDI pitch bend from the PD patch.
public IntIntEvent MidiPitchBend;
/// UnityEvent that will be invoked whenever we recieve a MIDI aftertouch from the PD patch.
public IntIntEvent MidiAftertouch;
/// UnityEvent that will be invoked whenever we recieve a polyphonic MIDI aftertouch from the PD patch.
public IntIntIntEvent MidiPolyAftertouch;
/// UnityEvent that will be invoked whenever we recieve a MIDI byte from the PD patch.
public IntIntEvent MidiByte;
};
public MidiEvents midiEvents;
#endregion
#region MonoBehaviour methods
//--------------------------------------------------------------------------
/// Initialise LibPd.
void Awake()
{
// Initialise libpd, if it's not already.
if (!pdInitialised)
{
// Setup hooks.
printHook = new LibPdPrintHook(PrintOutput);
libpd_set_queued_printhook(printHook);
bangHook = new LibPdBangHook(BangOutput);
libpd_set_queued_banghook(bangHook);
floatHook = new LibPdFloatHook(FloatOutput);
libpd_set_queued_floathook(floatHook);
symbolHook = new LibPdSymbolHook(SymbolOutput);
libpd_set_queued_symbolhook(symbolHook);
listHook = new LibPdListHook(ListOutput);
libpd_set_queued_listhook(listHook);
messageHook = new LibPdMessageHook(MessageOutput);
libpd_set_queued_messagehook(messageHook);
noteOnHook = new LibPdMidiNoteOnHook(MidiNoteOnOutput);
libpd_set_queued_noteonhook(noteOnHook);
controlChangeHook = new LibPdMidiControlChangeHook(MidiControlChangeOutput);
libpd_set_queued_controlchangehook(controlChangeHook);
programChangeHook = new LibPdMidiProgramChangeHook(MidiProgramChangeOutput);
libpd_set_queued_programchangehook(programChangeHook);
pitchBendHook = new LibPdMidiPitchBendHook(MidiPitchBendOutput);
libpd_set_queued_pitchbendhook(pitchBendHook);
aftertouchHook = new LibPdMidiAftertouchHook(MidiAftertouchOutput);
libpd_set_queued_aftertouchhook(aftertouchHook);
polyAftertouchHook = new LibPdMidiPolyAftertouchHook(MidiPolyAftertouchOutput);
libpd_set_queued_polyaftertouchhook(polyAftertouchHook);
midiByteHook = new LibPdMidiByteHook(MidiByteOutput);
libpd_set_queued_midibytehook(midiByteHook);
// Initialise libpd if possible, report any errors.
int initErr = libpd_queued_init();
if(initErr != 0)
{
Debug.LogWarning("Warning; libpd_init() returned " + initErr);
Debug.LogWarning("(if you're running this in the editor that probably just means this isn't the first time you've run your game, and is not a problem)");
}
pdInitialised = true;
// Try and add the patch directory to libpd's search path for
// loading externals (still can't seem to load externals when
// running in Unity though).
if(patchDir != String.Empty)
libpd_add_to_search_path(Application.streamingAssetsPath + patchDir);
// Make sure our static pipePrintToConsole variable is set
// correctly.
pipePrintToConsoleStatic = pipePrintToConsole;
}
else
pipePrintToConsole = pipePrintToConsoleStatic;
// Calc numTicks.
int bufferSize;
int noOfBuffers;
AudioSettings.GetDSPBufferSize (out bufferSize, out noOfBuffers);
numTicks = bufferSize/libpd_blocksize();
// Create our instance.
instance = libpd_new_instance();
// Set our instance.
libpd_set_instance(instance);
// Initialise audio.
int err = libpd_init_audio(2, 2, AudioSettings.outputSampleRate);
if(err != 0)
{
pdFail = true;
Debug.LogError(gameObject.name + ": Could not initialise Pure Data audio. Error = " + err);
}
else
{
if(patchName == String.Empty)
{
Debug.LogError(gameObject.name + ": No patch was assigned to this LibPdInstance.");
patchFail = true;
}
else
{
//Create our bindings dictionary.
bindings = new Dictionary<string, IntPtr>();
// Open our patch.
patchPointer = libpd_openfile(patchName + ".pd",
Application.streamingAssetsPath + patchDir);
if(patchPointer == IntPtr.Zero)
{
Debug.LogError(gameObject.name +
": Could not open patch. Directory: " +
Application.streamingAssetsPath + patchDir +
" Patch: " + patchName + ".pd");
patchFail = true;
}
// Turn on audio processing.
libpd_start_message(1);
libpd_add_float(1.0f);
libpd_finish_message("pd", "dsp");
}
}
}
//--------------------------------------------------------------------------
/// We only add ourselves to activeInstances when we're enabled.
void OnEnable()
{
if(!pdFail && !patchFail)
activeInstances.Add(this);
}
//--------------------------------------------------------------------------
/// Remove ourselves from activeInstances if we're disabled.
void OnDisable()
{
activeInstances.Remove(this);
}
//--------------------------------------------------------------------------
/// Close the patch file on quit.
void OnDestroy()
{
if(!pdFail && !patchFail)
{
libpd_set_instance(instance);
libpd_start_message(1);
libpd_add_float(0.0f);
libpd_finish_message("pd", "dsp");
foreach(var ptr in bindings.Values)
libpd_unbind(ptr);
bindings.Clear();
libpd_closefile(patchPointer);
libpd_free_instance(instance);
}
//If we're the last instance left, release libpd's ringbuffer.
if(pdInitialised && (activeInstances.Count < 1))
{
if(printHook != null)
{
printHook = null;
libpd_set_queued_printhook(printHook);
}
libpd_queued_release();
pdInitialised = false;
}
}
//--------------------------------------------------------------------------
/// We use this to dispatch any events that we've been sent from libpd.
/// Any send/MIDI events sent from libpd will be sent from the audio thread,
/// so we have to queue them and send them from the main thread, or Unity
/// will get very upset.
public void Update()
{
//Receive any queued messages.
/*!
We use this slightly hacky if statement to ensure we only receive
messages once per frame.
*/
if(this == activeInstances[0])
{
libpd_queued_receive_pd_messages();
libpd_queued_receive_midi_messages();
}
}
//--------------------------------------------------------------------------
/// This function updates our static pipePrintToConsole variable when the
/// public one changes, and ensures all other active LibPdInstances are
/// updated too.
private void OnValidate()
{
if(pipePrintToConsoleStatic != pipePrintToConsole)
{
pipePrintToConsoleStatic = pipePrintToConsole;
LibPdInstance[] activePatches = FindObjectsOfType<LibPdInstance>();
for(int i=0;i<activePatches.Length;++i)
activePatches[i].pipePrintToConsole = pipePrintToConsoleStatic;
}
#if UNITY_EDITOR
string lastName = patchName;
//We use this to store the name of our PD patch as a string, as we need
//to feed the filename and directory into libpd.
if(patch != null)
patchName = patch.name;
if((lastName != patchName) ||
((patch != null) && (patchDir == null)) ||
((patch != null) && (patchDir != null) && (patchDir.IndexOf("StreamingAssets") != -1))) //This is unfortunately necessary to upgrade the serialised data saved from versions of LibPdIntegration < v2.0.1.
{
patchDir = AssetDatabase.GetAssetPath(patch.GetInstanceID());
//Strip out "Assets/StreamingAssets", as the files won't be in the
//Assets folder in a built version, only when running in the editor.
patchDir = patchDir.Substring(patchDir.IndexOf("Assets/StreamingAssets") + 22);
//Remove the name of the patch, as we only need the directory.
patchDir = patchDir.Substring(0, patchDir.LastIndexOf('/') + 1);
}
#endif
}
//--------------------------------------------------------------------------
/// Process audio.
void OnAudioFilterRead(float[] data, int channels)
{
if(!pdFail && !patchFail)
{
libpd_set_instance(instance);
libpd_process_float(numTicks, data, data);
}
}
#endregion
#region public methods
//--------------------------------------------------------------------------
/// Returns the dollar-zero ID for the patch instance.
public int GetDollarZero()
{
return libpd_getdollarzero(patchPointer);
}
//--------------------------------------------------------------------------
/// Bind to a named object in the patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void Bind(string symbol)
{
libpd_set_instance(instance);
IntPtr ptr = libpd_bind(symbol);
bindings.Add(symbol, ptr);
}
//--------------------------------------------------------------------------
/// Release an existing binding.
[MethodImpl(MethodImplOptions.Synchronized)]
public void UnBind(string symbol)
{
//Don't try and unbind a symbol that doesn't exist in our bindings
//dictionary.
if(bindings.ContainsKey(symbol))
{
libpd_set_instance(instance);
libpd_unbind(bindings[symbol]);
bindings.Remove(symbol);
}
}
//--------------------------------------------------------------------------
/// Send a bang to the named receive object.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendBang(string receiver)
{
libpd_set_instance(instance);
int err = libpd_bang(receiver);
if(err == -1)
Debug.LogWarning(gameObject.name + "::SendBang(): Could not find " + receiver + " object.");
}
//--------------------------------------------------------------------------
/// Send a float to the named receive object.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendFloat(string receiver, float val)
{
libpd_set_instance(instance);
int err = libpd_float(receiver, val);
if(err == -1)
Debug.LogWarning(gameObject.name + "::SendFloat(): Could not find " + receiver + " object.");
}
//--------------------------------------------------------------------------
/// Send a symbol to the named receive object.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendSymbol(string receiver, string symbol)
{
libpd_set_instance(instance);
if(libpd_symbol(receiver, symbol) != 0)
Debug.LogWarning(gameObject.name + "::SendSymbol(): Could not find " + receiver + " object.");
}
//--------------------------------------------------------------------------
/// Send a list to the named receive object.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendList(string receiver, params object[] args)
{
ProcessArgs(args);
if(libpd_finish_list(receiver) != 0)
Debug.LogWarning(gameObject.name + "::SendList(): Could not send list. receiver = " + receiver);
}
//--------------------------------------------------------------------------
/// Send a message to the named receive object.
/// <param name="destination">The name of the object to send the message
/// to.</param>
/// <param name="symbol">The message keyword.</param>
/// <param name="args">A list of values to send to the named object.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMessage(string destination,
string symbol,
params object[] args)
{
ProcessArgs(args);
if(libpd_finish_message(destination, symbol) != 0)
Debug.LogWarning(gameObject.name + "::SendMessage(): Could not send message. destination = " + destination + " symbol = " + symbol);
}
//--------------------------------------------------------------------------
/// Send a MIDI note to the open patch.
/// <param name="channel">The MIDI channel number (libpd expects channels to
/// be in the range 0-15).</param>
/// <param name="pitch">The MIDI note number (0-127).</param>
/// <param name="velocity">The velocity of the note (0-127). Sending a
/// velocity of 0 will usually be interpreted as a note off.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiNoteOn(int channel, int pitch, int velocity)
{
libpd_set_instance(instance);
if(libpd_noteon(channel, pitch, velocity) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiNoteOn(): input parameter(s) out of range. channel = " + channel + " pitch = " + pitch + " velocity = " + velocity);
}
//--------------------------------------------------------------------------
/// Send a MIDI control change to the open patch.
/// <param name="controller">The controller number (0-127).</param>
/// <param name="value">The controller value (0-127).</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiCc(int channel, int controller, int value)
{
libpd_set_instance(instance);
if(libpd_controlchange(channel, controller, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiCc(): input parameter(s) out of range. channel = " + channel + " controller = " + controller + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI program change to the open patch.
/// <param name="value">The program to change to (0-127).</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiProgramChange(int channel, int value)
{
libpd_set_instance(instance);
if(libpd_programchange(channel, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiProgramChange(): input parameter(s) out of range. channel = " + channel + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI pitch bend to the open patch.
/// <param name ="value">The bend value has a range -8192 -> +8192.</param>
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiPitchBend(int channel, int value)
{
libpd_set_instance(instance);
if(libpd_pitchbend(channel, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidPitchBend(): input parameter(s) out of range. channel = " + channel + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI aftertouch message to the open patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiAftertouch(int channel, int value)
{
libpd_set_instance(instance);
if(libpd_aftertouch(channel, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiAftertouch(): input parameter(s) out of range. channel = " + channel + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI polyphonic aftertouch to the open patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiPolyAftertouch(int channel, int pitch, int value)
{
libpd_set_instance(instance);
if(libpd_polyaftertouch(channel, pitch, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiPolyAftertouch(): input parameter(s) out of range. channel = " + channel + " pitch = " + pitch + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI byte(?) to the open patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiByte(int port, int value)
{
libpd_set_instance(instance);
if(libpd_midibyte(port, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiByte(): input parameter(s) out of range. port = " + port + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI sysex byte(?) to the open patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiSysex(int port, int value)
{
libpd_set_instance(instance);
if(libpd_sysex(port, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidSysex(): input parameter(s) out of range. port = " + port + " value = " + value);
}
//--------------------------------------------------------------------------
/// Send a MIDI sysrealtime byte(?) to the open patch.
[MethodImpl(MethodImplOptions.Synchronized)]
public void SendMidiSysRealtime(int port, int value)
{
libpd_set_instance(instance);
if (libpd_sysrealtime(port, value) != 0)
Debug.LogWarning(gameObject.name + "::SendMidiSysRealtime(): input parameter(s) out of range. port = " + port + " value = " + value);
}
//--------------------------------------------------------------------------
/// Returns the size of the named array in the open patch.
/// <returns>Returns a negative value if the named array doesn't
/// exist.</returns>
public int ArraySize(string name)
{
libpd_set_instance(instance);
return libpd_arraysize(name);
}
//--------------------------------------------------------------------------
/// Reads a set of values from the named array.
/// <param name="dest">C# array to write the values of the Pd array into.
/// NOTE: make sure you allocate the array before you call this method, and
/// make sure its size is >= (count - offset).</param>
/// <param name="src">Name of the array in your Pd patch.</param>
/// <param name="offset">Offset into the Pd array to start reading
/// from.</param>
/// <param name="count">Number of elements to read from the Pd array.</param>
public void ReadArray(float[] dest, string src, int offset, int count)
{
libpd_set_instance(instance);
//Note: the wiki says libpd_read_array() is supposed to return an error
//code if the array doesn't exist or [offset -> offset+n] lies outside
//the array, but looking at the code in z_libpd.c it will always return
//0 (success). In case that ever changes we write any errors to Debug,
//but for the time being our error checking code is extraneous.
if(libpd_read_array(dest, src, offset, count) < 0)
Debug.LogWarning(gameObject.name + "::ReadArray(): Array [" + src + "] does not exist OR the desired range lies outside the array's range.");
}
//--------------------------------------------------------------------------
/// Writes an array of C# floats into the named Pd array.
/// <param name="dest">Name of the array in your Pd patch.</param>
/// <param name="offset">Offset into the Pd array to start writing
/// to.</param>
/// <param name="src">The C# array of values to write into the Pd patch.
/// Make sure this contains >= count elements.</param>
/// <param name="count">The number of elements to write into the Pd
/// array.</param>
public void WriteArray(string dest, int offset, float[] src, int count)
{
libpd_set_instance(instance);
//The same note (ReadArray) re: return values applies here.
if(libpd_write_array(dest, offset, src, count) < 0)
Debug.LogWarning(gameObject.name + "::WriteArray(): Array [" + dest + "] does not exist OR the desired range lies outside the array's range.");
}
#endregion
#region delegate definitions
//--------------------------------------------------------------------------
/// Receive print messages.
[MonoPInvokeCallback(typeof(LibPdPrintHook))]
private static void PrintOutput(string message)
{
if(pipePrintToConsoleStatic)
Debug.Log("libpd: " + message);
}
//--------------------------------------------------------------------------
/// Receive bang messages.
[MonoPInvokeCallback(typeof(LibPdBangHook))]
private static void BangOutput(string symbol)
{
foreach(LibPdInstance instance in activeInstances)
instance.pureDataEvents.Bang.Invoke(symbol);
}
//--------------------------------------------------------------------------
/// Receive float messages.
[MonoPInvokeCallback(typeof(LibPdFloatHook))]
private static void FloatOutput(string symbol, float val)
{
foreach(LibPdInstance instance in activeInstances)
instance.pureDataEvents.Float.Invoke(symbol, val);
}
//--------------------------------------------------------------------------
/// Receive symbol messages.
[MonoPInvokeCallback(typeof(LibPdSymbolHook))]
private static void SymbolOutput(string symbol, string val)
{
foreach(LibPdInstance instance in activeInstances)
instance.pureDataEvents.Symbol.Invoke(symbol, val);
}
//--------------------------------------------------------------------------
/// Receive lists.
[MonoPInvokeCallback(typeof(LibPdListHook))]
private static void ListOutput(string source, int argc, IntPtr argv)
{
var args = ConvertList(argc, argv);
foreach(LibPdInstance instance in activeInstances)
instance.pureDataEvents.List.Invoke(source, args);
}
//--------------------------------------------------------------------------
/// Receive messages.
[MonoPInvokeCallback(typeof(LibPdMessageHook))]
private static void MessageOutput(string source, string symbol, int argc, IntPtr argv)
{
var args = ConvertList(argc, argv);
foreach(LibPdInstance instance in activeInstances)
instance.pureDataEvents.Message.Invoke(source, symbol, args);
}
//--------------------------------------------------------------------------
/// Receive MIDI note on messages.
[MonoPInvokeCallback(typeof(LibPdMidiNoteOnHook))]
private static void MidiNoteOnOutput(int channel, int pitch, int velocity)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiNoteOn.Invoke(channel, pitch, velocity);
}
//--------------------------------------------------------------------------
/// Receive MIDI control change messages.
[MonoPInvokeCallback(typeof(LibPdMidiControlChangeHook))]
private static void MidiControlChangeOutput(int channel, int controller, int value)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiControlChange.Invoke(channel, controller, value);
}
//--------------------------------------------------------------------------
/// Receive MIDI program change messages.
[MonoPInvokeCallback(typeof(LibPdMidiProgramChangeHook))]
private static void MidiProgramChangeOutput(int channel, int program)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiProgramChange.Invoke(channel, program);
}
//--------------------------------------------------------------------------
/// Receive MIDI pitch bend messages.
[MonoPInvokeCallback(typeof(LibPdMidiPitchBendHook))]
private static void MidiPitchBendOutput(int channel, int value)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiPitchBend.Invoke(channel, value);
}
//--------------------------------------------------------------------------
/// Receive MIDI aftertouch messages.
[MonoPInvokeCallback(typeof(LibPdMidiAftertouchHook))]
private static void MidiAftertouchOutput(int channel, int value)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiAftertouch.Invoke(channel, value);
}
//--------------------------------------------------------------------------
/// Receive MIDI polyphonic aftertouch messages.
[MonoPInvokeCallback(typeof(LibPdMidiPolyAftertouchHook))]
private static void MidiPolyAftertouchOutput(int channel, int pitch, int value)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiPolyAftertouch.Invoke(channel, pitch, value);
}
//--------------------------------------------------------------------------
/// Receive MIDI byte messages.
[MonoPInvokeCallback(typeof(LibPdMidiByteHook))]
private static void MidiByteOutput(int channel, int value)
{
foreach(LibPdInstance instance in activeInstances)
instance.midiEvents.MidiByte.Invoke(channel, value);
}
#endregion
#region private methods
//--------------------------------------------------------------------------
/// Helper method used by SendList() and SendMessage().
private void ProcessArgs(object[] args)
{
if(args.Length < 1)
Debug.LogWarning(gameObject.name + "::ProcessArgs(): no arguments passed in for list or message.");
else
{
if(libpd_start_message(args.Length) != 0)
Debug.LogWarning(gameObject.name + "::ProcessArgs(): Could not allocate memory for list or message.");
else
{
foreach(object arg in args)
{
if(arg is int?)
libpd_add_float((float)((int?)arg));
else if(arg is float?)
libpd_add_float((float)((float?)arg));
else if(arg is double?)
libpd_add_float((float)((double?)arg));
else if(arg is string)
libpd_add_symbol((string)arg);
else
Debug.LogWarning(gameObject.name + "::ProcessArgs(): Cannot process argument of type " + arg.GetType() + " for list or message.");
}
}
}
}
//--------------------------------------------------------------------------
/// Helper method. Used by ListOutput() and MessageOutput().
private static object[] ConvertList(int argc, IntPtr argv)
{
var retval = new object[argc];
for(int i=0;i<argc;++i)
{
if(libpd_is_float(argv) != 0)
retval[i] = libpd_get_float(argv);
else if(libpd_is_symbol(argv) != 0)
retval[i] = Marshal.PtrToStringAnsi(libpd_get_symbol(argv));
if(i < (argc-1))
argv = libpd_next_atom(argv);
}
return retval;
}
#endregion
}