Real time MIDI in PureBasic using external Lib (PortMIDI)

Just starting out? Need help? Post your questions and find answers here.
Khorus
User
User
Posts: 25
Joined: Sat Nov 13, 2010 11:22 pm

Real time MIDI in PureBasic using external Lib (PortMIDI)

Post by Khorus »

Hello all,

I've been writing software in PureBasic for a while, I just love the platform! It's working awesome for me. Now, I wrote some piece of software that deals with real time MIDI messages. I learnt how to do MIDI the hard way, using Windows system calls (WinMM) and succeeded in writing a functional utility that converts incoming MIDI messages on the fly (some sort of a MIDI translator really).

Now, I'm planning version 2.0 of my software. I've received many requests for a Mac version. I thought to myself: Bah, easy! PureBasic is cross platform, shouldn't be too hard, *WRONG*! Since I'm using so many low level DLL calls, the program I wrote is almost impossible to port to OS X without heavy modifications. On top of all that, I have to learn the OS X system calls for MIDI and such... I tried but there's almost no documentation on the net and PureBasic doesn't implement system calls on OS X like it works on Windows.

In my research for cross-platform compatibility, I toyed around with Python 2.7. It's really a nice language, easy to learn. The problem is that it's interpreted and feels a bit slow. If Python was a car, it'd be a big Cadilac! Super comfortable but not built for speed, if you know what I mean. One good thing: PyGame (a Python addon) supports cross-platform MIDI. PyGame uses an open-sourced and cross platform library called PortMIDI.

I would like to use PortMIDI in PureBasic. Simplifying all of the MIDI handling for Windows and OS X. I'm able to compile PortMIDI using Visual Studio 2010. I think I might have a working "portmidi.lib" file which can be used in PureBasic (I tried a "import" command successfully).

The problem I have now is that I don't know how to port the header file (.h) from PortMIDI's source tree. I'm not an expert in C/C++ and I have no clue on how to port/write the header part to PureBasic. I think we would all greatly benefit from a real time, cross platform, PureBasic compatible MIDI library.

Here's the code:

Code: Select all

#ifndef PORT_MIDI_H
#define PORT_MIDI_H
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

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

#ifndef WIN32
// Linux and OS X have stdint.h
#include <stdint.h>
#else
#ifndef INT32_DEFINED
// rather than having users install a special .h file for windows, 
// just put the required definitions inline here. porttime.h uses
// these too, so the definitions are (unfortunately) duplicated there
typedef int int32_t;
typedef unsigned int uint32_t;
#define INT32_DEFINED
#endif
#endif

#ifdef _WINDLL
#define PMEXPORT __declspec(dllexport)
#else
#define PMEXPORT 
#endif

#ifndef FALSE
    #define FALSE 0
#endif
#ifndef TRUE
    #define TRUE 1
#endif

/* default size of buffers for sysex transmission: */
#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024

/** List of portmidi errors.*/
typedef enum {
    pmNoError = 0,
    pmNoData = 0, /**< A "no error" return that also indicates no data avail. */
    pmGotData = 1, /**< A "no error" return that also indicates data available */
    pmHostError = -10000,
    pmInvalidDeviceId, /** out of range or 
                        * output device when input is requested or 
                        * input device when output is requested or
                        * device is already opened 
                        */
    pmInsufficientMemory,
    pmBufferTooSmall,
    pmBufferOverflow,
    pmBadPtr, /* PortMidiStream parameter is NULL or
               * stream is not opened or
               * stream is output when input is required or
               * stream is input when output is required */
    pmBadData, /** illegal midi data, e.g. missing EOX */
    pmInternalError,
    pmBufferMaxSize /** buffer is already as large as it can be */
    /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */
} PmError;

/**
    Pm_Initialize() is the library initialisation function - call this before
    using the library.
*/
PMEXPORT PmError Pm_Initialize( void );

/**
    Pm_Terminate() is the library termination function - call this after
    using the library.
*/
PMEXPORT PmError Pm_Terminate( void );

/**  A single PortMidiStream is a descriptor for an open MIDI device.
*/
typedef void PortMidiStream;
#define PmStream PortMidiStream

/**
    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 Pm_HasHostError(). If true,
    the error can be accessed and cleared by calling Pm_GetErrorText(). 
    Errors are also cleared by calling other functions that can return
    errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The
    client does not need to call Pm_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.
*/
PMEXPORT int Pm_HasHostError( PortMidiStream * stream );


/**  Translate portmidi error number into human readable message.
    These strings are constants (set at compile time) so client has 
    no need to allocate storage
*/
PMEXPORT const char *Pm_GetErrorText( PmError 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. 
*/
PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);

#define HDRLENGTH 50
#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less 
                                      than this number of characters */

/**
    Device enumeration mechanism.

    Device ids range from 0 to Pm_CountDevices()-1.

*/
typedef int PmDeviceID;
#define pmNoDevice -1
typedef struct {
    int structVersion; /**< this internal structure version */ 
    const char *interf; /**< underlying MIDI API, e.g. MMSystem or DirectX */
    const char *name;   /**< device name, e.g. USB MidiSport 1x1 */
    int input; /**< true iff input is available */
    int output; /**< true iff output is available */
    int opened; /**< used by generic PortMidi code to do error checking on arguments */

} PmDeviceInfo;

/**  Get devices count, ids range from 0 to Pm_CountDevices()-1. */
PMEXPORT int Pm_CountDevices( void );
/**
    Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID()

    Return the default device ID or pmNoDevice if there are no devices.
    The result (but not pmNoDevice) can be passed to Pm_OpenMidi().
    
    The default device can be specified using a small application
    named pmdefaults that is part of the PortMidi distribution. This
    program in turn uses the Java Preferences object created by
    java.util.prefs.Preferences.userRoot().node("/PortMidi"); the
    preference is set by calling 
        prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName);
    or  prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName);
    
    In the statements above, prefName is a string describing the
    MIDI device in the form "interf, name" where interf identifies
    the underlying software system or API used by PortMdi to access
    devices and name is the name of the device. These correspond to 
    the interf and name fields of a PmDeviceInfo. (Currently supported
    interfaces are "MMSystem" for Win32, "ALSA" for Linux, and 
    "CoreMIDI" for OS X, so in fact, there is no choice of interface.)
    In "interf, name", the strings are actually substrings of 
    the full interface and name strings. For example, the preference 
    "Core, Sport" will match a device with interface "CoreMIDI"
    and name "In USB MidiSport 1x1". It will also match "CoreMIDI"
    and "In USB MidiSport 2x2". The devices are enumerated in device
    ID order, so the lowest device ID that matches the pattern becomes
    the default device. Finally, if the comma-space (", ") separator
    between interface and name parts of the preference is not found,
    the entire preference string is interpreted as a name, and the
    interface part is the empty string, which matches anything.

    On the MAC, preferences are stored in 
      /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist
    which is a binary file. In addition to the pmdefaults program,
    there are utilities that can read and edit this preference file.

    On the PC, 

    On Linux, 

*/
PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void );
/** see PmDeviceID Pm_GetDefaultInputDeviceID() */
PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void );

/**
    PmTimestamp is used to represent a millisecond clock with arbitrary
    start time. The type is used for all MIDI timestampes and clocks.
*/
typedef int32_t PmTimestamp;
typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);

/** TRUE if t1 before t2 */
#define PmBefore(t1,t2) ((t1-t2) < 0)
/** 
    \defgroup grp_device Input/Output Devices Handling
    @{
*/
/**
    Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure
    referring to the device specified by id.
    If id is out of range the function returns NULL.

    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 Pm_Initialize() and Pm_Terminate().
*/
PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id );

