From 5ba4a4dbb58018fe460a5ed327352447b5ca2b1c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 1 Jul 2014 09:53:18 -0700 Subject: [PATCH] initial pass of MIDI implementation that bubbles events to JS --- .gitignore | 4 + cmake/modules/FindRtMidi.cmake | 33 +++++++ interface/CMakeLists.txt | 94 ++++++++++++-------- interface/external/rtmidi/readme.txt | 41 +++++++++ interface/src/Application.cpp | 6 ++ interface/src/devices/MIDIManager.cpp | 64 +++++++++++++ interface/src/devices/MIDIManager.h | 46 ++++++++++ libraries/script-engine/src/MIDIEvent.cpp | 37 ++++++++ libraries/script-engine/src/MIDIEvent.h | 32 +++++++ libraries/script-engine/src/ScriptEngine.cpp | 2 + 10 files changed, 321 insertions(+), 38 deletions(-) create mode 100644 cmake/modules/FindRtMidi.cmake create mode 100644 interface/external/rtmidi/readme.txt create mode 100644 interface/src/devices/MIDIManager.cpp create mode 100644 interface/src/devices/MIDIManager.h create mode 100644 libraries/script-engine/src/MIDIEvent.cpp create mode 100644 libraries/script-engine/src/MIDIEvent.h diff --git a/.gitignore b/.gitignore index 8d537b993f..4176dcc652 100644 --- a/.gitignore +++ b/.gitignore @@ -50,5 +50,9 @@ interface/external/faceplus/* interface/external/priovr/* !interface/external/priovr/readme.txt +# Ignore RtMidi +interface/external/rtmidi/* +!interface/external/rtmidi/readme.txt + # Ignore interfaceCache for Linux users interface/interfaceCache/ diff --git a/cmake/modules/FindRtMidi.cmake b/cmake/modules/FindRtMidi.cmake new file mode 100644 index 0000000000..a54cc483e1 --- /dev/null +++ b/cmake/modules/FindRtMidi.cmake @@ -0,0 +1,33 @@ +# +# FindRtMidd.cmake +# +# Try to find the RtMidi library +# +# You can provide a RTMIDI_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# RTMIDI_FOUND - system found RtMidi +# RTMIDI_INCLUDE_DIRS - the RtMidi include directory +# RTMIDI_CPP - Include this with src to use RtMidi +# +# Created on 6/30/2014 by Stephen Birarda +# Copyright 2014 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +if (RTMIDI_LIBRARIES AND RTMIDI_INCLUDE_DIRS) + # in cache already + set(RTMIDI_FOUND TRUE) +else () + + set(RTMIDI_SEARCH_DIRS "${RTMIDI_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/rtmidi") + + find_path(RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS}) + find_file(RTMIDI_CPP NAMES RtMidi.cpp PATH_SUFFIXES src HINTS ${RTMIDI_SEARCH_DIRS}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_CPP) +endif () \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index cf203c41d9..35272ad22d 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -18,6 +18,7 @@ set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus") set(PRIOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/priovr") set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense") set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage") +set(RTMIDI_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/rtmidi") find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) @@ -110,6 +111,16 @@ if (APPLE) SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/interface.icns") endif() +# RtMidi for scripted MIDI control +find_package(RtMidi) + +if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI) + add_definitions(-DHAVE_RTMIDI) + include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR}) + + set(INTERFACE_SRCS ${INTERFACE_SRCS} "${RTMIDI_CPP}") +endif () + # create the executable, make it a bundle on OS X add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM}) @@ -143,71 +154,78 @@ find_package(Qxmpp) # include the Sixense library for Razer Hydra if available if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE) - add_definitions(-DHAVE_SIXENSE) - include_directories(SYSTEM "${SIXENSE_INCLUDE_DIRS}") - if (APPLE OR UNIX) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${SIXENSE_INCLUDE_DIRS}") - endif (APPLE OR UNIX) - target_link_libraries(${TARGET_NAME} "${SIXENSE_LIBRARIES}") + add_definitions(-DHAVE_SIXENSE) + include_directories(SYSTEM "${SIXENSE_INCLUDE_DIRS}") + if (APPLE OR UNIX) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${SIXENSE_INCLUDE_DIRS}") + endif (APPLE OR UNIX) + target_link_libraries(${TARGET_NAME} "${SIXENSE_LIBRARIES}") endif (SIXENSE_FOUND AND NOT DISABLE_SIXENSE) # likewise with Visage library for webcam feature tracking if (VISAGE_FOUND AND NOT DISABLE_VISAGE) - add_definitions(-DHAVE_VISAGE -DVISAGE_STATIC) - include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}") - if (APPLE) - add_definitions(-DMAC_OS_X) - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment") - include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}") - find_library(AVFoundation AVFoundation) - find_library(CoreMedia CoreMedia) - find_library(NEW_STD_LIBRARY libc++.dylib /usr/lib/) - target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY}) - endif (APPLE) - target_link_libraries(${TARGET_NAME} "${VISAGE_LIBRARIES}") + add_definitions(-DHAVE_VISAGE -DVISAGE_STATIC) + include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}") + if (APPLE) + add_definitions(-DMAC_OS_X) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment") + include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}") + find_library(AVFoundation AVFoundation) + find_library(CoreMedia CoreMedia) + find_library(NEW_STD_LIBRARY libc++.dylib /usr/lib/) + target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY}) + endif (APPLE) + target_link_libraries(${TARGET_NAME} "${VISAGE_LIBRARIES}") endif (VISAGE_FOUND AND NOT DISABLE_VISAGE) # and with Faceplus library, also for webcam feature tracking if (FACEPLUS_FOUND AND NOT DISABLE_FACEPLUS) - add_definitions(-DHAVE_FACEPLUS) - include_directories(SYSTEM "${FACEPLUS_INCLUDE_DIRS}") - target_link_libraries(${TARGET_NAME} "${FACEPLUS_LIBRARIES}") + add_definitions(-DHAVE_FACEPLUS) + include_directories(SYSTEM "${FACEPLUS_INCLUDE_DIRS}") + target_link_libraries(${TARGET_NAME} "${FACEPLUS_LIBRARIES}") endif (FACEPLUS_FOUND AND NOT DISABLE_FACEPLUS) # and with LibOVR for Oculus Rift if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) - add_definitions(-DHAVE_LIBOVR) - include_directories(SYSTEM "${LIBOVR_INCLUDE_DIRS}") - - if (APPLE OR UNIX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}") - endif () - - target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}") + add_definitions(-DHAVE_LIBOVR) + include_directories(SYSTEM "${LIBOVR_INCLUDE_DIRS}") + + if (APPLE OR UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}") + endif () + + target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}") endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) # and with PrioVR library if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR) - add_definitions(-DHAVE_PRIOVR) - include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}") - target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}") + add_definitions(-DHAVE_PRIOVR) + include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}") + target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}") endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR) # and with SDL for joysticks if (SDL_FOUND AND NOT DISABLE_SDL) - add_definitions(-DHAVE_SDL) - include_directories(SYSTEM "${SDL_INCLUDE_DIR}") - target_link_libraries(${TARGET_NAME} "${SDL_LIBRARY}") + add_definitions(-DHAVE_SDL) + include_directories(SYSTEM "${SDL_INCLUDE_DIR}") + target_link_libraries(${TARGET_NAME} "${SDL_LIBRARY}") endif (SDL_FOUND AND NOT DISABLE_SDL) # and with qxmpp for chat if (QXMPP_FOUND AND NOT DISABLE_QXMPP) - add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC) - include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) + add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC) + include_directories(SYSTEM ${QXMPP_INCLUDE_DIR}) - target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}") + target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}") endif (QXMPP_FOUND AND NOT DISABLE_QXMPP) +# link CoreMIDI if we're using RtMidi +if (RTMIDI_FOUND AND APPLE) + find_library(CoreMIDI CoreMIDI) + add_definitions(-D__MACOSX_CORE__) + target_link_libraries(${TARGET_NAME} ${CoreMIDI}) +endif() + # include headers for interface and InterfaceConfig. include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes") diff --git a/interface/external/rtmidi/readme.txt b/interface/external/rtmidi/readme.txt new file mode 100644 index 0000000000..d83d0c293e --- /dev/null +++ b/interface/external/rtmidi/readme.txt @@ -0,0 +1,41 @@ + +Instructions for adding the RtMidi library to Interface +Stephen Birarda, June 30, 2014 + +1. Download the RtMidi tarball from High Fidelity S3. + http://highfidelity-public.s3.amazonaws.com/dependencies/rtmidi-2.1.0.tar.gz + +2. Copy RtMidi.h to externals/rtmidi/include. + +3. Copy RtMidi.cpp to externals/rtmidi/src + +4. Delete your build directory, run cmake and build, and you should be all set. + +========================= + +RtMidi: realtime MIDI i/o C++ classes
+Copyright (c) 2003-2014 Gary P. Scavone + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Any person wishing to distribute modifications to the Software is +asked to send the modifications to the original developer so that +they can be incorporated into the canonical version. This is, +however, not a binding provision of this license. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9bc08aa188..d0a8bb15dd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -69,6 +69,7 @@ #include "Menu.h" #include "ModelUploader.h" #include "Util.h" +#include "devices/MIDIManager.h" #include "devices/OculusManager.h" #include "devices/TV3DManager.h" #include "renderer/ProgramObject.h" @@ -393,6 +394,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig(); _trayIcon->show(); + + // setup the MIDIManager + MIDIManager& midiManagerInstance = MIDIManager::getInstance(); + midiManagerInstance.openDefaultPort(); } Application::~Application() { @@ -3600,6 +3605,7 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript scriptEngine->registerGlobalObject("AnimationCache", &_animationCache); scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector); scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); QThread* workerThread = new QThread(this); diff --git a/interface/src/devices/MIDIManager.cpp b/interface/src/devices/MIDIManager.cpp new file mode 100644 index 0000000000..f88bcd1d3d --- /dev/null +++ b/interface/src/devices/MIDIManager.cpp @@ -0,0 +1,64 @@ +// +// MIDIManager.cpp +// +// +// Created by Stephen Birarda on 2014-06-30. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "MIDIManager.h" + +MIDIManager& MIDIManager::getInstance() { + static MIDIManager sharedInstance; + return sharedInstance; +} + +void MIDIManager::midiCallback(double deltaTime, std::vector* message, void* userData) { + + MIDIEvent callbackEvent; + callbackEvent.deltaTime = deltaTime; + + callbackEvent.type = message->at(0); + + if (message->size() > 1) { + callbackEvent.data1 = message->at(1); + } + + if (message->size() > 2) { + callbackEvent.data2 = message->at(2); + } + + emit getInstance().midiEvent(callbackEvent); +} + +MIDIManager::~MIDIManager() { + delete _midiInput; +} + +const int DEFAULT_MIDI_PORT = 0; + +void MIDIManager::openDefaultPort() { + if (!_midiInput) { + _midiInput = new RtMidiIn(); + + if (_midiInput->getPortCount() > 0) { + qDebug() << "MIDIManager opening port" << DEFAULT_MIDI_PORT; + + _midiInput->openPort(DEFAULT_MIDI_PORT); + + // don't ignore sysex, timing, or active sensing messages + _midiInput->ignoreTypes(false, false, false); + + _midiInput->setCallback(&MIDIManager::midiCallback); + } else { + qDebug() << "MIDIManager openDefaultPort called but there are no ports available."; + delete _midiInput; + _midiInput = NULL; + } + } +} \ No newline at end of file diff --git a/interface/src/devices/MIDIManager.h b/interface/src/devices/MIDIManager.h new file mode 100644 index 0000000000..da41050216 --- /dev/null +++ b/interface/src/devices/MIDIManager.h @@ -0,0 +1,46 @@ +// +// MIDIManager.h +// interface/src/devices +// +// Created by Stephen Birarda on 2014-06-30. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MIDIManager_h +#define hifi_MIDIManager_h + +#include +#include + +#include + +#include + +class MIDIManager : public QObject { + Q_OBJECT + + Q_PROPERTY(unsigned int NoteOn READ NoteOn) + Q_PROPERTY(unsigned int NoteOff READ NoteOff) +public: + static MIDIManager& getInstance(); + static void midiCallback(double deltaTime, std::vector* message, void* userData); + + ~MIDIManager(); + + void openDefaultPort(); + bool hasDevice() const { return !!_midiInput; } +public slots: + unsigned int NoteOn() const { return 144; } + unsigned int NoteOff() const { return 128; } +signals: + void midiEvent(const MIDIEvent& event); + +private: + RtMidiIn* _midiInput; +}; + + +#endif // hifi_MIDIManager_h \ No newline at end of file diff --git a/libraries/script-engine/src/MIDIEvent.cpp b/libraries/script-engine/src/MIDIEvent.cpp new file mode 100644 index 0000000000..b32c5d9d87 --- /dev/null +++ b/libraries/script-engine/src/MIDIEvent.cpp @@ -0,0 +1,37 @@ +// +// MIDIEvent.cpp +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2014-06-30. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "MIDIEvent.h" + +void registerMIDIMetaTypes(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, midiEventToScriptValue, midiEventFromScriptValue); +} + +const QString MIDI_DELTA_TIME_PROP_NAME = "deltaTime"; +const QString MIDI_EVENT_TYPE_PROP_NAME = "type"; +const QString MIDI_DATA_1_PROP_NAME = "data1"; +const QString MIDI_DATA_2_PROP_NAME = "data2"; + +QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event) { + QScriptValue obj = engine->newObject(); + obj.setProperty(MIDI_DELTA_TIME_PROP_NAME, event.deltaTime); + obj.setProperty(MIDI_EVENT_TYPE_PROP_NAME, event.type); + obj.setProperty(MIDI_DATA_1_PROP_NAME, event.data1); + obj.setProperty(MIDI_DATA_2_PROP_NAME, event.data2); + return obj; +} + +void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event) { + event.deltaTime = object.property(MIDI_DELTA_TIME_PROP_NAME).toVariant().toDouble(); + event.type = object.property(MIDI_EVENT_TYPE_PROP_NAME).toVariant().toUInt(); + event.data1 = object.property(MIDI_DATA_1_PROP_NAME).toVariant().toUInt(); + event.data2 = object.property(MIDI_DATA_2_PROP_NAME).toVariant().toUInt(); +} \ No newline at end of file diff --git a/libraries/script-engine/src/MIDIEvent.h b/libraries/script-engine/src/MIDIEvent.h new file mode 100644 index 0000000000..6bf4a4b72b --- /dev/null +++ b/libraries/script-engine/src/MIDIEvent.h @@ -0,0 +1,32 @@ +// +// MIDIEvent.h +// libraries/script-engine/src +// +// Created by Stephen Birarda on 2014-06-30. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#ifndef hifi_MIDIEvent_h +#define hifi_MIDIEvent_h + +class MIDIEvent { +public: + double deltaTime; + unsigned int type; + unsigned int data1; + unsigned int data2; +}; + +Q_DECLARE_METATYPE(MIDIEvent) + +void registerMIDIMetaTypes(QScriptEngine* engine); + +QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event); +void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event); + +#endif // hifi_MIDIEvent_h \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 350473cc87..630e585d07 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -33,6 +33,7 @@ #include "AnimationObject.h" #include "MenuItemProperties.h" +#include "MIDIEvent.h" #include "LocalVoxels.h" #include "ScriptEngine.h" #include "XMLHttpRequestClass.h" @@ -217,6 +218,7 @@ void ScriptEngine::init() { // register various meta-types registerMetaTypes(&_engine); + registerMIDIMetaTypes(&_engine); registerVoxelMetaTypes(&_engine); registerEventTypes(&_engine); registerMenuItemProperties(&_engine);