1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-17 00:44:19 +00:00
JUCE/src/native/windows/juce_win32_Midi.cpp

630 lines
16 KiB
C++

/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-9 by Raw Material Software Ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online 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.rawmaterialsoftware.com/juce for more information.
==============================================================================
*/
// (This file gets included by juce_win32_NativeCode.cpp, rather than being
// compiled on its own).
#if JUCE_INCLUDED_FILE
//==============================================================================
static const int midiBufferSize = 1024 * 10;
static const int numInHeaders = 32;
static const int inBufferSize = 256;
static Array <void*, CriticalSection> activeMidiThreads;
using ::free;
class MidiInThread : public Thread
{
public:
//==============================================================================
MidiInThread (MidiInput* const input_,
MidiInputCallback* const callback_)
: Thread ("Juce Midi"),
hIn (0),
input (input_),
callback (callback_),
isStarted (false),
startTime (0),
pendingLength(0)
{
for (int i = numInHeaders; --i >= 0;)
{
zeromem (&hdr[i], sizeof (MIDIHDR));
hdr[i].lpData = inData[i];
hdr[i].dwBufferLength = inBufferSize;
}
};
~MidiInThread()
{
stop();
if (hIn != 0)
{
int count = 5;
while (--count >= 0)
{
if (midiInClose (hIn) == MMSYSERR_NOERROR)
break;
Sleep (20);
}
}
}
//==============================================================================
void handle (const uint32 message, const uint32 timeStamp) throw()
{
const int byte = message & 0xff;
if (byte < 0x80)
return;
const int numBytes = MidiMessage::getMessageLengthFromFirstByte ((uint8) byte);
const double time = timeStampToTime (timeStamp);
lock.enter();
if (pendingLength < midiBufferSize - 12)
{
char* const p = pending + pendingLength;
*(double*) p = time;
*(uint32*) (p + 8) = numBytes;
*(uint32*) (p + 12) = message;
pendingLength += 12 + numBytes;
}
else
{
jassertfalse // midi buffer overflow! You might need to increase the size..
}
lock.exit();
notify();
}
void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) throw()
{
const int num = hdr->dwBytesRecorded;
if (num > 0)
{
const double time = timeStampToTime (timeStamp);
lock.enter();
if (pendingLength < midiBufferSize - (8 + num))
{
char* const p = pending + pendingLength;
*(double*) p = time;
*(uint32*) (p + 8) = num;
memcpy (p + 12, hdr->lpData, num);
pendingLength += 12 + num;
}
else
{
jassertfalse // midi buffer overflow! You might need to increase the size..
}
lock.exit();
notify();
}
}
void writeBlock (const int i) throw()
{
hdr[i].dwBytesRecorded = 0;
MMRESULT res = midiInPrepareHeader (hIn, &hdr[i], sizeof (MIDIHDR));
jassert (res == MMSYSERR_NOERROR);
res = midiInAddBuffer (hIn, &hdr[i], sizeof (MIDIHDR));
jassert (res == MMSYSERR_NOERROR);
}
void run()
{
MemoryBlock pendingCopy (64);
while (! threadShouldExit())
{
for (int i = 0; i < numInHeaders; ++i)
{
if ((hdr[i].dwFlags & WHDR_DONE) != 0)
{
MMRESULT res = midiInUnprepareHeader (hIn, &hdr[i], sizeof (MIDIHDR));
(void) res;
jassert (res == MMSYSERR_NOERROR);
writeBlock (i);
}
}
lock.enter();
int len = pendingLength;
if (len > 0)
{
pendingCopy.ensureSize (len);
pendingCopy.copyFrom (pending, 0, len);
pendingLength = 0;
}
lock.exit();
//xxx needs to figure out if blocks are broken up or not
if (len == 0)
{
wait (500);
}
else
{
const char* p = (const char*) pendingCopy.getData();
while (len > 0)
{
const double time = *(const double*) p;
const int messageLen = *(const int*) (p + 8);
const MidiMessage message ((const uint8*) (p + 12), messageLen, time);
callback->handleIncomingMidiMessage (input, message);
p += 12 + messageLen;
len -= 12 + messageLen;
}
}
}
}
void start() throw()
{
jassert (hIn != 0);
if (hIn != 0 && ! isStarted)
{
stop();
activeMidiThreads.addIfNotAlreadyThere (this);
int i;
for (i = 0; i < numInHeaders; ++i)
writeBlock (i);
startTime = Time::getMillisecondCounter();
MMRESULT res = midiInStart (hIn);
jassert (res == MMSYSERR_NOERROR);
if (res == MMSYSERR_NOERROR)
{
isStarted = true;
pendingLength = 0;
startThread (6);
}
}
}
void stop() throw()
{
if (isStarted)
{
stopThread (5000);
midiInReset (hIn);
midiInStop (hIn);
activeMidiThreads.removeValue (this);
lock.enter();
lock.exit();
for (int i = numInHeaders; --i >= 0;)
{
if ((hdr[i].dwFlags & WHDR_DONE) != 0)
{
int c = 10;
while (--c >= 0 && midiInUnprepareHeader (hIn, &hdr[i], sizeof (MIDIHDR)) == MIDIERR_STILLPLAYING)
Sleep (20);
jassert (c >= 0);
}
}
isStarted = false;
pendingLength = 0;
}
}
juce_UseDebuggingNewOperator
HMIDIIN hIn;
private:
MidiInput* input;
MidiInputCallback* callback;
bool isStarted;
uint32 startTime;
CriticalSection lock;
MIDIHDR hdr [numInHeaders];
char inData [numInHeaders] [inBufferSize];
int pendingLength;
char pending [midiBufferSize];
double timeStampToTime (uint32 timeStamp) throw()
{
timeStamp += startTime;
const uint32 now = Time::getMillisecondCounter();
if (timeStamp > now)
{
if (timeStamp > now + 2)
--startTime;
timeStamp = now;
}
return 0.001 * timeStamp;
}
MidiInThread (const MidiInThread&);
const MidiInThread& operator= (const MidiInThread&);
};
static void CALLBACK midiInCallback (HMIDIIN,
UINT uMsg,
DWORD_PTR dwInstance,
DWORD_PTR midiMessage,
DWORD_PTR timeStamp)
{
MidiInThread* const thread = (MidiInThread*) dwInstance;
if (thread != 0 && activeMidiThreads.contains (thread))
{
if (uMsg == MIM_DATA)
thread->handle ((uint32) midiMessage, (uint32) timeStamp);
else if (uMsg == MIM_LONGDATA)
thread->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
}
}
//==============================================================================
const StringArray MidiInput::getDevices()
{
StringArray s;
const int num = midiInGetNumDevs();
for (int i = 0; i < num; ++i)
{
MIDIINCAPS mc;
zerostruct (mc);
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 == 0)
return 0;
UINT deviceId = MIDI_MAPPER;
int n = 0;
String name;
const int num = midiInGetNumDevs();
for (int i = 0; i < num; ++i)
{
MIDIINCAPS mc;
zerostruct (mc);
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
{
if (index == n)
{
deviceId = i;
name = String (mc.szPname, sizeof (mc.szPname));
break;
}
++n;
}
}
MidiInput* const in = new MidiInput (name);
MidiInThread* const thread = new MidiInThread (in, callback);
HMIDIIN h;
HRESULT err = midiInOpen (&h, deviceId,
(DWORD_PTR) &midiInCallback,
(DWORD_PTR) thread,
CALLBACK_FUNCTION);
if (err == MMSYSERR_NOERROR)
{
thread->hIn = h;
in->internal = (void*) thread;
return in;
}
else
{
delete in;
delete thread;
return 0;
}
}
MidiInput::MidiInput (const String& name_)
: name (name_),
internal (0)
{
}
MidiInput::~MidiInput()
{
if (internal != 0)
{
MidiInThread* const thread = (MidiInThread*) internal;
delete thread;
}
}
void MidiInput::start()
{
((MidiInThread*) internal)->start();
}
void MidiInput::stop()
{
((MidiInThread*) internal)->stop();
}
//==============================================================================
struct MidiOutHandle
{
int refCount;
UINT deviceId;
HMIDIOUT handle;
juce_UseDebuggingNewOperator
};
static VoidArray handles (4);
//==============================================================================
const StringArray MidiOutput::getDevices()
{
StringArray s;
const int num = midiOutGetNumDevs();
for (int i = 0; i < num; ++i)
{
MIDIOUTCAPS mc;
zerostruct (mc);
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
s.add (String (mc.szPname, sizeof (mc.szPname)));
}
return s;
}
int MidiOutput::getDefaultDeviceIndex()
{
const int num = midiOutGetNumDevs();
int n = 0;
for (int i = 0; i < num; ++i)
{
MIDIOUTCAPS mc;
zerostruct (mc);
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 int num = midiOutGetNumDevs();
int i, n = 0;
for (i = 0; i < num; ++i)
{
MIDIOUTCAPS mc;
zerostruct (mc);
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 (T("microsoft")))
deviceId = i;
if (index == n)
{
deviceId = i;
break;
}
++n;
}
}
for (i = handles.size(); --i >= 0;)
{
MidiOutHandle* const han = (MidiOutHandle*) handles.getUnchecked(i);
if (han != 0 && han->deviceId == deviceId)
{
han->refCount++;
MidiOutput* const out = new MidiOutput();
out->internal = (void*) han;
return out;
}
}
for (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;
handles.add (han);
MidiOutput* const out = new MidiOutput();
out->internal = (void*) han;
return out;
}
else if (res == MMSYSERR_ALLOCATED)
{
Sleep (100);
}
else
{
break;
}
}
return 0;
}
MidiOutput::~MidiOutput()
{
MidiOutHandle* const h = (MidiOutHandle*) internal;
if (handles.contains ((void*) h) && --(h->refCount) == 0)
{
midiOutClose (h->handle);
handles.removeValue ((void*) h);
delete h;
}
}
void MidiOutput::reset()
{
const MidiOutHandle* const h = (MidiOutHandle*) internal;
midiOutReset (h->handle);
}
bool MidiOutput::getVolume (float& leftVol,
float& rightVol)
{
const MidiOutHandle* const handle = (const MidiOutHandle*) internal;
DWORD n;
if (midiOutGetVolume (handle->handle, &n) == MMSYSERR_NOERROR)
{
const unsigned short* const nn = (const unsigned short*) &n;
rightVol = nn[0] / (float) 0xffff;
leftVol = nn[1] / (float) 0xffff;
return true;
}
else
{
rightVol = leftVol = 1.0f;
return false;
}
}
void MidiOutput::setVolume (float leftVol,
float rightVol)
{
const MidiOutHandle* const handle = (MidiOutHandle*) internal;
DWORD n;
unsigned short* const nn = (unsigned short*) &n;
nn[0] = (unsigned short) jlimit (0, 0xffff, (int)(rightVol * 0xffff));
nn[1] = (unsigned short) jlimit (0, 0xffff, (int)(leftVol * 0xffff));
midiOutSetVolume (handle->handle, n);
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
const MidiOutHandle* const handle = (const MidiOutHandle*) internal;
if (message.getRawDataSize() > 3
|| message.isSysEx())
{
MIDIHDR h;
zerostruct (h);
h.lpData = (char*) message.getRawData();
h.dwBufferLength = message.getRawDataSize();
h.dwBytesRecorded = 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
{
midiOutShortMsg (handle->handle,
*(unsigned int*) message.getRawData());
}
}
#endif