Jay Hardesty made a few extensions and fixes to the SirenMIDIPlugin;
they are attached to this letter.
[ ... ]
Here's a slightly altered version of sqMacMIDI.OMS.c
which seems to fix the problem for us - I'm not sure
if this was the right way to go about it, but so far
so good. Since input is already working fine for
you these changes may not be very interesting, but
sending them back just in case they might be of some
use (or maybe some amusement) somehow.
The main changes were:
Uncommenting out a block of your code in sqOpenMIDI which
sets up OMS to read from all interfaces. This change made
me kind of nervous because I assume you're getting input
without running through this code...
I also added a condition in that same block of code
so that only devices checked 'Is a controller' in OMS Setup
are actually used for input:
(*nodeInfoList)->info[i].flags & kAnyIsController
Added curly braces for a couple of nested if-else
statements in sqMIDIPlaySizeOn. [ ... ]
Also here's a mindless little smalltalk method for
setting up the MIDICommands shared pool in MIDIPort
that adds a few more status messages.
--
stp
Stephen Travis Pope
http://www.create.ucsb.edu/~stp
'From Squeak2.8 of 13 June 2000 [latest update: #2359] on 28 November 2000 at 10:29:55 pm'!
!MIDIPort class methodsFor: 'as yet unclassified' stamp: 'jh 11/28/2000 22:28'!
initializeMIDICommands
"
MIDIPort initializeMIDICommands
"
MIDICommands
ifNil: [MIDICommands _ Dictionary new]
ifNotNil: [MIDICommands keysAndValuesRemove: [:a :b | true]].
MIDICommands
at: #noteOff put: 128;
at: #noteOn put: 144;
at: #polyTouch put: 160;
at: #ctrlChange put: 176;
at: #progChange put: 192;
at: #chanTouch put: 208;
at: #pitchWheel put: 224;
at: #sysEx put: 240;
at: #mtc put: 241;
at: #spp put: 242;
at: #midiClock put: 248;
at: #midiStart put: 250;
at: #midiContinue put: 251;
at: #midiStop put: 252.
(MIDIPort sharedPools includes: MIDICommands) ifFalse: [MIDIPort addSharedPool: MIDICommands].
MIDIPort compileAll
! !
/***************************************************************
* Squeak/Siren Interface to the Opcode MIDI System (OMS)
* Stephen T. Pope -- stp@create.ucsb.edu
*
* Version History: see sqMIDI.h
*
* N.B.: This driver requires OMS version 2.5.3 or greater.
* See
http://www.opcode.com to get the latest version of OMS for free.
*
* Because of the (Mac/DOS) portability of OMS, this code should be
* (more or less) portable to the DOS PC as well (please let me know).
*/
/***************************************************************
* Includes & Macros
*/
//#include "sq.h" // General Squeak definitions, includes sqMIDI.h
// MIDI Includes -- STP -- these used to be in sq.h
#include "OMS.h" // OMS definitions and structs
#include <MIDI.h> // Apple MIDI Libraries
#include "sqMIDI.h" // Squeak MIDI Structs and Prims
#include "sqVirtualMachine.h" // The virtual machine proxy definition
#include "Strings.h"
#define sqSignature 'SQEK' // Creator ID.
#define InputPortID 'in ' // I/O port IDs.
#define OutputPortID 'out '
//#define DEBUG_MODE // Enable/Disable printfs (see macros below)
// Debugging macros
#ifdef DEBUG_MODE
#define dprint1(str) printf(str)
#define dprint2(str, val) printf(str, val)
#define dprint3(str, v1, v2) printf(str, v1, v2)
Boolean debug_mode = true;
#else
#define dprint1(str)
#define dprint2(str, val)
#define dprint3(str, v1, v2)
Boolean debug_mode = false;
#endif DEBUG_MODE
extern struct VirtualMachine *interpreterProxy;
// A few macros for error checking
#define S_FAIL { interpreterProxy->success(false); return(-1); }
#define S_ERR { interpreterProxy->success(false); return((int) err); }
#define S_CHECK if(err != omsNoErr) { return(0 - (int) err); }
#define S_CHECK_FAIL if(err != omsNoErr) { interpreterProxy->success(false); \
return(0 - (int) err); }
#define S_CHECK_SIGNIN if(!gSignedInToMIDI) { return(-1024); }
// memory access macros from interp.c
#define longAtput(i, val) (*((int *) (i)) = val)
#define instVarAtPutInt(obj, slot, val) \
longAtput(((char *)obj + (slot << 2)), (((int)val << 1) | 1))
/***************************************************************
* Global state variabless
*/
Boolean gSignedInToMIDI; // are we signed into MIDI driver?
Boolean gEchoEnabled; // toggle to echo event input
Boolean gInputEnabled; // toggle to enable event input
Boolean gCacheControllers; // toggle to cache controller values
/***************************************************************
* Controller value caches -- This is the minimum complement
* A "larger" driver would cache 128*16 controllers and 128*16 key pressures
*/
unsigned char sqControllers[128]; // Controller value table
unsigned char sqKeyPressures[128]; // Polyphonic key pressure table
unsigned char sqChanPressures[16]; // Channel pressure value table
int sqPitchBend[16]; // The value of the pitch wheel
/***************************************************************
* OMS Driver Internals
*/
short gInPortRef; // refNum of the OMS input port
short gOutPortRef; // refNum of the OMS output port
short sqCallback; // semaphore for read callback
short gCompatMode; // OMS compatibility mode
#define NumPorts 8 // Max number of I/O ports
#define MaxNameLen 64 // Max length of device names
unsigned short gOutNodeRefs[NumPorts] = {0, 0, 0, 0, 0, 0, 0, 0};
sqMIDIioPort gNodeNames[NumPorts]; // The IO port table
#define MaxInInQ 64 // The size of the input Q
sqMIDIEvent sqMIDIInQ[MaxInInQ]; // the input event Q (an array)
int itemsInInQ; // Index to write in the input Q
int itemsInInQ2; // Pointer to input to read (<= iIIQ)
OMSMIDIPacket thePacket; // Small (4-byte) MIDI packet
OMSMIDIPacket255 theBigPacket; // Large packet for SysEx messages
/***************************************************************
* Function Prototypes for I/O call-back utilities
*/
#define USESROUTINEDESCRIPTORS
pascal void sqMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon);
pascal void sqMIDIReadHook(OMSPacket *pkt, long myRefCon);
/***************************************************************
***************************************************************
* sqOpenMIDI -- Sign on to OMS, register callbacks, create ports
* and connections, etc.
*/
int sqOpenMIDI(int callbackSemaphore, int inputMRSocket) {
OSErr err;
OMSAppHookUPP appHook; // pointer to call-back function
OMSReadHookUPP readHook; // pointer to call-back function
OMSAPI (long) versionID; // OMS driver version
// OMSAPI(OMSIDListH) sqNodes; // list of I/O node IDs
OMSConnectionParams sqConn; // connection parameter structure JH - uncommented this line
OMSNodeInfoListH nodeInfoList;
int i, j; // JH added tmp var j
long LMGetCurrentA5(void);
int interfaces;
char *nname;
Boolean found = false;
interpreterProxy->success(true);
//// It's not an error if we're already signed in.
if (gSignedInToMIDI)
return (0);
dprint3("\n\tMIDI open -- sem = %d sock = %d\n", callbackSemaphore, inputMRSocket);
sqCallback = 0;
//// Make sure that OMS V2 is installed.
versionID = OMSVersion();
if (versionID == 0) {
interpreterProxy->success(false);
return (-128);
}
if ((versionID & 0xff000000) != 0x02000000) {
interpreterProxy->success(false);
return (-129);
}
//// Set input event pointers
itemsInInQ = 0;
itemsInInQ2 = 0;
//// Clear controller value tables
for (i=0; i<128; i++) {
sqControllers[i] = 64;
sqKeyPressures[i] = 0;
}
for (i=0; i<16; i++) {
sqChanPressures[i] = 0;
sqPitchBend[i] = 0;
}
//// Set up call-back routine pointers.
appHook = NewOMSAppHook(sqMIDIAppHook);
readHook = NewOMSReadHook(sqMIDIReadHook);
//// Sign in to OMS.
err = OMSSignIn(sqSignature, (long)LMGetCurrentA5(),
(unsigned char *) "Squeak/Siren",
appHook, &gCompatMode);
S_CHECK_FAIL
gSignedInToMIDI = true;
//// Add an input port.
err = OMSAddPort(sqSignature, InputPortID, omsPortTypeInput,
readHook, (long)LMGetCurrentA5(), &gInPortRef);
if (err) goto errexit;
//// Add an output port.
err = OMSAddPort(sqSignature, OutputPortID, omsPortTypeOutput,
NULL, 0L, &gOutPortRef); // no read hook for output
if (err) goto errexit;
//// Set up OMS to play to all output ports.
nodeInfoList = OMSGetNodeInfo(omsIncludeOutputs + omsIncludeReal + omsIncludeVirtual);
if (nodeInfoList != NULL) {
interfaces = (*nodeInfoList)->numNodes;
for (i=0; i<(*nodeInfoList)->numNodes; i++) {
nname = (char *)(*nodeInfoList)->info[i].name;
dprint2("\tOpen interface %s\n", nname);
strcpy(gNodeNames[i].name, nname);
gNodeNames[i].direction = 2;
gOutNodeRefs[i] = (*nodeInfoList)->info[i].ioRefNum;
}
OMSDisposeHandle(nodeInfoList);
} else
goto errexit;
// JH - uncommented following block of code
//// Read from all input ports.
sqConn.appRefCon = 0;
nodeInfoList = OMSGetNodeInfo(omsIncludeInputs + omsIncludeReal + omsIncludeVirtual);
if (nodeInfoList != NULL) {
for (i=0; i<(*nodeInfoList)->numNodes; i++) {
nname = (char *)(*nodeInfoList)->info[i].name;
// JH - Actually only read from controllers
if ((*nodeInfoList)->info[i].flags & kAnyIsController) {
sqConn.nodeUniqueID = (*nodeInfoList)->info[i].uniqueID;
err = OMSOpenConnections(sqSignature, InputPortID, 1, &sqConn, false);
if (err) goto errexit;
for (j=0; j < interfaces; j++)
if (strcmp(nname, gNodeNames[j].name) == 0) {
gNodeNames[j].direction += 1;
found = true;
}
if (!found) {
strcpy(gNodeNames[interfaces].name, nname);
gNodeNames[interfaces].direction = 1;
interfaces ++;
}
dprint2("\tConnect input %s\n", nname);
}
}
OMSDisposeHandle(nodeInfoList);
}
//// Success
gInputEnabled = false; // off by default
gCacheControllers = false; // off by default
gEchoEnabled = false; // off by default
sqCallback = callbackSemaphore;
interpreterProxy->success(true);
return(interfaces); // Answer the number of interfaces found.
errexit:
OMSSignOut(sqSignature);
gSignedInToMIDI = false;
interpreterProxy->success(false);
return ((int) err);
}
/***************************************************************
* sqCloseMIDI -- Close up shop and sign out from OMS
*/
int sqCloseMIDI() {
interpreterProxy->success(true);
S_CHECK_SIGNIN
sqCallback = 0;
// Perhaps I should do allNotesOff here...
OMSSignOut(sqSignature);
gSignedInToMIDI = false;
gInputEnabled = false;
gCacheControllers = false;
gEchoEnabled = false;
return(0);
}
/***************************************************************
* sqMIDIPlaySize -- Send a MIDI message out the output ports
*/
int sqMIDIPlaySizeOn (int message, int length, int interface) {
int i;
unsigned char *ptr = (unsigned char *) message;
// It's an error if we're not signed in
S_CHECK_SIGNIN
// Play it now (i.e., no output buffer)
if (length < 4) { // Handle a "normal" packet of 3 bytes
thePacket.flags = 0;
thePacket.len = length;
for (i=0; i<length; i++) // copy output data into packet
thePacket.data[i] = *(ptr + i);
if (interface == 0) { // Interface 0 means play out all outputs
for (i=0; i<NumPorts; i++)
if (gOutNodeRefs[i] != 0)
OMSWritePacket2(&thePacket, gOutNodeRefs[i], gOutPortRef);
} // JH - added curly braces - CW seemed to disambiguate the if-if-else other than as intended(?)
else
OMSWritePacket2(&thePacket, gOutNodeRefs[interface - 1], gOutPortRef);
} else { // Handle a big packet
theBigPacket.flags = 0;
theBigPacket.len = length;
for (i=0; i<length; i++) // copy output data into packet
theBigPacket.data[i] = *(ptr + i);
if (interface == 0) { // Interface 0 means play out all outputs
for (i=0; i<NumPorts; i++)
if (gOutNodeRefs[i] != 0)
OMSWritePacket2((struct OMSMIDIPacket *)&theBigPacket, gOutNodeRefs[i], gOutPortRef);
// OMSWritePacket255(&theBigPacket, gOutNodeRefs[i], gOutPortRef);
} // JH - added curly braces - CW seemed to disambiguate the if-if-else other than as intended(?)
else
OMSWritePacket2((struct OMSMIDIPacket *)&theBigPacket, gOutNodeRefs[interface - 1], gOutPortRef);
// OMSWritePacket255(&theBigPacket, gOutNodeRefs[interface - 1], gOutPortRef);
}
interpreterProxy->success(true);
return(length);
}
unsigned char lastCommand;
int lastLength;
/***************************************************************
* sqMIDIReadHook -- Interrupt routine called on in-coming MIDI messages
*/
pascal void sqMIDIReadHook(OMSPacket *pkt, long myRefCon) {
sqMIDIEvent *inPtr;
unsigned char *idata, *odata;
int len, i, cmd, chan, value;
long olda5 = SetA5(myRefCon);
len = pkt->len;
if (len == 0) // return if empty packet
goto errexit;
if (itemsInInQ == MaxInInQ) // return if input Q full
goto errexit;
// echo input to the output
if (gEchoEnabled)
for (i=0; i<NumPorts; i++)
if (gOutNodeRefs[i] != 0)
OMSWritePacket(pkt, gOutNodeRefs[i], gOutPortRef);
cmd = (pkt->data[0]) & 0xF0; // Get "command" from 1st byte of packet
if (gCacheControllers) { // Cache controller values in the driver
if (cmd == ControlCmd) { // Read a control command
chan = pkt->data[1];
value = pkt->data[2];
sqControllers[chan] = value;
goto errexit;
} else if (cmd == PchWheelCmd) { // Read a pitch wheel change
chan = (pkt->data[0]) & 0x0F;
i = pkt->data[1];
value = pkt->data[2];
sqPitchBend[chan] = (value << 7) + i;
goto errexit;
} else if (cmd == PolyPressCmd) { // Read polyphonic key pressure
chan = pkt->data[1];
value = pkt->data[2];
sqKeyPressures[chan] = value;
goto errexit;
} else if (cmd == ChanPressCmd) { // Read channel key pressure
chan = (pkt->data[0]) & 0x0F;
value = pkt->data[1];
sqKeyPressures[chan] = value;
goto errexit;
}
}
if (!gInputEnabled)
goto errexit;
// Get the input Q record to use
inPtr = &sqMIDIInQ[itemsInInQ++];
// Catch OMS packet length bug
if ((cmd == PgmChngCmd) || (cmd == ChanPressCmd))
len = 2;
else if (cmd != SysExCmd)
len = 3;
inPtr->len = len;
// Copy the packet to the input Q
inPtr->flags = (int)pkt->flags;
inPtr->timeStamp = clock(); // = (int)pkt->beatTimeStamp; for OMS 2 input
idata = &(inPtr->data[0]);
odata = &(pkt->data[0]);
if (len <= PKT_LEN) // Copy the data bytes
for (i = 0; i < len; i++)
*idata++ = *odata++;
if (sqCallback != 0) {
// Signal the read semaphore
i = interpreterProxy->signalSemaphoreWithIndex(sqCallback);}
errexit:
SetA5(olda5);
}
/***************************************************************
* sqReadMIDIPacket -- Sent in response to the input semaphore
* This is to up-load MIDI messages into the arguments (a MIDIPacket,
* its max length, and its data ByteArray).
*/
int sqReadMIDIPacket(int ipacket, int maxSize, int idata) {
sqMIDIEvent *outPtr;
unsigned char *cdata;
int len, i;
unsigned char *pdata = (unsigned char *)idata;
// The packet object is defined as:
// Object subclass: #MIDIPacket
// instanceVariableNames: 'length time flags data '
interpreterProxy->success(true);
if (itemsInInQ == 0)
return (0);
if (sqCallback == 0)
return (-1);
// Set up pointers
outPtr = &sqMIDIInQ[itemsInInQ2++];
dprint2("Reading %d bytes -- ", outPtr->len);
if(debug_mode) printf("%x %x %x\n", outPtr->data[0], outPtr->data[1], outPtr->data[2]);
len = outPtr->len; // copy the response fields
if (len > maxSize) // packet longer than normal--answer length
return (len);
// copy the driver data into the packet object
instVarAtPutInt(ipacket, 1, len); // length, time, flags
instVarAtPutInt(ipacket, 2, (int)(outPtr->timeStamp));
instVarAtPutInt(ipacket, 3, (int)(outPtr->flags));
cdata = &(outPtr->data[0]); // copy MIDI message bytes into the packet
for (i=0; i<len; i++)
*pdata++ = *cdata++;
if (itemsInInQ2 == itemsInInQ) // clear counters if Q is empty
itemsInInQ2 = itemsInInQ = 0;
return (len); // Answer len
}
/***************************************************************
* sqReadMIDIControl -- Answer one or more controller values from the cache
*/
int sqReadMIDIControl(int fromC, int toC, int intoArray) {
int i;
unsigned char *pdata = (unsigned char *)intoArray;
// check controller range
if ((fromC < 0) || (toC > 287) || (fromC > toC)) {
interpreterProxy->success(false);
return (-1);
}
pdata += 4; // Skip object header
for (i = fromC; i <= toC; i++) { // Loop to read controllers
if (i < 128) // 0-127 = controllers
*pdata++ = ((int)sqControllers[i]);
else if (i < 256) // 128-255 = key pressures
*pdata++ = ((int)sqKeyPressures[i - 128]);
else if (i < 272) // 256-271 = channel pressures
*pdata++ = ((int)sqChanPressures[i - 256]);
else // 272-287 = pitch bend (16-bit!)
// Write single int into "Array"
longAtput(intoArray, (sqPitchBend[i - 272]));
}
interpreterProxy->success(true);
return(toC - fromC + 1);
}
/***************************************************************
* getDeviceName -- write the name of the selected device into the 2nd arg
* Answer the ports directionality.
*/
int sqGetMIDIDeviceName(int index, int name) {
if ((index < 0) || (index > NumPorts))
return(-1);
strcpy((char *)name, gNodeNames[index].name);
return(gNodeNames[index].direction);
}
/***************************************************************
* sqMIDIioctl -- General driver queries and status
*/
int sqMIDIioctl(int which, int onOff) {
int answer = 0;
interpreterProxy->success(true);
switch (which) {
case (sqMIDIInstalled): // Is a MIDI driver installed?
answer = (int)OMSVersion(); // Can I communicate with the "real" driver?
if (answer == 0)
answer = false;
answer = true;
break;
case (sqMIDIVersion): // What driver version is this?
answer = (int)OMSVersion();
break;
case (sqMIDIHasBuffer): // Is there a time-stamped output buffer?
answer = false; // Answer false in the "default" driver
break;
case (sqMIDIHasDurs): // Is there a 1-call note command?
answer = false; // Answer false in the "default" driver
break;
case (sqMIDIHasClock): // Does the driver have its own clock?
answer = false; // Answer false in the "default" driver
break;
case (sqMIDIUseSemaphore): // Should the driver signal a semaphore on input?
switch (onOff) {
case sqMIDIQuery: // Answer value of flag
answer = gInputEnabled;
break;
case sqMIDITurnOn:
gInputEnabled = true; // Set flag
break;
case sqMIDITurnOff:
gInputEnabled = false; // Clear flag
break;
}
break;
case (sqMIDIEcho): // Should we echo in-coming events in the driver?
switch (onOff) {
case sqMIDIQuery:
answer = gEchoEnabled; // Answer value of flag
break;
case sqMIDITurnOn:
gEchoEnabled = true; // Set flag
break;
case sqMIDITurnOff:
gEchoEnabled = false; // Clear flag
break;
}
break;
case (sqMIDIControllerCache): // Should we cache controller values for polling?
switch (onOff) {
case sqMIDIQuery:
answer = gCacheControllers; // Answer value of flag
break;
case sqMIDITurnOn:
gCacheControllers = true; // Set flag
break;
case sqMIDITurnOff:
gCacheControllers = false; // Clear flag
break;
}
break;
case (sqMIDIEventsAvailable): // How many events are in the input Q?
answer = itemsInInQ - itemsInInQ2;
break;
case (sqMIDIFlushDriver): // Special flag to flush the driver's I/O buffers,
itemsInInQ = itemsInInQ2 = 0;
OMSAllNotesOff(); // and send all-notes-off
break;
default: // Default = FAIL
interpreterProxy->success(false);
}
return(answer);
}
/***************************************************************
* sqMIDIAppHook -- Sent on OMS configuration changes; NO-OPs at present
*/
pascal void sqMIDIAppHook(OMSAppHookMsg *pkt, long myRefCon) {
long olda5 = SetA5(myRefCon);
switch (pkt->msgType) {
// What to do when compatibility mode changes?
case omsMsgModeChanged:
// What to do?
break;
// What to do if a receiver is deleted?
case omsMsgDestDeleted:
// What to do?
break;
// What to do if the studio layout changes?
case omsMsgNodesChanged:
// What to do?
break;
}
SetA5(olda5);
}
/***************************************************************
* E N D
*/