1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-25 02:04:23 +00:00
JUCE/src/native/windows/juce_win32_Midi.cpp
2010-04-14 20:08:21 +01:00

630 lines
17 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
//==============================================================================
using ::free;
namespace MidiConstants
{
static const int midiBufferSize = 1024 * 10;
static const int numInHeaders = 32;
static const int inBufferSize = 256;
}
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 = MidiConstants::numInHeaders; --i >= 0;)
{
zeromem (&hdr[i], sizeof (MIDIHDR));
hdr[i].lpData = inData[i];
hdr[i].dwBufferLength = MidiConstants::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);
{
const ScopedLock sl (lock);
if (pendingLength < MidiConstants::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..
}
}
notify();
}
void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) throw()
{
const int num = hdr->dwBytesRecorded;
if (num > 0)
{
const double time = timeStampToTime (timeStamp);
{
const ScopedLock sl (lock);
if (pendingLength < MidiConstants::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..
}
}
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 < MidiConstants::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);
}
}
int len;
{
const ScopedLock sl (lock);
len = pendingLength;
if (len > 0)
{
pendingCopy.ensureSize (len);
pendingCopy.copyFrom (pending, 0, len);
pendingLength = 0;
}
}
//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 < MidiConstants::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);
{ const ScopedLock sl (lock); }
for (int i = MidiConstants::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;
}
}
static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR midiMessage, DWORD_PTR timeStamp)
{
MidiInThread* const thread = reinterpret_cast <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);
}
}
juce_UseDebuggingNewOperator
HMIDIIN hIn;
private:
static Array <void*, CriticalSection> activeMidiThreads;
MidiInput* input;
MidiInputCallback* callback;
bool isStarted;
uint32 startTime;
CriticalSection lock;
MIDIHDR hdr [MidiConstants::numInHeaders];
char inData [MidiConstants::numInHeaders] [MidiConstants::inBufferSize];
int pendingLength;
char pending [MidiConstants::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&);
MidiInThread& operator= (const MidiInThread&);
};
Array <void*, CriticalSection> MidiInThread::activeMidiThreads;
//==============================================================================
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;
}
}
ScopedPointer <MidiInput> in (new MidiInput (name));
ScopedPointer <MidiInThread> thread (new MidiInThread (in, callback));
HMIDIIN h;
HRESULT err = midiInOpen (&h, deviceId,
(DWORD_PTR) &MidiInThread::midiInCallback,
(DWORD_PTR) (MidiInThread*) thread,
CALLBACK_FUNCTION);
if (err == MMSYSERR_NOERROR)
{
thread->hIn = h;
in->internal = thread.release();
return in.release();
}
return 0;
}
MidiInput::MidiInput (const String& name_)
: name (name_),
internal (0)
{
}
MidiInput::~MidiInput()
{
delete static_cast <MidiInThread*> (internal);
}
void MidiInput::start()
{
static_cast <MidiInThread*> (internal)->start();
}
void MidiInput::stop()
{
static_cast <MidiInThread*> (internal)->stop();
}
//==============================================================================
struct MidiOutHandle
{
int refCount;
UINT deviceId;
HMIDIOUT handle;
static Array<MidiOutHandle*> activeHandles;
juce_UseDebuggingNewOperator
};
Array<MidiOutHandle*> MidiOutHandle::activeHandles;
//==============================================================================
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 ("microsoft"))
deviceId = i;
if (index == n)
{
deviceId = i;
break;
}
++n;
}
}
for (i = MidiOutHandle::activeHandles.size(); --i >= 0;)
{
MidiOutHandle* const han = MidiOutHandle::activeHandles.getUnchecked(i);
if (han != 0 && han->deviceId == deviceId)
{
han->refCount++;
MidiOutput* const out = new MidiOutput();
out->internal = 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;
MidiOutHandle::activeHandles.add (han);
MidiOutput* const out = new MidiOutput();
out->internal = han;
return out;
}
else if (res == MMSYSERR_ALLOCATED)
{
Sleep (100);
}
else
{
break;
}
}
return 0;
}
MidiOutput::~MidiOutput()
{
MidiOutHandle* const h = static_cast <MidiOutHandle*> (internal);
if (MidiOutHandle::activeHandles.contains (h) && --(h->refCount) == 0)
{
midiOutClose (h->handle);
MidiOutHandle::activeHandles.removeValue (h);
delete h;
}
}
void MidiOutput::reset()
{
const MidiOutHandle* const h = static_cast <const MidiOutHandle*> (internal);
midiOutReset (h->handle);
}
bool MidiOutput::getVolume (float& leftVol,
float& rightVol)
{
const MidiOutHandle* const handle = static_cast <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 = static_cast <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 = static_cast <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