/* * * 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 } }