mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-04 06:38:47 +00:00
Add vendor:portmidi
This commit is contained in:
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
PortMidi Portable Real-Time MIDI Library
|
||||||
|
PortMidi API Header File
|
||||||
|
Latest version available at: http://sourceforge.net/projects/portmedia
|
||||||
|
|
||||||
|
Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||||
|
Copyright (c) 2001-2006 Roger B. Dannenberg
|
||||||
|
|
||||||
|
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.
|
||||||
Vendored
+89
@@ -0,0 +1,89 @@
|
|||||||
|
package portmidi
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PortMidi Portable Real-Time MIDI Library
|
||||||
|
* PortMidi API Header File
|
||||||
|
* Latest version available at: http://sourceforge.net/projects/portmedia
|
||||||
|
*
|
||||||
|
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
|
||||||
|
* Copyright (c) 2001-2006 Roger B. Dannenberg
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The text above constitutes the entire PortMidi license; however,
|
||||||
|
* the PortMusic community also makes the following non-binding requests:
|
||||||
|
*
|
||||||
|
* Any person wishing to distribute modifications to the Software is
|
||||||
|
* requested to send the modifications to the original developer so that
|
||||||
|
* they can be incorporated into the canonical version. It is also
|
||||||
|
* requested that these non-binding requests be included along with the
|
||||||
|
* license above.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* CHANGELOG FOR PORTMIDI
|
||||||
|
* (see ../CHANGELOG.txt)
|
||||||
|
*
|
||||||
|
* NOTES ON HOST ERROR REPORTING:
|
||||||
|
*
|
||||||
|
* PortMidi errors (of type PmError) are generic, system-independent errors.
|
||||||
|
* When an error does not map to one of the more specific PmErrors, the
|
||||||
|
* catch-all code pmHostError is returned. This means that PortMidi has
|
||||||
|
* retained a more specific system-dependent error code. The caller can
|
||||||
|
* get more information by calling Pm_HasHostError() to test if there is
|
||||||
|
* a pending host error, and Pm_GetHostErrorText() to get a text string
|
||||||
|
* describing the error. Host errors are reported on a per-device basis
|
||||||
|
* because only after you open a device does PortMidi have a place to
|
||||||
|
* record the host error code. I.e. only
|
||||||
|
* those routines that receive a (PortMidiStream *) argument check and
|
||||||
|
* report errors. One exception to this is that Pm_OpenInput() and
|
||||||
|
* Pm_OpenOutput() can report errors even though when an error occurs,
|
||||||
|
* there is no PortMidiStream* to hold the error. Fortunately, both
|
||||||
|
* of these functions return any error immediately, so we do not really
|
||||||
|
* need per-device error memory. Instead, any host error code is stored
|
||||||
|
* in a global, pmHostError is returned, and the user can call
|
||||||
|
* Pm_GetHostErrorText() to get the error message (and the invalid stream
|
||||||
|
* parameter will be ignored.) The functions
|
||||||
|
* pm_init and pm_term do not fail or raise
|
||||||
|
* errors. The job of pm_init is to locate all available devices so that
|
||||||
|
* the caller can get information via PmDeviceInfo(). If an error occurs,
|
||||||
|
* the device is simply not listed as available.
|
||||||
|
*
|
||||||
|
* Host errors come in two flavors:
|
||||||
|
* a) host error
|
||||||
|
* b) host error during callback
|
||||||
|
* These can occur w/midi input or output devices. (b) can only happen
|
||||||
|
* asynchronously (during callback routines), whereas (a) only occurs while
|
||||||
|
* synchronously running PortMidi and any resulting system dependent calls.
|
||||||
|
* Both (a) and (b) are reported by the next read or write call. You can
|
||||||
|
* also query for asynchronous errors (b) at any time by calling
|
||||||
|
* Pm_HasHostError().
|
||||||
|
*
|
||||||
|
* NOTES ON COMPILE-TIME SWITCHES
|
||||||
|
*
|
||||||
|
* DEBUG assumes stdio and a console. Use this if you want automatic, simple
|
||||||
|
* error reporting, e.g. for prototyping. If you are using MFC or some
|
||||||
|
* other graphical interface with no console, DEBUG probably should be
|
||||||
|
* undefined.
|
||||||
|
* PM_CHECK_ERRORS more-or-less takes over error checking for return values,
|
||||||
|
* stopping your program and printing error messages when an error
|
||||||
|
* occurs. This also uses stdio for console text I/O.
|
||||||
|
*/
|
||||||
Vendored
+508
@@ -0,0 +1,508 @@
|
|||||||
|
package portmidi
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
|
when ODIN_OS == "windows" do foreign import lib "portmidi.lib"
|
||||||
|
|
||||||
|
#assert(size_of(b32) == size_of(c.int));
|
||||||
|
|
||||||
|
DEFAULT_SYSEX_BUFFER_SIZE :: 1024;
|
||||||
|
|
||||||
|
Error :: enum c.int {
|
||||||
|
NoError = 0,
|
||||||
|
NoData = 0, /**< A "no error" return that also indicates no data avail. */
|
||||||
|
GotData = 1, /**< A "no error" return that also indicates data available */
|
||||||
|
HostError = -10000,
|
||||||
|
InvalidDeviceId, /** out of range or
|
||||||
|
* output device when input is requested or
|
||||||
|
* input device when output is requested or
|
||||||
|
* device is already opened
|
||||||
|
*/
|
||||||
|
InsufficientMemory,
|
||||||
|
BufferTooSmall,
|
||||||
|
BufferOverflow,
|
||||||
|
BadPtr, /* Stream parameter is nil or
|
||||||
|
* stream is not opened or
|
||||||
|
* stream is output when input is required or
|
||||||
|
* stream is input when output is required */
|
||||||
|
BadData, /** illegal midi data, e.g. missing EOX */
|
||||||
|
InternalError,
|
||||||
|
BufferMaxSize, /** buffer is already as large as it can be */
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A single Stream is a descriptor for an open MIDI device.
|
||||||
|
*/
|
||||||
|
Stream :: distinct rawptr;
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/**
|
||||||
|
Initialize() is the library initialisation function - call this before
|
||||||
|
using the library.
|
||||||
|
*/
|
||||||
|
Initialize :: proc() -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Terminate() is the library termination function - call this after
|
||||||
|
using the library.
|
||||||
|
*/
|
||||||
|
Terminate :: proc() -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Test whether stream has a pending host error. Normally, the client finds
|
||||||
|
out about errors through returned error codes, but some errors can occur
|
||||||
|
asynchronously where the client does not
|
||||||
|
explicitly call a function, and therefore cannot receive an error code.
|
||||||
|
The client can test for a pending error using HasHostError(). If true,
|
||||||
|
the error can be accessed and cleared by calling GetErrorText().
|
||||||
|
Errors are also cleared by calling other functions that can return
|
||||||
|
errors, e.g. OpenInput(), OpenOutput(), Read(), Write(). The
|
||||||
|
client does not need to call HasHostError(). Any pending error will be
|
||||||
|
reported the next time the client performs an explicit function call on
|
||||||
|
the stream, e.g. an input or output operation. Until the error is cleared,
|
||||||
|
no new error codes will be obtained, even for a different stream.
|
||||||
|
*/
|
||||||
|
HasHostError :: proc(stream: Stream) -> b32 ---
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translate portmidi error number into human readable message.
|
||||||
|
These strings are constants (set at compile time) so client has
|
||||||
|
no need to allocate storage
|
||||||
|
*/
|
||||||
|
GetErrorText :: proc (errnum: Error) -> string {
|
||||||
|
@(default_calling_convention="c")
|
||||||
|
foreign lib {
|
||||||
|
Pm_GetErrorText :: proc(errnum: Error) -> cstring ---
|
||||||
|
}
|
||||||
|
return string(Pm_GetErrorText(errnum));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translate portmidi host error into human readable message.
|
||||||
|
These strings are computed at run time, so client has to allocate storage.
|
||||||
|
After this routine executes, the host error is cleared.
|
||||||
|
*/
|
||||||
|
GetHostErrorText :: proc (buf: []byte) -> string {
|
||||||
|
@(default_calling_convention="c")
|
||||||
|
foreign lib {
|
||||||
|
Pm_GetHostErrorText :: proc(msg: [^]u8, len: c.uint) ---
|
||||||
|
}
|
||||||
|
Pm_GetHostErrorText(raw_data(buf), u32(len(buf)));
|
||||||
|
str := string(buf[:]);
|
||||||
|
return strings.truncate_to_byte(str, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HDRLENGTH :: 50;
|
||||||
|
HOST_ERROR_MSG_LEN :: 256; /* any host error msg will occupy less
|
||||||
|
than this number of characters */
|
||||||
|
|
||||||
|
DeviceID :: distinct c.int;
|
||||||
|
NoDevice :: DeviceID(-1);
|
||||||
|
DeviceInfo :: struct {
|
||||||
|
structVersion: c.int, /**< this internal structure version */
|
||||||
|
interf: cstring, /**< underlying MIDI API, e.g. MMSystem or DirectX */
|
||||||
|
name: cstring, /**< device name, e.g. USB MidiSport 1x1 */
|
||||||
|
input: c.int, /**< true iff input is available */
|
||||||
|
output: c.int, /**< true iff output is available */
|
||||||
|
opened: b32, /**< used by generic PortMidi code to do error checking on arguments */
|
||||||
|
}
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/** Get devices count, ids range from 0 to CountDevices()-1. */
|
||||||
|
CountDevices :: proc() -> c.int ---
|
||||||
|
|
||||||
|
GetDefaultInputDeviceID :: proc() -> DeviceID ---
|
||||||
|
GetDefaultOutputDeviceID :: proc() -> DeviceID ---
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Timestamp is used to represent a millisecond clock with arbitrary
|
||||||
|
start time. The type is used for all MIDI timestampes and clocks.
|
||||||
|
*/
|
||||||
|
Timestamp :: distinct i32;
|
||||||
|
TimeProc :: proc "c" (time_info: rawptr) -> Timestamp;
|
||||||
|
|
||||||
|
Before :: #force_inline proc "c" (t1, t2: Timestamp) -> b32 {
|
||||||
|
return b32((t1-t2) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/**
|
||||||
|
GetDeviceInfo() returns a pointer to a DeviceInfo structure
|
||||||
|
referring to the device specified by id.
|
||||||
|
If id is out of range the function returns nil.
|
||||||
|
|
||||||
|
The returned structure is owned by the PortMidi implementation and must
|
||||||
|
not be manipulated or freed. The pointer is guaranteed to be valid
|
||||||
|
between calls to Initialize() and Terminate().
|
||||||
|
*/
|
||||||
|
GetDeviceInfo :: proc(id: DeviceID) -> DeviceInfo ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
OpenInput() and OpenOutput() open devices.
|
||||||
|
|
||||||
|
stream is the address of a Stream pointer which will receive
|
||||||
|
a pointer to the newly opened stream.
|
||||||
|
|
||||||
|
inputDevice is the id of the device used for input (see DeviceID above).
|
||||||
|
|
||||||
|
inputDriverInfo is a pointer to an optional driver specific data structure
|
||||||
|
containing additional information for device setup or handle processing.
|
||||||
|
inputDriverInfo is never required for correct operation. If not used
|
||||||
|
inputDriverInfo should be nil.
|
||||||
|
|
||||||
|
outputDevice is the id of the device used for output (see DeviceID above.)
|
||||||
|
|
||||||
|
outputDriverInfo is a pointer to an optional driver specific data structure
|
||||||
|
containing additional information for device setup or handle processing.
|
||||||
|
outputDriverInfo is never required for correct operation. If not used
|
||||||
|
outputDriverInfo should be nil.
|
||||||
|
|
||||||
|
For input, the buffersize specifies the number of input events to be
|
||||||
|
buffered waiting to be read using Read(). For output, buffersize
|
||||||
|
specifies the number of output events to be buffered waiting for output.
|
||||||
|
(In some cases -- see below -- PortMidi does not buffer output at all
|
||||||
|
and merely passes data to a lower-level API, in which case buffersize
|
||||||
|
is ignored.)
|
||||||
|
|
||||||
|
latency is the delay in milliseconds applied to timestamps to determine
|
||||||
|
when the output should actually occur. (If latency is < 0, 0 is assumed.)
|
||||||
|
If latency is zero, timestamps are ignored and all output is delivered
|
||||||
|
immediately. If latency is greater than zero, output is delayed until the
|
||||||
|
message timestamp plus the latency. (NOTE: the time is measured relative
|
||||||
|
to the time source indicated by time_proc. Timestamps are absolute,
|
||||||
|
not relative delays or offsets.) In some cases, PortMidi can obtain
|
||||||
|
better timing than your application by passing timestamps along to the
|
||||||
|
device driver or hardware. Latency may also help you to synchronize midi
|
||||||
|
data to audio data by matching midi latency to the audio buffer latency.
|
||||||
|
|
||||||
|
time_proc is a pointer to a procedure that returns time in milliseconds. It
|
||||||
|
may be nil, in which case a default millisecond timebase (PortTime) is
|
||||||
|
used. If the application wants to use PortTime, it should start the timer
|
||||||
|
(call Pt_Start) before calling OpenInput or OpenOutput. If the
|
||||||
|
application tries to start the timer *after* OpenInput or OpenOutput,
|
||||||
|
it may get a ptAlreadyStarted error from Pt_Start, and the application's
|
||||||
|
preferred time resolution and callback function will be ignored.
|
||||||
|
time_proc result values are appended to incoming MIDI data, and time_proc
|
||||||
|
times are used to schedule outgoing MIDI data (when latency is non-zero).
|
||||||
|
|
||||||
|
time_info is a pointer passed to time_proc.
|
||||||
|
|
||||||
|
Example: If I provide a timestamp of 5000, latency is 1, and time_proc
|
||||||
|
returns 4990, then the desired output time will be when time_proc returns
|
||||||
|
timestamp+latency = 5001. This will be 5001-4990 = 11ms from now.
|
||||||
|
|
||||||
|
return value:
|
||||||
|
Upon success Open() returns NoError and places a pointer to a
|
||||||
|
valid Stream in the stream argument.
|
||||||
|
If a call to Open() fails a nonzero error code is returned (see
|
||||||
|
PMError above) and the value of port is invalid.
|
||||||
|
|
||||||
|
Any stream that is successfully opened should eventually be closed
|
||||||
|
by calling Close().
|
||||||
|
|
||||||
|
*/
|
||||||
|
OpenInput :: proc(stream: ^Stream,
|
||||||
|
inputDevice: DeviceID,
|
||||||
|
inputDriverInfo: rawptr,
|
||||||
|
bufferSize: i32,
|
||||||
|
time_proc: TimeProc,
|
||||||
|
time_info: rawptr) -> Error ---
|
||||||
|
|
||||||
|
OpenOutput :: proc(stream: ^Stream,
|
||||||
|
outputDevice: DeviceID,
|
||||||
|
outputDriverInfo: rawptr,
|
||||||
|
bufferSize: i32,
|
||||||
|
time_proc: TimeProc,
|
||||||
|
time_info: rawptr,
|
||||||
|
latency: i32) -> Error ---
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/**
|
||||||
|
SetFilter() sets filters on an open input stream to drop selected
|
||||||
|
input types. By default, only active sensing messages are filtered.
|
||||||
|
To prohibit, say, active sensing and sysex messages, call
|
||||||
|
SetFilter(stream, FILT_ACTIVE | FILT_SYSEX);
|
||||||
|
|
||||||
|
Filtering is useful when midi routing or midi thru functionality is being
|
||||||
|
provided by the user application.
|
||||||
|
For example, you may want to exclude timing messages (clock, MTC, start/stop/continue),
|
||||||
|
while allowing note-related messages to pass.
|
||||||
|
Or you may be using a sequencer or drum-machine for MIDI clock information but want to
|
||||||
|
exclude any notes it may play.
|
||||||
|
*/
|
||||||
|
SetFilter :: proc(stream: Stream, filters: i32) -> Error ---
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Filter bit-mask definitions */
|
||||||
|
/** filter active sensing messages (0xFE): */
|
||||||
|
FILT_ACTIVE :: 1 << 0x0E;
|
||||||
|
/** filter system exclusive messages (0xF0): */
|
||||||
|
FILT_SYSEX :: 1 << 0x00;
|
||||||
|
/** filter MIDI clock message (0xF8) */
|
||||||
|
FILT_CLOCK :: 1 << 0x08;
|
||||||
|
/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
|
||||||
|
FILT_PLAY :: (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B);
|
||||||
|
/** filter tick messages (0xF9) */
|
||||||
|
FILT_TICK :: 1 << 0x09;
|
||||||
|
/** filter undefined FD messages */
|
||||||
|
FILT_FD :: 1 << 0x0D;
|
||||||
|
/** filter undefined real-time messages */
|
||||||
|
FILT_UNDEFINED :: FILT_FD;
|
||||||
|
/** filter reset messages (0xFF) */
|
||||||
|
FILT_RESET :: 1 << 0x0F;
|
||||||
|
/** filter all real-time messages */
|
||||||
|
FILT_REALTIME :: FILT_ACTIVE | FILT_SYSEX | FILT_CLOCK | FILT_PLAY | FILT_UNDEFINED | FILT_RESET | FILT_TICK;
|
||||||
|
/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
|
||||||
|
FILT_NOTE :: (1 << 0x19) | (1 << 0x18);
|
||||||
|
/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
|
||||||
|
FILT_CHANNEL_AFTERTOUCH :: 1 << 0x1D;
|
||||||
|
/** per-note aftertouch (0xA0-0xAF) */
|
||||||
|
FILT_POLY_AFTERTOUCH :: 1 << 0x1A;
|
||||||
|
/** filter both channel and poly aftertouch */
|
||||||
|
FILT_AFTERTOUCH :: FILT_CHANNEL_AFTERTOUCH | FILT_POLY_AFTERTOUCH;
|
||||||
|
/** Program changes (0xC0-0xCF) */
|
||||||
|
FILT_PROGRAM :: 1 << 0x1C;
|
||||||
|
/** Control Changes (CC's) (0xB0-0xBF)*/
|
||||||
|
FILT_CONTROL :: 1 << 0x1B;
|
||||||
|
/** Pitch Bender (0xE0-0xEF*/
|
||||||
|
FILT_PITCHBEND :: 1 << 0x1E;
|
||||||
|
/** MIDI Time Code (0xF1)*/
|
||||||
|
FILT_MTC :: 1 << 0x01;
|
||||||
|
/** Song Position (0xF2) */
|
||||||
|
FILT_SONG_POSITION :: 1 << 0x02;
|
||||||
|
/** Song Select (0xF3)*/
|
||||||
|
FILT_SONG_SELECT :: 1 << 0x03;
|
||||||
|
/** Tuning request (0xF6)*/
|
||||||
|
FILT_TUNE :: 1 << 0x06;
|
||||||
|
/** All System Common messages (mtc, song position, song select, tune request) */
|
||||||
|
FILT_SYSTEMCOMMON :: FILT_MTC | FILT_SONG_POSITION | FILT_SONG_SELECT | FILT_TUNE;
|
||||||
|
|
||||||
|
Channel :: #force_inline proc "c" (channel: c.int) -> c.int {
|
||||||
|
return 1<<c.uint(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/**
|
||||||
|
SetChannelMask() filters incoming messages based on channel.
|
||||||
|
The mask is a 16-bit bitfield corresponding to appropriate channels.
|
||||||
|
The _Channel macro can assist in calling this function.
|
||||||
|
i.e. to set receive only input on channel 1, call with
|
||||||
|
SetChannelMask(Channel(1));
|
||||||
|
Multiple channels should be OR'd together, like
|
||||||
|
SetChannelMask(Channel(10) | Channel(11))
|
||||||
|
|
||||||
|
Note that channels are numbered 0 to 15 (not 1 to 16). Most
|
||||||
|
synthesizer and interfaces number channels starting at 1, but
|
||||||
|
PortMidi numbers channels starting at 0.
|
||||||
|
|
||||||
|
All channels are allowed by default
|
||||||
|
*/
|
||||||
|
SetChannelMask :: proc(stream: Stream, mask: c.int) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Abort() terminates outgoing messages immediately
|
||||||
|
The caller should immediately close the output port;
|
||||||
|
this call may result in transmission of a partial midi message.
|
||||||
|
There is no abort for Midi input because the user can simply
|
||||||
|
ignore messages in the buffer and close an input device at
|
||||||
|
any time.
|
||||||
|
*/
|
||||||
|
Abort :: proc(stream: Stream) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Close() closes a midi stream, flushing any pending buffers.
|
||||||
|
(PortMidi attempts to close open streams when the application
|
||||||
|
exits -- this is particularly difficult under Windows.)
|
||||||
|
*/
|
||||||
|
Close :: proc(stream: Stream) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Synchronize() instructs PortMidi to (re)synchronize to the
|
||||||
|
time_proc passed when the stream was opened. Typically, this
|
||||||
|
is used when the stream must be opened before the time_proc
|
||||||
|
reference is actually advancing. In this case, message timing
|
||||||
|
may be erratic, but since timestamps of zero mean
|
||||||
|
"send immediately," initialization messages with zero timestamps
|
||||||
|
can be written without a functioning time reference and without
|
||||||
|
problems. Before the first MIDI message with a non-zero
|
||||||
|
timestamp is written to the stream, the time reference must
|
||||||
|
begin to advance (for example, if the time_proc computes time
|
||||||
|
based on audio samples, time might begin to advance when an
|
||||||
|
audio stream becomes active). After time_proc return values
|
||||||
|
become valid, and BEFORE writing the first non-zero timestamped
|
||||||
|
MIDI message, call Synchronize() so that PortMidi can observe
|
||||||
|
the difference between the current time_proc value and its
|
||||||
|
MIDI stream time.
|
||||||
|
|
||||||
|
In the more normal case where time_proc
|
||||||
|
values advance continuously, there is no need to call
|
||||||
|
Synchronize. PortMidi will always synchronize at the
|
||||||
|
first output message and periodically thereafter.
|
||||||
|
*/
|
||||||
|
Synchronize :: proc(stream: Stream) -> Error ---
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
MessageMake() encodes a short Midi message into a 32-bit word. If data1
|
||||||
|
and/or data2 are not present, use zero.
|
||||||
|
|
||||||
|
MessageStatus(), MessageData1(), and
|
||||||
|
MessageData2() extract fields from a 32-bit midi message.
|
||||||
|
*/
|
||||||
|
MessageMake :: #force_inline proc "c" (status: c.int, data1, data2: c.int) -> Message {
|
||||||
|
return Message(((data2 << 16) & 0xFF0000) | ((data1 << 8) & 0xFF00) | (status & 0xFF));
|
||||||
|
}
|
||||||
|
MessageStatus :: #force_inline proc "c" (msg: Message) -> c.int {
|
||||||
|
return c.int(msg & 0xFF);
|
||||||
|
}
|
||||||
|
MessageData1 :: #force_inline proc "c" (msg: Message) -> c.int {
|
||||||
|
return c.int((msg >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
MessageData2 :: #force_inline proc "c" (msg: Message) -> c.int {
|
||||||
|
return c.int((msg >> 16) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message :: distinct i32;
|
||||||
|
/**
|
||||||
|
All midi data comes in the form of Event structures. A sysex
|
||||||
|
message is encoded as a sequence of Event structures, with each
|
||||||
|
structure carrying 4 bytes of the message, i.e. only the first
|
||||||
|
Event carries the status byte.
|
||||||
|
|
||||||
|
Note that MIDI allows nested messages: the so-called "real-time" MIDI
|
||||||
|
messages can be inserted into the MIDI byte stream at any location,
|
||||||
|
including within a sysex message. MIDI real-time messages are one-byte
|
||||||
|
messages used mainly for timing (see the MIDI spec). PortMidi retains
|
||||||
|
the order of non-real-time MIDI messages on both input and output, but
|
||||||
|
it does not specify exactly how real-time messages are processed. This
|
||||||
|
is particulary problematic for MIDI input, because the input parser
|
||||||
|
must either prepare to buffer an unlimited number of sysex message
|
||||||
|
bytes or to buffer an unlimited number of real-time messages that
|
||||||
|
arrive embedded in a long sysex message. To simplify things, the input
|
||||||
|
parser is allowed to pass real-time MIDI messages embedded within a
|
||||||
|
sysex message, and it is up to the client to detect, process, and
|
||||||
|
remove these messages as they arrive.
|
||||||
|
|
||||||
|
When receiving sysex messages, the sysex message is terminated
|
||||||
|
by either an EOX status byte (anywhere in the 4 byte messages) or
|
||||||
|
by a non-real-time status byte in the low order byte of the message.
|
||||||
|
If you get a non-real-time status byte but there was no EOX byte, it
|
||||||
|
means the sysex message was somehow truncated. This is not
|
||||||
|
considered an error; e.g., a missing EOX can result from the user
|
||||||
|
disconnecting a MIDI cable during sysex transmission.
|
||||||
|
|
||||||
|
A real-time message can occur within a sysex message. A real-time
|
||||||
|
message will always occupy a full Event with the status byte in
|
||||||
|
the low-order byte of the Event message field. (This implies that
|
||||||
|
the byte-order of sysex bytes and real-time message bytes may not
|
||||||
|
be preserved -- for example, if a real-time message arrives after
|
||||||
|
3 bytes of a sysex message, the real-time message will be delivered
|
||||||
|
first. The first word of the sysex message will be delivered only
|
||||||
|
after the 4th byte arrives, filling the 4-byte Event message field.
|
||||||
|
|
||||||
|
The timestamp field is observed when the output port is opened with
|
||||||
|
a non-zero latency. A timestamp of zero means "use the current time",
|
||||||
|
which in turn means to deliver the message with a delay of
|
||||||
|
latency (the latency parameter used when opening the output port.)
|
||||||
|
Do not expect PortMidi to sort data according to timestamps --
|
||||||
|
messages should be sent in the correct order, and timestamps MUST
|
||||||
|
be non-decreasing. See also "Example" for OpenOutput() above.
|
||||||
|
|
||||||
|
A sysex message will generally fill many Event structures. On
|
||||||
|
output to a Stream with non-zero latency, the first timestamp
|
||||||
|
on sysex message data will determine the time to begin sending the
|
||||||
|
message. PortMidi implementations may ignore timestamps for the
|
||||||
|
remainder of the sysex message.
|
||||||
|
|
||||||
|
On input, the timestamp ideally denotes the arrival time of the
|
||||||
|
status byte of the message. The first timestamp on sysex message
|
||||||
|
data will be valid. Subsequent timestamps may denote
|
||||||
|
when message bytes were actually received, or they may be simply
|
||||||
|
copies of the first timestamp.
|
||||||
|
|
||||||
|
Timestamps for nested messages: If a real-time message arrives in
|
||||||
|
the middle of some other message, it is enqueued immediately with
|
||||||
|
the timestamp corresponding to its arrival time. The interrupted
|
||||||
|
non-real-time message or 4-byte packet of sysex data will be enqueued
|
||||||
|
later. The timestamp of interrupted data will be equal to that of
|
||||||
|
the interrupting real-time message to insure that timestamps are
|
||||||
|
non-decreasing.
|
||||||
|
*/
|
||||||
|
Event :: struct {
|
||||||
|
message: Message,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
/**
|
||||||
|
Read() retrieves midi data into a buffer, and returns the number
|
||||||
|
of events read. Result is a non-negative number unless an error occurs,
|
||||||
|
in which case a Error value will be returned.
|
||||||
|
|
||||||
|
Buffer Overflow
|
||||||
|
|
||||||
|
The problem: if an input overflow occurs, data will be lost, ultimately
|
||||||
|
because there is no flow control all the way back to the data source.
|
||||||
|
When data is lost, the receiver should be notified and some sort of
|
||||||
|
graceful recovery should take place, e.g. you shouldn't resume receiving
|
||||||
|
in the middle of a long sysex message.
|
||||||
|
|
||||||
|
With a lock-free fifo, which is pretty much what we're stuck with to
|
||||||
|
enable portability to the Mac, it's tricky for the producer and consumer
|
||||||
|
to synchronously reset the buffer and resume normal operation.
|
||||||
|
|
||||||
|
Solution: the buffer managed by PortMidi will be flushed when an overflow
|
||||||
|
occurs. The consumer (Read()) gets an error message (.BufferOverflow)
|
||||||
|
and ordinary processing resumes as soon as a new message arrives. The
|
||||||
|
remainder of a partial sysex message is not considered to be a "new
|
||||||
|
message" and will be flushed as well.
|
||||||
|
|
||||||
|
*/
|
||||||
|
Read :: proc(stream: Stream, buffer: [^]Event, length: i32) -> c.int ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Poll() tests whether input is available.
|
||||||
|
*/
|
||||||
|
Poll :: proc(stream: Stream) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
Write() writes midi data from a buffer. This may contain:
|
||||||
|
- short messages
|
||||||
|
or
|
||||||
|
- sysex messages that are converted into a sequence of Event
|
||||||
|
structures, e.g. sending data from a file or forwarding them
|
||||||
|
from midi input.
|
||||||
|
|
||||||
|
Use WriteSysEx() to write a sysex message stored as a contiguous
|
||||||
|
array of bytes.
|
||||||
|
|
||||||
|
Sysex data may contain embedded real-time messages.
|
||||||
|
*/
|
||||||
|
Write :: proc(stream: Stream, buffer: [^]Event, length: i32) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
WriteShort() writes a timestamped non-system-exclusive midi message.
|
||||||
|
Messages are delivered in order as received, and timestamps must be
|
||||||
|
non-decreasing. (But timestamps are ignored if the stream was opened
|
||||||
|
with latency = 0.)
|
||||||
|
*/
|
||||||
|
WriteShort :: proc(stream: Stream, whence: Timestamp, msg: Message) -> Error ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
WriteSysEx() writes a timestamped system-exclusive midi message.
|
||||||
|
*/
|
||||||
|
WriteSysEx :: proc(stream: Stream, whence: Timestamp, msg: cstring) -> Error ---
|
||||||
|
}
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
+121
@@ -0,0 +1,121 @@
|
|||||||
|
package portmidi
|
||||||
|
|
||||||
|
/* util.odin -- some helpful utilities for building midi
|
||||||
|
applications that use PortMidi
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
|
|
||||||
|
when ODIN_OS == "windows" do foreign import lib "portmidi_s.lib"
|
||||||
|
|
||||||
|
|
||||||
|
Queue :: distinct rawptr;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
A single-reader, single-writer queue is created by
|
||||||
|
QueueCreate(), which takes the number of messages and
|
||||||
|
the message size as parameters. The queue only accepts
|
||||||
|
fixed sized messages. Returns nil if memory cannot be allocated.
|
||||||
|
This queue implementation uses the "light pipe" algorithm which
|
||||||
|
operates correctly even with multi-processors and out-of-order
|
||||||
|
memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
|
||||||
|
Communication," Dr. Dobbs Portal, http://www.ddj.com/,
|
||||||
|
articleID=189401457, June 15, 2006. This algorithm requires
|
||||||
|
that messages be translated to a form where no words contain
|
||||||
|
zeros. Each word becomes its own "data valid" tag. Because of
|
||||||
|
this translation, we cannot return a pointer to data still in
|
||||||
|
the queue when the "peek" method is called. Instead, a buffer
|
||||||
|
is preallocated so that data can be copied there. QueuePeek()
|
||||||
|
dequeues a message into this buffer and returns a pointer to
|
||||||
|
it. A subsequent Dequeue() will copy from this buffer.
|
||||||
|
This implementation does not try to keep reader/writer data in
|
||||||
|
separate cache lines or prevent thrashing on cache lines.
|
||||||
|
However, this algorithm differs by doing inserts/removals in
|
||||||
|
units of messages rather than units of machine words. Some
|
||||||
|
performance improvement might be obtained by not clearing data
|
||||||
|
immediately after a read, but instead by waiting for the end
|
||||||
|
of the cache line, especially if messages are smaller than
|
||||||
|
cache lines. See the Dokumentov article for explanation.
|
||||||
|
The algorithm is extended to handle "overflow" reporting. To report
|
||||||
|
an overflow, the sender writes the current tail position to a field.
|
||||||
|
The receiver must acknowlege receipt by zeroing the field. The sender
|
||||||
|
will not send more until the field is zeroed.
|
||||||
|
|
||||||
|
QueueDestroy() destroys the queue and frees its storage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@(default_calling_convention="c", link_prefix="Pm_")
|
||||||
|
foreign lib {
|
||||||
|
QueueCreate :: proc(num_msgs: c.long, bytes_per_msg: i32) -> Queue ---
|
||||||
|
QueueDestroy :: proc(queue: Queue) -> Error ---
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dequeue() removes one item from the queue, copying it into msg.
|
||||||
|
Returns 1 if successful, and 0 if the queue is empty.
|
||||||
|
Returns .BufferOverflow if what would have been the next thing
|
||||||
|
in the queue was dropped due to overflow. (So when overflow occurs,
|
||||||
|
the receiver can receive a queue full of messages before getting the
|
||||||
|
overflow report. This protocol ensures that the reader will be
|
||||||
|
notified when data is lost due to overflow.
|
||||||
|
*/
|
||||||
|
Dequeue :: proc(queue: Queue, msg: rawptr) -> Error ---
|
||||||
|
|
||||||
|
/*
|
||||||
|
Enqueue() inserts one item into the queue, copying it from msg.
|
||||||
|
Returns .NoError if successful and .BufferOverflow if the queue was
|
||||||
|
already full. If .BufferOverflow is returned, the overflow flag is set.
|
||||||
|
*/
|
||||||
|
Enqueue :: proc(queue: Queue, msg: rawptr) -> Error ---
|
||||||
|
|
||||||
|
/*
|
||||||
|
QueueFull() returns non-zero if the queue is full
|
||||||
|
QueueEmpty() returns non-zero if the queue is empty
|
||||||
|
Either condition may change immediately because a parallel
|
||||||
|
enqueue or dequeue operation could be in progress. Furthermore,
|
||||||
|
QueueEmpty() is optimistic: it may say false, when due to
|
||||||
|
out-of-order writes, the full message has not arrived. Therefore,
|
||||||
|
Dequeue() could still return 0 after QueueEmpty() returns
|
||||||
|
false. On the other hand, QueueFull() is pessimistic: if it
|
||||||
|
returns false, then Enqueue() is guaranteed to succeed.
|
||||||
|
Error conditions: QueueFull() returns .BadPtr if queue is nil.
|
||||||
|
QueueEmpty() returns false if queue is nil.
|
||||||
|
*/
|
||||||
|
QueueFull :: proc(queue: Queue) -> b32 ---
|
||||||
|
QueueEmpty :: proc(queue: Queue) -> b32 ---
|
||||||
|
|
||||||
|
/*
|
||||||
|
QueuePeek() returns a pointer to the item at the head of the queue,
|
||||||
|
or NULL if the queue is empty. The item is not removed from the queue.
|
||||||
|
QueuePeek() will not indicate when an overflow occurs. If you want
|
||||||
|
to get and check .BufferOverflow messages, use the return value of
|
||||||
|
QueuePeek() *only* as an indication that you should call
|
||||||
|
Dequeue(). At the point where a direct call to Dequeue() would
|
||||||
|
return .BufferOverflow, QueuePeek() will return NULL but internally
|
||||||
|
clear the .BufferOverflow flag, enabling Enqueue() to resume
|
||||||
|
enqueuing messages. A subsequent call to QueuePeek()
|
||||||
|
will return a pointer to the first message *after* the overflow.
|
||||||
|
Using this as an indication to call Dequeue(), the first call
|
||||||
|
to Dequeue() will return .BufferOverflow. The second call will
|
||||||
|
return success, copying the same message pointed to by the previous
|
||||||
|
QueuePeek().
|
||||||
|
When to use QueuePeek(): (1) when you need to look at the message
|
||||||
|
data to decide who should be called to receive it. (2) when you need
|
||||||
|
to know a message is ready but cannot accept the message.
|
||||||
|
Note that QueuePeek() is not a fast check, so if possible, you
|
||||||
|
might as well just call Dequeue() and accept the data if it is there.
|
||||||
|
*/
|
||||||
|
QueuePeek :: proc(queue: Queue) -> rawptr ---
|
||||||
|
|
||||||
|
/*
|
||||||
|
SetOverflow() allows the writer (enqueuer) to signal an overflow
|
||||||
|
condition to the reader (dequeuer). E.g. when transfering data from
|
||||||
|
the OS to an application, if the OS indicates a buffer overrun,
|
||||||
|
SetOverflow() can be used to insure that the reader receives a
|
||||||
|
.BufferOverflow result from Dequeue(). Returns .BadPtr if queue
|
||||||
|
is NULL, returns .BufferOverflow if buffer is already in an overflow
|
||||||
|
state, returns .NoError if successfully set overflow state.
|
||||||
|
*/
|
||||||
|
SetOverflow :: proc(queue: Queue) -> Error ---
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user