/**
    Pm_OpenInput() and Pm_OpenOutput() open devices.

    stream is the address of a PortMidiStream pointer which will receive
    a pointer to the newly opened stream.

    inputDevice is the id of the device used for input (see PmDeviceID 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 NULL.

    outputDevice is the id of the device used for output (see PmDeviceID 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 NULL.

    For input, the buffersize specifies the number of input events to be 
    buffered waiting to be read using Pm_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 NULL, 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 Pm_OpenInput or Pm_OpenOutput. If the
    application tries to start the timer *after* Pm_OpenInput or Pm_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 Pm_Open() returns PmNoError and places a pointer to a
    valid PortMidiStream in the stream argument.
    If a call to Pm_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 Pm_Close().

*/
PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream,
                PmDeviceID inputDevice,
                void *inputDriverInfo,
                int32_t bufferSize,
                PmTimeProcPtr time_proc,
                void *time_info );

PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream,
                PmDeviceID outputDevice,
                void *outputDriverInfo,
                int32_t bufferSize,
                PmTimeProcPtr time_proc,
                void *time_info,
                int32_t latency );
  /** @} */

/**
   \defgroup grp_events_filters Events and Filters Handling
   @{
*/

/*  \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters )
    Pm_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
    Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_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.
 */
    
/* Filter bit-mask definitions */
/** filter active sensing messages (0xFE): */
#define PM_FILT_ACTIVE (1 << 0x0E)
/** filter system exclusive messages (0xF0): */
#define PM_FILT_SYSEX (1 << 0x00)
/** filter MIDI clock message (0xF8) */
#define PM_FILT_CLOCK (1 << 0x08)
/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
/** filter tick messages (0xF9) */
#define PM_FILT_TICK (1 << 0x09)
/** filter undefined FD messages */
#define PM_FILT_FD (1 << 0x0D)
/** filter undefined real-time messages */
#define PM_FILT_UNDEFINED PM_FILT_FD
/** filter reset messages (0xFF) */
#define PM_FILT_RESET (1 << 0x0F)
/** filter all real-time messages */
#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
    PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
/** per-note aftertouch (0xA0-0xAF) */
#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
/** filter both channel and poly aftertouch */
#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH)
/** Program changes (0xC0-0xCF) */
#define PM_FILT_PROGRAM (1 << 0x1C)
/** Control Changes (CC's) (0xB0-0xBF)*/
#define PM_FILT_CONTROL (1 << 0x1B)
/** Pitch Bender (0xE0-0xEF*/
#define PM_FILT_PITCHBEND (1 << 0x1E)
/** MIDI Time Code (0xF1)*/
#define PM_FILT_MTC (1 << 0x01)
/** Song Position (0xF2) */
#define PM_FILT_SONG_POSITION (1 << 0x02)
/** Song Select (0xF3)*/
#define PM_FILT_SONG_SELECT (1 << 0x03)
/** Tuning request (0xF6)*/
#define PM_FILT_TUNE (1 << 0x06)
/** All System Common messages (mtc, song position, song select, tune request) */
#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE)


PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters );

#define Pm_Channel(channel) (1<<(channel))
/**
    Pm_SetChannelMask() filters incoming messages based on channel.
    The mask is a 16-bit bitfield corresponding to appropriate channels.
    The Pm_Channel macro can assist in calling this function.
    i.e. to set receive only input on channel 1, call with
    Pm_SetChannelMask(Pm_Channel(1));
    Multiple channels should be OR'd together, like
    Pm_SetChannelMask(Pm_Channel(10) | Pm_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
*/
PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);

/**
    Pm_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.
 */
PMEXPORT PmError Pm_Abort( PortMidiStream* stream );
     
/**
    Pm_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.)
*/
PMEXPORT PmError Pm_Close( PortMidiStream* stream );

/**
    Pm_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 Pm_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 
    Pm_Synchronize. PortMidi will always synchronize at the 
    first output message and periodically thereafter.
*/
PmError Pm_Synchronize( PortMidiStream* stream );


/**
    Pm_Message() encodes a short Midi message into a 32-bit word. If data1
    and/or data2 are not present, use zero.

    Pm_MessageStatus(), Pm_MessageData1(), and 
    Pm_MessageData2() extract fields from a 32-bit midi message.
*/
#define Pm_Message(status, data1, data2) \
         ((((data2) << 16) & 0xFF0000) | \
          (((data1) << 8) & 0xFF00) | \
          ((status) & 0xFF))
#define Pm_MessageStatus(msg) ((msg) & 0xFF)
#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)

typedef int32_t PmMessage; /**< see PmEvent */
/**
   All midi data comes in the form of PmEvent structures. A sysex
   message is encoded as a sequence of PmEvent structures, with each
   structure carrying 4 bytes of the message, i.e. only the first
   PmEvent 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 PmEvent with the status byte in 
   the low-order byte of the PmEvent 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 PmEvent 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 Pm_OpenOutput() above.

   A sysex message will generally fill many PmEvent structures. On 
   output to a PortMidiStream 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.
 */
typedef struct {
    PmMessage      message;
    PmTimestamp    timestamp;
} PmEvent;

/** 
    @}
*/
/** \defgroup grp_io Reading and Writing Midi Messages
    @{
*/
/**
    Pm_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 PmError 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 (Pm_Read()) gets an error message (pmBufferOverflow)
    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.

*/
PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length );

/**
    Pm_Poll() tests whether input is available, 
    returning TRUE, FALSE, or an error value.
*/
PMEXPORT PmError Pm_Poll( PortMidiStream *stream);

/** 
    Pm_Write() writes midi data from a buffer. This may contain:
        - short messages 
    or 
        - sysex messages that are converted into a sequence of PmEvent
          structures, e.g. sending data from a file or forwarding them
          from midi input.

    Use Pm_WriteSysEx() to write a sysex message stored as a contiguous 
    array of bytes.

    Sysex data may contain embedded real-time messages.
*/
PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length );

/**
    Pm_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.)
*/
PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg);

/**
    Pm_WriteSysEx() writes a timestamped system-exclusive midi message.
*/
PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg);

/** @} */

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* PORT_MIDI_H */
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Hi,

try this:
Save it as libPortMidi.pbi

Code: Select all

;
; libPortMidi
;
; a wrapper for libPortMidi.dll
;


#PM_DEFAULT_SYSEX_BUFFER_SIZE = 1024

#pmNoError = 0
#pmNoData = 0   ; < A "no error" Return that also indicates no Data avail.
#pmGotData = 1  ; < A "no error" Return that also indicates Data available

Enumeration
  #pmHostError = -10000
  #pmInvalidDeviceId  ; out of range Or 
                      ; output device when input is requested Or 
                      ; input device when output is requested Or
                      ; device is already opened 
  #pmInsufficientMemory
  #pmBufferTooSmall
  #pmBufferOverflow
  #pmBadPtr           ; PortMidiStream parameter is NULL Or
                      ; stream is Not opened Or
                      ; stream is output when input is required Or
                      ; stream is input when output is required
  #pmBadData          ; illegal midi Data, e.g. missing EOX
  #pmInternalError
  #pmBufferMaxSize    ; buffer is already As large As it can be
EndEnumeration



#HDRLENGTH = 50
#PM_HOST_ERROR_MSG_LEN = 256  ; any host error msg will occupy less than this number of characters

#pmNoDevice = -1





#ptNoError = 0    ; success
Enumeration
  #ptHostError = -10000 ; a system-specific error occurred
  #ptAlreadyStarted     ; cannot start timer because it is already started
  #ptAlreadyStopped     ; cannot stop timer because it is already stopped
  #ptInsufficientMemory ; memory could Not be allocated
EndEnumeration





