You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
dexed/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp

489 lines
13 KiB

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2015 - ROLI Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE 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.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
class MidiInCollector
{
public:
MidiInCollector (MidiInput* const input_,
MidiInputCallback& callback_)
: deviceHandle (0),
input (input_),
callback (callback_),
concatenator (4096),
isStarted (false),
startTime (0)
{
}
~MidiInCollector()
{
stop();
if (deviceHandle != 0)
{
for (int count = 5; --count >= 0;)
{
if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
break;
Sleep (20);
}
}
}
//==============================================================================
void handleMessage (const uint8* bytes, const uint32 timeStamp)
{
if (bytes[0] >= 0x80 && isStarted)
{
concatenator.pushMidiData (bytes, MidiMessage::getMessageLengthFromFirstByte (bytes[0]),
convertTimeStamp (timeStamp), input, callback);
writeFinishedBlocks();
}
}
void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp)
{
if (isStarted && hdr->dwBytesRecorded > 0)
{
concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded,
convertTimeStamp (timeStamp), input, callback);
writeFinishedBlocks();
}
}
void start()
{
if (deviceHandle != 0 && ! isStarted)
{
activeMidiCollectors.addIfNotAlreadyThere (this);
for (int i = 0; i < (int) numHeaders; ++i)
{
headers[i].prepare (deviceHandle);
headers[i].write (deviceHandle);
}
startTime = Time::getMillisecondCounterHiRes();
MMRESULT res = midiInStart (deviceHandle);
if (res == MMSYSERR_NOERROR)
{
concatenator.reset();
isStarted = true;
}
else
{
unprepareAllHeaders();
}
}
}
void stop()
{
if (isStarted)
{
isStarted = false;
midiInReset (deviceHandle);
midiInStop (deviceHandle);
activeMidiCollectors.removeFirstMatchingValue (this);
unprepareAllHeaders();
concatenator.reset();
}
}
static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR midiMessage, DWORD_PTR timeStamp)
{
MidiInCollector* const collector = reinterpret_cast <MidiInCollector*> (dwInstance);
if (activeMidiCollectors.contains (collector))
{
if (uMsg == MIM_DATA)
collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp);
else if (uMsg == MIM_LONGDATA)
collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
}
}
HMIDIIN deviceHandle;
private:
static Array <MidiInCollector*, CriticalSection> activeMidiCollectors;
MidiInput* input;
MidiInputCallback& callback;
MidiDataConcatenator concatenator;
bool volatile isStarted;
double startTime;
class MidiHeader
{
public:
MidiHeader() {}
void prepare (HMIDIIN device)
{
zerostruct (hdr);
hdr.lpData = data;
hdr.dwBufferLength = (DWORD) numElementsInArray (data);
midiInPrepareHeader (device, &hdr, sizeof (hdr));
}
void unprepare (HMIDIIN device)
{
if ((hdr.dwFlags & WHDR_DONE) != 0)
{
int c = 10;
while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING)
Thread::sleep (20);
jassert (c >= 0);
}
}
void write (HMIDIIN device)
{
hdr.dwBytesRecorded = 0;
midiInAddBuffer (device, &hdr, sizeof (hdr));
}
void writeIfFinished (HMIDIIN device)
{
if ((hdr.dwFlags & WHDR_DONE) != 0)
write (device);
}
private:
MIDIHDR hdr;
char data [256];
JUCE_DECLARE_NON_COPYABLE (MidiHeader)
};
enum { numHeaders = 32 };
MidiHeader headers [numHeaders];
void writeFinishedBlocks()
{
for (int i = 0; i < (int) numHeaders; ++i)
headers[i].writeIfFinished (deviceHandle);
}
void unprepareAllHeaders()
{
for (int i = 0; i < (int) numHeaders; ++i)
headers[i].unprepare (deviceHandle);
}
double convertTimeStamp (uint32 timeStamp)
{
double t = startTime + timeStamp;
const double now = Time::getMillisecondCounterHiRes();
if (t > now)
{
if (t > now + 2.0)
startTime -= 1.0;
t = now;
}
return t * 0.001;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector)
};
Array <MidiInCollector*, CriticalSection> MidiInCollector::activeMidiCollectors;
//==============================================================================
StringArray MidiInput::getDevices()
{
StringArray s;
const UINT num = midiInGetNumDevs();
for (UINT i = 0; i < num; ++i)
{
MIDIINCAPS mc = { 0 };
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
s.add (String (mc.szPname, sizeof (mc.szPname)));
}
return s;
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback)
{
if (callback == nullptr)
return nullptr;
UINT deviceId = MIDI_MAPPER;
int n = 0;
String name;
const UINT num = midiInGetNumDevs();
for (UINT i = 0; i < num; ++i)
{
MIDIINCAPS mc = { 0 };
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
{
if (index == n)
{
deviceId = i;
name = String (mc.szPname, (size_t) numElementsInArray (mc.szPname));
break;
}
++n;
}
}
ScopedPointer <MidiInput> in (new MidiInput (name));
ScopedPointer <MidiInCollector> collector (new MidiInCollector (in, *callback));
HMIDIIN h;
MMRESULT err = midiInOpen (&h, deviceId,
(DWORD_PTR) &MidiInCollector::midiInCallback,
(DWORD_PTR) (MidiInCollector*) collector,
CALLBACK_FUNCTION);
if (err == MMSYSERR_NOERROR)
{
collector->deviceHandle = h;
in->internal = collector.release();
return in.release();
}
return nullptr;
}
MidiInput::MidiInput (const String& name_)
: name (name_),
internal (0)
{
}
MidiInput::~MidiInput()
{
delete static_cast<MidiInCollector*> (internal);
}
void MidiInput::start() { static_cast<MidiInCollector*> (internal)->start(); }
void MidiInput::stop() { static_cast<MidiInCollector*> (internal)->stop(); }
//==============================================================================
struct MidiOutHandle
{
int refCount;
UINT deviceId;
HMIDIOUT handle;
static Array<MidiOutHandle*> activeHandles;
private:
JUCE_LEAK_DETECTOR (MidiOutHandle)
};
Array<MidiOutHandle*> MidiOutHandle::activeHandles;
//==============================================================================
StringArray MidiOutput::getDevices()
{
StringArray s;
const UINT num = midiOutGetNumDevs();
for (UINT i = 0; i < num; ++i)
{
MIDIOUTCAPS mc = { 0 };
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
s.add (String (mc.szPname, sizeof (mc.szPname)));
}
return s;
}
int MidiOutput::getDefaultDeviceIndex()
{
const UINT num = midiOutGetNumDevs();
int n = 0;
for (UINT i = 0; i < num; ++i)
{
MIDIOUTCAPS mc = { 0 };
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
{
if ((mc.wTechnology & MOD_MAPPER) != 0)
return n;
++n;
}
}
return 0;
}
MidiOutput* MidiOutput::openDevice (int index)
{
UINT deviceId = MIDI_MAPPER;
const UINT num = midiOutGetNumDevs();
int n = 0;
for (UINT i = 0; i < num; ++i)
{
MIDIOUTCAPS mc = { 0 };
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
{
// use the microsoft sw synth as a default - best not to allow deviceId
// to be MIDI_MAPPER, or else device sharing breaks
if (String (mc.szPname, sizeof (mc.szPname)).containsIgnoreCase ("microsoft"))
deviceId = i;
if (index == n)
{
deviceId = i;
break;
}
++n;
}
}
for (int i = MidiOutHandle::activeHandles.size(); --i >= 0;)
{
MidiOutHandle* const han = MidiOutHandle::activeHandles.getUnchecked(i);
if (han->deviceId == deviceId)
{
han->refCount++;
MidiOutput* const out = new MidiOutput();
out->internal = han;
return out;
}
}
for (int i = 4; --i >= 0;)
{
HMIDIOUT h = 0;
MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL);
if (res == MMSYSERR_NOERROR)
{
MidiOutHandle* const han = new MidiOutHandle();
han->deviceId = deviceId;
han->refCount = 1;
han->handle = h;
MidiOutHandle::activeHandles.add (han);
MidiOutput* const out = new MidiOutput();
out->internal = han;
return out;
}
else if (res == MMSYSERR_ALLOCATED)
{
Sleep (100);
}
else
{
break;
}
}
return nullptr;
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
MidiOutHandle* const h = static_cast<MidiOutHandle*> (internal);
if (MidiOutHandle::activeHandles.contains (h) && --(h->refCount) == 0)
{
midiOutClose (h->handle);
MidiOutHandle::activeHandles.removeFirstMatchingValue (h);
delete h;
}
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
const MidiOutHandle* const handle = static_cast<const MidiOutHandle*> (internal);
if (message.getRawDataSize() > 3 || message.isSysEx())
{
MIDIHDR h = { 0 };
h.lpData = (char*) message.getRawData();
h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize();
if (midiOutPrepareHeader (handle->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR)
{
MMRESULT res = midiOutLongMsg (handle->handle, &h, sizeof (MIDIHDR));
if (res == MMSYSERR_NOERROR)
{
while ((h.dwFlags & MHDR_DONE) == 0)
Sleep (1);
int count = 500; // 1 sec timeout
while (--count >= 0)
{
res = midiOutUnprepareHeader (handle->handle, &h, sizeof (MIDIHDR));
if (res == MIDIERR_STILLPLAYING)
Sleep (2);
else
break;
}
}
}
}
else
{
for (int i = 0; i < 50; ++i)
{
if (midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY)
break;
Sleep (1);
}
}
}