/*
*
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.
*
*/
using LibPDBinding.Native;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
namespace LibPDBinding
{
///
/// LibPD provides basic C# bindings for pd. It follows the libpd Java bingings as good as possible.
///
/// Some random notes from Peter Brinkmann on the java bindings:
///
/// - This is a low-level library that aims to leave most design decisions to
/// higher-level code. In particular, it will throw no exceptions (except for the
/// methods for opening files, which may throw an when appropriate).
/// At the same time, it is designed to be
/// fairly robust in that it is thread-safe and does as much error checking as I
/// find reasonable at this level. Client code is still responsible for proper
/// dimensioning of buffers and such, though.
///
/// - The MIDI methods choose sanity over consistency with pd or the MIDI
/// standard. To wit, channel numbers always start at 0, and pitch bend values
/// are centered at 0, i.e., they range from -8192 to 8191.
///
/// - The basic idea is to turn pd into a library that essentially offers a
/// rendering callback (process) mimicking the design of JACK, the JACK Audio
/// Connection Kit.
///
/// - The release method is mostly there as a reminder that some sort of cleanup
/// might be necessary; for the time being, it only releases the resources held
/// by the print handler, closes all patches, and cancels all subscriptions.
/// Shutting down pd itself wouldn't make sense because it might be needed in the
/// future, at which point the native library may not be reloaded.
///
/// - I'm a little fuzzy on how/when to use sys_lock, sys_unlock, etc., and so I
/// decided to handle all synchronization on the Java side. It appears that
/// sys_lock is for top-level locking in scheduling routines only, and so
/// Java-side sync conveys the same benefits without the risk of deadlocks.
///
///
/// Author: Tebjan Halm (tebjan@vvvv.org)
///
///
[Obsolete ("Use the new class based API instead and initialize LibPDBinding.Managed.Pd")]
public static partial class LibPD
{
//only call this once
static LibPD ()
{
Init ();
}
#region Environment
static void Init ()
{
SetupHooks ();
General.libpd_init ();
}
///
/// You almost never have to call this! The only case is when the libpdcsharp.dll
/// was unloaded and you load it again into your application.
/// So be careful, it will also call Release() to clear all state.
/// The first initialization is done automatically when using a LibPD method.
///
[MethodImpl (MethodImplOptions.Synchronized)]
[Obsolete ("Use the new class based API instead and initialize a instance of LibPDBinding.Managed.Pd")]
public static void ReInit ()
{
Release ();
Init ();
}
//store open patches
private static Dictionary Patches = new Dictionary ();
///
/// clears the search path for pd externals
///
[MethodImpl (MethodImplOptions.Synchronized)]
public static void ClearSearchPath ()
{
General.clear_search_path ();
}
///
/// adds a directory to the search paths
///
/// directory to add
[MethodImpl (MethodImplOptions.Synchronized)]
[Obsolete ("Use searchPaths parameter in LibPDBinding.Managed.Pd()")]
public static void AddToSearchPath (string sym)
{
General.add_to_search_path (sym);
}
///
/// reads a patch from a file
///
/// to the file
/// an integer handle that identifies this patch; this handle is the
/// $0 value of the patch
///
/// thrown if the file doesn't exist or can't be opened
[MethodImpl (MethodImplOptions.Synchronized)]
[Obsolete ("Use LibPDBinding.Managed.Pd.LoadPatch()")]
public static int OpenPatch (string filepath)
{
if (filepath.StartsWith (".")) {
string currentDirectory = AppDomain.CurrentDomain.BaseDirectory;
filepath = Path.Combine (currentDirectory, filepath);
}
if (!File.Exists (filepath)) {
throw new FileNotFoundException (filepath);
}
var ptr = General.openfile (Path.GetFileName (filepath), Path.GetDirectoryName (filepath));
if (ptr == IntPtr.Zero) {
throw new IOException ("unable to open patch " + filepath);
}
var handle = General.getdollarzero (ptr);
Patches [handle] = ptr;
return handle;
}
///
/// closes a patch; will do nothing if the handle is invalid
///
/// $0 of the patch, as returned by OpenPatch
/// true if file was found and closed,
[Obsolete ("Use LibPDBinding.Managed.Patch.Dispose()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static bool ClosePatch (int p)
{
if (!Patches.ContainsKey (p))
return false;
var ptr = Patches [p];
General.closefile (ptr);
return Patches.Remove (p);
}
///
/// checks whether a symbol represents a pd object
///
/// String representing pd symbol
/// true if and only if the symbol given by s is associated with
/// something in pd
[Obsolete]
[MethodImpl (MethodImplOptions.Synchronized)]
public static bool Exists (string sym)
{
return General.exists (sym) != 0;
}
///
/// releases resources held by native bindings (PdReceiver object and
/// subscriptions); otherwise, the state of pd will remain unaffected
///
/// Note: It would be nice to free pd's I/O buffers here, but sys_close_audio
/// doesn't seem to do that, and so we'll just skip this for now.
///
[Obsolete ("Use LibPDBinding.Managed.Pd.Dispose()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static void Release ()
{
ComputeAudio (false);
foreach (var ptr in Bindings.Values) {
Messaging.unbind (ptr);
}
Bindings.Clear ();
foreach (var ptr in Patches.Values) {
General.closefile (ptr);
}
Patches.Clear ();
}
#endregion Environment
#region Audio
///
/// same as "compute audio" checkbox in pd gui, or [;pd dsp 0/1(
///
/// Note: Maintaining a DSP state that's separate from the state of the audio
/// rendering thread doesn't make much sense in libpd. In most applications,
/// you probably just want to call {@code computeAudio(true)} at the
/// beginning and then forget that this method exists.
///
///
[Obsolete ("Use LibPDBinding.Managed.Pd.Start() and LibPDBinding.Managed.Pd.Stop()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static void ComputeAudio (bool state)
{
SendMessage ("pd", "dsp", state ? 1 : 0);
}
///
/// default pd block size, DEFDACBLKSIZE (currently 64) (aka number
/// of samples per tick per channel)
///
[Obsolete ("Use LibPDBinding.Managed.Pd.BlockSize")]
public static int BlockSize {
[MethodImpl(MethodImplOptions.Synchronized)]
get {
return Audio.blocksize ();
}
}
///
/// sets up pd audio; must be called before process callback
///
///
///
///
/// error code, 0 on success
[Obsolete ("Use parameters inputChannels, outputChannels. and sampleRate in LibPDBinding.Managed.Pd()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int OpenAudio (int inputChannels, int outputChannels, int sampleRate)
{
return Audio.init_audio (inputChannels, outputChannels, sampleRate);
}
///
/// raw process callback, processes one pd tick, writes raw data to buffers
/// without interlacing
///
///
/// must be an array of the right size, never null; use inBuffer =
/// new short[0] if no input is desired
///
/// must be an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int ProcessRaw (float[] inBuffer, float[] outBuffer)
{
return Audio.process_raw (inBuffer, outBuffer);
}
///
/// raw process callback, processes one pd tick, writes raw data to buffers
/// without interlacing. use this method if you have a pointer to the local memory or raw byte arrays in the right format.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &myMemory[offset]
///
///
/// pointer to an array of the right size, never null; use inBuffer =
/// new short[0] if no input is desired
///
/// pointer to an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int ProcessRaw (float* inBuffer, float* outBuffer)
{
return Audio.process_raw (inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type float
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// must be an array of the right size, never null; use inBuffer =
/// new short[0] if no input is desired
///
/// must be an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int Process (int ticks, short[] inBuffer, short[] outBuffer)
{
return Audio.process_short (ticks, inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type float. use this method if you have a pointer to the local memory or raw byte arrays in the right format.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &myMemory[offset]
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// pointer to an array of the right size, never null; use inBuffer =
/// new short[0] if no input is desired
///
/// pointer to an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int Process (int ticks, short* inBuffer, short* outBuffer)
{
return Audio.process_short (ticks, inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type float
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// must be an array of the right size, never null; use inBuffer =
/// new float[0] if no input is desired
///
/// must be an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int Process (int ticks, float[] inBuffer, float[] outBuffer)
{
return Audio.process_float (ticks, inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type float. use this method if you have a pointer to the local memory or raw byte arrays in the right format.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &myMemory[offset]
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// pointer to an array of the right size, never null; use inBuffer =
/// new float[0] if no input is desired
///
/// pointer to an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int Process (int ticks, float* inBuffer, float* outBuffer)
{
return Audio.process_float (ticks, inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type float
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// must be an array of the right size, never null; use inBuffer =
/// new double[0] if no input is desired
///
/// must be an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int Process (int ticks, double[] inBuffer, double[] outBuffer)
{
return Audio.process_double (ticks, inBuffer, outBuffer);
}
///
/// main process callback, reads samples from inBuffer and writes samples to
/// outBuffer, using arrays of type double. use this method if you have a pointer to the local memory or raw byte arrays in the right format.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &myMemory[offset]
///
///
/// the number of Pd ticks (i.e., blocks of 64 frames) to compute
///
/// pointer to an array of the right size, never null; use inBuffer =
/// new double[0] if no input is desired
///
/// pointer an array of size outBufferSize from openAudio call
/// error code, 0 on success
[Obsolete ("Use LibPDBinding.Managed.Pd.Process()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int Process (int ticks, double* inBuffer, double* outBuffer)
{
return Audio.process_double (ticks, inBuffer, outBuffer);
}
#endregion Audio
#region Array
///
/// Get the size of an array
///
/// Identifier of array
/// array size
[Obsolete ("Use LibPDBinding.Managed.PdArray.Size")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int ArraySize (string name)
{
return Audio.arraysize (name);
}
///
/// read values from an array in Pd. if you need an offset use the pointer method and use pointer arithmetic.
///
/// float array to write to
/// array in Pd to read from
/// index at which to start reading
/// number of values to read
/// 0 on success, or a negative error code on failure
[Obsolete ("Use LibPDBinding.Managed.PdArray.Read()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int ReadArray (float[] destination, string source, int srcOffset, int n)
{
if (n > destination.Length) {
return -2;
}
return Audio.read_array (destination, source, srcOffset, n);
}
///
/// read values from an array in Pd. use this method if you have a pointer to the local memory.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &myDestinationMemory[offset]
///
/// pointer to float array to write to
/// array in Pd to read from
/// index at which to start reading
/// number of values to read
/// 0 on success, or a negative error code on failure
[Obsolete ("Use LibPDBinding.Managed.PdArray.Read()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int ReadArray (float* destination, string source, int srcOffset, int n)
{
return Audio.read_array (destination, source, srcOffset, n);
}
///
/// write values to an array in Pd. if you need an offset use the pointer method and use pointer arithmetic.
///
/// name of the array in Pd to write to
/// index at which to start writing
/// float array to read from
/// number of values to write
/// 0 on success, or a negative error code on failure
[Obsolete ("Use LibPDBinding.Managed.PdArray.Write()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static int WriteArray (string destination, int destOffset, float[] source, int n)
{
if (n > source.Length) {
return -2;
}
return Audio.write_array (destination, destOffset, source, n);
}
///
/// write values to an array in Pd. use this method if you have a pointer to the local memory.
/// you need to pin the memory yourself with a fixed{} code block. it also allows an offset using
/// pointer arithmetic like: &mySourceMemory[offset]
///
/// name of the array in Pd to write to
/// index at which to start writing
/// pointer to a float array to read from
/// number of values to write
/// 0 on success, or a negative error code on failure
[Obsolete ("Use LibPDBinding.Managed.PdArray.Write()")]
[MethodImpl (MethodImplOptions.Synchronized)]
public static unsafe int WriteArray (string destination, int destOffset, float* source, int n)
{
return Audio.write_array (destination, destOffset, source, n);
}
#endregion Array
}
}