; Filter bit-mask definitions
; filter active sensing messages ($FE):
#PM_FILT_ACTIVE = (1 << $0E)
; filter system exclusive messages ($F0):
#PM_FILT_SYSEX = (1 << $00)
; filter MIDI clock message ($F8) 
#PM_FILT_CLOCK = (1 << $08)
; filter play messages (start $FA, stop $FC, Continue $FB)
#PM_FILT_PLAY = ((1 << $0A) | (1 << $0C) | (1 << $0B))
; filter tick messages ($F9)
#PM_FILT_TICK = (1 << $09)
; filter undefined FD messages
#PM_FILT_FD = (1 << $0D)
; filter undefined real-time messages
#PM_FILT_UNDEFINED = #PM_FILT_FD
; filter reset messages ($FF)
#PM_FILT_RESET = (1 << $0F)
; filter all real-time messages
#PM_FILT_REALTIME = (#PM_FILT_ACTIVE | #PM_FILT_SYSEX | #PM_FILT_CLOCK | #PM_FILT_PLAY | #PM_FILT_UNDEFINED | #PM_FILT_RESET | #PM_FILT_TICK)
; filter note-on And note-off ($90-$9F And $80-$8F
#PM_FILT_NOTE = ((1 << $19) | (1 << $18))
; filter channel aftertouch (most midi controllers use this) ($D0-$DF)
#PM_FILT_CHANNEL_AFTERTOUCH = (1 << $1D)
; per-note aftertouch ($A0-$AF)
#PM_FILT_POLY_AFTERTOUCH = (1 << $1A)
; filter both channel And poly aftertouch
#PM_FILT_AFTERTOUCH = (#PM_FILT_CHANNEL_AFTERTOUCH | #PM_FILT_POLY_AFTERTOUCH)
; Program changes ($C0-$CF)
#PM_FILT_PROGRAM = (1 << $1C)
; Control Changes (CC's) ($B0-$BF)
#PM_FILT_CONTROL = (1 << $1B)
; Pitch Bender ($E0-$EF
#PM_FILT_PITCHBEND = (1 << $1E)
; MIDI Time Code ($F1)
#PM_FILT_MTC = (1 << $01)
; Song Position ($F2)
#PM_FILT_SONG_POSITION = (1 << $02)
; Song Select ($F3)
#PM_FILT_SONG_SELECT = (1 << $03)
; Tuning request ($F6)
#PM_FILT_TUNE = (1 << $06)
; All System Common messages (mtc, song position, song Select, tune request)
#PM_FILT_SYSTEMCOMMON = (#PM_FILT_MTC | #PM_FILT_SONG_POSITION | #PM_FILT_SONG_SELECT | #PM_FILT_TUNE)




Macro Pm_Message(status, data1, data2)
  ((((data2) << 16) & $FF0000) | (((data1) << 8) & $FF00) | ((status) & $FF))
EndMacro
        
Macro Pm_MessageStatus(msg)
  ((msg) & $FF)
EndMacro

Macro Pm_MessageData1(msg)
  (((msg) >> 8) & $FF)
EndMacro

Macro Pm_MessageData2(msg)
  (((msg) >> 16) & $FF)
EndMacro



Macro Pm_Channel(channel)
  (1<<(channel))
EndMacro




Structure PmDeviceInfo
  structVersion.i   ; < this internal structure version
  *interf           ; < underlying MIDI API, e.g. MMSystem or DirectX
  *name             ; < device name, e.g. USB MidiSport 1x1
  input.i           ; < true iff input is available
  output.i          ; < true iff output is available
  opened.i          ; < used by generic PortMidi code to do error checking on arguments
EndStructure




Structure PmEvent
  message.l
  timestamp.l
EndStructure


CompilerSelect #PB_Compiler_OS
  CompilerCase #PB_OS_Windows
    Global libPortMidi_Filename$ = "libPortMidi.dll"
    Macro OSPrototype
      Prototype
    EndMacro
  CompilerCase #PB_OS_Linux
    Global libPortMidi_Filename$ = "libPortMidi.so"
    Macro OSPrototype
      PrototypeC
    EndMacro
  CompilerCase #PB_OS_MacOS
    Global libPortMidi_Filename$ = "libPortMidi.so"
    Macro OSPrototype
      PrototypeC
    EndMacro
CompilerEndSelect


OSPrototype.i Proto_Pm_Abort(*stream)
OSPrototype.i Proto_Pm_Close(*stream)
OSPrototype.i Proto_Pm_CountDevices()
OSPrototype.i Proto_Pm_Dequeue()
OSPrototype.i Proto_Pm_Enqueue()
OSPrototype.i Proto_Pm_GetDefaultInputDeviceID()
OSPrototype.i Proto_Pm_GetDefaultOutputDeviceID()
OSPrototype.i Proto_Pm_GetDeviceInfo(id.i)
OSPrototype.i Proto_Pm_GetErrorText(errnum.i)
OSPrototype Proto_Pm_GetHostErrorText(*msg, len.i)
OSPrototype.i Proto_Pm_HasHostError(*stream)
OSPrototype.i Proto_Pm_Initialize()
OSPrototype.i Proto_Pm_OpenInput(*stream, inputDevice.i, *inputDriverInfo, bufferSize.l, *time_proc, *time_info)
OSPrototype.i Proto_Pm_OpenOutput(*stream, outputDevice.i, *outputDriverInfo, bufferSize.l, *time_proc, *time_info, latency.l)
OSPrototype.i Proto_Pm_Poll(*stream)
OSPrototype.i Proto_Pm_QueueCreate()
OSPrototype.i Proto_Pm_QueueDestroy()
OSPrototype.i Proto_Pm_QueueEmpty()
OSPrototype.i Proto_Pm_QueueFull()
OSPrototype.i Proto_Pm_QueuePeek()
OSPrototype.i Proto_Pm_Read(*stream, *buffer.PmEvent, length.l)
OSPrototype.i Proto_Pm_SetChannelMask(*stream, mask.i)
OSPrototype.i Proto_Pm_SetFilter(*stream, filter.l)
OSPrototype.i Proto_Pm_SetOverflow()
OSPrototype.i Proto_Pm_Terminate()
OSPrototype.i Proto_Pm_Write(*stream, *buffer.PmEvent, length.l)
OSPrototype.i Proto_Pm_WriteShort(*stream, when.l, msg.l)
OSPrototype.i Proto_Pm_WriteSysEx()
OSPrototype.i Proto_Pt_Sleep(duration.l)
OSPrototype.i Proto_Pt_Start(resolution.i, *callback, *userData)
OSPrototype.i Proto_Pt_Started()
OSPrototype.i Proto_Pt_Stop()
OSPrototype.i Proto_Pt_Time()
OSPrototype.i Proto_caps()
OSPrototype.i Proto_descriptors()
OSPrototype.i Proto_midi_in_caps()
OSPrototype.i Proto_midi_in_mapper_caps()
OSPrototype.i Proto_midi_num_inputs()
OSPrototype.i Proto_midi_num_outputs()
OSPrototype.i Proto_midi_out_caps()
OSPrototype.i Proto_midi_out_mapper_caps()
OSPrototype.i Proto_none_synchronize()
OSPrototype.i Proto_none_write_byte()
OSPrototype.i Proto_none_write_short()
OSPrototype.i Proto_pm_add_device()
OSPrototype.i Proto_pm_alloc()
OSPrototype.i Proto_pm_descriptor_index()
OSPrototype.i Proto_pm_descriptor_max()
OSPrototype.i Proto_pm_fail_fn()
OSPrototype.i Proto_pm_fail_timestamp_fn()
OSPrototype.i Proto_pm_free()
OSPrototype.i Proto_pm_hosterror()
OSPrototype.i Proto_pm_hosterror_text()
OSPrototype.i Proto_pm_init()
OSPrototype.i Proto_pm_none_dictionary()
OSPrototype.i Proto_pm_read_bytes()
OSPrototype.i Proto_pm_read_short()
OSPrototype.i Proto_pm_success_fn()
OSPrototype.i Proto_pm_term()
OSPrototype.i Proto_pm_winmm_in_dictionary()
OSPrototype.i Proto_pm_winmm_init()
OSPrototype.i Proto_pm_winmm_out_dictionary()
OSPrototype.i Proto_pm_winmm_term()
;OSPrototype.i Proto_winmm_time_callback()


Global Pm_Abort.Proto_Pm_Abort
Global Pm_Close.Proto_Pm_Close
Global Pm_CountDevices.Proto_Pm_CountDevices
Global Pm_Dequeue.Proto_Pm_Dequeue
Global Pm_Enqueue.Proto_Pm_Enqueue
Global Pm_GetDefaultInputDeviceID.Proto_Pm_GetDefaultInputDeviceID
Global Pm_GetDefaultOutputDeviceID.Proto_Pm_GetDefaultOutputDeviceID
Global Pm_GetDeviceInfo.Proto_Pm_GetDeviceInfo
Global Pm_GetErrorText.Proto_Pm_GetErrorText
Global Pm_GetHostErrorText.Proto_Pm_GetHostErrorText
Global Pm_HasHostError.Proto_Pm_HasHostError
Global Pm_Initialize.Proto_Pm_Initialize
Global Pm_OpenInput.Proto_Pm_OpenInput
Global Pm_OpenOutput.Proto_Pm_OpenOutput
Global Pm_Poll.Proto_Pm_Poll
Global Pm_QueueCreate.Proto_Pm_QueueCreate
Global Pm_QueueDestroy.Proto_Pm_QueueDestroy
Global Pm_QueueEmpty.Proto_Pm_QueueEmpty
Global Pm_QueueFull.Proto_Pm_QueueFull
Global Pm_QueuePeek.Proto_Pm_QueuePeek
Global Pm_Read.Proto_Pm_Read
Global Pm_SetChannelMask.Proto_Pm_SetChannelMask
Global Pm_SetFilter.Proto_Pm_SetFilter
Global Pm_SetOverflow.Proto_Pm_SetOverflow
Global Pm_Terminate.Proto_Pm_Terminate
Global Pm_Write.Proto_Pm_Write
Global Pm_WriteShort.Proto_Pm_WriteShort
Global Pm_WriteSysEx.Proto_Pm_WriteSysEx
Global Pt_Sleep.Proto_Pt_Sleep
Global Pt_Start.Proto_Pt_Start
Global Pt_Started.Proto_Pt_Started
Global Pt_Stop.Proto_Pt_Stop
Global Pt_Time.Proto_Pt_Time
Global caps.Proto_caps
Global descriptors.Proto_descriptors
Global midi_in_caps.Proto_midi_in_caps
Global midi_in_mapper_caps.Proto_midi_in_mapper_caps
Global midi_num_inputs.Proto_midi_num_inputs
Global midi_num_outputs.Proto_midi_num_outputs
Global midi_out_caps.Proto_midi_out_caps
Global midi_out_mapper_caps.Proto_midi_out_mapper_caps
Global none_synchronize.Proto_none_synchronize
Global none_write_byte.Proto_none_write_byte
Global none_write_short.Proto_none_write_short
Global pm_add_device.Proto_pm_add_device
Global pm_alloc.Proto_pm_alloc
Global pm_descriptor_index.Proto_pm_descriptor_index
Global pm_descriptor_max.Proto_pm_descriptor_max
Global pm_fail_fn.Proto_pm_fail_fn
Global pm_fail_timestamp_fn.Proto_pm_fail_timestamp_fn
Global pm_free.Proto_pm_free
Global pm_hosterror.Proto_pm_hosterror
Global pm_hosterror_text.Proto_pm_hosterror_text
Global pm_init.Proto_pm_init
Global pm_none_dictionary.Proto_pm_none_dictionary
Global pm_read_bytes.Proto_pm_read_bytes
Global pm_read_short.Proto_pm_read_short
Global pm_success_fn.Proto_pm_success_fn
Global pm_term.Proto_pm_term
Global pm_winmm_in_dictionary.Proto_pm_winmm_in_dictionary
Global pm_winmm_init.Proto_pm_winmm_init
Global pm_winmm_out_dictionary.Proto_pm_winmm_out_dictionary
Global pm_winmm_term.Proto_pm_winmm_term
;Global winmm_time_callback.Proto_winmm_time_callback


Global libPortMidi.i


Procedure.i libPortMidi_LibInit()
  
  Result = #False
  
  libPortMidi = OpenLibrary(#PB_Any, libPortMidi_Filename$)
  If libPortMidi
    Pm_Abort = GetFunction(libPortMidi, "Pm_Abort")
    If Pm_Abort = #Null : Result + 1 : EndIf
    Pm_Close = GetFunction(libPortMidi, "Pm_Close")
    If Pm_Close = #Null : Result + 1 : EndIf
    Pm_CountDevices = GetFunction(libPortMidi, "Pm_CountDevices")
    If Pm_CountDevices = #Null : Result + 1 : EndIf
    Pm_Dequeue = GetFunction(libPortMidi, "Pm_Dequeue")
    If Pm_Dequeue = #Null : Result + 1 : EndIf
    Pm_Enqueue = GetFunction(libPortMidi, "Pm_Enqueue")
    If Pm_Enqueue = #Null : Result + 1 : EndIf
    Pm_GetDefaultInputDeviceID = GetFunction(libPortMidi, "Pm_GetDefaultInputDeviceID")
    If Pm_GetDefaultInputDeviceID = #Null : Result + 1 : EndIf
    Pm_GetDefaultOutputDeviceID = GetFunction(libPortMidi, "Pm_GetDefaultOutputDeviceID")
    If Pm_GetDefaultOutputDeviceID = #Null : Result + 1 : EndIf
    Pm_GetDeviceInfo = GetFunction(libPortMidi, "Pm_GetDeviceInfo")
    If Pm_GetDeviceInfo = #Null : Result + 1 : EndIf
    Pm_GetErrorText = GetFunction(libPortMidi, "Pm_GetErrorText")
    If Pm_GetErrorText = #Null : Result + 1 : EndIf
    Pm_GetHostErrorText = GetFunction(libPortMidi, "Pm_GetHostErrorText")
    If Pm_GetHostErrorText = #Null : Result + 1 : EndIf
    Pm_HasHostError = GetFunction(libPortMidi, "Pm_HasHostError")
    If Pm_HasHostError = #Null : Result + 1 : EndIf
    Pm_Initialize = GetFunction(libPortMidi, "Pm_Initialize")
    If Pm_Initialize = #Null : Result + 1 : EndIf
    Pm_OpenInput = GetFunction(libPortMidi, "Pm_OpenInput")
    If Pm_OpenInput = #Null : Result + 1 : EndIf
    Pm_OpenOutput = GetFunction(libPortMidi, "Pm_OpenOutput")
    If Pm_OpenOutput = #Null : Result + 1 : EndIf
    Pm_Poll = GetFunction(libPortMidi, "Pm_Poll")
    If Pm_Poll = #Null : Result + 1 : EndIf
    Pm_QueueCreate = GetFunction(libPortMidi, "Pm_QueueCreate")
    If Pm_QueueCreate = #Null : Result + 1 : EndIf
    Pm_QueueDestroy = GetFunction(libPortMidi, "Pm_QueueDestroy")
    If Pm_QueueDestroy = #Null : Result + 1 : EndIf
    Pm_QueueEmpty = GetFunction(libPortMidi, "Pm_QueueEmpty")
    If Pm_QueueEmpty = #Null : Result + 1 : EndIf
    Pm_QueueFull = GetFunction(libPortMidi, "Pm_QueueFull")
    If Pm_QueueFull = #Null : Result + 1 : EndIf
    Pm_QueuePeek = GetFunction(libPortMidi, "Pm_QueuePeek")
    If Pm_QueuePeek = #Null : Result + 1 : EndIf
    Pm_Read = GetFunction(libPortMidi, "Pm_Read")
    If Pm_Read = #Null : Result + 1 : EndIf
    Pm_SetChannelMask = GetFunction(libPortMidi, "Pm_SetChannelMask")
    If Pm_SetChannelMask = #Null : Result + 1 : EndIf
    Pm_SetFilter = GetFunction(libPortMidi, "Pm_SetFilter")
    If Pm_SetFilter = #Null : Result + 1 : EndIf
    Pm_SetOverflow = GetFunction(libPortMidi, "Pm_SetOverflow")
    If Pm_SetOverflow = #Null : Result + 1 : EndIf
    Pm_Terminate = GetFunction(libPortMidi, "Pm_Terminate")
    If Pm_Terminate = #Null : Result + 1 : EndIf
    Pm_Write = GetFunction(libPortMidi, "Pm_Write")
    If Pm_Write = #Null : Result + 1 : EndIf
    Pm_WriteShort = GetFunction(libPortMidi, "Pm_WriteShort")
    If Pm_WriteShort = #Null : Result + 1 : EndIf
    Pm_WriteSysEx = GetFunction(libPortMidi, "Pm_WriteSysEx")
    If Pm_WriteSysEx = #Null : Result + 1 : EndIf
    Pt_Sleep = GetFunction(libPortMidi, "Pt_Sleep")
    If Pt_Sleep = #Null : Result + 1 : EndIf
    Pt_Start = GetFunction(libPortMidi, "Pt_Start")
    If Pt_Start = #Null : Result + 1 : EndIf
    Pt_Started = GetFunction(libPortMidi, "Pt_Started")
    If Pt_Started = #Null : Result + 1 : EndIf
    Pt_Stop = GetFunction(libPortMidi, "Pt_Stop")
    If Pt_Stop = #Null : Result + 1 : EndIf
    Pt_Time = GetFunction(libPortMidi, "Pt_Time")
    If Pt_Time = #Null : Result + 1 : EndIf
    caps = GetFunction(libPortMidi, "caps")
    If caps = #Null : Result + 1 : EndIf
    descriptors = GetFunction(libPortMidi, "descriptors")
    If descriptors = #Null : Result + 1 : EndIf
    midi_in_caps = GetFunction(libPortMidi, "midi_in_caps")
    If midi_in_caps = #Null : Result + 1 : EndIf
    midi_in_mapper_caps = GetFunction(libPortMidi, "midi_in_mapper_caps")
    If midi_in_mapper_caps = #Null : Result + 1 : EndIf
    midi_num_inputs = GetFunction(libPortMidi, "midi_num_inputs")
    If midi_num_inputs = #Null : Result + 1 : EndIf
    midi_num_outputs = GetFunction(libPortMidi, "midi_num_outputs")
    If midi_num_outputs = #Null : Result + 1 : EndIf
    midi_out_caps = GetFunction(libPortMidi, "midi_out_caps")
    If midi_out_caps = #Null : Result + 1 : EndIf
    midi_out_mapper_caps = GetFunction(libPortMidi, "midi_out_mapper_caps")
    If midi_out_mapper_caps = #Null : Result + 1 : EndIf
    none_synchronize = GetFunction(libPortMidi, "none_synchronize")
    If none_synchronize = #Null : Result + 1 : EndIf
    none_write_byte = GetFunction(libPortMidi, "none_write_byte")
    If none_write_byte = #Null : Result + 1 : EndIf
    none_write_short = GetFunction(libPortMidi, "none_write_short")
    If none_write_short = #Null : Result + 1 : EndIf
    pm_add_device = GetFunction(libPortMidi, "pm_add_device")
    If pm_add_device = #Null : Result + 1 : EndIf
    pm_alloc = GetFunction(libPortMidi, "pm_alloc")
    If pm_alloc = #Null : Result + 1 : EndIf
    pm_descriptor_index = GetFunction(libPortMidi, "pm_descriptor_index")
    If pm_descriptor_index = #Null : Result + 1 : EndIf
    pm_descriptor_max = GetFunction(libPortMidi, "pm_descriptor_max")
    If pm_descriptor_max = #Null : Result + 1 : EndIf
    pm_fail_fn = GetFunction(libPortMidi, "pm_fail_fn")
    If pm_fail_fn = #Null : Result + 1 : EndIf
    pm_fail_timestamp_fn = GetFunction(libPortMidi, "pm_fail_timestamp_fn")
    If pm_fail_timestamp_fn = #Null : Result + 1 : EndIf
    pm_free = GetFunction(libPortMidi, "pm_free")
    If pm_free = #Null : Result + 1 : EndIf
    pm_hosterror = GetFunction(libPortMidi, "pm_hosterror")
    If pm_hosterror = #Null : Result + 1 : EndIf
    pm_hosterror_text = GetFunction(libPortMidi, "pm_hosterror_text")
    If pm_hosterror_text = #Null : Result + 1 : EndIf
    pm_init = GetFunction(libPortMidi, "pm_init")
    If pm_init = #Null : Result + 1 : EndIf
    pm_none_dictionary = GetFunction(libPortMidi, "pm_none_dictionary")
    If pm_none_dictionary = #Null : Result + 1 : EndIf
    pm_read_bytes = GetFunction(libPortMidi, "pm_read_bytes")
    If pm_read_bytes = #Null : Result + 1 : EndIf
    pm_read_short = GetFunction(libPortMidi, "pm_read_short")
    If pm_read_short = #Null : Result + 1 : EndIf
    pm_success_fn = GetFunction(libPortMidi, "pm_success_fn")
    If pm_success_fn = #Null : Result + 1 : EndIf
    pm_term = GetFunction(libPortMidi, "pm_term")
    If pm_term = #Null : Result + 1 : EndIf
    pm_winmm_in_dictionary = GetFunction(libPortMidi, "pm_winmm_in_dictionary")
    If pm_winmm_in_dictionary = #Null : Result + 1 : EndIf
    pm_winmm_init = GetFunction(libPortMidi, "pm_winmm_init")
    If pm_winmm_init = #Null : Result + 1 : EndIf
    pm_winmm_out_dictionary = GetFunction(libPortMidi, "pm_winmm_out_dictionary")
    If pm_winmm_out_dictionary = #Null : Result + 1 : EndIf
    pm_winmm_term = GetFunction(libPortMidi, "pm_winmm_term")
    If pm_winmm_term = #Null : Result + 1 : EndIf
    ;winmm_time_callback = GetFunction(libPortMidi, "winmm_time_callback")
    ;If winmm_time_callback = #Null : Result + 1 : EndIf
    
    If Result = #False
      Result = #True
    Else
      Result = #False
    EndIf
  EndIf
  
  ProcedureReturn Result

EndProcedure


Procedure libPortMidi_LibFree()
  If IsLibrary(libPortMidi) : CloseLibrary(libPortMidi) : EndIf
  libPortMidi = 0
EndProcedure
And a small test: (the clock example from libPortMidi)

Code: Select all

;
; libPortMidi Test
; 



XIncludeFile "libPortMidi.pbi"


#MIDI_Q_FRAME    = $f1
#MIDI_TIME_CLOCK = $f8
#MIDI_START      = $fa
#MIDI_CONTINUE	 = $fb
#MIDI_STOP       = $fc

#TEMPO_TO_CLOCK = 2500.0



#OUTPUT_BUFFER_SIZE = 0
#DRIVER_INFO = #Null
#TIME_INFO = #Null
#LATENCY = 0 



Global active.i
Global tempo.f = 60.0
Global *midi

Procedure TimerPoll(timestamp.l, *userData)
  
  Static callback_owns_portmidi.i = #False
  Static clock_start_time.l = 0
  Static next_clock_time.d = 0
  ; SMPTE time
  Static frames.i = 0
  Static seconds.i = 0
  Static minutes.i = 0
  Static hours.i = 0
  Static mtc_count.i = 0  ; where are we in quarter frame sequence?
  Static smpte_start_time.i = 0
  Static next_smpte_time.d = 0
  #QUARTER_FRAME_PERIOD = (1.0 / 120.0)
  
  If callback_owns_portmidi And Not active
    ; main is requesting (by setting active To false) that we shut down
    callback_owns_portmidi = #False
    ProcedureReturn
  EndIf
  
  If Not active
    ProcedureReturn ; main still getting ready or it's closing down
  EndIf
  
  callback_owns_portmidi = #True  ; main is ready, we have portmidi
  
  If send_start_stop
    If clock_running
      Pm_WriteShort(*midi, 0, #MIDI_STOP)
    Else
      Pm_WriteShort(*midi, 0, #MIDI_START)
      clock_start_time = timestamp
      next_clock_time = #TEMPO_TO_CLOCK / tempo
    EndIf
    clock_running = ~ clock_running
    send_start_stop = #False ; until main sets it again
    ; note that there's a slight race condition here: main could
    ; set send_start_stop asynchronously, but we assume user is 
    ; typing slower than the clock rate
  EndIf
   
  If clock_running
    If (timestamp - clock_start_time) > next_clock_time
      Pm_WriteShort(*midi, 0, #MIDI_TIME_CLOCK)
      next_clock_time + (#TEMPO_TO_CLOCK / tempo)
    EndIf
  EndIf
  
  
  
  If time_code_running
    UData.i = 0 ; // initialization avoids compiler warning
    If (timestamp - smpte_start_time) < next_smpte_time
      ProcedureReturn
    EndIf
      
    Select mtc_count
      Case 0  ; frames low nibble
        UData = frames
      Case 1  ; frames high nibble
        UData = frames >> 4
      Case 2  ; frames seconds low nibble
        UData = seconds
      Case 3  ; frames seconds high nibble
        UData = seconds >> 4
      Case 4  ; frames minutes low nibble
        UData = minutes
      Case 5  ; frames minutes high nibble
        UData = minutes >> 4
      Case 6  ; hours low nibble
        UData = hours
      Case 7  ; hours high nibble
        UData = hours >> 4
    EndSelect
    
    UData & $0F ; take only 4 bits
    Pm_WriteShort(midi, 0, Pm_Message(#MIDI_Q_FRAME, (mtc_count << 4) + UData, 0))
    mtc_count = (mtc_count + 1) & $07 ; wrap around
    If mtc_count = 0 ; update time by two frames
      frames + 2
      If frames >= 30
        frames = 0
        seconds + 1
        If seconds >= 60
          seconds = 0
          minutes + 1
          If minutes >= 60
            minutes = 0
            hours + 1
            ; just let hours wrap If it gets that far */
          EndIf
        EndIf
      EndIf
    EndIf
    next_smpte_time + #QUARTER_FRAME_PERIOD
  Else ; time_code_running is false
    smpte_start_time = timestamp
    ; so that when it finally starts, we'll be in sync
  EndIf
  
EndProcedure




Define outp.i

If libPortMidi_LibInit()
  
  If Pm_Initialize() = 0
    
    Devices = Pm_CountDevices()
    Debug "Found " + Str(Devices) + " devices"
    
    Devices - 1
    Define *info.PmDeviceInfo
    For i = 0 To Devices
      *info = Pm_GetDeviceInfo(i);
      If *info\output
        Debug Str(i + 1) + " (output) - " + PeekS(*info\interf) + " - " + PeekS(*info\name)
      Else
        Debug Str(i + 1) + " (input) - " + PeekS(*info\interf) + " - " + PeekS(*info\name)
      EndIf
    Next i
    
    
    Pt_Start(1, @TimerPoll(), #Null)
    
    outp = 2
    err = Pm_OpenOutput(@*midi, outp, #DRIVER_INFO, #OUTPUT_BUFFER_SIZE, Pt_Time, #TIME_INFO, #LATENCY)
    
    
    active = #True
    Delay(5000)
    active = #False
    
    Pt_Sleep(2)
    
    Pt_Stop()
    
    Pm_Terminate()
    
  EndIf
  
  libPortMidi_LibFree()
EndIf
Test it.

Bernd
Last edited by infratec on Sun Dec 30, 2012 12:18 am, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

A second test

Code: Select all

XIncludeFile "libPortMidi.pbi"


Define *midi, Event.PmEvent, outputDevice.i


If libPortMidi_LibInit()
  
  If Pm_Initialize() = 0
    
    outputDevice = Pm_GetDefaultOutputDeviceID()
    ;outputDevice = 2
    
    ; It is recommended To start timer before PortMidi
    Pt_Start(1, #Null, #Null) ; timer started w/millisecond accuracy
    
    If Pm_OpenOutput(@*midi, outputDevice, #Null, 1024, #Null, #Null, 0) = 0
      
      Event\timestamp = Pt_Time()
      Event\message = Pm_Message($C0, 0, 0)
      Pm_Write(*midi, @Event, 1)
      
      Event\timestamp = Pt_Time()
      Event\message = Pm_Message($90, 60, 100)
      Pm_Write(*midi, @Event, 1)
      
      Delay(2000)
      
      Event\timestamp = Pt_Time()
      Event\message = Pm_Message($90, 60, 0)
      Pm_Write(*midi, @Event, 1)
      
      Pm_Close(*midi)
    EndIf
    
    Pt_Stop()
    
    Pm_Terminate()
  EndIf
  
  libPortMidi_LibFree()
EndIf
Generates a single note for 2 seconds.
Maybe you have to adjust the output device.

(This codes works, tested on my PC :mrgreen: )

Bernd
Last edited by infratec on Sun Dec 30, 2012 6:09 pm, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Btw.:

The ready compiled dll is inside the file portmidi-src-217.zip
look in the file portmidi\portmidi_cdt.zip in Release you can find it.

Bernd
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Oh, for showing an error message you need something like this:

Code: Select all

err = Pm_OpenInput(@*midi_in, 3, #Null, 512, #Null, #Null)
If err
  *err = Pm_GetErrorText(err)
   MessageRequester("Error", PeekS(*err))
EndIf
Bernd
Last edited by infratec on Sun Dec 30, 2012 1:04 am, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

If you use the dll from the zip file,
than you have to change

Code: Select all

Prototype
to

Code: Select all

PrototypeC
else Pm_Read() will destroy the stack.

Bernd
Khorus
User
User
Posts: 25
Joined: Sat Nov 13, 2010 11:22 pm

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by Khorus »

Thanks Infratec for all the work! It's greatly appreciated!!

Now, I tried your test programs. The one that lists the MIDI ports available is working good, the other one does nothing, I'll have to investigate more on it.

Next step: Try to get the libPortMIDI running on OSX. So far, no so good, Xcode 4.3 doesn't compile the source.

Once again, thanks a million!

-K
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Hi Khorus,

did you adjust the output device ?
Did you changed Prototype to PrototypeC ?

At my PC (Win XP 32bit) everything works fine.
I just converted the MidiMonitor example from the sourcecode:

Code: Select all

XIncludeFile "libPortMidi.pbi"


#STRING_MAX = 80

#MIDI_CODE_MASK         = $f0
#MIDI_CHN_MASK          = $0f
;#MIDI_REALTIME         = $f8
;#MIDI_CHAN_MODE        = $fa
#MIDI_OFF_NOTE          = $80
#MIDI_ON_NOTE           = $90
#MIDI_POLY_TOUCH        = $a0
#MIDI_CTRL              = $b0
#MIDI_CH_PROGRAM        = $c0
#MIDI_TOUCH             = $d0
#MIDI_BEND              = $e0

#MIDI_SYSEX             = $f0
#MIDI_Q_FRAME	          = $f1
#MIDI_SONG_POINTER      = $f2
#MIDI_SONG_SELECT       = $f3
#MIDI_TUNE_REQ	        = $f6
#MIDI_EOX               = $f7
#MIDI_TIME_CLOCK        = $f8
#MIDI_START             = $fa
#MIDI_CONTINUE	        = $fb
#MIDI_STOP              = $fc
#MIDI_ACTIVE_SENSING    = $fe
#MIDI_SYS_RESET         = $ff

#MIDI_ALL_SOUND_OFF     = $78
#MIDI_RESET_CONTROLLERS = $79
#MIDI_LOCAL	            = $7a
#MIDI_ALL_OFF	          = $7b
#MIDI_OMNI_OFF	        = $7c
#MIDI_OMNI_ON	          = $7d
#MIDI_MONO_ON	          = $7e
#MIDI_POLY_ON	          = $7f


#sysex_max = 16



Enumeration
  #DeviceSelect
  #MidiList
EndEnumeration



Global *midi_in               ; midi input
Global active.i = #False      ; set when midi_in is ready for reading
Global in_sysex.i = #False    ; we are reading a sysex message
Global inited.i = #False      ; suppress printing during command line parsing
Global done.i = #False        ; when true, exit
Global notes.i = #True        ; show notes?
Global controls.i = #True     ; show continuous controllers
Global bender.i = #True       ; record pitch bend etc.?
Global excldata.i = #True     ; record system exclusive data?
Global verbose.i = #True      ; show text representation?
Global realdata.i = #True     ; record real time messages?
Global clksencnt.i = #True    ; clock and active sense count on
Global chmode.i = #True       ; show channel mode messages
Global pgchanges.i = #True    ; show program changes
Global flush.i = #False       ; flush all pending MIDI data

Global filter.l = 0           ; remember state of midi filter

Global clockcount.l = 0       ; count of clocks
Global actsensecount.l = 0    ; count of active sensing bytes
Global notescount.l = 0       ; #notes since last request
Global notestotal.l = 0       ; total #notes

Global MidiEvents.i


Procedure showbytes(Command$, MidiData.l, len.i, comment$="")
  
  Protected i.i, Line$
  
  Line$ = Str(MidiEvents) + #LF$ + Command$ + #LF$
  MidiEvents + 1
  For i = 1 To 4
    If i <= Len
      Line$ + RSet(Hex(MidiData & $FF, #PB_Byte), 2, "0")
      MidiData >> 8
    EndIf
    Line$ + #LF$
  Next i
  Line$ + comment$
  
  AddGadgetItem(#MidiList, 0 , Line$)
  
EndProcedure



Procedure.s put_pitch(p.i)
  
  Protected result$
  Static Dim ptos.s(11)
  
  If ptos(0) = ""
    ptos(0) = "c"
    ptos(1) = "cs"
    ptos(2) = "d"
    ptos(3) = "ef"
    ptos(4) = "e"
    ptos(5) = "f"
    ptos(6) = "fs"
    ptos(7) = "g"
    ptos(8) = "gs"
    ptos(9) = "a"
    ptos(10) = "bf"
    ptos(11) = "b"
  EndIf
  
  ; note octave correction below
   ;sprintf(result, "%s%d", ptos[p % 12], (p / 12) - 1);
   Result$ = ptos(p % 12) + Str((p / 12) - 1)
   
   ProcedureReturn Result$
    
EndProcedure





Procedure output(MidiData.l)
  
  Protected command.i ; the current command
  Protected chan.i    ; the midi channel of the current event
  Protected len.i     ;
  Protected MidiDataCopy.l
  Protected comment$
  
  
  command = Pm_MessageStatus(MidiData) & #MIDI_CODE_MASK
  chan = Pm_MessageStatus(MidiData) & #MIDI_CHN_MASK
  chan + 1
  
  If in_sysex Or Pm_MessageStatus(MidiData) = #MIDI_SYSEX
    MidiDataCopy = MidiData
    in_sysex = #True
    ; look For MIDI_EOX in first 3 bytes
    ; If realtime messages are embedded in sysex message, they will
    ; be printed As If they are part of the sysex message
    While i < 4 And (MidiDataCopy & $FF) <> #MIDI_EOX
      MidiDataCopy >> 8
      i + 1
    Wend
    If i < 4
      in_sysex = #False
      i + 1  ; include the EOX byte in output
    EndIf
    showbytes("sysex", MidiData, i)
  ElseIf command = #MIDI_ON_NOTE And Pm_MessageData2(MidiData) <> 0
    notescount + 1
    If notes
      comment$ = "On  Chan " + Str(chan) + "  Key " + Str(Pm_MessageData1(MidiData)) + "  "
      comment$ + "Vel " + Str(Pm_MessageData2(MidiData)) + " " + put_pitch(Pm_MessageData1(MidiData))
      showbytes("note", MidiData, 3, comment$)
    EndIf
  ElseIf (command = #MIDI_ON_NOTE Or command = #MIDI_OFF_NOTE) And notes
    comment$ = "Off  Chan " + Str(chan) + "  Key " + Str(Pm_MessageData1(MidiData)) + "  "
    comment$ + "Vel " + Str(Pm_MessageData2(MidiData)) + " " + put_pitch(Pm_MessageData1(MidiData))
    showbytes("note", MidiData, 3, comment$)
  ElseIf command = #MIDI_CH_PROGRAM And pgchanges
    comment$ = "ProgChg Chan " + Str(chan) + "  Prog " + Str(Pm_MessageData1(MidiData))
    showbytes("change program", MidiData, 2, comment$)
  ElseIf command = #MIDI_CTRL
    ; controls 121 (MIDI_RESET_CONTROLLER) To 127 are channel mode messages.
    If Pm_MessageData1(MidiData) < #MIDI_ALL_SOUND_OFF
      comment$ = "CtrlChg Chan " + Str(chan) + " Ctrl " + Str(Pm_MessageData1(MidiData)) + " Val " + Str(Pm_MessageData2(MidiData))
      showbytes("ctrl", MidiData, 3, comment$)
    ElseIf chmode  ; channel mode
      Select Pm_MessageData1(MidiData)
        Case #MIDI_ALL_SOUND_OFF
          comment$ = "All Sound Off, Chan " + Str(chan)
        Case #MIDI_RESET_CONTROLLERS
          comment$ = "Reset All Controllers, Chan " + Str(chan)
        Case #MIDI_LOCAL
          comment$ = "LocCtrl Chan " + Str(chan) + " "
          If Pm_MessageData2(MidiData)
            comment$ + "On"
          Else
            comment$ + "Off"
          EndIf
        Case #MIDI_ALL_OFF
          comment$ = "All Off Chan " + Str(chan)
        Case #MIDI_OMNI_OFF
          comment$ = "Omni Off Chan " + Str(chan)
        Case #MIDI_OMNI_ON
          comment$ = "Omni On Chan " + Str(chan)
        Case #MIDI_MONO_ON
          comment$ = "Mono On Chan " + Str(chan)
          If Pm_MessageData2(MidiData)
            comment$ + " to " + Str(Pm_MessageData2(MidiData)) + " received channels"
          Else
            comment$ + " to all received channels"
          EndIf
        Case #MIDI_POLY_ON
          comment$ = "Poly On Chan " + Str(chan)
      EndSelect
      showbytes("ctrl", MidiData, 3, comment$)
    EndIf
  ElseIf command = #MIDI_POLY_TOUCH And bender
    comment$ = "P.touch  Chan " + Str(chan) + "  Key " + Str(Pm_MessageData1(MidiData)) + "  "
    comment$ + "Vel " + Str(Pm_MessageData2(MidiData)) + " " + put_pitch(Pm_MessageData1(MidiData))
    showbytes("poly touch", MidiData, 3, comment$)
  ElseIf command = #MIDI_TOUCH And bender
    comment$ = "A.Touch Chan " + Str(chan) + "  Val " + Str(Pm_MessageData1(MidiData))
    showbytes("touch", MidiData, 2, comment$)
  ElseIf command = #MIDI_BEND And bender
    comment$ = "P.Bend Chan " + Str(chan) + "  Val " + Str(Pm_MessageData1(MidiData) + (Pm_MessageData2(MidiData)<<7))
    showbytes("bend", MidiData, 3, comment$)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_SONG_POINTER
    comment$ = "Song Position " + Str(Pm_MessageData1(MidiData) + (Pm_MessageData2(MidiData)<<7))
    showbytes("song pointer", MidiData, 3, comment$)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_SONG_SELECT
    comment$ = "Song Select " + Str(Pm_MessageData1(MidiData))
    showbytes("song select", MidiData, 2, comment$)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_TUNE_REQ
    showbytes("tune request", MidiData, 1)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_Q_FRAME And realdata
    comment$ = "Time Code Quarter Frame Type " + Str((Pm_MessageData1(MidiData) & $70) >> 4) + " Values " + Str(Pm_MessageData1(MidiData) & $f)
    showbytes("time code", MidiData, 2, comment$)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_START And realdata
    showbytes("start", MidiData, 1)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_CONTINUE And realdata
    showbytes("continue", MidiData, 1)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_STOP And realdata
    showbytes("stop", MidiData, 1)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_SYS_RESET And realdata
    showbytes("sys reset", MidiData, 1)
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_TIME_CLOCK
    If clksencnt
      clockcount + 1
      StatusBarText(0, 1, "clock " + Str(clockcount), #PB_StatusBar_Center)
    ElseIf realdata
      showbytes("clock", MidiData, 1)
    EndIf
  ElseIf Pm_MessageStatus(MidiData) = #MIDI_ACTIVE_SENSING
    If clksencnt
      actsensecount + 1
      StatusBarText(0, 1, "actsense " + Str(actsensecount), #PB_StatusBar_Center)
    ElseIf realdata
      showbytes("active sensing", MidiData, 1)
    EndIf
  Else
    showbytes("unknown", MidiData, 3)
  EndIf
  
EndProcedure




Procedure receive_poll(timestamp.l, *userData)
  
  Protected Event.PmEvent, count.i, *err
  
  
  If Not active
    ProcedureReturn
  EndIf
  
  count = Pm_Read(*midi_in, @Event, 1)
  While count <> 0
    If count = 1
      output(Event\message)
    Else
      *err = Pm_GetErrorText(count)
      MessageRequester("Error", PeekS(*err))
    EndIf
    count = Pm_Read(*midi_in, @Event, 1)
  Wend
  
EndProcedure




Define err.i, DeviceNo.i, *info.PmDeviceInfo, i.i, j.i


If libPortMidi_LibInit()
  
  If Pm_Initialize() = 0
    
    OpenWindow(0, 0, 0, 520, 200, "libPortMidi Monitor", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered|#PB_Window_SizeGadget)
    
    ComboBoxGadget(#DeviceSelect, 10, 10, 150, 20)
    AddGadgetItem(#DeviceSelect, -1, "none")
    SetGadgetState(#DeviceSelect, 0)
    SetGadgetItemData(#DeviceSelect, 0, -1)

    DeviceNo = Pm_CountDevices() - 1
    j = 1
    For i = 0 To DeviceNo
      *info = Pm_GetDeviceInfo(i)
      If *info\input
        AddGadgetItem(#DeviceSelect, j, PeekS(*info\name))
        SetGadgetItemData(#DeviceSelect, j, i)
        j + 1
      EndIf
    Next i
    
    ListIconGadget(#MidiList, 10, 40, 500, 120, "No", 50)
    AddGadgetColumn(#MidiList, 1, "command", 100)
    AddGadgetColumn(#MidiList, 2, "1", 30)
    AddGadgetColumn(#MidiList, 3, "2", 30)
    AddGadgetColumn(#MidiList, 4, "3", 30)
    AddGadgetColumn(#MidiList, 5, "4", 30)
    AddGadgetColumn(#MidiList, 6, "comment", 200)
    
    
    CreateStatusBar(0, WindowID(0))
    AddStatusBarField(150)
    AddStatusBarField(150)
    
    
    Exit = #False
    Repeat
      
      Event = WaitWindowEvent()
      
      Select Event
        Case #PB_Event_Gadget
          Select EventGadget()
            Case #DeviceSelect
              If EventType() = #PB_EventType_Change
                DeviceNo = GetGadgetItemData(#DeviceSelect, GetGadgetState(#DeviceSelect))
                If DeviceNo = -1
                  active = #False
                  Pm_Close(*midi_in)
                  Pt_Stop()
                Else
                  ClearGadgetItems(#MidiList)
                  MidiEvents = 1
                  clockcount = 0
                  actsensecount = 0
                  StatusBarText(0, 0, "")
                  StatusBarText(0, 1, "")
                  
                  Pt_Start(1, @receive_poll(), #Null)
                  
                  err = Pm_OpenInput(@*midi_in, DeviceNo, #Null, 512, #Null, #Null)
                  If err
                    Pt_Stop()
                    *err = Pm_GetErrorText(err)
                    MessageRequester("Error", PeekS(*err))
                  Else
                    Pm_SetFilter(*midi_in, filter)
                    inited = #True  ; now can document changes, set filter
                    active = #True
                  EndIf
                  
                EndIf
              EndIf
          EndSelect
        Case #PB_Event_SizeWindow
          ResizeGadget(#MidiList, #PB_Ignore, #PB_Ignore, WindowWidth(0) - 20, WindowHeight(0) - 80)
        Case #PB_Event_CloseWindow
          Exit = #True
      EndSelect
      
    Until Exit
    
    If active
      Pm_Close(*midi_in)
      Pt_Stop()
    EndIf
    Pm_Terminate()
    
  EndIf
  libPortMidi_LibFree()
EndIf
Works fine too.

I still have to add a few things, like setting the filters.

Maybe you can download MIXX for OSX inside is a version of libPortMidi for OSX.

Bernd
Last edited by infratec on Sun Dec 30, 2012 10:23 pm, edited 1 time in total.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

I changed my output example from above to use the default output device.

Bernd
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Btw.:

you have to enable 'Thread save' as compiler option.
ozzie
Enthusiast
Enthusiast
Posts: 429
Joined: Sun Apr 06, 2008 12:54 pm
Location: Brisbane, Qld, Australia
Contact:

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by ozzie »

PortMidi looks useful, but from what I can see there's no Windows 64-bit version available. Is that correct?
Khorus
User
User
Posts: 25
Joined: Sat Nov 13, 2010 11:22 pm

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by Khorus »

Hello Infratec,

Finally got the time to fiddle around our thing. (Well, your thing really!)

I just downloaded Mixxx for Mac, used their dylib file instead of some random one I found on the net... I've got no result. It doesn't crash or anything but it's producing no debug output... Is there a catch 22 for Mac? Thanks!

Khorus
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by infratec »

Hi Khorus,

with Apple I have a problem:

I don't own one :cry:

So I can tell you nothing about this.
If no one answeres here, ask this question again in the Mac section of this forum.

Bernd
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Real time MIDI in PureBasic using external Lib (PortMIDI

Post by wilbert »

I tried it on OS X 10.8.2 (PureBasic 5.10beta x64) and it didn't work.
The OpenLibrary command returns zero.
Maybe the dylib from Mixx I tried wasn't compiled for x64 :?
It might be best to compile from the PortMidi source but unfortunately I couldn't get that working.
marcos.exe
User
User
Posts: 20
Joined: Fri Jan 17, 2020 8:20 pm

Re: Real time MIDI in PureBasic using external Lib (PortMIDI)

Post by marcos.exe »

I know this post is old, but I just started using portmidi.pbi, and I thought I could get my keyboard working easily.

Well, that wasn't possible.

I couldn't find something that already exists here or on the web in general.
could someone help me with a code to use my keyboard with this script?
It is already installed and working properly. But I don't know how to make my script receive messages from it.

Thanks in advance for the help.

translated by google
Eu sei que o post já é antigo, mas comecei usar agora o portmidi.pbi, e achei que conseguiria fazer meu teclado funcionar facilmente.

Bem, isso não foi possível.

Não consegui encontrar algo já existente nem aqui, nem pela web de uma forma geral.
alguém poderia me ajudar com um código para usar meu teclado com esse script?
Ele já está instalado e funcionando corretamente. Mas não sei como fazer para meu script receber mensagens dele.

Grato desde já pela ajuda.
When our generation/OS updates, we either update ourselves, or we are removed.
But we are never fully uninstalled.
Post Reply