/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified 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. 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. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "win32_headers.h" #include "../../../src/juce_core/basics/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "../../../src/juce_appframework/audio/devices/juce_MidiOutput.h" #include "../../../src/juce_appframework/audio/devices/juce_MidiInput.h" #include "../../../src/juce_core/basics/juce_Time.h" #include "../../../src/juce_core/threads/juce_Thread.h" #include "../../../src/juce_core/containers/juce_MemoryBlock.h" #if JUCE_MSVC #pragma warning (disable: 4312) #endif using ::free; //============================================================================== static const int midiBufferSize = 1024 * 10; static const int numInHeaders = 32; static const int inBufferSize = 256; static Array activeMidiThreads; 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()); } } END_JUCE_NAMESPACE