1045 lines
37 KiB
C#
1045 lines
37 KiB
C#
// LibPdInstance.cs - Unity integration of libpd, supporting multiple instances.
|
|
// -----------------------------------------------------------------------------
|
|
// Copyright (c) 2018 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 System.Collections.Generic;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
/// <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.
|
|
///
|
|
/// Also, as it stands, this requires a small patch to z_libpd.c to allow us to
|
|
/// install our own print hook (so we can pipe print messages to Unity's
|
|
/// Console). Unfortunately, libpd requires the print hook to be set up before
|
|
/// libpd_init() is called, and will not accept any changes after that.
|
|
///
|
|
/// This causes major problems with Unity, as we want to set the print hook when
|
|
/// we start our game, and clear it when the game exits. However, because Unity
|
|
/// keeps native dlls active as long as the editor is running, libpd_init()
|
|
/// (being a one-time function) will effectively never get called again, and the
|
|
/// print hook will remain set to the value set when we first ran our game from
|
|
/// the editor. The result: if we try to run our game from the editor a second
|
|
/// time, we crash the entire editor.
|
|
///
|
|
/// For this reason the repository for this code includes pre-built libpd
|
|
/// binaries which include the print hook patch.
|
|
///
|
|
/// If you're building libpd from source yourself, you can get around this issue
|
|
/// by adding the following line to the end of libpd_set_printhook() in
|
|
/// z_libpd.c:
|
|
///
|
|
/// sys_printhook = libpd_printhook;
|
|
///
|
|
///
|
|
/// 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>
|
|
public class LibPdInstance : MonoBehaviour {
|
|
|
|
#region libpd imports
|
|
//--------------------------------------------------------------------------
|
|
/// libpd functions that we need to be able to call from C#.
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_init();
|
|
|
|
[DllImport("libpd")]
|
|
private static extern IntPtr libpd_new_instance();
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_instance(IntPtr instance);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_free_instance(IntPtr instance);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_init_audio(int inChans, int outChans, int sampleRate);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern IntPtr libpd_openfile([In] [MarshalAs(UnmanagedType.LPStr)] string basename,
|
|
[In] [MarshalAs(UnmanagedType.LPStr)] string dirname);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_closefile(IntPtr p);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_process_float(int ticks,
|
|
[In] float[] inBuffer,
|
|
[Out] float[] outBuffer);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_blocksize();
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_start_message(int max_length);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_add_float(float x);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_add_symbol([In] [MarshalAs(UnmanagedType.LPStr)] string sym);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_finish_list([In] [MarshalAs(UnmanagedType.LPStr)] string recv);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_finish_message([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
|
|
[In] [MarshalAs(UnmanagedType.LPStr)] string msg);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_bang([In] [MarshalAs(UnmanagedType.LPStr)] string recv);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_float([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
|
|
float x);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_symbol([In] [MarshalAs(UnmanagedType.LPStr)] string recv,
|
|
[In] [MarshalAs(UnmanagedType.LPStr)] string sym);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_exists([In] [MarshalAs(UnmanagedType.LPStr)] string obj);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern IntPtr libpd_bind([In] [MarshalAs(UnmanagedType.LPStr)] string symbol);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_unbind(IntPtr binding);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_verbose(int verbose);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_is_float(IntPtr atom);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_is_symbol(IntPtr atom);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern float libpd_get_float(IntPtr atom);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern IntPtr libpd_get_symbol(IntPtr atom);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern IntPtr libpd_next_atom(IntPtr atom);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_noteon(int channel,
|
|
int pitch,
|
|
int velocity);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_controlchange(int channel,
|
|
int controller,
|
|
int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_programchange(int channel, int program);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_pitchbend(int channel, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_aftertouch(int channel, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_polyaftertouch(int channel,
|
|
int pitch,
|
|
int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_midibyte(int port, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_sysex(int port, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_sysrealtime(int port, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_arraysize([In] [MarshalAs(UnmanagedType.LPStr)] string name);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_read_array([Out] float[] dest,
|
|
[In] [MarshalAs(UnmanagedType.LPStr)] string src,
|
|
int offset,
|
|
int n);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern int libpd_write_array([In] [MarshalAs(UnmanagedType.LPStr)] string dest,
|
|
int offset,
|
|
[In] float[] src,
|
|
int n);
|
|
#endregion
|
|
|
|
#region delegate/libpd callback declarations
|
|
//--------------------------------------------------------------------------
|
|
// Delegates/libpd callbacks.
|
|
|
|
//-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("libpd")]
|
|
private static extern void libpd_set_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("libpd")]
|
|
private static extern void libpd_set_banghook(LibPdBangHook hook);
|
|
|
|
private LibPdBangHook bangHook;
|
|
|
|
/// Public delegate for receiving bang events.
|
|
public delegate void LibPdBang(string receiver);
|
|
/// Bang event; subscribe to this to receive bangs.
|
|
public static event LibPdBang Bang = delegate{};
|
|
|
|
//-Float hook---------------------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdFloatHook([In] [MarshalAs(UnmanagedType.LPStr)] string symbol,
|
|
float val);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_floathook(LibPdFloatHook hook);
|
|
|
|
private LibPdFloatHook floatHook;
|
|
|
|
/// Public delegate for receiving float events.
|
|
public delegate void LibPdFloat(string receiver, float val);
|
|
/// Bang event; subscribe to this to receive floats.
|
|
public static event LibPdFloat Float = delegate{};
|
|
|
|
//-Symbol hook--------------------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdSymbolHook([In] [MarshalAs(UnmanagedType.LPStr)] string symbol,
|
|
[In] [MarshalAs(UnmanagedType.LPStr)] string val);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_symbolhook(LibPdSymbolHook hook);
|
|
|
|
private LibPdSymbolHook symbolHook;
|
|
|
|
/// Public delegate for receiving symbol events.
|
|
public delegate void LibPdSymbol(string receiver, string val);
|
|
/// Bang event; subscribe to this to receive symbols.
|
|
public static event LibPdSymbol Symbol = delegate{};
|
|
|
|
//-List hook----------------------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdListHook([In] [MarshalAs(UnmanagedType.LPStr)] string source,
|
|
int argc,
|
|
IntPtr argv);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_listhook(LibPdListHook hook);
|
|
|
|
private LibPdListHook listHook;
|
|
|
|
/// Public delegate for receiving lists.
|
|
public delegate void LibPdList(string source, object[] args);
|
|
/// Bang event; subscribe to this to receive lists.
|
|
public static event LibPdList List = delegate{};
|
|
|
|
//-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("libpd")]
|
|
private static extern void libpd_set_messagehook(LibPdMessageHook hook);
|
|
|
|
private LibPdMessageHook messageHook;
|
|
|
|
/// Public delegate for receiving messages.
|
|
public delegate void LibPdMessage(string source,
|
|
string symbol,
|
|
object[] args);
|
|
/// Bang event; subscribe to this to messages.
|
|
public static event LibPdMessage Message = delegate{};
|
|
|
|
//-MIDI Note On hook--------------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiNoteOnHook(int channel,
|
|
int pitch,
|
|
int velocity);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_noteonhook(LibPdMidiNoteOnHook hook);
|
|
|
|
private LibPdMidiNoteOnHook noteOnHook;
|
|
|
|
/// Public delegate for receiving MIDI note on events.
|
|
public delegate void LibPdMidiNoteOn(int channel, int pitch, int velocity);
|
|
/// Bang event; subscribe to this to receive MIDI note on events.
|
|
public static event LibPdMidiNoteOn MidiNoteOn = delegate {};
|
|
|
|
//-MIDI Control Change hook-------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiControlChangeHook(int channel,
|
|
int controller,
|
|
int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_controlchangehook(LibPdMidiControlChangeHook hook);
|
|
|
|
private LibPdMidiControlChangeHook controlChangeHook;
|
|
|
|
/// Public delegate for receiving MIDI control change events.
|
|
public delegate void LibPdMidiControlChange(int channel,
|
|
int controller,
|
|
int value);
|
|
/// Bang event; subscribe to this to receive MIDI control change events.
|
|
public static event LibPdMidiControlChange MidiControlChange = delegate {};
|
|
|
|
//-MIDI Program Change hook-------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiProgramChangeHook(int channel, int program);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_programchangehook(LibPdMidiProgramChangeHook hook);
|
|
|
|
private LibPdMidiProgramChangeHook programChangeHook;
|
|
|
|
/// Public delegate for receiving MIDI program change events.
|
|
public delegate void LibPdMidiProgramChange(int channel, int program);
|
|
/// Bang event; subscribe to this to receive MIDI program change events.
|
|
public static event LibPdMidiProgramChange MidiProgramChange = delegate {};
|
|
|
|
//-MIDI Pitch Bend hook-----------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiPitchBendHook(int channel, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_pitchbendhook(LibPdMidiPitchBendHook hook);
|
|
|
|
private LibPdMidiPitchBendHook pitchBendHook;
|
|
|
|
/// Public delegate for receiving MIDI pitch bend events.
|
|
public delegate void LibPdMidiPitchBend(int channel, int value);
|
|
/// Bang event; subscribe to this to receive MIDI pitch bend events.
|
|
public static event LibPdMidiPitchBend MidiPitchBend = delegate {};
|
|
|
|
//-MIDI Aftertouch hook-----------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiAftertouchHook(int channel, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_aftertouchhook(LibPdMidiAftertouchHook hook);
|
|
|
|
private LibPdMidiAftertouchHook aftertouchHook;
|
|
|
|
/// Public delegate for receiving MIDI aftertouch events.
|
|
public delegate void LibPdMidiAftertouch(int channel, int value);
|
|
/// Bang event; subscribe to this to receive MIDI aftertouch events.
|
|
public static event LibPdMidiAftertouch MidiAftertouch = delegate {};
|
|
|
|
//-MIDI Polyphonic Aftertouch hook------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiPolyAftertouchHook(int channel, int pitch, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_polyaftertouchhook(LibPdMidiPolyAftertouchHook hook);
|
|
|
|
private LibPdMidiPolyAftertouchHook polyAftertouchHook;
|
|
|
|
/// Public delegate for receiving MIDI polyphonic aftertouch events.
|
|
public delegate void LibPdMidiPolyAftertouch(int channel, int pitch, int value);
|
|
/// Bang event; subscribe to this to receive MIDI polyphonic aftertouch events.
|
|
public static event LibPdMidiPolyAftertouch MidiPolyAftertouch = delegate {};
|
|
|
|
//-MIDI Byte hook-----------------------------------------------------------
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
public delegate void LibPdMidiByteHook(int channel, int value);
|
|
|
|
[DllImport("libpd")]
|
|
private static extern void libpd_set_midibytehook(LibPdMidiByteHook hook);
|
|
|
|
private LibPdMidiByteHook midiByteHook;
|
|
|
|
/// Public delegate for receiving MIDI byte events.
|
|
public delegate void LibPdMidiByte(int channel, int value);
|
|
/// Bang event; subscribe to this to receive MIDI byte events.
|
|
public static event LibPdMidiByte MidiByte = delegate {};
|
|
#endregion
|
|
|
|
#region member variables
|
|
//--------------------------------------------------------------------------
|
|
/// The Pd patch this instance is running.
|
|
[HideInInspector]
|
|
public string patchName;
|
|
/// The path to the directory of the Pd patch this instance is running.
|
|
[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;
|
|
|
|
/// 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 MonoBehaviour methods
|
|
//--------------------------------------------------------------------------
|
|
/// Initialise LibPd.
|
|
void Awake ()
|
|
{
|
|
// Initialise libpd, if it's not already.
|
|
if(!pdInitialised)
|
|
{
|
|
// Setup hooks.
|
|
printHook = new LibPdPrintHook(PrintOutput);
|
|
libpd_set_printhook(printHook);
|
|
|
|
bangHook = new LibPdBangHook(BangOutput);
|
|
libpd_set_banghook(bangHook);
|
|
|
|
floatHook = new LibPdFloatHook(FloatOutput);
|
|
libpd_set_floathook(floatHook);
|
|
|
|
symbolHook = new LibPdSymbolHook(SymbolOutput);
|
|
libpd_set_symbolhook(symbolHook);
|
|
|
|
listHook = new LibPdListHook(ListOutput);
|
|
libpd_set_listhook(listHook);
|
|
|
|
messageHook = new LibPdMessageHook(MessageOutput);
|
|
libpd_set_messagehook(messageHook);
|
|
|
|
noteOnHook = new LibPdMidiNoteOnHook(MidiNoteOnOutput);
|
|
libpd_set_noteonhook(noteOnHook);
|
|
|
|
controlChangeHook = new LibPdMidiControlChangeHook(MidiControlChangeOutput);
|
|
libpd_set_controlchangehook(controlChangeHook);
|
|
|
|
programChangeHook = new LibPdMidiProgramChangeHook(MidiProgramChangeOutput);
|
|
libpd_set_programchangehook(programChangeHook);
|
|
|
|
pitchBendHook = new LibPdMidiPitchBendHook(MidiPitchBendOutput);
|
|
libpd_set_pitchbendhook(pitchBendHook);
|
|
|
|
aftertouchHook = new LibPdMidiAftertouchHook(MidiAftertouchOutput);
|
|
libpd_set_aftertouchhook(aftertouchHook);
|
|
|
|
polyAftertouchHook = new LibPdMidiPolyAftertouchHook(MidiPolyAftertouchOutput);
|
|
libpd_set_polyaftertouchhook(polyAftertouchHook);
|
|
|
|
midiByteHook = new LibPdMidiByteHook(MidiByteOutput);
|
|
libpd_set_midibytehook(midiByteHook);
|
|
|
|
// Initialise libpd if possible, report any errors.
|
|
int initErr = libpd_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;
|
|
|
|
// 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.dataPath + patchDir);
|
|
if(patchPointer == IntPtr.Zero)
|
|
{
|
|
Debug.LogError(gameObject.name + ": Could not open patch. Directory: " + (Application.dataPath + patchDir) + " Patch: " + patchName + ".pd");
|
|
patchFail = true;
|
|
}
|
|
|
|
// Turn on audio processing.
|
|
libpd_start_message(1);
|
|
libpd_add_float(1.0f);
|
|
libpd_finish_message("pd", "dsp");
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Close the patch file on quit.
|
|
void OnApplicationQuit ()
|
|
{
|
|
if(!pdFail && !patchFail)
|
|
{
|
|
libpd_set_instance(instance);
|
|
|
|
libpd_start_message(1);
|
|
libpd_add_float(0.0f);
|
|
libpd_finish_message("pd", "dsp");
|
|
|
|
//TODO: Is this correct? What happens if one LibPdInstance is
|
|
//destroyed while another stays alive?
|
|
if(printHook != null)
|
|
{
|
|
printHook = null;
|
|
libpd_set_printhook(printHook);
|
|
}
|
|
|
|
foreach(var ptr in bindings.Values)
|
|
libpd_unbind(ptr);
|
|
bindings.Clear();
|
|
|
|
libpd_closefile(patchPointer);
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// 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.
|
|
patchName = patch.name;
|
|
|
|
if(lastName != patchName) {
|
|
patchDir = AssetDatabase.GetAssetPath(patch.GetInstanceID());
|
|
|
|
//Strip out "Assets", 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") + 6);
|
|
|
|
//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
|
|
//--------------------------------------------------------------------------
|
|
/// 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)
|
|
{
|
|
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>
|
|
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>
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
static void PrintOutput(string message)
|
|
{
|
|
if(pipePrintToConsoleStatic)
|
|
Debug.Log("libpd: " + message);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive bang messages.
|
|
void BangOutput(string symbol)
|
|
{
|
|
Bang(symbol);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive float messages.
|
|
void FloatOutput(string symbol, float val)
|
|
{
|
|
Float(symbol, val);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive symbol messages.
|
|
void SymbolOutput(string symbol, string val)
|
|
{
|
|
Symbol(symbol, val);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive lists.
|
|
void ListOutput(string source, int argc, IntPtr argv)
|
|
{
|
|
var args = ConvertList(argc, argv);
|
|
|
|
List(source, args);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive messages.
|
|
void MessageOutput(string source, string symbol, int argc, IntPtr argv)
|
|
{
|
|
var args = ConvertList(argc, argv);
|
|
|
|
Message(source, symbol, args);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI note on messages.
|
|
void MidiNoteOnOutput(int channel, int pitch, int velocity)
|
|
{
|
|
MidiNoteOn(channel, pitch, velocity);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI control change messages.
|
|
void MidiControlChangeOutput(int channel, int controller, int value)
|
|
{
|
|
MidiControlChange(channel, controller, value);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI program change messages.
|
|
void MidiProgramChangeOutput(int channel, int program)
|
|
{
|
|
MidiProgramChange(channel, program);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI pitch bend messages.
|
|
void MidiPitchBendOutput(int channel, int value)
|
|
{
|
|
MidiPitchBend(channel, value);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI aftertouch messages.
|
|
void MidiAftertouchOutput(int channel, int value)
|
|
{
|
|
MidiAftertouch(channel, value);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI polyphonic aftertouch messages.
|
|
void MidiPolyAftertouchOutput(int channel, int pitch, int value)
|
|
{
|
|
MidiPolyAftertouch(channel, pitch, value);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
/// Receive MIDI byte messages.
|
|
void MidiByteOutput(int channel, int value)
|
|
{
|
|
MidiByte(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 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
|
|
}
|