|
|
|
|
@@ -0,0 +1,643 @@
|
|
|
|
|
/*
|
|
|
|
|
* MidiApple.cpp - Apple MIDI client
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
|
|
|
|
* Copyright (c) 2015 Maurizio Lo Bosco (rageboge on github)
|
|
|
|
|
*
|
|
|
|
|
* This file is part of LMMS - http://lmms.io
|
|
|
|
|
*
|
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU General Public
|
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
* General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public
|
|
|
|
|
* License along with this program (see COPYING); if not, write to the
|
|
|
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
|
* Boston, MA 02110-1301 USA.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "MidiApple.h"
|
|
|
|
|
|
|
|
|
|
#include <QLabel>
|
|
|
|
|
#include <QLineEdit>
|
|
|
|
|
#include <QtAlgorithms>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
#include "ConfigManager.h"
|
|
|
|
|
#include "MidiPort.h"
|
|
|
|
|
#include "Note.h"
|
|
|
|
|
|
|
|
|
|
#ifdef LMMS_BUILD_APPLE
|
|
|
|
|
|
|
|
|
|
#include <CoreMIDI/CoreMIDI.h>
|
|
|
|
|
|
|
|
|
|
const unsigned int SYSEX_LENGTH=1024;
|
|
|
|
|
|
|
|
|
|
MidiApple::MidiApple() :
|
|
|
|
|
MidiClient(),
|
|
|
|
|
m_inputDevices(),
|
|
|
|
|
m_outputDevices(),
|
|
|
|
|
m_inputSubs(),
|
|
|
|
|
m_outputSubs()
|
|
|
|
|
{
|
|
|
|
|
openDevices();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MidiApple::~MidiApple()
|
|
|
|
|
{
|
|
|
|
|
closeDevices();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::processOutEvent( const MidiEvent& event, const MidiTime& time, const MidiPort* port )
|
|
|
|
|
{
|
|
|
|
|
qDebug("MidiApple:processOutEvent displayName:'%s'",port->displayName().toLatin1().constData());
|
|
|
|
|
|
|
|
|
|
QStringList outDevs;
|
|
|
|
|
for( SubMap::ConstIterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
for( MidiPortList::ConstIterator jt = it.value().begin(); jt != it.value().end(); ++jt )
|
|
|
|
|
{
|
|
|
|
|
if( *jt == port )
|
|
|
|
|
{
|
|
|
|
|
outDevs += it.key();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for( QMap<QString, MIDIEndpointRef>::Iterator it = m_outputDevices.begin(); it != m_outputDevices.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
if( outDevs.contains( it.key() ) )
|
|
|
|
|
{
|
|
|
|
|
sendMidiOut( it.value(), event );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::sendMidiOut( MIDIEndpointRef &endPointRef, const MidiEvent& event )
|
|
|
|
|
{
|
|
|
|
|
MIDIPacketList packetList=createMidiPacketList(event);
|
|
|
|
|
MIDIPortRef port = m_sourcePortRef.value(endPointRef);
|
|
|
|
|
MIDISend(port, endPointRef, &packetList);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MIDIPacketList MidiApple::createMidiPacketList( const MidiEvent& event )
|
|
|
|
|
{
|
|
|
|
|
MIDIPacketList packetList;
|
|
|
|
|
packetList.numPackets = 1;
|
|
|
|
|
MIDIPacket* firstPacket = &packetList.packet[0];
|
|
|
|
|
firstPacket->timeStamp = 0; // send immediately
|
|
|
|
|
firstPacket->length = 3;
|
|
|
|
|
firstPacket->data[0] = ( event.type() + event.channel() );
|
|
|
|
|
firstPacket->data[1] = ( event.param( 0 ) & 0xff );
|
|
|
|
|
firstPacket->data[2] = ( event.param( 1 ) & 0xff );
|
|
|
|
|
return packetList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::applyPortMode( MidiPort* port )
|
|
|
|
|
{
|
|
|
|
|
qDebug("applyPortMode displayName:'%s'",port->displayName().toLatin1().constData());
|
|
|
|
|
// make sure no subscriptions exist which are not possible with
|
|
|
|
|
// current port-mode
|
|
|
|
|
if( !port->isInputEnabled() )
|
|
|
|
|
{
|
|
|
|
|
for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
it.value().removeAll( port );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( !port->isOutputEnabled() )
|
|
|
|
|
{
|
|
|
|
|
for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
it.value().removeAll( port );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::removePort( MidiPort* port )
|
|
|
|
|
{
|
|
|
|
|
qDebug("removePort displayName:'%s'",port->displayName().toLatin1().constData());
|
|
|
|
|
for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
it.value().removeAll( port );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
it.value().removeAll( port );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MidiClient::removePort( port );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
QString MidiApple::sourcePortName( const MidiEvent& event ) const
|
|
|
|
|
{
|
|
|
|
|
qDebug("sourcePortName return '%s'?\n", event.sourcePort());
|
|
|
|
|
/*
|
|
|
|
|
if( event.sourcePort() )
|
|
|
|
|
{
|
|
|
|
|
return m_inputDevices.value( *static_cast<const HMIDIIN *>( event.sourcePort() ) );
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
return MidiClient::sourcePortName( event );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::subscribeReadablePort( MidiPort* port, const QString& dest, bool subscribe )
|
|
|
|
|
{
|
|
|
|
|
qDebug("subscribeReadablePort %s subscribe=%d",dest.toLatin1().constData(),subscribe);
|
|
|
|
|
if( subscribe && port->isInputEnabled() == false )
|
|
|
|
|
{
|
|
|
|
|
qWarning( "port %s can't be (un)subscribed!", port->displayName().toLatin1().constData() );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_inputSubs[dest].removeAll( port );
|
|
|
|
|
if( subscribe )
|
|
|
|
|
{
|
|
|
|
|
qDebug("Subscribing %s",dest.toLatin1().constData());
|
|
|
|
|
m_inputSubs[dest].push_back( port );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MidiPortList list = m_inputSubs[dest];
|
|
|
|
|
if(list.empty()){
|
|
|
|
|
m_inputSubs.remove(dest);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::subscribeWritablePort( MidiPort* port, const QString& dest, bool subscribe )
|
|
|
|
|
{
|
|
|
|
|
qDebug("subscribeWritablePort %s", port->displayName().toLatin1().constData());
|
|
|
|
|
|
|
|
|
|
if( subscribe && port->isOutputEnabled() == false )
|
|
|
|
|
{
|
|
|
|
|
qWarning( "port %s can't be (un)subscribed!", port->displayName().toLatin1().constData() );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_outputSubs[dest].removeAll( port );
|
|
|
|
|
if( subscribe )
|
|
|
|
|
{
|
|
|
|
|
m_outputSubs[dest].push_back( port );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MidiPortList list = m_outputSubs[dest];
|
|
|
|
|
if(list.empty()){
|
|
|
|
|
m_outputSubs.remove(dest);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::ReadCallback( const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon )
|
|
|
|
|
{
|
|
|
|
|
MidiApple *caller = static_cast<MidiApple*>(readProcRefCon);
|
|
|
|
|
if (!caller)
|
|
|
|
|
{
|
|
|
|
|
qDebug("Error: !caller: MidiApple::ReadCallback");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
caller->HandleReadCallback(pktlist,srcConnRefCon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConnRefCon )
|
|
|
|
|
{
|
|
|
|
|
const char * refName = (const char *) srcConnRefCon;
|
|
|
|
|
|
|
|
|
|
MIDIEndpointRef endPointRef = m_inputDevices.value(refName);
|
|
|
|
|
if( !m_inputSubs.contains( refName ) )
|
|
|
|
|
{
|
|
|
|
|
// qDebug("HandleReadCallback '%s' not subscribed",refName);
|
|
|
|
|
// printQStringKeys("m_inputDevices", m_inputDevices);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// qDebug("HandleReadCallback '%s' subscribed",refName);
|
|
|
|
|
bool continueSysEx = false;
|
|
|
|
|
unsigned int nBytes;
|
|
|
|
|
const MIDIPacket *packet = &pktlist->packet[0];
|
|
|
|
|
unsigned char sysExMessage[SYSEX_LENGTH];
|
|
|
|
|
unsigned int sysExLength = 0;
|
|
|
|
|
|
|
|
|
|
for (uint32_t i=0; i<pktlist->numPackets; ++i)
|
|
|
|
|
{
|
|
|
|
|
nBytes = packet->length;
|
|
|
|
|
// Check if this is the end of a continued SysEx message
|
|
|
|
|
if (continueSysEx) {
|
|
|
|
|
unsigned int lengthToCopy = qMin(nBytes, SYSEX_LENGTH - sysExLength);
|
|
|
|
|
// Copy the message into our SysEx message buffer,
|
|
|
|
|
// making sure not to overrun the buffer
|
|
|
|
|
memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
|
|
|
|
|
sysExLength += lengthToCopy;
|
|
|
|
|
// Check if the last byte is SysEx End.
|
|
|
|
|
continueSysEx = (packet->data[nBytes - 1] == 0xF7);
|
|
|
|
|
if (!continueSysEx || sysExLength == SYSEX_LENGTH) {
|
|
|
|
|
// We would process the SysEx message here, as it is we're just ignoring it
|
|
|
|
|
|
|
|
|
|
sysExLength = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UInt16 iByte, size;
|
|
|
|
|
|
|
|
|
|
iByte = 0;
|
|
|
|
|
while (iByte < nBytes)
|
|
|
|
|
{
|
|
|
|
|
size = 0;
|
|
|
|
|
|
|
|
|
|
// First byte should be status
|
|
|
|
|
unsigned char status = packet->data[iByte];
|
|
|
|
|
if (status < 0xC0) {
|
|
|
|
|
size = 3;
|
|
|
|
|
}
|
|
|
|
|
else if (status < 0xE0)
|
|
|
|
|
{
|
|
|
|
|
size = 2;
|
|
|
|
|
}
|
|
|
|
|
else if (status < 0xF0)
|
|
|
|
|
{
|
|
|
|
|
size = 3;
|
|
|
|
|
}
|
|
|
|
|
else if (status == 0xF0)
|
|
|
|
|
{
|
|
|
|
|
// MIDI SysEx then we copy the rest of the message into the SysEx message buffer
|
|
|
|
|
unsigned int lengthLeftInMessage = nBytes - iByte;
|
|
|
|
|
unsigned int lengthToCopy = qMin(lengthLeftInMessage, SYSEX_LENGTH);
|
|
|
|
|
|
|
|
|
|
memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
|
|
|
|
|
sysExLength += lengthToCopy;
|
|
|
|
|
|
|
|
|
|
size = 0;
|
|
|
|
|
iByte = nBytes;
|
|
|
|
|
|
|
|
|
|
// Check whether the message at the end is the end of the SysEx
|
|
|
|
|
continueSysEx = (packet->data[nBytes - 1] != 0xF7);
|
|
|
|
|
}
|
|
|
|
|
else if (status < 0xF3)
|
|
|
|
|
{
|
|
|
|
|
size = 3;
|
|
|
|
|
}
|
|
|
|
|
else if (status == 0xF3)
|
|
|
|
|
{
|
|
|
|
|
size = 2;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
size = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned char messageChannel = status & 0xF;
|
|
|
|
|
const MidiEventTypes cmdtype = static_cast<MidiEventTypes>( status & 0xF0 );
|
|
|
|
|
const int par1 = packet->data[iByte + 1];
|
|
|
|
|
const int par2 = packet->data[iByte + 2];
|
|
|
|
|
|
|
|
|
|
switch (cmdtype)
|
|
|
|
|
{
|
|
|
|
|
case MidiNoteOff: //0x80:
|
|
|
|
|
case MidiNoteOn: //0x90:
|
|
|
|
|
case MidiKeyPressure: //0xA0:
|
|
|
|
|
notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 - KeysPerOctave, par2 & 0xff, &endPointRef ));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MidiControlChange: //0xB0:
|
|
|
|
|
case MidiProgramChange: //0xC0:
|
|
|
|
|
case MidiChannelPressure: //0xD0:
|
|
|
|
|
notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1, par2 & 0xff, &endPointRef ));
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MidiPitchBend: //0xE0:
|
|
|
|
|
notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 + par2 * 128, 0, &endPointRef ));
|
|
|
|
|
break;
|
|
|
|
|
case MidiActiveSensing: //0xF0
|
|
|
|
|
case 0xF0:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
qDebug("endPointRef name='%s':Some other message %d", refName, cmdtype);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
iByte += size;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
packet = MIDIPacketNext(packet);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::updateDeviceList()
|
|
|
|
|
{
|
|
|
|
|
closeDevices();
|
|
|
|
|
openDevices();
|
|
|
|
|
|
|
|
|
|
emit readablePortsChanged();
|
|
|
|
|
emit writablePortsChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::closeDevices()
|
|
|
|
|
{
|
|
|
|
|
m_inputSubs.clear();
|
|
|
|
|
m_outputSubs.clear();
|
|
|
|
|
|
|
|
|
|
QMapIterator<QString, MIDIEndpointRef> i( m_inputDevices );
|
|
|
|
|
while( i.hasNext() )
|
|
|
|
|
{
|
|
|
|
|
midiInClose( i.next().value() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QMapIterator<QString, MIDIEndpointRef> o( m_outputDevices );
|
|
|
|
|
while( o.hasNext() )
|
|
|
|
|
{
|
|
|
|
|
midiInClose( o.next().value() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_inputDevices.clear();
|
|
|
|
|
m_outputDevices.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::midiInClose( MIDIEndpointRef reference )
|
|
|
|
|
{
|
|
|
|
|
qDebug("midiInClose '%s'", getFullName(reference));
|
|
|
|
|
MIDIPortRef portRef = m_sourcePortRef[reference];
|
|
|
|
|
MIDIPortDisconnectSource(portRef, reference);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char *getName( MIDIObjectRef &object )
|
|
|
|
|
{
|
|
|
|
|
// Returns the name of a given MIDIObjectRef as char *
|
|
|
|
|
CFStringRef name = nil;
|
|
|
|
|
if (noErr != MIDIObjectGetStringProperty(object, kMIDIPropertyName, &name))
|
|
|
|
|
return nil;
|
|
|
|
|
int len = CFStringGetLength(name)+1;
|
|
|
|
|
char *value = (char *) malloc(len);
|
|
|
|
|
|
|
|
|
|
CFStringGetCString(name, value, len, 0);
|
|
|
|
|
CFRelease(name);
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const void printQStringKeys( char const * mapName, const QMap<QString,MIDIEndpointRef> &inputMap )
|
|
|
|
|
{
|
|
|
|
|
qDebug("%s:", mapName);
|
|
|
|
|
QMapIterator<QString, MIDIEndpointRef> i( inputMap );
|
|
|
|
|
while( i.hasNext() )
|
|
|
|
|
{
|
|
|
|
|
QString key = i.next().key();
|
|
|
|
|
qDebug(" key='%s'", key.toLatin1().constData());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::openDevices()
|
|
|
|
|
{
|
|
|
|
|
qDebug("openDevices");
|
|
|
|
|
m_inputDevices.clear();
|
|
|
|
|
// How many MIDI devices do we have?
|
|
|
|
|
ItemCount deviceCount = MIDIGetNumberOfDevices();
|
|
|
|
|
|
|
|
|
|
// Iterate through all MIDI devices
|
|
|
|
|
for (ItemCount i = 0 ; i < deviceCount ; ++i)
|
|
|
|
|
{
|
|
|
|
|
// Grab a reference to current device
|
|
|
|
|
MIDIDeviceRef device = MIDIGetDevice(i);
|
|
|
|
|
char * deviceName = getName(device);
|
|
|
|
|
QString qsDeviceName = QString::fromUtf8((char*)(deviceName));
|
|
|
|
|
qDebug("Device name:%s",deviceName);
|
|
|
|
|
|
|
|
|
|
// Is this device online? (Currently connected?)
|
|
|
|
|
SInt32 isOffline = 0;
|
|
|
|
|
MIDIObjectGetIntegerProperty(device, kMIDIPropertyOffline, &isOffline);
|
|
|
|
|
qDebug(" is online: %s", (isOffline ? "No" : "Yes"));
|
|
|
|
|
// How many entities do we have?
|
|
|
|
|
ItemCount entityCount = MIDIDeviceGetNumberOfEntities(device);
|
|
|
|
|
|
|
|
|
|
// Iterate through this device's entities
|
|
|
|
|
for (ItemCount j = 0 ; j < entityCount ; ++j)
|
|
|
|
|
{
|
|
|
|
|
// Grab a reference to an entity
|
|
|
|
|
MIDIEntityRef entity = MIDIDeviceGetEntity(device, j);
|
|
|
|
|
qDebug(" Entity: %s", getName(entity));
|
|
|
|
|
|
|
|
|
|
// Iterate through this device's source endpoints (MIDI In)
|
|
|
|
|
ItemCount sourceCount = MIDIEntityGetNumberOfSources(entity);
|
|
|
|
|
for ( ItemCount k = 0 ; k < sourceCount ; ++k )
|
|
|
|
|
{
|
|
|
|
|
// Grab a reference to a source endpoint
|
|
|
|
|
MIDIEndpointRef source = MIDIEntityGetSource(entity, k);
|
|
|
|
|
char * name = getName(source);
|
|
|
|
|
qDebug(" Source: '%s'", name);
|
|
|
|
|
QString sourceName = qsDeviceName + ":" + QString::fromUtf8((char*)(name));
|
|
|
|
|
qDebug(" Source name: '%s'", sourceName.toLatin1().constData() );
|
|
|
|
|
m_inputDevices.insert(sourceName, source);
|
|
|
|
|
openMidiReference(source,sourceName,true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iterate through this device's destination endpoints (MIDI Out)
|
|
|
|
|
ItemCount destCount = MIDIEntityGetNumberOfDestinations(entity);
|
|
|
|
|
for ( ItemCount k = 0 ; k < destCount ; ++k )
|
|
|
|
|
{
|
|
|
|
|
// Grab a reference to a destination endpoint
|
|
|
|
|
MIDIEndpointRef dest = MIDIEntityGetDestination(entity, k);
|
|
|
|
|
char * name = getName(dest);
|
|
|
|
|
qDebug(" Destination: '%s'", name);
|
|
|
|
|
QString destinationName = qsDeviceName + ":" + QString::fromUtf8((char*)(name));
|
|
|
|
|
qDebug(" Destination name: '%s'", destinationName.toLatin1().constData() );
|
|
|
|
|
m_outputDevices.insert(destinationName, dest);
|
|
|
|
|
openMidiReference(dest,destinationName,false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qDebug("------");
|
|
|
|
|
}
|
|
|
|
|
printQStringKeys("m_inputDevices:",m_inputDevices);
|
|
|
|
|
printQStringKeys("m_outputDevices:",m_outputDevices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::openMidiReference( MIDIEndpointRef reference, QString refName, bool isIn )
|
|
|
|
|
{
|
|
|
|
|
char * registeredName = (char*) malloc(refName.length()+1);
|
|
|
|
|
sprintf(registeredName, "%s",refName.toLatin1().constData());
|
|
|
|
|
qDebug("openMidiReference refName '%s'",refName.toLatin1().constData());
|
|
|
|
|
|
|
|
|
|
MIDIClientRef mClient = getMidiClientRef();
|
|
|
|
|
MIDIPortRef mPort = 0;
|
|
|
|
|
|
|
|
|
|
CFStringRef inName = CFStringCreateWithCString(0, registeredName, kCFStringEncodingASCII);
|
|
|
|
|
if(isIn)
|
|
|
|
|
{
|
|
|
|
|
MIDIInputPortCreate(mClient, inName, &MidiApple::ReadCallback, this, &mPort);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MIDIOutputPortCreate(mClient, inName, &mPort);
|
|
|
|
|
}
|
|
|
|
|
MIDIPortConnectSource(mPort, reference, (void *)registeredName);
|
|
|
|
|
m_sourcePortRef.insert(reference, mPort);
|
|
|
|
|
CFRelease(inName);
|
|
|
|
|
qDebug("openMidiReference registeredName '%s'",registeredName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MIDIClientRef MidiApple::getMidiClientRef()
|
|
|
|
|
{
|
|
|
|
|
if(mClient==0)
|
|
|
|
|
{
|
|
|
|
|
CFStringRef deviceClientName = CFSTR("MIDI In Device Client");
|
|
|
|
|
MIDIClientCreate(deviceClientName, NotifyCallback, this, &mClient);
|
|
|
|
|
CFRelease(deviceClientName);
|
|
|
|
|
}
|
|
|
|
|
return mClient;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::notifyMidiPortList( MidiPortList l, MidiEvent event )
|
|
|
|
|
{
|
|
|
|
|
for( MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it )
|
|
|
|
|
{
|
|
|
|
|
( *it )->processInEvent( event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void MidiApple::NotifyCallback( const MIDINotification *message, void *refCon )
|
|
|
|
|
{
|
|
|
|
|
//refCon is a pointer to MidiApple class
|
|
|
|
|
MidiApple *midiApple = (MidiApple *)refCon;
|
|
|
|
|
qDebug("MidiApple::NotifyCallback '%d'",message->messageID);
|
|
|
|
|
switch (message->messageID)
|
|
|
|
|
{
|
|
|
|
|
case kMIDIMsgObjectAdded:
|
|
|
|
|
{
|
|
|
|
|
MIDIObjectAddRemoveNotification* msg = (MIDIObjectAddRemoveNotification*)message ;
|
|
|
|
|
MIDIEndpointRef endpoint_ref = (MIDIEndpointRef)msg->child ;
|
|
|
|
|
char * fullName = midiApple->getFullName(endpoint_ref);
|
|
|
|
|
|
|
|
|
|
if (msg->childType == kMIDIObjectType_Source) {
|
|
|
|
|
qDebug("kMIDIMsgObjectAdded source '%s'",fullName);
|
|
|
|
|
// Here your code to save the new source ref
|
|
|
|
|
// and update your internal "clients" (eg
|
|
|
|
|
// update a popup menu, a list, ...)
|
|
|
|
|
}
|
|
|
|
|
if (msg->childType == kMIDIObjectType_Destination) {
|
|
|
|
|
qDebug("kMIDIMsgObjectAdded destination '%s'",fullName);
|
|
|
|
|
// Here your code to save the new destination ref
|
|
|
|
|
// and update your internal "clients"
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kMIDIMsgObjectRemoved:
|
|
|
|
|
{
|
|
|
|
|
MIDIObjectAddRemoveNotification*
|
|
|
|
|
msg = (MIDIObjectAddRemoveNotification*)message ;
|
|
|
|
|
MIDIEndpointRef endpoint_ref = (MIDIEndpointRef)msg->child ;
|
|
|
|
|
char * fullName = midiApple->getFullName(endpoint_ref);
|
|
|
|
|
|
|
|
|
|
if (msg->childType == kMIDIObjectType_Source) {
|
|
|
|
|
// Here your code to remove the source ref
|
|
|
|
|
// and update your internal "clients"
|
|
|
|
|
qDebug("kMIDIMsgObjectRemoved source '%s'",fullName);
|
|
|
|
|
}
|
|
|
|
|
if (msg->childType == kMIDIObjectType_Destination) {
|
|
|
|
|
// Here your code to remove the destination ref
|
|
|
|
|
// and update your internal "clients"
|
|
|
|
|
qDebug("kMIDIMsgObjectRemoved destination '%s'",fullName); }
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case kMIDIMsgPropertyChanged:
|
|
|
|
|
// Currently ignored
|
|
|
|
|
qDebug("kMIDIMsgPropertyChanged");
|
|
|
|
|
break;
|
|
|
|
|
case kMIDIMsgThruConnectionsChanged:
|
|
|
|
|
// Currently ignored
|
|
|
|
|
qDebug("kMIDIMsgThruConnectionsChanged");
|
|
|
|
|
break;
|
|
|
|
|
case kMIDIMsgSerialPortOwnerChanged:
|
|
|
|
|
// Currently ignored
|
|
|
|
|
qDebug("kMIDIMsgSerialPortOwnerChanged");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
qDebug("unhandled message type");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char * MidiApple::getFullName(MIDIEndpointRef &endpoint_ref)
|
|
|
|
|
{
|
|
|
|
|
MIDIEntityRef entity = 0;
|
|
|
|
|
MIDIEndpointGetEntity(endpoint_ref, &entity); //get the entity
|
|
|
|
|
MIDIDeviceRef device = 0;
|
|
|
|
|
MIDIEntityGetDevice(entity, &device);
|
|
|
|
|
char * deviceName = getName(device);
|
|
|
|
|
char * endPointName = getName(endpoint_ref);
|
|
|
|
|
qDebug("device name='%s' endpoint name='%s'",deviceName,endPointName);
|
|
|
|
|
char * fullName = (char *)malloc(strlen(deviceName) + strlen(endPointName)+1);
|
|
|
|
|
sprintf(fullName, "%s:%s", deviceName,endPointName);
|
|
|
|
|
return fullName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MidiApple::setupWidget::setupWidget( QWidget* parent ) :
|
|
|
|
|
MidiClient::setupWidget( MidiApple::name(), parent )
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MidiApple::setupWidget::~setupWidget()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|