├── midiExampleIOS ├── addons.make ├── bin │ └── data │ │ ├── Icon.png │ │ └── Default.png └── src │ ├── main.mm │ ├── ofApp.h │ └── ofApp.mm ├── midiInputExample ├── addons.make └── src │ ├── main.cpp │ ├── ofApp.h │ └── ofApp.cpp ├── midiOutputExample ├── addons.make └── src │ ├── main.cpp │ ├── ofApp.h │ └── ofApp.cpp ├── midiTimingExample ├── addons.make └── src │ ├── main.cpp │ ├── ofApp.h │ └── ofApp.cpp ├── res ├── midi_din.png ├── ios-framework-search-paths.png └── miditest.pd ├── ofxaddons_thumbnail.png ├── libs ├── pgmidi │ ├── PGMidiAllSources.h │ ├── PGMidiFind.h │ ├── PGArc.h │ ├── PGMidiAllSources.mm │ ├── iOSVersionDetection.h │ ├── PGMidiFind.mm │ ├── README.md │ └── PGMidi.h └── rtmidi │ └── README.md ├── scripts ├── update_pgmidi.sh └── update_rtmidi.sh ├── src ├── ios │ ├── ofxPGMidiDelegate.h │ ├── ofxPGMidiOut.h │ ├── ofxPGMidiContext.h │ ├── ofxPGMidiSourceDelegate.h │ ├── ofxPGMidiIn.h │ ├── ofxPGMidiContext.mm │ ├── ofxPGMidiDelegate.mm │ ├── ofxPGMidiSourceDelegate.mm │ ├── ofxPGMidiOut.mm │ └── ofxPGMidiIn.mm ├── desktop │ ├── ofxRtMidiOut.h │ ├── ofxRtMidiIn.h │ ├── ofxRtMidiOut.cpp │ └── ofxRtMidiIn.cpp ├── ofxMidiConstants.h ├── ofxMidi.h ├── ofxMidiClock.h ├── ofxMidiTypes.h ├── ofxMidiMessage.h ├── ofxMidi.cpp ├── ofxMidiClock.cpp ├── ofxMidiIn.cpp ├── ofxBaseMidi.h ├── ofxMidiMessage.cpp ├── ofxMidiTimecode.h ├── ofxMidiIn.h ├── ofxMidiOut.h ├── ofxMidiOut.cpp ├── ofxMidiTimecode.cpp └── ofxBaseMidi.cpp ├── .gitignore ├── LICENSE.txt ├── addon_config.mk ├── CHANGES.txt └── README.md /midiExampleIOS/addons.make: -------------------------------------------------------------------------------- 1 | ofxMidi 2 | -------------------------------------------------------------------------------- /midiInputExample/addons.make: -------------------------------------------------------------------------------- 1 | ofxMidi 2 | -------------------------------------------------------------------------------- /midiOutputExample/addons.make: -------------------------------------------------------------------------------- 1 | ofxMidi 2 | -------------------------------------------------------------------------------- /midiTimingExample/addons.make: -------------------------------------------------------------------------------- 1 | ofxMidi 2 | -------------------------------------------------------------------------------- /res/midi_din.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danomatika/ofxMidi/HEAD/res/midi_din.png -------------------------------------------------------------------------------- /ofxaddons_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danomatika/ofxMidi/HEAD/ofxaddons_thumbnail.png -------------------------------------------------------------------------------- /midiExampleIOS/bin/data/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danomatika/ofxMidi/HEAD/midiExampleIOS/bin/data/Icon.png -------------------------------------------------------------------------------- /res/ios-framework-search-paths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danomatika/ofxMidi/HEAD/res/ios-framework-search-paths.png -------------------------------------------------------------------------------- /midiExampleIOS/bin/data/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danomatika/ofxMidi/HEAD/midiExampleIOS/bin/data/Default.png -------------------------------------------------------------------------------- /midiInputExample/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofMain.h" 12 | #include "ofApp.h" 13 | 14 | int main(){ 15 | ofSetupOpenGL(640, 480, OF_WINDOW); 16 | ofRunApp(new ofApp()); 17 | } 18 | -------------------------------------------------------------------------------- /midiOutputExample/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofMain.h" 12 | #include "ofApp.h" 13 | 14 | int main() { 15 | ofSetupOpenGL(640, 480, OF_WINDOW); 16 | ofRunApp(new ofApp()); 17 | } 18 | -------------------------------------------------------------------------------- /midiTimingExample/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofMain.h" 12 | #include "ofApp.h" 13 | 14 | int main() { 15 | ofSetupOpenGL(640, 480, OF_WINDOW); 16 | ofRunApp(new ofApp()); 17 | } 18 | -------------------------------------------------------------------------------- /libs/pgmidi/PGMidiAllSources.h: -------------------------------------------------------------------------------- 1 | // 2 | // PGMidiAllSources.h 3 | // PGMidi 4 | // 5 | 6 | #import 7 | 8 | @class PGMidi; 9 | @protocol PGMidiSourceDelegate; 10 | 11 | @interface PGMidiAllSources : NSObject 12 | { 13 | PGMidi *midi; 14 | id delegate; 15 | } 16 | 17 | #if ! __has_feature(objc_arc) 18 | 19 | @property (nonatomic,assign) PGMidi *midi; 20 | @property (nonatomic,assign) id delegate; 21 | 22 | #else 23 | 24 | @property (nonatomic,strong) PGMidi *midi; 25 | @property (nonatomic,strong) id delegate; 26 | 27 | #endif 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /libs/pgmidi/PGMidiFind.h: -------------------------------------------------------------------------------- 1 | // 2 | // PGMidiFind.h 3 | // PGMidi 4 | // 5 | 6 | #import "PGMidi.h" 7 | 8 | @interface PGMidi (FindingConnections) 9 | 10 | - (PGMidiSource*) findSourceCalled:(NSString*)name; 11 | 12 | - (PGMidiDestination*) findDestinationCalled:(NSString*)name; 13 | 14 | - (void) findMatchingSource:(PGMidiSource**)source 15 | andDestination:(PGMidiDestination**)destination; 16 | 17 | - (void) findMatchingSource:(PGMidiSource**)source 18 | andDestination:(PGMidiDestination**)destination 19 | avoidNames:(NSArray*)namesToAvoid; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /scripts/update_pgmidi.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # this script automatically updates the sources for the PGMidi ios library 4 | # 5 | # to upgrade to a new version, change the version number below 6 | # 7 | # as long as the download link is formatted in the same way and folder 8 | # structure are the same, this script should *just work* 9 | # 10 | # 2012 Dan Wilcox 11 | # 12 | 13 | SRC=PGMidi 14 | DEST=../libs/pgmidi 15 | 16 | ### 17 | 18 | # move to this scripts dir 19 | cd $(dirname $0) 20 | 21 | # get latest source 22 | git clone https://github.com/petegoodliffe/PGMidi.git 23 | 24 | # create destination dir 25 | mkdir -p $DEST 26 | 27 | # copy readme/license 28 | cp -v $SRC/README.md $DEST 29 | 30 | # copy sources 31 | cp -v $SRC/Sources/PGMidi/* $DEST 32 | 33 | # cleanup 34 | rm -rf $SRC 35 | -------------------------------------------------------------------------------- /midiOutputExample/src/ofApp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofMain.h" 14 | #include "ofxMidi.h" 15 | 16 | class ofApp : public ofBaseApp { 17 | 18 | public: 19 | 20 | void setup(); 21 | void update(); 22 | void draw(); 23 | void exit(); 24 | 25 | void keyPressed(int key); 26 | void keyReleased(int key); 27 | 28 | void mouseMoved(int x, int y); 29 | void mouseDragged(int x, int y, int button); 30 | void mousePressed(int x, int y, int button); 31 | void mouseReleased(); 32 | 33 | ofxMidiOut midiOut; 34 | int channel; 35 | 36 | unsigned int currentPgm; 37 | int note, velocity; 38 | int pan, bend, touch, polytouch; 39 | }; 40 | -------------------------------------------------------------------------------- /midiExampleIOS/src/main.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofMain.h" 12 | #include "ofApp.h" 13 | 14 | // quick fix until 64bit OF on iOS, 15 | // from https://github.com/openframeworks/openFrameworks/issues/3178 16 | #if OF_VERSION_MAJOR == 0 && OF_VERSION_MINOR <= 8 17 | extern "C" { 18 | size_t fwrite$UNIX2003(const void *a, size_t b, size_t c, FILE *d) { 19 | return fwrite(a, b, c, d); 20 | } 21 | char* strerror$UNIX2003(int errnum) { 22 | return strerror(errnum); 23 | } 24 | time_t mktime$UNIX2003(struct tm * a) { 25 | return mktime(a); 26 | } 27 | double strtod$UNIX2003(const char * a, char ** b) { 28 | return strtod(a, b); 29 | } 30 | } 31 | #endif 32 | 33 | int main() { 34 | ofSetupOpenGL(1024, 768, OF_FULLSCREEN); 35 | ofRunApp(new ofApp); 36 | } 37 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #import "PGMidi.h" 14 | 15 | class ofxMidiConnectionListener; 16 | 17 | /// interface to global pgmidi events 18 | @interface ofxPGMidiDelegate : NSObject { 19 | ofxMidiConnectionListener *listenerPtr; ///< object to send receieved events to 20 | } 21 | 22 | - (void)midi:(PGMidi *)midi sourceAdded:(PGMidiSource *)source; 23 | - (void)midi:(PGMidi *)midi sourceRemoved:(PGMidiSource *)source; 24 | - (void)midi:(PGMidi *)midi destinationAdded:(PGMidiDestination *)destination; 25 | - (void)midi:(PGMidi *)midi destinationRemoved:(PGMidiDestination *)destination; 26 | 27 | /// set the pointer to the ofxMidiConnectionListener object to send messages to 28 | - (void)setListenerPtr:(void *)p; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiOut.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "../ofxBaseMidi.h" 14 | 15 | class ofxPGMidiOut : public ofxBaseMidiOut { 16 | 17 | public: 18 | 19 | ofxPGMidiOut(const std::string name, ofxMidiApi api); 20 | virtual ~ofxPGMidiOut(); 21 | 22 | void listOutPorts(); 23 | std::vector getOutPortList(); 24 | int getNumOutPorts(); 25 | std::string getOutPortName(unsigned int portNumber); 26 | 27 | bool openPort(unsigned int portNumber); 28 | bool openPort(std::string deviceName); 29 | bool openVirtualPort(std::string portName); ///< currently noop on iOS 30 | void closePort(); 31 | 32 | private: 33 | 34 | void sendMessage(std::vector &message); 35 | 36 | struct Destination; // forward declaration for Obj-C wrapper 37 | Destination * destination; ///< output destination 38 | }; 39 | -------------------------------------------------------------------------------- /scripts/update_rtmidi.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # this script automatically updates the sources for the rtmidi library 4 | # 5 | # to upgrade to a new version, change the version number below 6 | # 7 | # as long as the download link is formatted in the same way and folder 8 | # structure are the same, this script should *just work* 9 | # 10 | # 2012 Dan Wilcox 11 | # 12 | 13 | VER=6.0.0 14 | 15 | SRC=rtmidi-$VER 16 | DEST=../libs/rtmidi 17 | 18 | ### 19 | 20 | # move to this scripts dir 21 | cd $(dirname $0) 22 | 23 | # get latest source 24 | curl http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-$VER.tar.gz -O 25 | 26 | # unpack 27 | tar -xvf rtmidi-$VER.tar.gz 28 | 29 | # create destination dir 30 | mkdir -p $DEST 31 | 32 | # copy license 33 | cp -v $SRC/README.md $DEST 34 | 35 | # don't need C API 36 | rm $SRC/rtmidi_c.* 37 | 38 | # copy sources 39 | cp -v $SRC/*.h $DEST 40 | cp -v $SRC/*.cpp $DEST 41 | 42 | # add include so MIDI api is set when compiling 43 | # the line to add may need to be changed as the RtMidi.h header changes 44 | printf "%s\n\n" 47i "#include \"ofxMidiConstants.h\"" . x | ex $DEST/RtMidi.h 45 | 46 | # cleanup 47 | rm -rf $SRC rtmidi-$VER.tar.gz 48 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiContext.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #import "PGMidi.h" 14 | #import "ofxPGMidiDelegate.h" 15 | 16 | /// global static wrapper around PGMidi Obj-C instance 17 | class ofxPGMidiContext { 18 | 19 | public: 20 | 21 | /// creates the PGMidi instance if not already existing 22 | static void setup(); 23 | 24 | /// get the PGMidi instance 25 | static PGMidi* getMidi(); 26 | 27 | /// set the listener for device (dis)connection events 28 | static void setConnectionListener(ofxMidiConnectionListener *listener); 29 | static void clearConnectionListener(); 30 | 31 | /// enable the iOS CoreMidi network interface? 32 | static void enableNetwork(bool enable=true); 33 | 34 | private: 35 | 36 | static PGMidi *midi; ///< global Obj-C PGMidi instance 37 | static ofxPGMidiDelegate *delegate; ///< device (dis)connection interface 38 | }; 39 | -------------------------------------------------------------------------------- /src/desktop/ofxRtMidiOut.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "RtMidi.h" 14 | #include "../ofxBaseMidi.h" 15 | 16 | class ofxRtMidiOut : public ofxBaseMidiOut { 17 | 18 | public: 19 | 20 | /// set the output client name (optional) 21 | ofxRtMidiOut(const std::string name, ofxMidiApi api=MIDI_API_DEFAULT); 22 | virtual ~ofxRtMidiOut(); 23 | 24 | void listOutPorts(); 25 | std::vector getOutPortList(); 26 | int getNumOutPorts(); 27 | std::string getOutPortName(unsigned int portNumber); 28 | 29 | bool openPort(unsigned int portNumber); 30 | bool openPort(std::string deviceName); 31 | bool openVirtualPort(std::string portName); 32 | void closePort(); 33 | 34 | private: 35 | 36 | void sendMessage(std::vector &message); 37 | 38 | /// static error callback for RtMidi 39 | static void _midiErrorCallback(RtMidiError::Type type, const std::string &errorText, void *userData); 40 | 41 | RtMidiOut midiOut; 42 | }; 43 | -------------------------------------------------------------------------------- /libs/pgmidi/PGArc.h: -------------------------------------------------------------------------------- 1 | // 2 | // PGArc.h 3 | // PGMidi 4 | // 5 | 6 | #pragma once 7 | 8 | //============================================================================== 9 | // Cope gracefully if we're not using LLVM3 10 | 11 | #ifndef __has_feature 12 | #define __has_feature(x) 0 13 | #endif 14 | 15 | #ifndef __has_extension 16 | #define __has_extension __has_feature 17 | #endif 18 | 19 | #if __has_feature(objc_arc) && __clang_major__ >= 3 20 | #define PGMIDI_ARC 1 21 | #else 22 | #define PGMIDI_ARC 0 23 | #endif 24 | 25 | //============================================================================== 26 | // arc_cast 27 | 28 | #ifdef __cplusplus 29 | 30 | #if PGMIDI_ARC 31 | 32 | template 33 | inline 34 | OBJC_TYPE *arc_cast(SOURCE_TYPE *source) 35 | { 36 | @autoreleasepool 37 | { 38 | return (__bridge OBJC_TYPE*)source; 39 | } 40 | } 41 | 42 | #define PG_RELEASE(a) a = nil; 43 | 44 | #else 45 | 46 | template 47 | inline 48 | OBJC_TYPE *arc_cast(SOURCE_TYPE *source) 49 | { 50 | return (OBJC_TYPE*)source; 51 | } 52 | 53 | #define PG_RELEASE(a) [a release]; a = nil; 54 | 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/desktop/ofxRtMidiIn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "RtMidi.h" 14 | #include "../ofxBaseMidi.h" 15 | 16 | class ofxRtMidiIn : public ofxBaseMidiIn { 17 | 18 | public: 19 | 20 | ofxRtMidiIn(const std::string name, ofxMidiApi api=MIDI_API_DEFAULT); 21 | virtual ~ofxRtMidiIn(); 22 | 23 | void listInPorts(); 24 | std::vector getInPortList(); 25 | int getNumInPorts(); 26 | std::string getInPortName(unsigned int portNumber); 27 | 28 | bool openPort(unsigned int portNumber); 29 | bool openPort(std::string deviceName); 30 | bool openVirtualPort(std::string portName); 31 | void closePort(); 32 | 33 | void ignoreTypes(bool midiSysex, bool midiTiming, bool midiSense); 34 | 35 | private: 36 | 37 | RtMidiIn midiIn; 38 | 39 | /// static callback for RtMidi 40 | static void _midiMessageCallback(double deltatime, std::vector *message, void *userData); 41 | 42 | /// static error callback for RtMidi 43 | static void _midiErrorCallback(RtMidiError::Type type, const std::string &errorText, void *userData); 44 | }; 45 | -------------------------------------------------------------------------------- /libs/pgmidi/PGMidiAllSources.mm: -------------------------------------------------------------------------------- 1 | // 2 | // PGMidiAllSources.mm 3 | // PGMidi 4 | // 5 | 6 | #import "PGMidiAllSources.h" 7 | 8 | #import "PGMidi.h" 9 | #import "PGArc.h" 10 | 11 | @interface PGMidiAllSources () 12 | @end 13 | 14 | @implementation PGMidiAllSources 15 | 16 | - (void) dealloc 17 | { 18 | self.midi = nil; 19 | #if ! PGMIDI_ARC 20 | [super dealloc]; 21 | #endif 22 | } 23 | 24 | @synthesize midi, delegate; 25 | 26 | - (void) setMidi:(PGMidi *)newMidi 27 | { 28 | midi.delegate = nil; 29 | for (PGMidiSource *source in midi.sources) [source removeDelegate:self]; 30 | 31 | midi = newMidi; 32 | 33 | midi.delegate = self; 34 | for (PGMidiSource *source in midi.sources) [source addDelegate:self]; 35 | } 36 | 37 | #pragma mark PGMidiDelegate 38 | 39 | - (void) midi:(PGMidi*)midi sourceAdded:(PGMidiSource *)source 40 | { 41 | [source addDelegate:self]; 42 | } 43 | 44 | - (void) midi:(PGMidi*)midi sourceRemoved:(PGMidiSource *)source {} 45 | - (void) midi:(PGMidi*)midi destinationAdded:(PGMidiDestination *)destination {} 46 | - (void) midi:(PGMidi*)midi destinationRemoved:(PGMidiDestination *)destination {} 47 | 48 | #pragma mark PGMidiSourceDelegate 49 | 50 | - (void) midiSource:(PGMidiSource*)input midiReceived:(const MIDIPacketList *)packetList 51 | { 52 | [delegate midiSource:input midiReceived:packetList]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # xcode 3 | *.pbxuser 4 | *.perspectivev3 5 | *.mode*v3 6 | xcuserdata 7 | xcshareddata 8 | build 9 | *.app 10 | *.a 11 | 12 | # OSX 13 | *.DS_* 14 | 15 | # codeblocks 16 | *.depend 17 | *.layout 18 | example-input/obj 19 | example-output/obj 20 | *.cbTemp 21 | 22 | # Win 23 | *.exe 24 | *.dll 25 | *.sdf 26 | *.suo 27 | *.vcxproj.user 28 | 29 | # svn 30 | .svn/ 31 | obj/ 32 | bin/ 33 | build/ 34 | !data/ 35 | 36 | # example project files 37 | 38 | midiExampleIOS/Project.xcconfig 39 | midiExampleIOS/config.make 40 | midiExampleIOS/midiExampleIOS.xcodeproj 41 | midiExampleIOS/ofxiOS-Info.plist 42 | midiExampleIOS/ofxiOS_Prefix.pch 43 | midiExampleIOS/mediaAssets 44 | midiExampleIOS/of.entitlements 45 | 46 | midiInputExample/Makefile 47 | midiInputExample/Project.xcconfig 48 | midiInputExample/config.make 49 | midiInputExample/midiInputExample.xcodeproj 50 | midiInputExample/openFrameworks-Info.plist 51 | midiInputExample/of.entitlements 52 | 53 | midiOutputExample/Makefile 54 | midiOutputExample/Project.xcconfig 55 | midiOutputExample/config.make 56 | midiOutputExample/midiOutputExample.xcodeproj 57 | midiOutputExample/openFrameworks-Info.plist 58 | midiOutputExample/of.entitlements 59 | 60 | midiTimingExample/Makefile 61 | midiTimingExample/Project.xcconfig 62 | midiTimingExample/config.make 63 | midiTimingExample/midiTimingExample.xcodeproj 64 | midiTimingExample/openFrameworks-Info.plist 65 | midiTimingExample/of.entitlements 66 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiSourceDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #import "PGMidi.h" 14 | #include 15 | 16 | class ofxPGMidiIn; 17 | 18 | /// interface to pgmidi input 19 | @interface ofxPGMidiSourceDelegate : NSObject { 20 | 21 | ofxPGMidiIn *inputPtr; ///< object to send receieved midi messages to 22 | 23 | bool bIgnoreSysex, bIgnoreTiming, bIgnoreSense; ///< ignore midi types? 24 | 25 | unsigned long long lastTime; ///< timestamp from last packet 26 | bool bFirstPacket; ///< is this the first received packet? 27 | bool bContinueSysex; ///< is this packet part of a sysex message? 28 | unsigned int maxMessageLen; ///< max size of the byte buffer 29 | 30 | std::vector message; ///< raw byte buffer 31 | } 32 | 33 | /// pgmidi callback 34 | - (void)midiSource:(PGMidiSource *)input midiReceived:(const MIDIPacketList *)packetList; 35 | 36 | /// set the pointer to the ofxPGMidiIn object to send messages to 37 | - (void)setInputPtr:(void *)p; 38 | 39 | @property bool bIgnoreSysex; 40 | @property bool bIgnoreTiming; 41 | @property bool bIgnoreSense; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /libs/pgmidi/iOSVersionDetection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * iOSVersionDetection.h 3 | * PGMidi 4 | * 5 | */ 6 | 7 | // Adapted from http://cocoawithlove.com/2010/07/tips-tricks-for-conditional-ios3-ios32.html 8 | 9 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0 10 | #define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32 11 | #endif 12 | 13 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_1 14 | #define kCFCoreFoundationVersionNumber_iPhoneOS_4_1 550.38 15 | #endif 16 | 17 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_2 18 | // NOTE: THIS IS NOT THE FINAL NUMBER 19 | // 4.2 is not out of beta yet, so took the beta 1 build number 20 | #define kCFCoreFoundationVersionNumber_iPhoneOS_4_2 550.47 21 | #endif 22 | 23 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 24 | #define IF_IOS4_OR_GREATER(...) \ 25 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \ 26 | { \ 27 | __VA_ARGS__ \ 28 | } 29 | #else 30 | #define IF_IOS4_OR_GREATER(...) \ 31 | if (false) \ 32 | { \ 33 | } 34 | #endif 35 | 36 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40200 37 | #define IF_IOS4_2_OR_GREATER(...) \ 38 | if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_2) \ 39 | { \ 40 | __VA_ARGS__ \ 41 | } 42 | #else 43 | #define IF_IOS4_2_OR_GREATER(...) \ 44 | if (false) \ 45 | { \ 46 | } 47 | #endif 48 | 49 | #define IF_IOS_HAS_COREMIDI(...) IF_IOS4_2_OR_GREATER(__VA_ARGS__) 50 | -------------------------------------------------------------------------------- /libs/pgmidi/PGMidiFind.mm: -------------------------------------------------------------------------------- 1 | // 2 | // PGMidiFind.mm 3 | // PGMidi 4 | // 5 | 6 | #import "PGMidiFind.h" 7 | 8 | 9 | @implementation PGMidi (FindingConnections) 10 | 11 | - (PGMidiSource*) findSourceCalled:(NSString*)name 12 | { 13 | for (PGMidiSource *source in self.sources) 14 | { 15 | if ([source.name isEqualToString:name]) return source; 16 | } 17 | return nil; 18 | } 19 | 20 | - (PGMidiDestination*) findDestinationCalled:(NSString*)name 21 | { 22 | for (PGMidiDestination *destination in self.destinations) 23 | { 24 | if ([destination.name isEqualToString:name]) return destination; 25 | } 26 | return nil; 27 | } 28 | 29 | - (void) findMatchingSource:(PGMidiSource**)source 30 | andDestination:(PGMidiDestination**)destination 31 | avoidNames:(NSArray*)namesToAvoid 32 | { 33 | *source = nil; 34 | *destination = nil; 35 | 36 | for (PGMidiSource *s in self.sources) 37 | { 38 | if (s.isNetworkSession) continue; 39 | if (namesToAvoid && [namesToAvoid containsObject:s.name]) continue; 40 | 41 | PGMidiDestination *d = [self findDestinationCalled:s.name]; 42 | if (d) 43 | { 44 | *source = s; 45 | *destination = d; 46 | return; 47 | } 48 | } 49 | } 50 | 51 | - (void) findMatchingSource:(PGMidiSource**)source 52 | andDestination:(PGMidiDestination**)destination 53 | { 54 | return [self findMatchingSource:source andDestination:destination avoidNames:nil]; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiIn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "../ofxBaseMidi.h" 14 | 15 | class ofxMidiConnectionListener; 16 | 17 | // reference http://syntheticbits.com/blog/?p=878 18 | class ofxPGMidiIn : public ofxBaseMidiIn { 19 | 20 | public: 21 | 22 | ofxPGMidiIn(const std::string name, ofxMidiApi api=MIDI_API_DEFAULT); 23 | virtual ~ofxPGMidiIn(); 24 | 25 | void listInPorts(); 26 | std::vector getInPortList(); 27 | int getNumInPorts(); 28 | std::string getInPortName(unsigned int portNumber); 29 | 30 | bool openPort(unsigned int portNumber); 31 | bool openPort(std::string deviceName); 32 | bool openVirtualPort(std::string portName); ///< currently noop on iOS 33 | void closePort(); 34 | 35 | void ignoreTypes(bool midiSysex, bool midiTiming, bool midiSense); 36 | 37 | /// wrapper around manageNewMessage 38 | void messageReceived(double deltatime, std::vector *message); 39 | 40 | // iOS specific global stuff, 41 | // easier to route through here thanks to Obj-C/C++ mix 42 | static void setConnectionListener(ofxMidiConnectionListener *listener); 43 | static void clearConnectionListener(); 44 | static void enableNetworking(); 45 | 46 | private: 47 | 48 | struct InputDelegate; // forward declaration for Obj-C wrapper 49 | InputDelegate *inputDelegate; ///< Obj-C midi input interface 50 | }; 51 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2023 Dan Wilcox 2 | All rights reserved. 3 | 4 | The following terms (the "Standard Improved BSD License") apply to all files 5 | associated with the software unless explicitly disclaimed in individual files: 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 3. The name of the author may not be used to endorse or promote 18 | products derived from this software without specific prior 19 | written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY 22 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 23 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 24 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR 25 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 26 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 27 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 29 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 31 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 32 | THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiContext.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxPGMidiContext.h" 12 | 13 | #import "pgmidi/iOSVersionDetection.h" 14 | #include "ofLog.h" 15 | 16 | PGMidi *ofxPGMidiContext::midi = nil; 17 | ofxPGMidiDelegate *ofxPGMidiContext::delegate = nil; 18 | 19 | // ----------------------------------------------------------------------------- 20 | void ofxPGMidiContext::setup() { 21 | if(midi != nil) 22 | return; 23 | IF_IOS_HAS_COREMIDI ( 24 | @autoreleasepool { 25 | midi = [[PGMidi alloc] init]; 26 | delegate = [[ofxPGMidiDelegate alloc] init]; 27 | midi.delegate = delegate; 28 | } 29 | ) 30 | } 31 | 32 | // ----------------------------------------------------------------------------- 33 | PGMidi* ofxPGMidiContext::getMidi() { 34 | setup(); 35 | return midi; 36 | } 37 | 38 | // ----------------------------------------------------------------------------- 39 | void ofxPGMidiContext::setConnectionListener(ofxMidiConnectionListener *listener) { 40 | [delegate setListenerPtr:(void *)listener]; 41 | } 42 | 43 | // ----------------------------------------------------------------------------- 44 | void ofxPGMidiContext::clearConnectionListener() { 45 | [delegate setListenerPtr:NULL]; 46 | } 47 | 48 | // ----------------------------------------------------------------------------- 49 | void ofxPGMidiContext::enableNetwork(bool enable) { 50 | if(enable) { 51 | midi.networkEnabled = YES; 52 | ofLogVerbose("ofxMidi") << "iOS Midi Networking enabled"; 53 | } 54 | else { 55 | midi.networkEnabled = NO; 56 | ofLogVerbose("ofxMidi") << "iOS Midi Networking disabled"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /midiInputExample/src/ofApp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofMain.h" 14 | #include "ofxMidi.h" 15 | 16 | /// MIDI input messages can be handled in one of two ways: direct or queued 17 | /// 18 | /// direct 19 | /// Subclass ofxMidiListener if you want to receive messages directly on the 20 | /// MIDI thread. Messages should be manually buffered for use on the GUI thread. 21 | /// This is faster as it is not limited by the draw framerate but you have to be 22 | /// careful of memory access. 23 | /// 24 | /// queued 25 | /// Manually handle messages using hasWaitingMessages()/getNextMessage(). 26 | /// Received messages are thread safe. 27 | /// 28 | /// queued message passing is enabled by default and is disabled when an 29 | /// ofxMidiListener is added 30 | /// 31 | class ofApp : public ofBaseApp, public ofxMidiListener { 32 | 33 | public: 34 | 35 | void setup(); 36 | void update(); 37 | void draw(); 38 | void exit(); 39 | 40 | void keyPressed(int key); 41 | void keyReleased(int key); 42 | 43 | void mouseMoved(int x, int y); 44 | void mouseDragged(int x, int y, int button); 45 | void mousePressed(int x, int y, int button); 46 | void mouseReleased(); 47 | 48 | /// direct message handling 49 | /// ofxMidiListener callback, called by ofxMidiIn instance if set as listener 50 | /// note: this is not needed if you use queued message passing 51 | void newMidiMessage(ofxMidiMessage &message); 52 | 53 | ofxMidiIn midiIn; 54 | std::vector midiMessages; ///< received messages 55 | std::size_t maxMessages = 10; ///< max number of messages to keep track of 56 | }; 57 | -------------------------------------------------------------------------------- /midiExampleIOS/src/ofApp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofMain.h" 14 | #include "ofxiOS.h" 15 | #include "ofxiOSExtras.h" 16 | #include "ofxMidi.h" 17 | 18 | #include 19 | 20 | /// subclasses ofxMidiListener for direct message handling 21 | class ofApp : public ofxiOSApp, public ofxMidiListener, public ofxMidiConnectionListener { 22 | 23 | public: 24 | void setup(); 25 | void update(); 26 | void draw(); 27 | void exit(); 28 | 29 | void touchDown(ofTouchEventArgs &touch); 30 | void touchMoved(ofTouchEventArgs &touch); 31 | void touchUp(ofTouchEventArgs &touch); 32 | void touchDoubleTap(ofTouchEventArgs &touch); 33 | void touchCancelled(ofTouchEventArgs &touch); 34 | 35 | void lostFocus(); 36 | void gotFocus(); 37 | void gotMemoryWarning(); 38 | void deviceOrientationChanged(int newOrientation); 39 | 40 | // add a message to the display queue 41 | void addMessage(std::string msg); 42 | 43 | // midi message callback (direct message handling) 44 | void newMidiMessage(ofxMidiMessage &message); 45 | 46 | // midi device (dis)connection event callbacks 47 | void midiInputAdded(std::string name, bool isNetwork); 48 | void midiInputRemoved(std::string name, bool isNetwork); 49 | 50 | void midiOutputAdded(std::string nam, bool isNetwork); 51 | void midiOutputRemoved(std::string name, bool isNetwork); 52 | 53 | std::vector inputs; 54 | std::vector outputs; 55 | 56 | std::deque messages; 57 | int maxMessages; 58 | ofMutex messageMutex; // make sure we don't read from queue while writing 59 | 60 | int note, ctl; 61 | std::vector bytes; 62 | }; 63 | -------------------------------------------------------------------------------- /midiTimingExample/src/ofApp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofMain.h" 14 | #include "ofxMidi.h" 15 | #include "ofxMidiClock.h" 16 | #include "ofxMidiTimecode.h" 17 | 18 | /// subclasses ofxMidiListener for direct message handling 19 | class ofApp : public ofBaseApp, public ofxMidiListener { 20 | 21 | public: 22 | 23 | void setup(); 24 | void update(); 25 | void draw(); 26 | void exit(); 27 | 28 | void keyPressed(int key); 29 | void keyReleased(int key); 30 | void mouseMoved(int x, int y); 31 | void mouseDragged(int x, int y, int button); 32 | void mousePressed(int x, int y, int button); 33 | void mouseReleased(int x, int y, int button); 34 | void mouseEntered(int x, int y); 35 | void mouseExited(int x, int y); 36 | void windowResized(int w, int h); 37 | void dragEvent(ofDragInfo dragInfo); 38 | void gotMessage(ofMessage message); 39 | 40 | /// ofxMidiListener callback 41 | void newMidiMessage(ofxMidiMessage &message); 42 | 43 | ofxMidiIn midiIn; 44 | bool verbose = false; 45 | 46 | // MIDI CLOCK 47 | 48 | ofxMidiClock clock; ///< clock message parser 49 | bool clockRunning = false; ///< is the clock sync running? 50 | unsigned int beats = 0; ///< song pos in beats 51 | double seconds = 0; ///< song pos in seconds, computed from beats 52 | double bpm = 120; ///< song tempo in bpm, computed from clock length 53 | 54 | // MIDI TIMECODE 55 | 56 | ofxMidiTimecode timecode; ///< timecode message parser 57 | bool timecodeRunning = false; ///< is the timecode sync running? 58 | long timecodeTimestamp = 0; ///< when last quarter frame message was received 59 | ofxMidiTimecodeFrame frame; ///< timecode frame data, ie. H M S frame rate 60 | }; 61 | -------------------------------------------------------------------------------- /libs/pgmidi/README.md: -------------------------------------------------------------------------------- 1 | PGMidi 2 | ====== 3 | 4 | CoreMidi made simple on iOS. 5 | 6 | What is PGMidi? 7 | --------------- 8 | 9 | PGMidi is a simple library for access to MIDI devices presented via the CoreMidi framework on iOS. It comes with an example project to illustrate how to use the library in your own iOS application. 10 | 11 | It has become the de-facto iOS API for simple MIDI access, incorporated into many of the popular MIDI applications for iOS. Thanks to everyone who has used it and provided feedback. 12 | 13 | It doesn't illustrate the entire CoreMidi API, but will give you a suitable head start into it. (I couldn't find any good examples myself when I needed some.) 14 | 15 | The PGMidi classes are pretty full featured and easy to incorporate into your own project. Many MIDI apps have just pulled in these classes verbatim. 16 | 17 | If you find this useful, please do let me know! 18 | 19 | The demo application presents a message when MIDI devices are attached and detached, and prints the first three bytes of any blocks of MIDI input received. 20 | 21 | It works on iPhones or iPads, but will only do something useful if the device is running iOS 4.2 or higher (CoreMidi is not available in earlier iOS versions). 22 | 23 | The latest version of the project supports ARC and non-ARC projects from the same source. 24 | 25 | 26 | What to look at 27 | --------------- 28 | 29 | Look inside the Sources/PGMidi directory. There is a neat standalone class called `PGMidi` that does all the heavy lifting. It has a delegate interface through which it reports events. 30 | 31 | In the PGMidi directory is also a header that will help you selectively enable MIDI functionality in your application. 32 | 33 | Remember to link against CoreMidi weakly if you want to support devices running iOS version earlier than 4.2. 34 | 35 | 36 | License 37 | ------- 38 | 39 | Feel free to incorporate this code in your own applications. 40 | 41 | I'd appreciate hearing from you if you do so. It's nice to know that I've been helpful. Attribution is welcomed, but not required. 42 | 43 | Copyright (c) 2010-2015 Pete Goodliffe. All rights reserved. 44 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiDelegate.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxPGMidiDelegate.h" 12 | 13 | #include "ofMain.h" 14 | 15 | #include "../ofxMidi.h" 16 | 17 | // ----------------------------------------------------------------------------- 18 | @implementation ofxPGMidiDelegate 19 | 20 | // ----------------------------------------------------------------------------- 21 | - (instancetype)init { 22 | self = [super init]; 23 | if(self) { 24 | listenerPtr = NULL; 25 | } 26 | return self; 27 | } 28 | 29 | // ----------------------------------------------------------------------------- 30 | - (void)midi:(PGMidi *)midi sourceAdded:(PGMidiSource *)source { 31 | if(listenerPtr) { 32 | listenerPtr->midiInputAdded([source.name UTF8String], source.isNetworkSession); 33 | } 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | - (void)midi:(PGMidi *)midi sourceRemoved:(PGMidiSource *)source { 38 | if(listenerPtr) { 39 | listenerPtr->midiInputRemoved([source.name UTF8String], source.isNetworkSession); 40 | } 41 | } 42 | 43 | // ----------------------------------------------------------------------------- 44 | - (void)midi:(PGMidi *)midi destinationAdded:(PGMidiDestination *)destination { 45 | if(listenerPtr) { 46 | listenerPtr->midiOutputAdded([destination.name UTF8String], destination.isNetworkSession); 47 | } 48 | } 49 | 50 | // ----------------------------------------------------------------------------- 51 | - (void)midi:(PGMidi *)midi destinationRemoved:(PGMidiDestination *)destination { 52 | if(listenerPtr) { 53 | listenerPtr->midiOutputRemoved([destination.name UTF8String], destination.isNetworkSession); 54 | } 55 | } 56 | 57 | // ----------------------------------------------------------------------------- 58 | - (void)setListenerPtr:(void *)p { 59 | listenerPtr = (ofxMidiConnectionListener *)p; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /src/ofxMidiConstants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | * references: 11 | * http://www.srm.com/qtma/davidsmidispec.html 12 | */ 13 | #pragma once 14 | 15 | #include "ofConstants.h" 16 | 17 | // for RtMidi 18 | #if defined(TARGET_LINUX) 19 | #ifndef __LINUX_ALSA__ 20 | #define __LINUX_ALSA__ 21 | #endif 22 | #elif defined(TARGET_WIN32) 23 | #ifndef __WINDOWS_MM__ 24 | #define __WINDOWS_MM__ 25 | #endif 26 | #elif defined(TARGET_MACOSX) 27 | #ifndef __MACOSX_CORE__ 28 | #define __MACOSX_CORE__ 29 | #endif 30 | #endif 31 | 32 | // api types, most of these match RtMidi::Api enums 33 | enum ofxMidiApi { 34 | MIDI_API_DEFAULT, // choose platform default 35 | MIDI_API_COREMIDI, // CoreMidi macOS or iOS 36 | MIDI_API_ALSA, // ALSA Linux 37 | MIDI_API_JACK, // JACK 38 | MIDI_API_WINDOWS_MM // Windows Multimedia MIDI 39 | }; 40 | 41 | // MIDI status bytes 42 | enum MidiStatus { 43 | 44 | MIDI_UNKNOWN = 0x00, 45 | 46 | // channel voice messages 47 | MIDI_NOTE_OFF = 0x80, 48 | MIDI_NOTE_ON = 0x90, 49 | MIDI_CONTROL_CHANGE = 0xB0, 50 | MIDI_PROGRAM_CHANGE = 0xC0, 51 | MIDI_PITCH_BEND = 0xE0, 52 | MIDI_AFTERTOUCH = 0xD0, // aka channel pressure 53 | MIDI_POLY_AFTERTOUCH = 0xA0, // aka key pressure 54 | 55 | // system messages 56 | MIDI_SYSEX = 0xF0, 57 | MIDI_TIME_CODE = 0xF1, 58 | MIDI_SONG_POS_POINTER = 0xF2, 59 | MIDI_SONG_SELECT = 0xF3, 60 | MIDI_TUNE_REQUEST = 0xF6, 61 | MIDI_SYSEX_END = 0xF7, 62 | MIDI_TIME_CLOCK = 0xF8, 63 | MIDI_START = 0xFA, 64 | MIDI_CONTINUE = 0xFB, 65 | MIDI_STOP = 0xFC, 66 | MIDI_ACTIVE_SENSING = 0xFE, 67 | MIDI_SYSTEM_RESET = 0xFF 68 | }; 69 | 70 | // number range defines 71 | // because it's sometimes hard to remember these ... 72 | #define MIDI_MIN_BEND 0 73 | #define MIDI_MAX_BEND 16383 74 | -------------------------------------------------------------------------------- /src/ofxMidi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofxMidiIn.h" 14 | #include "ofxMidiOut.h" 15 | #include "ofxMidiClock.h" 16 | #include "ofxMidiTimecode.h" 17 | 18 | /// receives iOS MIDI device (dis)connection events, 19 | /// currently does nothing on desktop 20 | class ofxMidiConnectionListener { 21 | 22 | public: 23 | 24 | ofxMidiConnectionListener() {} 25 | virtual ~ofxMidiConnectionListener() {} 26 | 27 | virtual void midiInputAdded(std::string name, bool isNetwork=false); 28 | virtual void midiInputRemoved(std::string name, bool isNetwork=false); 29 | 30 | virtual void midiOutputAdded(std::string nam, bool isNetwork=false); 31 | virtual void midiOutputRemoved(std::string name, bool isNetwork=false); 32 | }; 33 | 34 | // global access 35 | namespace ofxMidi { 36 | 37 | /// \section Util 38 | 39 | /// convert MIDI note to frequency in Hz 40 | /// ala the [mtof] object in Max & Pure Data 41 | float mtof(float note); 42 | 43 | /// convert a frequency in Hz to a MIDI note 44 | /// ala the [ftom] object in Max & Pure Data 45 | float ftom(float frequency); 46 | 47 | /// convert raw MIDI bytes to a printable string, ex. "F0 0C 33" 48 | std::string bytesToString(std::vector &bytes); 49 | 50 | /// \section iOS Specific 51 | 52 | /// set a listener to receive iOS device (dis)connection events 53 | /// 54 | /// don't forget to clear before the listener is deallocated! 55 | /// 56 | /// note: these are noops on Mac, Win, & Linux 57 | /// 58 | void setConnectionListener(ofxMidiConnectionListener *listener); 59 | 60 | /// clear iOS device event receiver 61 | void clearConnectionListener(); 62 | 63 | /// enables the network MIDI session between iOS and macOS on a 64 | /// local wifi network 65 | /// 66 | /// in ofxMidi: open the input/outport network ports named "Session 1" 67 | /// 68 | /// on OSX: use the Audio MIDI Setup Utility to connect to the iOS device 69 | /// 70 | /// note: this is a noop on Mac, Win, & Linux 71 | /// 72 | void enableNetworking(); 73 | }; 74 | -------------------------------------------------------------------------------- /src/ofxMidiClock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | * Adapted from code written in Swift for ZKM | Karlsruhe 11 | * 12 | */ 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include "ofxMidiConstants.h" 18 | 19 | /// 20 | /// MIDI clock message parser 21 | /// 22 | /// currently input only, does not send clock ticks 23 | /// 24 | class ofxMidiClock { 25 | 26 | public: 27 | 28 | ofxMidiClock(); 29 | virtual ~ofxMidiClock() {} 30 | 31 | /// update clock from a raw MIDI message, 32 | /// returns true if the message was handled 33 | bool update(std::vector &message); 34 | 35 | /// manually increment ticks and measure length since last tick 36 | void tick(); 37 | 38 | /// reset timestamp 39 | void reset(); 40 | 41 | /// \section Status 42 | 43 | /// get the song position in beats 44 | unsigned int getBeats(); 45 | 46 | /// set the song position in beats 47 | void setBeats(unsigned int b); 48 | 49 | /// get the song position in seconds 50 | double getSeconds(); 51 | 52 | /// set the song position in seconds 53 | void setSeconds(double s); 54 | 55 | /// get tempo in beats per minute calculated from clock tick length 56 | double getBpm(); 57 | 58 | /// set clock tick length from tempo in beats per minute 59 | void setBpm(double bpm); 60 | 61 | /// \section Util 62 | 63 | /// get the song position in seconds from a beat position, 64 | /// 1 beat = 1/16 note = 6 clock ticks 65 | double beatsToSeconds(unsigned int beats); 66 | 67 | /// get the song position in beats from seconds, 68 | /// 1 beat = 1/16 note = 6 clock ticks 69 | unsigned int secondsToBeats(double seconds); 70 | 71 | /// calculate MIDI clock length in ms from a give tempo bpm 72 | static double bpmToMs(double bpm); 73 | 74 | /// calculate tempo bpm from a give MIDI clock length in ms 75 | static double msToBpm(double ms); 76 | 77 | protected: 78 | 79 | double length = 20.833; ///< averaged tick length in ms, default 120 bpm 80 | unsigned long ticks = 0.0; ///< current song pos in ticks (6 ticks = 1 beat) 81 | std::chrono::steady_clock::time_point timestamp; ///< last timestamp 82 | }; 83 | -------------------------------------------------------------------------------- /libs/rtmidi/README.md: -------------------------------------------------------------------------------- 1 | # RtMidi 2 | 3 | ![Build Status](https://github.com/thestk/rtmidi/actions/workflows/ci.yml/badge.svg) 4 | [![Conan Center](https://shields.io/conan/v/rtmidi)](https://conan.io/center/rtmidi) 5 | 6 | A set of C++ classes that provide a common API for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMIDI & JACK), Windows (Multimedia Library & UWP), Web MIDI, iOS and Android. 7 | 8 | By Gary P. Scavone, 2003-2023. 9 | 10 | This distribution of RtMidi contains the following: 11 | 12 | - `doc`: RtMidi documentation (also online at http://www.music.mcgill.ca/~gary/rtmidi/) 13 | - `tests`: example RtMidi programs 14 | 15 | On Unix systems, type `./configure` in the top level directory, then `make` in the `tests/` directory to compile the test programs. In Windows, open the Visual C++ workspace file located in the `tests/` directory. 16 | 17 | If you checked out the code from git, please run `./autogen.sh` before `./configure`. 18 | 19 | ## Overview 20 | 21 | RtMidi is a set of C++ classes (`RtMidiIn`, `RtMidiOut`, and API specific classes) that provide a common API (Application Programming Interface) for realtime MIDI input/output across Linux (ALSA, JACK), Macintosh OS X (CoreMIDI, JACK), and Windows (Multimedia Library) operating systems. RtMidi significantly simplifies the process of interacting with computer MIDI hardware and software. It was designed with the following goals: 22 | 23 | - object oriented C++ design 24 | - simple, common API across all supported platforms 25 | - only one header and one source file for easy inclusion in programming projects 26 | - MIDI device enumeration 27 | 28 | MIDI input and output functionality are separated into two classes, `RtMidiIn` and `RtMidiOut`. Each class instance supports only a single MIDI connection. RtMidi does not provide timing functionality (i.e., output messages are sent immediately). Input messages are timestamped with delta times in seconds (via a `double` floating point type). MIDI data is passed to the user as raw bytes using an `std::vector`. 29 | 30 | ## Windows 31 | 32 | In some cases, for example to use RtMidi with GS Synth, it may be necessary for your program to call `CoInitializeEx` and `CoUninitialize` on entry to and exit from the thread that uses RtMidi. 33 | 34 | ## Further reading 35 | 36 | For complete documentation on RtMidi, see the `doc` directory of the distribution or surf to http://www.music.mcgill.ca/~gary/rtmidi/. 37 | 38 | ## Legal and ethical 39 | 40 | The RtMidi license is similar to the MIT License, with the added *feature* that modifications be sent to the developer. Please see [LICENSE](LICENSE). 41 | -------------------------------------------------------------------------------- /src/ofxMidiTypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | /// \section stream interface MIDI objects 14 | /// ref: http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html 15 | 16 | /// send a note on event (also set velocity = 0 for noteoff) 17 | struct NoteOn { 18 | 19 | const int channel; ///< channel 1 - 16 20 | const int pitch; ///< pitch 0 - 127 21 | const int velocity; ///< velocity 0 - 127 22 | 23 | explicit NoteOn(const int channel, const int pitch, const int velocity=64) : 24 | channel(channel), pitch(pitch), velocity(velocity) {} 25 | }; 26 | 27 | /// send a note off event (velocity is usually ignored) 28 | struct NoteOff { 29 | 30 | const int channel; ///< channel 1 - 16 31 | const int pitch; ///< pitch 0 - 127 32 | const int velocity; ///< velocity 0 - 127 33 | 34 | explicit NoteOff(const int channel, const int pitch, const int velocity=64) : 35 | channel(channel), pitch(pitch), velocity(velocity) {} 36 | }; 37 | 38 | /// change a control value aka send a CC message 39 | struct ControlChange { 40 | 41 | const int channel; ///< channel 1 - 16 42 | const int control; ///< control 0 - 127 43 | const int value; ///< value 0 - 127 44 | 45 | explicit ControlChange(const int channel, const int control, const int value) : 46 | channel(channel), control(control), value(value) {} 47 | }; 48 | 49 | /// change a program value (ie. an instrument) 50 | struct ProgramChange { 51 | 52 | const int channel; ///< channel 1 - 16 53 | const int value; ///< value 0 - 127 54 | 55 | explicit ProgramChange(const int channel, const int value) : 56 | channel(channel), value(value) {} 57 | }; 58 | 59 | /// change the pitch bend value 60 | struct PitchBend { 61 | 62 | const int channel; ///< channel 1 - 16 63 | const int value; ///< value 0 - 16383 64 | 65 | explicit PitchBend(const int channel, const int value) : 66 | channel(channel), value(value) {} 67 | }; 68 | 69 | /// change an aftertouch value 70 | struct Aftertouch { 71 | 72 | const int channel; ///< channel 1 - 16 73 | const int value; ///< value 0 - 127 74 | 75 | explicit Aftertouch(const int channel, const int value) : 76 | channel(channel), value(value) {} 77 | }; 78 | 79 | /// change a poly aftertouch value 80 | struct PolyAftertouch { 81 | 82 | const int channel; ///< channel 1 - 16 83 | const int pitch; ///< controller 0 - 127 84 | const int value; ///< value 0 - 127 85 | 86 | explicit PolyAftertouch(const int channel, const int pitch, const int value) : 87 | channel(channel), pitch(pitch), value(value) {} 88 | }; 89 | 90 | /// start a raw MIDI byte stream 91 | struct StartMidi { 92 | explicit StartMidi() {} 93 | }; 94 | 95 | /// finish a MIDI byte stream 96 | struct FinishMidi { 97 | explicit FinishMidi() {} 98 | }; 99 | -------------------------------------------------------------------------------- /src/ofxMidiMessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofEvents.h" 14 | #include "ofxMidiConstants.h" 15 | 16 | class ofxMidiMessage; 17 | 18 | /// MIDI message receiver base class 19 | /// subclass and add to an ofxMidiIn instance to receive messages directly 20 | class ofxMidiListener { 21 | 22 | public: 23 | 24 | ofxMidiListener() {} 25 | virtual ~ofxMidiListener() {} 26 | 27 | /// called on the MIDI thread, copy and/or buffer message content on usage 28 | virtual void newMidiMessage(ofxMidiMessage &message) = 0; 29 | }; 30 | 31 | /// a single multi byte MIDI message 32 | /// 33 | /// check status type and grab data: 34 | /// 35 | /// if(message.status == MIDI_NOTE_ON) { 36 | /// ofLog() << "note on " << message.channel 37 | /// << message.note << " " << message.velocity; 38 | /// } 39 | /// 40 | /// the message-specific types are only set for the appropriate 41 | /// message types ie. pitch is only set for noteon, noteoff, and 42 | /// polyaftertouch messages 43 | /// 44 | class ofxMidiMessage: public ofEventArgs { 45 | 46 | public: 47 | 48 | /// \section Variables 49 | 50 | MidiStatus status; 51 | int channel; ///< 1 - 16 52 | 53 | /// message-specific values, 54 | /// converted from raw bytes 55 | int pitch; ///< 0 - 127 56 | int velocity; ///< 0 - 127 57 | int control; ///< 0 - 127 58 | int value; ///< depends on message status type 59 | 60 | /// raw bytes 61 | std::vector bytes; 62 | 63 | /// delta time since last message in ms 64 | double deltatime; 65 | 66 | /// the input port we received this message from 67 | /// 68 | /// note: portNum will be -1 from a virtual port 69 | /// 70 | int portNum; 71 | std::string portName; 72 | 73 | /// \section Main 74 | 75 | ofxMidiMessage(); 76 | ofxMidiMessage(std::vector *rawBytes); ///< parses 77 | ofxMidiMessage(const ofxMidiMessage &from); 78 | ofxMidiMessage& operator=(const ofxMidiMessage &from); 79 | void copy(const ofxMidiMessage &from); 80 | 81 | /// parse message from raw MIDI bytes 82 | void fromBytes(std::vector *rawBytes); 83 | 84 | /// clear the message contents, also resets status 85 | void clear(); 86 | 87 | /// \section Util 88 | 89 | /// get the raw message as a string in the format: 90 | /// 91 | /// PortName: status channel [ raw bytes in hex ] deltatime 92 | /// 93 | std::string toString(); 94 | 95 | /// get a MIDI status byte as a string 96 | /// ie "Note On", "Note Off", "Control Change", etc 97 | static std::string getStatusString(MidiStatus status); 98 | }; 99 | 100 | typedef ofEvent ofxMidiEvent; 101 | -------------------------------------------------------------------------------- /src/ofxMidi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxMidi.h" 12 | 13 | // choose the MIDI backend 14 | #ifdef TARGET_OF_IPHONE 15 | #include "ios/ofxPGMidiIn.h" 16 | #endif 17 | #include "ofLog.h" 18 | #include 19 | #include 20 | #include 21 | 22 | // ----------------------------------------------------------------------------- 23 | void ofxMidiConnectionListener::midiInputAdded(std::string name, bool isNetwork) { 24 | ofLogNotice("ofxMidi") << "input added: " << name << " network: " << isNetwork; 25 | } 26 | 27 | void ofxMidiConnectionListener::midiInputRemoved(std::string name, bool isNetwork) { 28 | ofLogNotice("ofxMidi") << "input removed: " << name << " network: " << isNetwork; 29 | } 30 | 31 | void ofxMidiConnectionListener::midiOutputAdded(std::string name, bool isNetwork) { 32 | ofLogNotice("ofxMidi") << "output added: " << name << " network: " << isNetwork; 33 | } 34 | 35 | void ofxMidiConnectionListener::midiOutputRemoved(std::string name, bool isNetwork) { 36 | ofLogNotice("ofxMidi") << "output removed: " << name << " network: " << isNetwork; 37 | } 38 | 39 | namespace ofxMidi { 40 | 41 | // ----------------------------------------------------------------------------- 42 | // from Pure Data x_acoustics.c 43 | float mtof(float note) { 44 | if(note <= -1500) return (0); 45 | else if(note > 1499) return (mtof(1499)); 46 | else return (8.17579891564 * exp(.0577622650 * note)); 47 | } 48 | 49 | // from Pure Data x_acoustics.c 50 | float ftom(float frequency) { 51 | return (frequency > 0 ? 17.3123405046 * log(.12231220585 * frequency) : -1500); 52 | } 53 | 54 | // ----------------------------------------------------------------------------- 55 | std::string bytesToString(std::vector &bytes) { 56 | std::stringstream stream; 57 | stream << std::hex << std::uppercase << std::setw(2) << std::setfill('0'); 58 | for(unsigned int i = 0; i < bytes.size(); ++i) { 59 | stream << (int)bytes[i]; 60 | if(i < bytes.size()-1) { 61 | stream << " "; 62 | } 63 | } 64 | return stream.str(); 65 | } 66 | 67 | // ----------------------------------------------------------------------------- 68 | void setConnectionListener(ofxMidiConnectionListener *listener) { 69 | #ifdef TARGET_OF_IPHONE 70 | ofxPGMidiIn::setConnectionListener(listener); 71 | #endif 72 | } 73 | 74 | // ----------------------------------------------------------------------------- 75 | void clearConnectionListener() { 76 | #ifdef TARGET_OF_IPHONE 77 | ofxPGMidiIn::clearConnectionListener(); 78 | #endif 79 | } 80 | 81 | // ----------------------------------------------------------------------------- 82 | void enableNetworking() { 83 | #ifdef TARGET_OF_IPHONE 84 | ofxPGMidiIn::enableNetworking(); 85 | #endif 86 | } 87 | 88 | }; // namespace 89 | -------------------------------------------------------------------------------- /src/ofxMidiClock.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | * Adapted from code written in Swift for ZKM | Karlsruhe 11 | * 12 | */ 13 | #include "ofxMidiClock.h" 14 | 15 | #include "ofLog.h" 16 | 17 | using namespace std::chrono; 18 | 19 | // ----------------------------------------------------------------------------- 20 | ofxMidiClock::ofxMidiClock() { 21 | reset(); 22 | } 23 | 24 | // ----------------------------------------------------------------------------- 25 | bool ofxMidiClock::update(std::vector &message) { 26 | switch(message[0]) { 27 | case MIDI_TIME_CLOCK: 28 | tick(); 29 | return true; 30 | case MIDI_SONG_POS_POINTER: 31 | if(message.size() < 3) {return false;} 32 | unsigned int beats = (message[2] << 7) + message[1]; 33 | ticks = beats * 6; 34 | ofLogVerbose("ofxMidiClock") << "Song Pos " << beats; 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | // ----------------------------------------------------------------------------- 41 | void ofxMidiClock::tick() { 42 | double us = duration_cast(steady_clock::now()-timestamp).count(); 43 | if(us < 200000) { // filter obviously bad values 44 | length += ((us/1000.0) - length) / 5.0; // average last 5 ticks 45 | ticks++; 46 | } 47 | timestamp = steady_clock::now(); 48 | } 49 | 50 | // ----------------------------------------------------------------------------- 51 | void ofxMidiClock::reset() { 52 | timestamp = steady_clock::now(); 53 | ticks = 0; 54 | } 55 | 56 | /// Status 57 | 58 | // ----------------------------------------------------------------------------- 59 | unsigned int ofxMidiClock::getBeats() { 60 | return (unsigned int)(ticks / 6); 61 | } 62 | 63 | // ----------------------------------------------------------------------------- 64 | void ofxMidiClock::setBeats(unsigned int beats) { 65 | ticks = beats * 6; 66 | } 67 | 68 | // ----------------------------------------------------------------------------- 69 | double ofxMidiClock::getSeconds() { 70 | return beatsToSeconds((unsigned int)(ticks / 6)); 71 | } 72 | 73 | // ----------------------------------------------------------------------------- 74 | void ofxMidiClock::setSeconds(double s) { 75 | ticks = secondsToBeats(s) * 6; 76 | } 77 | 78 | // ----------------------------------------------------------------------------- 79 | double ofxMidiClock::getBpm() { 80 | return msToBpm(length); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | void ofxMidiClock::setBpm(double bpm) { 85 | length = bpmToMs(bpm); 86 | } 87 | 88 | // Util 89 | 90 | // ----------------------------------------------------------------------------- 91 | double ofxMidiClock::beatsToSeconds(unsigned int beats) { 92 | return (double)beats * length * 0.006; // 6 / 1000.0; 93 | } 94 | 95 | // ----------------------------------------------------------------------------- 96 | unsigned int ofxMidiClock::secondsToBeats(double seconds) { 97 | return (seconds * 1000) / (6 * length); 98 | } 99 | 100 | // ----------------------------------------------------------------------------- 101 | double ofxMidiClock::bpmToMs(double bpm) { 102 | if(bpm == 0) {return 0;} 103 | return 2500.0 / bpm; // 1000.0 / ((bpm * 24.0) / 60.0); 104 | } 105 | 106 | // ----------------------------------------------------------------------------- 107 | double ofxMidiClock::msToBpm(double ms) { 108 | if(ms == 0) {return 0;} 109 | return 2500.0 / ms; // (1000.0 / ms / 24.0) * 60.0; 110 | } 111 | -------------------------------------------------------------------------------- /src/ofxMidiIn.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxMidiIn.h" 12 | 13 | // ----------------------------------------------------------------------------- 14 | ofxMidiIn::ofxMidiIn(const std::string name, ofxMidiApi api) { 15 | midiIn = std::shared_ptr(new OFX_MIDI_IN_TYPE(name, api)); 16 | } 17 | 18 | // ----------------------------------------------------------------------------- 19 | ofxMidiIn::~ofxMidiIn() {} 20 | 21 | // ----------------------------------------------------------------------------- 22 | void ofxMidiIn::listInPorts() { 23 | midiIn->listInPorts(); 24 | } 25 | 26 | // ----------------------------------------------------------------------------- 27 | std::vector ofxMidiIn::getInPortList() { 28 | return midiIn->getInPortList(); 29 | } 30 | 31 | // ----------------------------------------------------------------------------- 32 | int ofxMidiIn::getNumInPorts() { 33 | return midiIn->getNumInPorts(); 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | std::string ofxMidiIn::getInPortName(unsigned int portNumber) { 38 | return midiIn->getInPortName(portNumber); 39 | } 40 | 41 | // ----------------------------------------------------------------------------- 42 | bool ofxMidiIn::openPort(unsigned int portNumber) { 43 | return midiIn->openPort(portNumber); 44 | } 45 | 46 | // ----------------------------------------------------------------------------- 47 | bool ofxMidiIn::openPort(std::string deviceName) { 48 | return midiIn->openPort(deviceName); 49 | } 50 | 51 | // ----------------------------------------------------------------------------- 52 | bool ofxMidiIn::openVirtualPort(std::string portName) { 53 | return midiIn->openVirtualPort(portName); 54 | } 55 | 56 | // ----------------------------------------------------------------------------- 57 | void ofxMidiIn::closePort() { 58 | midiIn->closePort(); 59 | } 60 | 61 | // ----------------------------------------------------------------------------- 62 | int ofxMidiIn::getPort() { 63 | return midiIn->getPort(); 64 | } 65 | 66 | // ----------------------------------------------------------------------------- 67 | std::string ofxMidiIn::getName() { 68 | return midiIn->getName(); 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | bool ofxMidiIn::isOpen() { 73 | return midiIn->isOpen(); 74 | } 75 | 76 | // ----------------------------------------------------------------------------- 77 | bool ofxMidiIn::isVirtual() { 78 | return midiIn->isVirtual(); 79 | } 80 | 81 | // ----------------------------------------------------------------------------- 82 | bool ofxMidiIn::isQueued() { 83 | return midiIn->isQueued(); 84 | } 85 | 86 | // ----------------------------------------------------------------------------- 87 | void ofxMidiIn::ignoreTypes(bool midiSysex, bool midiTiming, bool midiSense) { 88 | midiIn->ignoreTypes(midiSysex, midiTiming, midiSense); 89 | } 90 | 91 | // ----------------------------------------------------------------------------- 92 | void ofxMidiIn::addListener(ofxMidiListener *listener) { 93 | midiIn->addListener(listener); 94 | } 95 | 96 | // ----------------------------------------------------------------------------- 97 | void ofxMidiIn::removeListener(ofxMidiListener *listener) { 98 | midiIn->removeListener(listener); 99 | } 100 | 101 | // ----------------------------------------------------------------------------- 102 | bool ofxMidiIn::hasWaitingMessages() const { 103 | return midiIn->hasWaitingMessages(); 104 | } 105 | 106 | // ----------------------------------------------------------------------------- 107 | bool ofxMidiIn::getNextMessage(ofxMidiMessage &message) { 108 | return midiIn->getNextMessage(message); 109 | } 110 | 111 | // ----------------------------------------------------------------------------- 112 | void ofxMidiIn::setVerbose(bool verbose) { 113 | midiIn->setVerbose(verbose); 114 | } 115 | -------------------------------------------------------------------------------- /src/ofxBaseMidi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofxMidiConstants.h" 14 | #include "ofxMidiMessage.h" 15 | #include "ofxMidiTypes.h" 16 | #include "ofThreadChannel.h" 17 | 18 | /// a base MIDI input port 19 | /// 20 | /// see ofxMidiIn for functional documentation 21 | /// 22 | class ofxBaseMidiIn { 23 | 24 | public: 25 | 26 | ofxBaseMidiIn(const std::string name, ofxMidiApi api); 27 | virtual ~ofxBaseMidiIn() {} 28 | 29 | virtual bool openPort(unsigned int portNumber) = 0; 30 | virtual bool openPort(std::string deviceName) = 0; 31 | virtual bool openVirtualPort(std::string portName) = 0; 32 | virtual void closePort() = 0; 33 | 34 | virtual void listInPorts() = 0; 35 | virtual std::vector getInPortList() = 0; 36 | virtual int getNumInPorts() = 0; 37 | virtual std::string getInPortName(unsigned int portNumber) = 0; 38 | 39 | int getPort(); 40 | std::string getName(); 41 | bool isOpen(); 42 | bool isVirtual(); 43 | bool isQueued(); 44 | ofxMidiApi getApi(); 45 | 46 | virtual void ignoreTypes(bool midiSysex=true, bool midiTiming=true, 47 | bool midiSense=true) = 0; 48 | 49 | void addListener(ofxMidiListener *listener); 50 | void removeListener(ofxMidiListener *listener); 51 | 52 | bool hasWaitingMessages() const; 53 | bool getNextMessage(ofxMidiMessage &message); 54 | 55 | void setVerbose(bool verbose); 56 | 57 | protected: 58 | 59 | /// parses and sends received raw messages to listeners 60 | void manageNewMessage(double deltatime, std::vector *message); 61 | 62 | int portNum; ///< current port num, -1 if not connected 63 | std::string portName; ///< current port name, "" if not connected 64 | 65 | ofEvent newMessageEvent; ///< current message event 66 | 67 | bool bOpen; ///< is the port currently open? 68 | bool bVerbose; ///< print incoming bytes? 69 | bool bVirtual; ///< are we connected to a virtual port? 70 | ofxMidiApi api; ///< backend api 71 | 72 | private: 73 | 74 | /// message passing thread channel 75 | std::unique_ptr> messagesChannel; 76 | }; 77 | 78 | /// a MIDI output port 79 | /// 80 | /// see ofxMidiOut for functional documentation 81 | /// 82 | class ofxBaseMidiOut { 83 | 84 | public: 85 | 86 | ofxBaseMidiOut(const std::string name, ofxMidiApi api); 87 | virtual ~ofxBaseMidiOut() {} 88 | 89 | virtual bool openPort(unsigned int portNumber=0) = 0; 90 | virtual bool openPort(std::string deviceName) = 0; 91 | virtual bool openVirtualPort(std::string portName) = 0; 92 | virtual void closePort() = 0; 93 | 94 | virtual void listOutPorts() = 0; 95 | virtual std::vector getOutPortList() = 0; 96 | virtual int getNumOutPorts() = 0; 97 | virtual std::string getOutPortName(unsigned int portNumber) = 0; 98 | 99 | int getPort(); 100 | std::string getName(); 101 | bool isOpen(); 102 | bool isVirtual(); 103 | ofxMidiApi getApi(); 104 | 105 | void sendNoteOn(int channel, int pitch, int velocity); 106 | void sendNoteOff(int channel, int pitch, int velocity); 107 | void sendControlChange(int channel, int control, int value); 108 | void sendProgramChange(int channel, int value); 109 | void sendPitchBend(int channel, int value); 110 | void sendPitchBend(int channel, unsigned char lsb, unsigned char msb); 111 | void sendAftertouch(int channel, int value); 112 | void sendPolyAftertouch(int channel, int pitch, int value); 113 | 114 | void sendMidiByte(unsigned char byte); 115 | void sendMidiBytes(std::vector &bytes); 116 | 117 | void startMidiStream(); 118 | void finishMidiStream(); 119 | 120 | protected: 121 | 122 | /// send a raw byte message 123 | virtual void sendMessage(std::vector &message) = 0; 124 | 125 | int portNum; ///< current port num, -1 if not connected 126 | std::string portName; ///< current port name, "" if not connected 127 | 128 | std::vector stream; ///< byte stream message byte buffer 129 | 130 | bool bOpen; ///< is the port currently open? 131 | bool bStreamInProgress; ///< used with byte stream 132 | bool bVirtual; ///< are we connected to a virtual port? 133 | ofxMidiApi api; ///< backend api 134 | }; 135 | -------------------------------------------------------------------------------- /res/miditest.pd: -------------------------------------------------------------------------------- 1 | #N canvas 137 187 802 341 10; 2 | #X obj 6 54 notein; 3 | #X floatatom 6 97 5 0 0 0 - - -, f 5; 4 | #X floatatom 44 98 5 0 0 0 - - -, f 5; 5 | #X floatatom 82 97 5 0 0 0 - - -, f 5; 6 | #X obj 132 52 ctlin; 7 | #X floatatom 132 97 5 0 0 0 - - -, f 5; 8 | #X floatatom 172 97 5 0 0 0 - - -, f 5; 9 | #X floatatom 213 97 5 0 0 0 - - -, f 5; 10 | #X obj 263 53 pgmin; 11 | #X floatatom 261 97 5 0 0 0 - - -, f 5; 12 | #X floatatom 300 97 5 0 0 0 - - -, f 5; 13 | #X floatatom 349 94 5 0 0 0 - - -, f 5; 14 | #X obj 349 52 bendin; 15 | #X floatatom 391 95 5 0 0 0 - - -, f 5; 16 | #X obj 446 53 touchin; 17 | #X floatatom 443 94 5 0 0 0 - - -, f 5; 18 | #X floatatom 488 94 5 0 0 0 - - -, f 5; 19 | #X floatatom 546 95 5 0 0 0 - - -, f 5; 20 | #X obj 540 56 polytouchin; 21 | #X floatatom 586 94 5 0 0 0 - - -, f 5; 22 | #X floatatom 630 94 5 0 0 0 - - -, f 5; 23 | #X text 3 9 midi in; 24 | #X text 2 135 midi out; 25 | #X text 245 25 pgm 1-128; 26 | #X obj 685 51 sysexin; 27 | #X floatatom 685 91 5 0 0 0 - - -, f 5; 28 | #X floatatom 729 91 5 0 0 0 - - -, f 5; 29 | #X obj 685 121 print sysex; 30 | #X obj 4 279 noteout; 31 | #X obj 693 167 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 32 | -1 -1; 33 | #X obj 4 222 unpack f f; 34 | #X msg 4 184 64 64; 35 | #X msg 55 184 72 100; 36 | #X obj 116 281 ctlout 1; 37 | #X obj 116 252 unpack f f; 38 | #X obj 119 170 hsl 128 15 0 127 0 0 empty empty empty -2 -8 0 10 -262144 39 | -1 -1 0 1; 40 | #X obj 207 253 pgmout; 41 | #X obj 210 202 hsl 128 15 0 127 0 0 empty empty empty -2 -8 0 10 -262144 42 | -1 -1 0 1; 43 | #X floatatom 207 227 5 0 0 0 - - -, f 5; 44 | #X floatatom 116 198 5 0 0 0 - - -, f 5; 45 | #X obj 307 301 bendout; 46 | #X obj 310 245 hsl 128 15 -8192 8192 0 0 empty empty empty -2 -8 0 47 | 10 -262144 -1 -1 0 1; 48 | #X floatatom 307 270 5 0 0 0 - - -, f 5; 49 | #X text 337 29 bend 0 - 16383; 50 | #N canvas 551 149 369 179 sysexout 0; 51 | #X obj 26 25 inlet; 52 | #X obj 26 114 midiout 1; 53 | #X msg 26 85 240 \, 71 \, 0 \, 66 \, 72 \, 0 \, 0 \, 1 \, 75 \, 0 \, 54 | 1 \, 0 \, 4 \, 0 \, 247; 55 | #X obj 26 56 t b; 56 | #X text 103 29 this may not work on Windows; 57 | #X connect 0 0 3 0; 58 | #X connect 2 0 1 0; 59 | #X connect 3 0 2 0; 60 | #X restore 693 194 pd sysexout; 61 | #X obj 412 149 hsl 128 15 0 127 0 0 empty empty empty -2 -8 0 10 -262144 62 | -1 -1 0 1; 63 | #X floatatom 409 174 5 0 0 0 - - -, f 5; 64 | #X obj 409 200 touchout; 65 | #X obj 518 266 unpack f f; 66 | #X obj 521 184 hsl 128 15 0 127 0 0 empty empty empty -2 -8 0 10 -262144 67 | -1 -1 0 1; 68 | #X floatatom 518 212 5 0 0 0 - - -, f 5; 69 | #X obj 518 295 polytouchout; 70 | #X text 323 228 bend -8192 - 8192 in pd; 71 | #X msg 116 224 \$1 7; 72 | #X msg 518 238 \$1 64; 73 | #X text 564 238 set value for pitch 64; 74 | #X text 93 303 set value for ctl 7 (vol); 75 | #X obj 4 250 makenote 64 1000; 76 | #N canvas 299 383 433 247 (subpatch) 0; 77 | #X obj 72 69 inlet; 78 | #X obj 103 134 delay 2000; 79 | #X obj 72 104 t b b; 80 | #X msg 103 160 0; 81 | #X msg 72 158 127; 82 | #X text 233 65 for checking delta times; 83 | #X obj 144 69 metro 4000; 84 | #X obj 144 38 inlet; 85 | #X obj 72 191 ctlout 10 1; 86 | #X connect 0 0 2 0; 87 | #X connect 1 0 3 0; 88 | #X connect 2 0 4 0; 89 | #X connect 2 1 1 0; 90 | #X connect 3 0 8 0; 91 | #X connect 4 0 8 0; 92 | #X connect 6 0 2 0; 93 | #X connect 7 0 6 0; 94 | #X restore 693 286 pd 2 sec ctl; 95 | #X obj 693 263 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 96 | -1 -1; 97 | #X obj 762 265 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 98 | 1; 99 | #X connect 0 0 1 0; 100 | #X connect 0 1 2 0; 101 | #X connect 0 2 3 0; 102 | #X connect 4 0 5 0; 103 | #X connect 4 1 6 0; 104 | #X connect 4 2 7 0; 105 | #X connect 8 0 9 0; 106 | #X connect 8 1 10 0; 107 | #X connect 12 0 11 0; 108 | #X connect 12 1 13 0; 109 | #X connect 14 0 15 0; 110 | #X connect 14 1 16 0; 111 | #X connect 18 0 17 0; 112 | #X connect 18 1 19 0; 113 | #X connect 18 2 20 0; 114 | #X connect 24 0 25 0; 115 | #X connect 24 1 26 0; 116 | #X connect 25 0 27 0; 117 | #X connect 29 0 44 0; 118 | #X connect 30 0 57 0; 119 | #X connect 30 1 57 1; 120 | #X connect 31 0 30 0; 121 | #X connect 32 0 30 0; 122 | #X connect 34 0 33 0; 123 | #X connect 34 1 33 1; 124 | #X connect 35 0 39 0; 125 | #X connect 37 0 38 0; 126 | #X connect 38 0 36 0; 127 | #X connect 39 0 53 0; 128 | #X connect 41 0 42 0; 129 | #X connect 42 0 40 0; 130 | #X connect 45 0 46 0; 131 | #X connect 46 0 47 0; 132 | #X connect 48 0 51 0; 133 | #X connect 48 1 51 1; 134 | #X connect 49 0 50 0; 135 | #X connect 50 0 54 0; 136 | #X connect 53 0 34 0; 137 | #X connect 54 0 48 0; 138 | #X connect 57 0 28 0; 139 | #X connect 57 1 28 1; 140 | #X connect 59 0 58 0; 141 | #X connect 60 0 58 1; 142 | -------------------------------------------------------------------------------- /src/ofxMidiMessage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxMidiMessage.h" 12 | 13 | #include "ofxMidi.h" 14 | 15 | // ----------------------------------------------------------------------------- 16 | ofxMidiMessage::ofxMidiMessage() { 17 | clear(); 18 | } 19 | 20 | // ----------------------------------------------------------------------------- 21 | ofxMidiMessage::ofxMidiMessage(std::vector *rawBytes) { 22 | fromBytes(rawBytes); 23 | } 24 | 25 | // ----------------------------------------------------------------------------- 26 | ofxMidiMessage::ofxMidiMessage(const ofxMidiMessage &from) { 27 | copy(from); 28 | } 29 | 30 | // ----------------------------------------------------------------------------- 31 | ofxMidiMessage& ofxMidiMessage::operator=(const ofxMidiMessage &from) { 32 | copy(from); 33 | return *this; 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | void ofxMidiMessage::copy(const ofxMidiMessage &from) { 38 | bytes.clear(); 39 | for(unsigned int i = 0; i < from.bytes.size(); ++i) { 40 | bytes.push_back(from.bytes[i]); 41 | } 42 | status = from.status; 43 | channel = from.channel; 44 | pitch = from.pitch; 45 | velocity = from.velocity; 46 | control = from.control; 47 | value = from.value; 48 | bytes = from.bytes; 49 | deltatime = from.deltatime; 50 | portNum = from.portNum; 51 | portName = from.portName; 52 | } 53 | 54 | // ----------------------------------------------------------------------------- 55 | void ofxMidiMessage::fromBytes(std::vector *rawBytes) { 56 | 57 | // copy bytes 58 | clear(); 59 | for(unsigned int i = 0; i < rawBytes->size(); ++i) { 60 | bytes.push_back(rawBytes->at(i)); 61 | } 62 | 63 | // parse 64 | if(bytes[0] >= MIDI_SYSEX) { 65 | status = (MidiStatus) (bytes[0] & 0xFF); 66 | channel = 0; 67 | } else { 68 | status = (MidiStatus) (bytes[0] & 0xF0); 69 | channel = (int) (bytes[0] & 0x0F)+1; 70 | } 71 | switch(status) { 72 | case MIDI_NOTE_ON: 73 | case MIDI_NOTE_OFF: 74 | pitch = (int) bytes[1]; 75 | velocity = (int) bytes[2]; 76 | break; 77 | case MIDI_CONTROL_CHANGE: 78 | control = (int) bytes[1]; 79 | value = (int) bytes[2]; 80 | break; 81 | case MIDI_PROGRAM_CHANGE: 82 | case MIDI_AFTERTOUCH: 83 | value = (int) bytes[1]; 84 | break; 85 | case MIDI_PITCH_BEND: 86 | value = (int) (bytes[2] << 7) + (int) bytes[1]; // msb + lsb 87 | break; 88 | case MIDI_POLY_AFTERTOUCH: 89 | pitch = (int) bytes[1]; 90 | value = (int) bytes[2]; 91 | break; 92 | case MIDI_SONG_POS_POINTER: 93 | value = (int) (bytes[2] << 7) + (int) bytes[1]; // msb + lsb 94 | break; 95 | default: 96 | break; 97 | } 98 | } 99 | 100 | // ----------------------------------------------------------------------------- 101 | void ofxMidiMessage::clear() { 102 | status = MIDI_UNKNOWN; 103 | channel = 0; 104 | pitch = 0; 105 | velocity = 0; 106 | control = 0; 107 | value = 0; 108 | deltatime = 0; 109 | portNum = -1; 110 | portName = ""; 111 | bytes.clear(); 112 | } 113 | 114 | // ----------------------------------------------------------------------------- 115 | std::string ofxMidiMessage::toString() { 116 | std::stringstream stream; 117 | stream << portName << ": " << getStatusString(status) << " " 118 | << channel << " [ "; 119 | stream << ofxMidi::bytesToString(bytes); 120 | stream << "] " << deltatime; 121 | return stream.str(); 122 | } 123 | 124 | // ----------------------------------------------------------------------------- 125 | std::string ofxMidiMessage::getStatusString(MidiStatus status) { 126 | switch(status) { 127 | case MIDI_NOTE_OFF: 128 | return "Note Off"; 129 | case MIDI_NOTE_ON: 130 | return "Note On"; 131 | case MIDI_CONTROL_CHANGE: 132 | return "Control Change"; 133 | case MIDI_PROGRAM_CHANGE: 134 | return "Program Change"; 135 | case MIDI_PITCH_BEND: 136 | return "Pitch Bend"; 137 | case MIDI_AFTERTOUCH: 138 | return "Aftertouch"; 139 | case MIDI_POLY_AFTERTOUCH: 140 | return "Poly Aftertouch"; 141 | case MIDI_SYSEX: 142 | return "Sysex"; 143 | case MIDI_TIME_CODE: 144 | return "Time Code"; 145 | case MIDI_SONG_POS_POINTER: 146 | return "Song Pos"; 147 | case MIDI_SONG_SELECT: 148 | return "Song Select"; 149 | case MIDI_TUNE_REQUEST: 150 | return "Tune Request"; 151 | case MIDI_SYSEX_END: 152 | return "Sysex End"; 153 | case MIDI_TIME_CLOCK: 154 | return "Time Clock"; 155 | case MIDI_START: 156 | return "Start"; 157 | case MIDI_CONTINUE: 158 | return "Continue"; 159 | case MIDI_STOP: 160 | return "Stop"; 161 | case MIDI_ACTIVE_SENSING: 162 | return "Active Sensing"; 163 | case MIDI_SYSTEM_RESET: 164 | return "System Reset"; 165 | default: 166 | return "Unknown"; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/ofxMidiTimecode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | * Adapted from code written in Swift for the Hertz-Lab @ ZKM | Karlsruhe 11 | * 12 | */ 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include "ofEvents.h" 18 | #include "ofxMidiConstants.h" 19 | 20 | /// MTC frame 21 | struct ofxMidiTimecodeFrame { 22 | 23 | int hours = 0; ///< hours 0-23 24 | int minutes = 0; ///< minutes 0-59 25 | int seconds = 0; ///< seconds 0-59 26 | int frames = 0; ///< frames 0-29 (depending on framerate) 27 | unsigned char rate = 0x0; ///< 0x0: 24, 0x1: 25, 0x2: 29.97, 0x3: 30 28 | 29 | /// get the framerate value in fps 30 | double getFps() const; 31 | 32 | /// \section Util 33 | 34 | /// convert to a string: hh:mm:ss:ff 35 | std::string toString() const; 36 | 37 | /// convert to time in seconds 38 | double toSeconds() const; 39 | 40 | /// convert from time in seconds, uses default 24 fps 41 | void fromSeconds(double s); 42 | 43 | /// convert from time in seconds & framerate value (not fps!) 44 | void fromSeconds(double s, unsigned char r); 45 | }; 46 | 47 | /// 48 | /// MIDI timecode message parser 49 | /// 50 | /// currently input only, does not send timecode 51 | /// 52 | class ofxMidiTimecode { 53 | 54 | public: 55 | 56 | ofxMidiTimecode() {} 57 | virtual ~ofxMidiTimecode() {} 58 | 59 | /// update the timecode frame from a raw MIDI message, 60 | /// returns true if the frame is new 61 | bool update(std::vector &message); 62 | 63 | /// reset current frame data 64 | void reset(); 65 | 66 | /// \section Status 67 | 68 | /// get the last complete (current) timecode frame 69 | ofxMidiTimecodeFrame getFrame() {return frame;} 70 | 71 | /// \section Util 72 | 73 | /// framerate values 74 | enum Framerate : unsigned char { 75 | FRAMERATE_24 = 0x0, 76 | FRAMERATE_25 = 0x1, 77 | FRAMERATE_30_DROP = 0x2, // 29.997 drop frame 78 | FRAMERATE_30 = 0x3 79 | }; 80 | 81 | /// calculate ms from a frame count & framerate value 82 | static int framesToMs(int frames, unsigned char rate); 83 | 84 | /// calculate frame count from ms & framerate value 85 | static int msToFrames(int ms, unsigned char rate); 86 | 87 | /// returns the fps for a framerate value, ie. 0x0 -> 24 fps 88 | static double rateToFps(unsigned char rate); 89 | 90 | /// convert fps to the closest framerate value, ie. 24 fps -> 0x0 91 | static unsigned char fpsToRate(double fps); 92 | 93 | /// returns conversion multiplier from framerate value, ie. 1.0/fps 94 | static double rateToMultiplier(unsigned char rate); 95 | 96 | protected: 97 | 98 | /// current frame, ie. last complete Quarter or Full Frame message 99 | ofxMidiTimecodeFrame frame; 100 | 101 | /// number of bytes in a FF message 102 | static const int FULLFRAME_LEN = 10; 103 | 104 | /// number of QF messages to make up a full MTC frame 105 | static const int QUARTERFRAME_LEN = 8; 106 | 107 | /// MTC quarter frame info 108 | struct QuarterFrame { 109 | 110 | /// detected time direction 111 | enum Direction { 112 | BACKWARDS = -1, ///< time is moving backwards ie. rewinding 113 | UNKNOWN = 0, ///< unknown so far 114 | FORWARDS = 1 ///< time is advancing 115 | }; 116 | 117 | // data 118 | ofxMidiTimecodeFrame frame; 119 | 120 | // protocol handling 121 | unsigned int count = 0; ///< current received QF message count 122 | bool receivedFirst = false; ///< did we receive the first message? (0x0* frames) 123 | bool receivedLast = false; ///< did we receive the last message? (0x7* hours) 124 | unsigned int lastDataByte = 0x00; ///< last received data byte for direction detection 125 | Direction direction = UNKNOWN; ///< forwards or backwards? 126 | }; 127 | 128 | /// current quarter frame info 129 | QuarterFrame quarterFrame; 130 | 131 | /// decode a Quarter Frame message, update when we have a full set of 8 messages 132 | /// 133 | /// if we receive too many, just overwrite until we have both the first and last 134 | /// messages (aka 0xF10* & 0xF17*) since a DAW could stop in the middle and start 135 | /// a new set of Quarter Frame messages based on user input aka start/stop 136 | /// 137 | /// also try to detect direction based on last received byte, 138 | /// this should hopefully handle both forwards and backwards playback: 139 | /// * forwards: 0xF10*, 0xF11*, 0xF12*, 0xF13*, 0xF14*, 0xF15*, 0xF16*, 0xF17* 140 | /// * backwards: 0xF17*, 0xF16*, 0xF15*, 0xF14*, 0xF13*, 0xF12*, 0xF11*, 0xF10* 141 | /// 142 | /// note: Quarter Frame state is cleared when a Full Frame message is received 143 | /// 144 | /// returns true if a complete quarter frame was decoded 145 | bool decodeQuarterFrame(std::vector &message); 146 | 147 | /// decode a FF message: F0 7F 7F 01 01 hh mm ss ff F7 148 | /// returns true if a full frame was decoded 149 | bool decodeFullFrame(std::vector &message); 150 | 151 | /// check if all bytes are correct 152 | bool isFullFrame(std::vector &message); 153 | }; 154 | -------------------------------------------------------------------------------- /src/desktop/ofxRtMidiOut.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxRtMidiOut.h" 12 | 13 | #include "ofUtils.h" 14 | #include "ofLog.h" 15 | 16 | // ----------------------------------------------------------------------------- 17 | ofxRtMidiOut::ofxRtMidiOut(const std::string name, ofxMidiApi api) : 18 | ofxBaseMidiOut(name, api), midiOut((RtMidi::Api)api, name) { 19 | midiOut.setErrorCallback(&_midiErrorCallback, this); 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | ofxRtMidiOut::~ofxRtMidiOut() { 24 | closePort(); 25 | } 26 | 27 | // ----------------------------------------------------------------------------- 28 | void ofxRtMidiOut::listOutPorts() { 29 | ofLogNotice("ofxMidiOut") << midiOut.getPortCount() << " ports available"; 30 | for(unsigned int i = 0; i < midiOut.getPortCount(); ++i){ 31 | ofLogNotice("ofxMidiOut") << i << ": " << midiOut.getPortName(i); 32 | } 33 | } 34 | 35 | // ----------------------------------------------------------------------------- 36 | std::vector ofxRtMidiOut::getOutPortList() { 37 | std::vector portList; 38 | for(unsigned int i = 0; i < midiOut.getPortCount(); ++i) { 39 | portList.push_back(midiOut.getPortName(i)); 40 | } 41 | return portList; 42 | } 43 | 44 | // ----------------------------------------------------------------------------- 45 | int ofxRtMidiOut::getNumOutPorts() { 46 | return midiOut.getPortCount(); 47 | } 48 | 49 | // ----------------------------------------------------------------------------- 50 | std::string ofxRtMidiOut::getOutPortName(unsigned int portNumber) { 51 | // handle RtMidi exceptions 52 | try { 53 | return midiOut.getPortName(portNumber); 54 | } 55 | catch(RtMidiError& err) { 56 | ofLogError("ofxMidiOut") << "couldn't get name for port " << portNumber << ": " << err.what(); 57 | } 58 | return ""; 59 | } 60 | 61 | // ----------------------------------------------------------------------------- 62 | bool ofxRtMidiOut::openPort(unsigned int portNumber) { 63 | // handle RtMidi exceptions 64 | try { 65 | closePort(); 66 | midiOut.openPort(portNumber, "ofxMidi Output "+ofToString(portNumber)); 67 | } 68 | catch(RtMidiError& err) { 69 | ofLogError("ofxMidiOut") << "couldn't open port " << portNumber << ": " << err.what(); 70 | return false; 71 | } 72 | portNum = portNumber; 73 | portName = midiOut.getPortName(portNumber); 74 | bOpen = true; 75 | ofLogVerbose("ofxMidiOut") << "opened port " << portNum << " " << portName; 76 | return true; 77 | } 78 | 79 | // ----------------------------------------------------------------------------- 80 | bool ofxRtMidiOut::openPort(std::string deviceName) { 81 | 82 | // iterate through MIDI ports, find requested device 83 | int port = -1; 84 | for(unsigned int i = 0; i < midiOut.getPortCount(); ++i) { 85 | std::string name = midiOut.getPortName(i); 86 | if(name == deviceName) { 87 | port = i; 88 | break; 89 | } 90 | } 91 | 92 | // bail if not found 93 | if(port == -1) { 94 | ofLogError("ofxMidiOut") << "port \"" << deviceName << "\" is not available"; 95 | return false; 96 | } 97 | 98 | return openPort(port); 99 | } 100 | 101 | // ----------------------------------------------------------------------------- 102 | bool ofxRtMidiOut::openVirtualPort(std::string portName) { 103 | // handle RtMidi exceptions 104 | try { 105 | closePort(); 106 | midiOut.openVirtualPort(portName); 107 | } 108 | catch(RtMidiError& err) { 109 | ofLogError("ofxMidiOut") << "couldn't open virtual port \"" << portName << "\": " << err.what(); 110 | return false; 111 | } 112 | 113 | this->portName = portName; 114 | bOpen = true; 115 | bVirtual = true; 116 | ofLogVerbose("ofxMidiOut") << "opened virtual port " << portName; 117 | return true; 118 | } 119 | 120 | // ----------------------------------------------------------------------------- 121 | void ofxRtMidiOut::closePort() { 122 | if(bVirtual && bOpen) { 123 | ofLogVerbose("ofxMidiOut") << "closing virtual port " << portName; 124 | } 125 | else if(bOpen && portNum > -1) { 126 | ofLogVerbose("ofxMidiOut") << "closing port " << portNum << " " << portName; 127 | } 128 | midiOut.closePort(); 129 | portNum = -1; 130 | portName = ""; 131 | bOpen = false; 132 | bStreamInProgress = false; 133 | bVirtual = false; 134 | } 135 | 136 | // PRIVATE 137 | // ----------------------------------------------------------------------------- 138 | void ofxRtMidiOut::sendMessage(std::vector &message) { 139 | // handle RtMidi exceptions 140 | try { 141 | midiOut.sendMessage(&message); 142 | } 143 | catch(RtMidiError &err) { 144 | ofLogError("ofxMidiOut") << "couldn't send message: " << err.what(); 145 | } 146 | } 147 | 148 | // ----------------------------------------------------------------------------- 149 | void ofxRtMidiOut::_midiErrorCallback(RtMidiError::Type type, const std::string &errorText, void *userData) { 150 | ofxRtMidiOut *midiOut = (ofxRtMidiOut *)userData; 151 | ofLogError("ofxMidiOut") << midiOut->getName() << ": " << errorText; 152 | } 153 | -------------------------------------------------------------------------------- /addon_config.mk: -------------------------------------------------------------------------------- 1 | # All variables and this file are optional, if they are not present the PG and the 2 | # makefiles will try to parse the correct values from the file system. 3 | # 4 | # Variables that specify exclusions can use % as a wildcard to specify that anything in 5 | # that position will match. A partial path can also be specified to, for example, exclude 6 | # a whole folder from the parsed paths from the file system 7 | # 8 | # Variables can be specified using = or += 9 | # = will clear the contents of that variable both specified from the file or the ones parsed 10 | # from the file system 11 | # += will add the values to the previous ones in the file or the ones parsed from the file 12 | # system 13 | # 14 | # The PG can be used to detect errors in this file, just create a new project with this addon 15 | # and the PG will write to the console the kind of error and in which line it is 16 | 17 | meta: 18 | ADDON_NAME = ofxMidi 19 | ADDON_DESCRIPTION = MIDI interface for OpenFrameworks 20 | ADDON_AUTHOR = Dan Wilcox (original implementation by Chris OShea, Arturo Castro, Kyle McDonald) 21 | ADDON_TAGS = "MIDI" "audio" "control" 22 | ADDON_URL = http://github.com/danomatika/ofxMidi 23 | 24 | common: 25 | # dependencies with other addons, a list of them separated by spaces 26 | # or use += in several lines 27 | # ADDON_DEPENDENCIES = 28 | 29 | # include search paths, this will be usually parsed from the file system 30 | # but if the addon or addon libraries need special search paths they can be 31 | # specified here separated by spaces or one per line using += 32 | # ADDON_INCLUDES = 33 | 34 | # any special flag that should be passed to the compiler when using this 35 | # addon 36 | # ADDON_CFLAGS = 37 | 38 | # any special flag that should be passed to the linker when using this 39 | # addon, also used for system libraries with -lname 40 | # ADDON_LDFLAGS = 41 | 42 | # linux only, any library that should be included in the project using 43 | # pkg-config 44 | # ADDON_PKG_CONFIG_LIBRARIES = 45 | 46 | # osx/iOS only, any framework that should be included in the project 47 | # ADDON_FRAMEWORKS = 48 | 49 | # source files, these will be usually parsed from the file system looking 50 | # in the src folders in libs and the root of the addon. if your addon needs 51 | # to include files in different places or a different set of files per platform 52 | # they can be specified here 53 | # ADDON_SOURCES = 54 | 55 | # some addons need resources to be copied to the bin/data folder of the project 56 | # specify here any files that need to be copied, you can use wildcards like * and ? 57 | # ADDON_DATA = 58 | 59 | # when parsing the file system looking for libraries exclude this for all or 60 | # a specific platform 61 | # ADDON_LIBS_EXCLUDE = 62 | 63 | # when parsing the file system looking for sources exclude this for all or 64 | # a specific platform 65 | # ADDON_SOURCES_EXCLUDE = 66 | 67 | # when parsing the file system looking for include paths exclude this for all or 68 | # a specific platform 69 | # ADDON_INCLUDES_EXCLUDE = 70 | 71 | linux64: 72 | ADDON_PKG_CONFIG_LIBRARIES = alsa jack 73 | 74 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 75 | ADDON_SOURCES_EXCLUDE += src/ios/% 76 | 77 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 78 | ADDON_INCLUDES_EXCLUDE += src/ios/% 79 | 80 | linux: 81 | ADDON_PKG_CONFIG_LIBRARIES = alsa jack 82 | 83 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 84 | ADDON_SOURCES_EXCLUDE += src/ios/% 85 | 86 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 87 | ADDON_INCLUDES_EXCLUDE += src/ios/% 88 | 89 | linuxarmv6l: 90 | ADDON_PKG_CONFIG_LIBRARIES = alsa 91 | 92 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 93 | ADDON_SOURCES_EXCLUDE += src/ios/% 94 | 95 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 96 | ADDON_INCLUDES_EXCLUDE += src/ios/% 97 | 98 | linuxarmv7l: 99 | ADDON_PKG_CONFIG_LIBRARIES = alsa 100 | 101 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 102 | ADDON_SOURCES_EXCLUDE += src/ios/% 103 | 104 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 105 | ADDON_INCLUDES_EXCLUDE += src/ios/% 106 | 107 | linuxaarch64: 108 | ADDON_PKG_CONFIG_LIBRARIES = alsa 109 | 110 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 111 | ADDON_SOURCES_EXCLUDE += src/ios/% 112 | 113 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 114 | ADDON_INCLUDES_EXCLUDE += src/ios/% 115 | 116 | msys2: 117 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 118 | ADDON_SOURCES_EXCLUDE += src/ios/% 119 | 120 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 121 | ADDON_INCLUDES_EXCLUDE += src/ios/% 122 | 123 | vs: 124 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 125 | ADDON_SOURCES_EXCLUDE += src/ios/% 126 | 127 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 128 | ADDON_INCLUDES_EXCLUDE += src/ios/% 129 | 130 | android/armeabi: 131 | 132 | android/armeabi-v7a: 133 | 134 | osx: 135 | # osx/iOS only 136 | ADDON_FRAMEWORKS = CoreMIDI 137 | 138 | ADDON_SOURCES_EXCLUDE = libs/pgmidi/% 139 | ADDON_SOURCES_EXCLUDE += src/ios/% 140 | 141 | ADDON_INCLUDES_EXCLUDE = libs/pgmidi/% 142 | ADDON_INCLUDES_EXCLUDE += src/ios/% 143 | 144 | ios: 145 | # osx/iOS only 146 | ADDON_FRAMEWORKS = CoreMIDI 147 | 148 | ADDON_SOURCES_EXCLUDE = libs/rtmidi/% 149 | ADDON_SOURCES_EXCLUDE += libs/portmidi/% 150 | ADDON_SOURCES_EXCLUDE += src/desktop/% 151 | 152 | ADDON_INCLUDES_EXCLUDE = libs/rtmidi/% 153 | ADDON_INCLUDES_EXCLUDE += libs/portmidi/% 154 | ADDON_INCLUDES_EXCLUDE += src/desktop/% 155 | -------------------------------------------------------------------------------- /libs/pgmidi/PGMidi.h: -------------------------------------------------------------------------------- 1 | // 2 | // PGMidi.h 3 | // PGMidi 4 | // 5 | 6 | #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE 7 | #import 8 | #else 9 | #import 10 | #endif 11 | 12 | #import 13 | 14 | #import "PGArc.h" 15 | 16 | @class PGMidi; 17 | @class PGMidiSource; 18 | 19 | extern NSString * const PGMidiSourceAddedNotification; 20 | extern NSString * const PGMidiSourceRemovedNotification; 21 | extern NSString * const PGMidiDestinationAddedNotification; 22 | extern NSString * const PGMidiDestinationRemovedNotification; 23 | 24 | extern NSString * const PGMidiConnectionKey; 25 | 26 | 27 | /// Represents a source/destination for MIDI data 28 | /// 29 | /// @see PGMidiSource 30 | /// @see PGMidiDestination 31 | @interface PGMidiConnection : NSObject 32 | { 33 | PGMidi *midi; 34 | MIDIEndpointRef endpoint; 35 | NSString *name; 36 | BOOL isNetworkSession; 37 | } 38 | @property (nonatomic,readonly) PGMidi *midi; 39 | @property (nonatomic,readonly) MIDIEndpointRef endpoint; 40 | @property (nonatomic,readonly) NSString *name; 41 | @property (nonatomic,readonly) BOOL isNetworkSession; 42 | @end 43 | 44 | 45 | /// Delegate protocol for PGMidiSource class. 46 | /// Adopt this protocol in your object to receive MIDI events 47 | /// 48 | /// IMPORTANT NOTE: 49 | /// MIDI input is received from a high priority background thread 50 | /// 51 | /// @see PGMidiSource 52 | @protocol PGMidiSourceDelegate 53 | 54 | // Raised on main run loop 55 | /// NOTE: Raised on high-priority background thread. 56 | /// 57 | /// To do anything UI-ish, you must forward the event to the main runloop 58 | /// (e.g. use performSelectorOnMainThread:withObject:waitUntilDone:) 59 | /// 60 | /// Be careful about autoreleasing objects here - there is no NSAutoReleasePool. 61 | /// 62 | /// Handle the data like this: 63 | /// 64 | /// // for some function HandlePacketData(Byte *data, UInt16 length) 65 | /// const MIDIPacket *packet = &packetList->packet[0]; 66 | /// for (int i = 0; i < packetList->numPackets; ++i) 67 | /// { 68 | /// HandlePacketData(packet->data, packet->length); 69 | /// packet = MIDIPacketNext(packet); 70 | /// } 71 | - (void) midiSource:(PGMidiSource*)input midiReceived:(const MIDIPacketList *)packetList; 72 | 73 | @end 74 | 75 | /// Represents a source of MIDI data identified by CoreMIDI 76 | /// 77 | /// @see PGMidiSourceDelegate 78 | @interface PGMidiSource : PGMidiConnection 79 | - (void)addDelegate:(id)delegate; 80 | - (void)removeDelegate:(id)delegate; 81 | 82 | @property (strong, nonatomic, readonly) NSArray *delegates; 83 | @end 84 | 85 | //============================================================================== 86 | 87 | /// Represents a destination for MIDI data identified by CoreMIDI 88 | @interface PGMidiDestination : PGMidiConnection 89 | { 90 | } 91 | - (void) flushOutput; 92 | - (void) sendBytes:(const UInt8*)bytes size:(UInt32)size; 93 | - (void) sendPacketList:(MIDIPacketList *)packetList; 94 | @end 95 | 96 | //============================================================================== 97 | 98 | /// Delegate protocol for PGMidi class. 99 | /// 100 | /// @see PGMidi 101 | @protocol PGMidiDelegate 102 | - (void) midi:(PGMidi*)midi sourceAdded:(PGMidiSource *)source; 103 | - (void) midi:(PGMidi*)midi sourceRemoved:(PGMidiSource *)source; 104 | - (void) midi:(PGMidi*)midi destinationAdded:(PGMidiDestination *)destination; 105 | - (void) midi:(PGMidi*)midi destinationRemoved:(PGMidiDestination *)destination; 106 | @end 107 | 108 | /// Class for receiving MIDI input from any MIDI device. 109 | /// 110 | /// If you intend your app to support iOS 3.x which does not have CoreMIDI 111 | /// support, weak link to the CoreMIDI framework, and only create a 112 | /// PGMidi object if you are running the right version of iOS. 113 | /// 114 | /// @see PGMidiDelegate 115 | @interface PGMidi : NSObject 116 | { 117 | MIDIClientRef client; 118 | MIDIPortRef outputPort; 119 | MIDIPortRef inputPort; 120 | NSString *virtualEndpointName; 121 | MIDIEndpointRef virtualSourceEndpoint; 122 | MIDIEndpointRef virtualDestinationEndpoint; 123 | PGMidiSource *virtualDestinationSource; 124 | PGMidiDestination *virtualSourceDestination; 125 | NSMutableArray *sources, *destinations; 126 | } 127 | 128 | + (BOOL)midiAvailable; 129 | 130 | @property (nonatomic,assign) id delegate; 131 | @property (nonatomic,readonly) NSUInteger numberOfConnections; 132 | @property (nonatomic,readonly) NSMutableArray *sources; 133 | @property (nonatomic,readonly) NSMutableArray *destinations; 134 | @property (nonatomic,readonly) PGMidiSource *virtualDestinationSource; 135 | @property (nonatomic,readonly) PGMidiDestination *virtualSourceDestination; 136 | @property (nonatomic,retain) NSString *virtualEndpointName; 137 | @property (nonatomic,assign) BOOL networkEnabled; 138 | 139 | /// Remember to set the UIBackgroundModes plist property for virtual sources to work 140 | @property (nonatomic,assign) BOOL virtualSourceEnabled; 141 | @property (nonatomic,assign) BOOL virtualDestinationEnabled; 142 | 143 | /// Send a MIDI byte stream to every connected MIDI port 144 | - (void) sendBytes:(const UInt8*)bytes size:(UInt32)size; 145 | - (void) sendPacketList:(const MIDIPacketList *)packetList; 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 1.3.1: 2024 Jan 11 2 | 3 | * added addons_config.mk linuxaarch64 target for 64 bit RPi 4 (Andy Fischer) 4 | 5 | 1.3.0: 2023 Nov 15 6 | 7 | * fixed ofxMidiClock getBeat and getSeconds conversion warnings (Dimitre Lima) 8 | 9 | * added queued message passing for ofxMidiIn via the hasWaitingMessages() and 10 | getNextMessage() functions (Alexandre Burton) 11 | * added rtmidi in & out error callbacks, just print error for now 12 | 13 | * updated for OF 0.12.0 14 | * updated to RtMidi 0.6.0 15 | * iOS Obj-C++ sources now use ARC 16 | * updated midiInputExample with optional queued messaging support 17 | * readme typo fix (Darío Hereñú) 18 | * various source formatting updates 19 | 20 | * remove unused variable (reported by Dimitre Lima) 21 | 22 | 1.2.0: 2020 Dec 10 23 | 24 | * fixed incorrect timecode casting of ms to seconds (Alex Ramos) 25 | * fixed missing ofUtils.h include for ofToString usage (ISHII 2bit) 26 | * fixed wrong readme ofxMidiConstants.h include note (reported by Steven Noreyko) 27 | 28 | * added ofxMidiClock and ofxMidiTimecode message parsers 29 | * added pkg_config for Jack on Linux (Edwin van Ouwerkerk Moria) 30 | * added note about receiving message mutex to readme (suggested by Arnaud Loonstra) 31 | 32 | * small update to midiExampleIOS for OF 0.11.0 33 | * updated to RtMidi 4.0.0 34 | 35 | 1.1.1: 2018 Jul 28 36 | 37 | * fixed RtMidi.h missing ofxMidiConstants.h include (reported by Nicola Pisanti) 38 | 39 | 1.1.0: 2018 Jul 24 40 | 41 | * added backend api type and constructor arguments (Sol Bekic) 42 | * added MIDI clock & timecode parsers and midiTimingExample 43 | 44 | * updated for OF 0.10.0 45 | * updated to RtMidi 3.0.0 46 | * readme consistency fixes (tpltnt) 47 | * moved input parsing to ofxMidiMessage to ease raw byte handling 48 | (reported by Damian Stewart) 49 | 50 | * fixed ofxMidiOut crash in a multi-thread context (reported by Hiroshi Matoba) 51 | 52 | * removed static port info functions as a in/out port instance api might be 53 | different than the default (listPorts, getPortList, getNumPorts, getPortName) 54 | * renamed port info functions to *InPort and *OutPort naming (ie. listInPorts, 55 | listOutPorts, etc) 56 | 57 | 1.0.6: 2016 Oct 24 58 | 59 | * added ProjectGenerator usage info to readme 60 | 61 | * updated to RtMidi 2.1.1 62 | 63 | * fixed Linux build due to OF no longer including ALSA by default 64 | (reported by Nicola Pisanti) 65 | * fixed missing value parsing for MIDI song pos pointer message 66 | (reported by James Morris) 67 | 68 | 1.0.5: 2015 Dec 24 69 | 70 | * added mtof() & ftom() util functions (suggested by Thomas Geissl) 71 | * added info to readme about possible issued when setting things up in 72 | constructors (reported by flyingrub) 73 | 74 | * updated for OF 0.8.5 75 | * updated to RtMidi 2.1.0 (note: RtError.h was removed in this version) 76 | * updated PGMidi 77 | 78 | * fixed iOS crash caused by occasional out of bounds PGMidi index 79 | 80 | * removed example project files (rebuild them with the OF ProjectGenerator) 81 | 82 | 1.0.4: 2015 Aug 16 83 | 84 | * source code whitespace cleanups 85 | * updated exampleIOS for OF 0.8.3 (thanks leico) 86 | 87 | 1.0.3: 2013 Dec 29 88 | 89 | * fixed stale readme repo links 90 | * fixed exampleIOS message queue crash (reported by kritzikratzi) 91 | * fixed missing CoreMIDI framework to input example (reported by danzeeeman) 92 | * fixed an RtMidi bug that was causing null characters to be included in the 93 | returned device name string (thanks Tim Murray-Browne) 94 | 95 | 1.0.2: 2013 Aug 13 96 | 97 | repo moved to github.com/danomatika 98 | 99 | * added BSD license 100 | * added logo 101 | 102 | * updated for OF 0.8.0 103 | * updated to RtMidi 2.0.1 104 | * replaced ofMain.h includes with specific required files, 105 | should save a little on compile times 106 | * updated logging to use "ofxMidi" module name 107 | * renamed examples 108 | 109 | * replaced RtMidi static instance pointers with ofPtrs 110 | 111 | 1.0.1: 2012 Jul 30 112 | 113 | * added iOS support using PGMidi Obj-C library 114 | * added delta time to input example 115 | * added ios example project 116 | 117 | * updated for OF 0071 118 | * split in and out classes into bases and platform-specific implementations 119 | * listPorts(), getPortList(), getNumPorts(), & getPortName() are now static 120 | functions for usage without having to create ofxMidiIn/Out instances 121 | 122 | * fixed RtMidi bug where multiple alsa seq clients were being opened 123 | * fixed improper port counting in alsa RtMidi implementation 124 | * fixed SYSEX message handling (thanks Mukei) 125 | 126 | 1.0.0: 2012 Feb 21 127 | 128 | cross-platform rewrite 129 | 130 | * added stream types 131 | * added pure data midi testing patch 132 | * added sendMidiBytes() 133 | * added Linux & Visual Studio project files 134 | 135 | * updated ofxMidiOut: 136 | - clean up 137 | - exception handling 138 | - stream handling 139 | * updated ofxMidiIn: 140 | - clean up 141 | - exception handling 142 | * renamed and expanded ofxMidiMessage 143 | * updated MIDI defines 144 | * updated input and output examples 145 | * updated readme 146 | 147 | * virtual output port fixes 148 | * bugfix: open first port not 2nd 149 | 150 | 0.2.0: 2011 Nov 1 151 | 152 | * added sendProgramChange() and sendPitchBend() to midiOut 153 | * added openVirtualPort() 154 | * added findPorts() function, tidied up openPort(string name) 155 | * added overloaded OpenPort to open a port by name (thanks momo-the-monster) 156 | * added readme 157 | * added output example 158 | 159 | * massive source cleanup (Kyle McDonald) 160 | * updated to RtMidi 1.0.15 161 | 162 | 0.1.0: 2011 Aug 2 163 | 164 | initial version (Chris O'Shea) 165 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiSourceDelegate.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxPGMidiSourceDelegate.h" 12 | 13 | #include "ofMain.h" 14 | 15 | #import "pgmidi/iOSVersionDetection.h" 16 | #include 17 | #include "ofxPGMidiIn.h" 18 | 19 | // ----------------------------------------------------------------------------- 20 | // there is no conversion fucntion on iOS, so we make one here 21 | // from https://developer.apple.com/library/mac/#qa/qa1398/_index.html 22 | uint64_t AbsoluteToNanos(uint64_t time) { 23 | static struct mach_timebase_info timebaseInfo; 24 | 25 | // only init once 26 | if(timebaseInfo.denom == 0) { 27 | mach_timebase_info(&timebaseInfo); 28 | } 29 | 30 | return time * timebaseInfo.numer / timebaseInfo.denom; 31 | } 32 | 33 | // PG MIDI IN DELEGATE 34 | // ----------------------------------------------------------------------------- 35 | @implementation ofxPGMidiSourceDelegate 36 | 37 | @synthesize bIgnoreSysex, bIgnoreTiming, bIgnoreSense; 38 | 39 | // ----------------------------------------------------------------------------- 40 | - (instancetype)init { 41 | self = [super init]; 42 | if(self) { 43 | inputPtr = NULL; 44 | 45 | lastTime = 0; 46 | bFirstPacket = true; 47 | bContinueSysex = false; 48 | 49 | maxMessageLen = 100; // default RtMidiIn length 50 | } 51 | return self; 52 | } 53 | 54 | // ----------------------------------------------------------------------------- 55 | // adapted from RTMidi CoreMidi message parsing 56 | - (void)midiSource:(PGMidiSource *)input midiReceived:(const MIDIPacketList *)packetList { 57 | 58 | const MIDIPacket *packet = &packetList->packet[0]; 59 | stringstream msg; 60 | unsigned char statusByte; 61 | unsigned short nBytes, curByte, msgSize; 62 | unsigned long long time; 63 | double delta = 0.0; 64 | 65 | for(int i = 0; i < packetList->numPackets; ++i) { 66 | 67 | nBytes = packet->length; 68 | if(nBytes == 0) 69 | continue; 70 | 71 | // calc time stamp 72 | time = 0; 73 | if(bFirstPacket) { 74 | bFirstPacket = false; 75 | } 76 | else { 77 | time = packet->timeStamp; 78 | if(time == 0) { // this happens when receiving asynchronous sysex messages 79 | time = mach_absolute_time(); 80 | } 81 | time -= lastTime; 82 | 83 | // set the delta time between individual messages 84 | if(!bContinueSysex) { 85 | delta = AbsoluteToNanos(time) * 0.000001; // convert to ms 86 | } 87 | } 88 | lastTime = packet->timeStamp; 89 | if(lastTime == 0 ) { // this happens when receiving asynchronous sysex messages 90 | lastTime = mach_absolute_time(); 91 | } 92 | 93 | // handle segmented sysex messages 94 | curByte = 0; 95 | if(bContinueSysex) { 96 | 97 | // copy the packet if not ignoring 98 | if(!bIgnoreSysex) { 99 | for(int i = 0; i < nBytes; ++i) { 100 | message.push_back(packet->data[i]); 101 | } 102 | } 103 | bContinueSysex = packet->data[nBytes-1] != 0xF7; // look for stop 104 | 105 | if(!bContinueSysex) { 106 | // send message if sysex message complete 107 | if(!message.empty()) { 108 | inputPtr->messageReceived(delta, &message); 109 | } 110 | message.clear(); 111 | } 112 | } 113 | else { // not sysex, parse bytes 114 | 115 | while(curByte < nBytes) { 116 | msgSize = 0; 117 | 118 | // next byte in the packet should be a status byte 119 | statusByte = packet->data[curByte]; 120 | if((!statusByte) & 0x80) 121 | break; 122 | 123 | // determine number of bytes in midi message 124 | if(statusByte < 0xC0) 125 | msgSize = 3; 126 | else if(statusByte < 0xE0) 127 | msgSize = 2; 128 | else if(statusByte < 0xF0) 129 | msgSize = 3; 130 | else if(statusByte == 0xF0) { // sysex message 131 | 132 | if(bIgnoreSysex) { 133 | msgSize = 0; 134 | curByte = nBytes; 135 | } 136 | else { 137 | msgSize = nBytes - curByte; 138 | } 139 | bContinueSysex = packet->data[nBytes-1] != 0xF7; 140 | } 141 | else if(statusByte == 0xF1) { // time code message 142 | 143 | if(bIgnoreTiming) { 144 | msgSize = 0; 145 | curByte += 2; 146 | } 147 | else { 148 | msgSize = 2; 149 | } 150 | } 151 | else if(statusByte == 0xF2) 152 | msgSize = 3; 153 | else if(statusByte == 0xF3) 154 | msgSize = 2; 155 | else if(statusByte == 0xF8 && bIgnoreTiming) { // timing tick message 156 | // ignoring ... 157 | msgSize = 0; 158 | curByte += 1; 159 | } 160 | else if(statusByte == 0xFE && bIgnoreSense) { // active sense message 161 | // ignoring ... 162 | msgSize = 0; 163 | curByte += 1; 164 | } 165 | else { 166 | msgSize = 1; 167 | } 168 | 169 | // copy packet 170 | if(msgSize) { 171 | 172 | message.assign(&packet->data[curByte], &packet->data[curByte+msgSize]); 173 | 174 | if(!bContinueSysex) { 175 | // send message if sysex message complete 176 | if(!message.empty()) { 177 | inputPtr->messageReceived(delta, &message); 178 | } 179 | message.clear(); 180 | } 181 | curByte += msgSize; 182 | } 183 | } 184 | } 185 | 186 | packet = MIDIPacketNext(packet); 187 | } 188 | } 189 | 190 | // ----------------------------------------------------------------------------- 191 | - (void)setInputPtr:(void *)p { 192 | inputPtr = (ofxPGMidiIn*) p; 193 | } 194 | 195 | @end 196 | -------------------------------------------------------------------------------- /src/desktop/ofxRtMidiIn.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxRtMidiIn.h" 12 | 13 | #include "ofUtils.h" 14 | #include "ofLog.h" 15 | 16 | // ----------------------------------------------------------------------------- 17 | ofxRtMidiIn::ofxRtMidiIn(const std::string name, ofxMidiApi api) : 18 | ofxBaseMidiIn(name, api), midiIn((RtMidi::Api)api, name) { 19 | midiIn.setErrorCallback(&_midiErrorCallback, this); 20 | } 21 | 22 | // ----------------------------------------------------------------------------- 23 | ofxRtMidiIn::~ofxRtMidiIn() { 24 | closePort(); 25 | } 26 | 27 | // ----------------------------------------------------------------------------- 28 | void ofxRtMidiIn::listInPorts() { 29 | ofLogNotice("ofxMidiIn") << midiIn.getPortCount() << " ports available"; 30 | for(unsigned int i = 0; i < midiIn.getPortCount(); ++i){ 31 | ofLogNotice("ofxMidiIn") << i << ": " << midiIn.getPortName(i); 32 | } 33 | } 34 | 35 | // ----------------------------------------------------------------------------- 36 | std::vector ofxRtMidiIn::getInPortList() { 37 | std::vector portList; 38 | for(unsigned int i = 0; i < midiIn.getPortCount(); ++i) { 39 | portList.push_back(midiIn.getPortName(i)); 40 | } 41 | return portList; 42 | } 43 | 44 | // ----------------------------------------------------------------------------- 45 | int ofxRtMidiIn::getNumInPorts() { 46 | return midiIn.getPortCount(); 47 | } 48 | 49 | // ----------------------------------------------------------------------------- 50 | std::string ofxRtMidiIn::getInPortName(unsigned int portNumber) { 51 | // handle RtMidi exceptions 52 | try { 53 | return midiIn.getPortName(portNumber); 54 | } 55 | catch(RtMidiError& err) { 56 | ofLogError("ofxMidiIn") << "couldn't get name for port " << portNumber << ": " << err.what(); 57 | } 58 | return ""; 59 | } 60 | 61 | // ----------------------------------------------------------------------------- 62 | bool ofxRtMidiIn::openPort(unsigned int portNumber) { 63 | // handle RtMidi exceptions 64 | try { 65 | closePort(); 66 | midiIn.setCallback(&_midiMessageCallback, this); 67 | midiIn.openPort(portNumber, "ofxMidi Input "+ofToString(portNumber)); 68 | } 69 | catch(RtMidiError& err) { 70 | ofLogError("ofxMidiIn") << "couldn't get open port " << portNumber << ": " << err.what(); 71 | return false; 72 | } 73 | portNum = portNumber; 74 | portName = midiIn.getPortName(portNumber); 75 | bOpen = true; 76 | ofLogVerbose("ofxMidiIn") << "opened port " << portNumber << " " << portName; 77 | return true; 78 | } 79 | 80 | // ----------------------------------------------------------------------------- 81 | bool ofxRtMidiIn::openPort(std::string deviceName) { 82 | 83 | // iterate through MIDI ports, find requested device 84 | int port = -1; 85 | for(unsigned int i = 0; i < midiIn.getPortCount(); ++i) { 86 | std::string name = midiIn.getPortName(i); 87 | if(name == deviceName) { 88 | port = i; 89 | break; 90 | } 91 | } 92 | 93 | // bail if not found 94 | if(port == -1) { 95 | ofLogError("ofxMidiIn") << "port \"" << deviceName << "\" is not available"; 96 | return false; 97 | } 98 | 99 | return openPort(port); 100 | } 101 | 102 | // ----------------------------------------------------------------------------- 103 | bool ofxRtMidiIn::openVirtualPort(std::string portName) { 104 | // handle RtMidi exceptions 105 | try { 106 | closePort(); 107 | midiIn.setCallback(&_midiMessageCallback, this); 108 | midiIn.openVirtualPort(portName); 109 | } 110 | catch(RtMidiError& err) { 111 | ofLogError("ofxMidiIn") << "couldn't open virtual port \"" << portName << "\": " << err.what(); 112 | return false; 113 | } 114 | this->portName = portName; 115 | bOpen = true; 116 | bVirtual = true; 117 | ofLogVerbose("ofxMidiIn") << "opened virtual port " << portName; 118 | return true; 119 | } 120 | 121 | // ----------------------------------------------------------------------------- 122 | void ofxRtMidiIn::closePort() { 123 | if(bVirtual && bOpen) { 124 | ofLogVerbose("ofxMidiIn") << "closing virtual port " << portName; 125 | } 126 | else if(bOpen && portNum > -1) { 127 | ofLogVerbose("ofxMidiIn") << "closing port " << portNum << " " << portName; 128 | } 129 | midiIn.closePort(); 130 | if(bOpen) { 131 | midiIn.cancelCallback(); 132 | } 133 | portNum = -1; 134 | portName = ""; 135 | bOpen = false; 136 | bVirtual = false; 137 | } 138 | 139 | // ----------------------------------------------------------------------------- 140 | void ofxRtMidiIn::ignoreTypes(bool midiSysex, bool midiTiming, bool midiSense) { 141 | midiIn.ignoreTypes(midiSysex, midiTiming, midiSense); 142 | ofLogVerbose("ofxMidiIn") << "ignore types on " << portName << ": sysex: " << midiSysex 143 | << " timing: " << midiTiming << " sense: " << midiSense; 144 | } 145 | 146 | // PRIVATE 147 | // ----------------------------------------------------------------------------- 148 | void ofxRtMidiIn::_midiMessageCallback(double deltatime, std::vector *message, void *userData) { 149 | ((ofxRtMidiIn *) userData)->manageNewMessage(deltatime * 1000, message); // convert s to ms 150 | } 151 | 152 | // ----------------------------------------------------------------------------- 153 | void ofxRtMidiIn::_midiErrorCallback(RtMidiError::Type type, const std::string &errorText, void *userData) { 154 | ofxRtMidiIn *midiIn = (ofxRtMidiIn *)userData; 155 | ofLogError("ofxMidiIn") << midiIn->getName() << ": " << errorText; 156 | } 157 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiOut.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxPGMidiOut.h" 12 | 13 | #include "ofLog.h" 14 | 15 | #import "ofxPGMidiContext.h" 16 | 17 | // PIMPL wrapper from http://stackoverflow.com/questions/7132755/wrapping-objective-c-in-objective-c-c 18 | struct ofxPGMidiOut::Destination { 19 | PGMidiDestination *d; ///< output destination 20 | }; 21 | 22 | // ----------------------------------------------------------------------------- 23 | ofxPGMidiOut::ofxPGMidiOut(const std::string name, ofxMidiApi api) : ofxBaseMidiOut(name, MIDI_API_COREMIDI) { 24 | 25 | // setup global midi instance 26 | ofxPGMidiContext::setup(); 27 | 28 | // setup destination pointer 29 | destination = new Destination; 30 | destination->d = nil; 31 | } 32 | 33 | // ----------------------------------------------------------------------------- 34 | ofxPGMidiOut::~ofxPGMidiOut() { 35 | closePort(); 36 | delete destination; 37 | } 38 | 39 | // ----------------------------------------------------------------------------- 40 | void ofxPGMidiOut::listOutPorts() { 41 | PGMidi *midi = ofxPGMidiContext::getMidi(); 42 | int count = [midi.destinations count]; 43 | ofLogNotice("ofxMidiOut") << count << " ports available"; 44 | for(NSUInteger i = 0; i < count; ++i) { 45 | PGMidiDestination *dest = [midi.destinations objectAtIndex:i]; 46 | ofLogNotice("ofxMidiOut") << i << ": " << [dest.name UTF8String]; 47 | } 48 | } 49 | 50 | // ----------------------------------------------------------------------------- 51 | std::vector ofxPGMidiOut::getOutPortList() { 52 | PGMidi *midi = ofxPGMidiContext::getMidi(); 53 | std::vector portList; 54 | for(PGMidiDestination *dest in midi.destinations) { 55 | portList.push_back([dest.name UTF8String]); 56 | } 57 | return portList; 58 | } 59 | 60 | // ----------------------------------------------------------------------------- 61 | int ofxPGMidiOut::getNumOutPorts() { 62 | return [ofxPGMidiContext::getMidi().destinations count]; 63 | } 64 | 65 | // ----------------------------------------------------------------------------- 66 | std::string ofxPGMidiOut::getOutPortName(unsigned int portNumber) { 67 | 68 | PGMidi *midi = ofxPGMidiContext::getMidi(); 69 | 70 | // handle OBJ-C exceptions 71 | @try { 72 | PGMidiDestination *dest = [midi.destinations objectAtIndex:portNumber]; 73 | return [dest.name UTF8String]; 74 | } 75 | @catch(NSException *ex) { 76 | ofLogError("ofxMidiOut") << "couldn't get name for port " << portNumber 77 | << " " << [ex.name UTF8String] << ": " << [ex.reason UTF8String]; 78 | } 79 | return ""; 80 | } 81 | 82 | // ----------------------------------------------------------------------------- 83 | bool ofxPGMidiOut::openPort(unsigned int portNumber) { 84 | 85 | PGMidi *midi = ofxPGMidiContext::getMidi(); 86 | PGMidiDestination *dest = nil; 87 | 88 | // handle OBJ-C exceptions 89 | @try { 90 | dest = [midi.destinations objectAtIndex:portNumber]; 91 | } 92 | @catch(NSException *ex) { 93 | ofLogError("ofxMidiOut") << "couldn't open port " << portNumber 94 | << " " << [ex.name UTF8String] << ": " << [ex.reason UTF8String]; 95 | return false; 96 | } 97 | destination->d = dest; 98 | portNum = portNumber; 99 | portName = [dest.name UTF8String]; 100 | bOpen = true; 101 | ofLogVerbose("ofxMidiOut") << "opened port " << portNum << " " << portName; 102 | return true; 103 | } 104 | 105 | // ----------------------------------------------------------------------------- 106 | bool ofxPGMidiOut::openPort(std::string deviceName) { 107 | 108 | PGMidi *midi = ofxPGMidiContext::getMidi(); 109 | 110 | // iterate through MIDI ports, find requested device 111 | int port = -1; 112 | for(NSUInteger i = 0; i < [midi.destinations count]; ++i) { 113 | PGMidiSource *dest = [midi.destinations objectAtIndex:i]; 114 | if([dest.name UTF8String] == deviceName) { 115 | port = i; 116 | break; 117 | } 118 | } 119 | 120 | // bail if not found 121 | if(port == -1) { 122 | ofLogError("ofxMidiOut") << "port \"" << deviceName << "\" is not available"; 123 | return false; 124 | } 125 | 126 | return openPort(port); 127 | } 128 | 129 | // ----------------------------------------------------------------------------- 130 | bool ofxPGMidiOut::openVirtualPort(std::string portName) { 131 | ofLogWarning("ofxMidiOut") << "couldn't open virtual port \"" << portName << "\""; 132 | ofLogWarning("ofxMidiOut") << "virtual ports are currently not supported on iOS"; 133 | return false; 134 | } 135 | 136 | // ----------------------------------------------------------------------------- 137 | void ofxPGMidiOut::closePort() { 138 | 139 | if(destination->d != nil) { 140 | ofLogVerbose("ofxMidiOut") << "closing port " << portNum << " " << portName; 141 | } 142 | @autoreleasepool { 143 | destination->d = nil; 144 | } 145 | 146 | portNum = -1; 147 | portName = ""; 148 | bOpen = false; 149 | bVirtual = false; 150 | } 151 | 152 | // PRIVATE 153 | // ----------------------------------------------------------------------------- 154 | // adapted from PGMidi sendBytes 155 | void ofxPGMidiOut::sendMessage(std::vector &message) { 156 | 157 | Byte packetBuffer[message.size()+100]; 158 | MIDIPacketList *packetList = (MIDIPacketList *)packetBuffer; 159 | MIDIPacket *packet = MIDIPacketListInit(packetList); 160 | 161 | packet = MIDIPacketListAdd(packetList, sizeof(packetBuffer), packet, 0, message.size(), &message[0]); 162 | 163 | [destination->d sendPacketList:packetList]; 164 | } 165 | -------------------------------------------------------------------------------- /src/ofxMidiIn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofxBaseMidi.h" 14 | 15 | // choose the MIDI backend 16 | #ifdef TARGET_OF_IPHONE 17 | #include "ios/ofxPGMidiIn.h" 18 | #define OFX_MIDI_IN_TYPE ofxPGMidiIn 19 | #else // OSX, Win, Linux 20 | #include "desktop/ofxRtMidiIn.h" 21 | #define OFX_MIDI_IN_TYPE ofxRtMidiIn 22 | #endif 23 | 24 | /// 25 | /// a MIDI input port 26 | /// 27 | /// create multiple instances to connect to multiple ports 28 | /// 29 | /// message passing is either direct on the MIDI thread or queued via thread channel: 30 | /// * direct: add an ofxMidiListener instance, messages sent to newMidiMessage() 31 | /// * queued: manually handle messages using hasWaitingMessages()/getNextMessage() 32 | /// 33 | /// queued message passing is enabled by default and is disabled when a listener 34 | /// is added 35 | /// 36 | /// warning: *do not* create static instances as this will lead to a crash on 37 | /// Linux, instead create a static std::shared_pttr and initialize it later: 38 | /// 39 | /// in .h: 40 | /// class MyClass { 41 | /// 42 | /// ... 43 | /// 44 | /// static std::shared_ptr s_midiIn; 45 | /// } 46 | /// 47 | /// in .cpp: 48 | /// std::shared_ptr MyClass::s_midiIn; 49 | /// 50 | /// ... 51 | /// 52 | /// // initialize somewhere else 53 | /// void MyClass::setup() { 54 | /// if(s_midiIn == nullptr) { 55 | /// s_midiIn = std::shared_ptr(new ofxMidiIn("ofxMidi Client")); 56 | /// } 57 | /// } 58 | /// 59 | class ofxMidiIn { 60 | 61 | public: 62 | 63 | /// set the input client name (optional) and api (optional) 64 | ofxMidiIn(const std::string name="ofxMidiIn Client", ofxMidiApi api=MIDI_API_DEFAULT); 65 | virtual ~ofxMidiIn(); 66 | 67 | /// \section Global Port Info 68 | 69 | /// print the connected input ports 70 | void listInPorts(); 71 | 72 | /// get a list of input port names 73 | /// 74 | /// the vector index corresponds with the name's port number 75 | /// 76 | /// note: this order may change when new devices are added/removed 77 | /// from the system 78 | /// 79 | std::vector getInPortList(); 80 | 81 | /// get the number of input ports 82 | int getNumInPorts(); 83 | 84 | /// get the name of an input port by it's number 85 | /// 86 | /// returns "" if number is invalid 87 | /// 88 | std::string getInPortName(unsigned int portNumber); 89 | 90 | /// \section Connection 91 | 92 | /// connect to an input port 93 | /// 94 | /// setting port = 0 will open the first available 95 | /// 96 | bool openPort(unsigned int portNumber=0); 97 | 98 | /// connect to an input port by device name 99 | bool openPort(std::string deviceName); 100 | 101 | /// create and connect to a virtual output port (macOS and Linux ALSA only) 102 | /// 103 | /// allows for connections between software 104 | /// 105 | /// note: a connected virtual port has a portNum = -1 106 | /// note: an open virtual port ofxMidiIn object cannot see it's virtual 107 | /// own virtual port when listing ports 108 | /// 109 | bool openVirtualPort(std::string portName="ofxMidi Virtual Input"); 110 | 111 | /// close the port connection 112 | void closePort(); 113 | 114 | /// get the port number if connected 115 | /// 116 | /// returns -1 if not connected or this is a virtual port 117 | /// 118 | int getPort(); 119 | 120 | /// get the connected input port name 121 | /// 122 | /// returns "" if not connected 123 | /// 124 | std::string getName(); 125 | 126 | /// returns true if connected 127 | bool isOpen(); 128 | 129 | /// returns true if this is a virtual port 130 | bool isVirtual(); 131 | 132 | /// returns true if queued message passing is enabled 133 | bool isQueued(); 134 | 135 | /// \section Receiving 136 | 137 | /// specify if certain message types should be ignored 138 | /// 139 | /// sysex messages are ignored by default 140 | /// 141 | /// timing and active sensing messages have high data rates 142 | /// and are ignored by default 143 | /// 144 | void ignoreTypes(bool midiSysex=true, bool midiTiming=true, bool midiSense=true); 145 | 146 | /// add listener for incoming MIDI events 147 | /// 148 | /// listeners receive from *all* incoming MIDI channels, 149 | /// listeners receive on the midi thread 150 | /// 151 | /// note: adding a listener disables queued message passing 152 | /// 153 | void addListener(ofxMidiListener *listener); 154 | 155 | /// remove listener for incoming MIDI events 156 | /// 157 | /// listeners receive from *all* incoming MIDI channels 158 | /// 159 | /// note: queued message passing re-enabled on last listener removal 160 | /// 161 | void removeListener(ofxMidiListener *listener); 162 | 163 | /// returns true if there are any messages waiting in the queue 164 | /// 165 | /// note: queued message passing disabled if any listeners added 166 | /// 167 | /// returns true if there are any messages waiting 168 | bool hasWaitingMessages() const; 169 | 170 | /// remove a message from the queue and copy it's data into message 171 | /// 172 | /// note: queued message passing disabled if any listeners added 173 | /// 174 | /// returns false if there are no waiting messages, otherwise true 175 | bool getNextMessage(ofxMidiMessage &message); 176 | 177 | /// set to verbose = true to print received byte info 178 | /// 179 | /// warning: this will impact performance with large numbers 180 | /// of MIDI messages 181 | /// 182 | void setVerbose(bool verbose); 183 | 184 | private: 185 | 186 | std::shared_ptr midiIn; 187 | }; 188 | -------------------------------------------------------------------------------- /src/ofxMidiOut.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #pragma once 12 | 13 | #include "ofxBaseMidi.h" 14 | 15 | // choose the MIDI backend 16 | #ifdef TARGET_OF_IPHONE 17 | #include "ios/ofxPGMidiOut.h" 18 | #define OFX_MIDI_OUT_TYPE ofxPGMidiOut 19 | #else // OSX, Win, Linux 20 | #include "desktop/ofxRtMidiOut.h" 21 | #define OFX_MIDI_OUT_TYPE ofxRtMidiOut 22 | #endif 23 | 24 | /// 25 | /// a MIDI output port 26 | /// 27 | /// create multiple instances to connect to multiple ports 28 | /// 29 | /// *do not* create static instances as this will lead to a crash on Linux, 30 | /// instead create a static std::shared_ptr and initialize it later: 31 | /// 32 | /// in .h: 33 | /// class MyClass { 34 | /// 35 | /// ... 36 | /// 37 | /// static std::shared_ptr s_midiOut; 38 | /// } 39 | /// 40 | /// in .cpp: 41 | /// std::shared_ptr MyClass::s_midiOut; 42 | /// 43 | /// ... 44 | /// 45 | /// // initialize somewhere else 46 | /// void MyClass::setup() { 47 | /// if(s_midiOut == NULL) { 48 | /// s_midiOut = std::shared_ptr(new ofxMidiOut("ofxMidi Client")); 49 | /// } 50 | /// } 51 | /// 52 | class ofxMidiOut { 53 | 54 | public: 55 | 56 | /// set the output client name (optional) 57 | ofxMidiOut(const std::string name="ofxMidiOut Client", ofxMidiApi api=MIDI_API_DEFAULT); 58 | virtual ~ofxMidiOut(); 59 | 60 | /// \section Global Port Info 61 | 62 | /// print the connected output ports 63 | void listOutPorts(); 64 | 65 | /// get a list of output port names 66 | /// 67 | /// the vector index corresponds with the name's port number 68 | /// 69 | /// note: this order may change when new devices are added/removed 70 | /// from the system 71 | /// 72 | std::vector getOutPortList(); 73 | 74 | /// get the number of output ports 75 | int getNumOutPorts(); 76 | 77 | /// get the name of an output port by it's number 78 | /// 79 | /// returns "" if number is invalid 80 | /// 81 | std::string getOutPortName(unsigned int portNumber); 82 | 83 | /// \section Connection 84 | 85 | /// connect to an output port 86 | /// 87 | /// setting port = 0 will open the first available 88 | /// 89 | bool openPort(unsigned int portNumber=0); 90 | bool openPort(std::string deviceName); 91 | 92 | /// create and connect to a virtual output port (macOS and Linux ALSA only) 93 | /// 94 | /// allows for connections between software 95 | /// 96 | /// note: a connected virtual port has a portNum = -1 97 | /// note: an open virtual port ofxMidiOut object cannot see it's virtual 98 | /// own virtual port when listing ports 99 | /// 100 | bool openVirtualPort(std::string portName="ofxMidi Virtual Output"); 101 | 102 | /// close the port connection 103 | void closePort(); 104 | 105 | /// get the port number if connected 106 | /// 107 | /// returns -1 if not connected or this is a virtual port 108 | /// 109 | int getPort(); 110 | 111 | /// get the connected output port name 112 | /// 113 | /// returns "" if not connected 114 | /// 115 | std::string getName(); 116 | 117 | /// returns true if connected 118 | bool isOpen(); 119 | 120 | /// returns true if this is a virtual port 121 | bool isVirtual(); 122 | 123 | /// \section Sending 124 | 125 | /// MIDI events 126 | /// 127 | /// number ranges: 128 | /// channel 1 - 16 129 | /// pitch 0 - 127 130 | /// velocity 0 - 127 131 | /// control value 0 - 127 132 | /// program value 0 - 127 133 | /// bend value 0 - 16383 134 | /// touch value 0 - 127 135 | /// 136 | /// note: 137 | /// - a noteon with vel = 0 is equivalent to a noteoff 138 | /// - send velocity = 64 if not using velocity values 139 | /// - most synths don't use the velocity value in a noteoff 140 | /// - the lsb & msb for raw pitch bend bytes are 7 bit 141 | /// 142 | /// references: 143 | /// http://www.srm.com/qtma/davidsmidispec.html 144 | /// 145 | void sendNoteOn(int channel, int pitch, int velocity=64); 146 | void sendNoteOff(int channel, int pitch, int velocity=64); 147 | void sendControlChange(int channel, int control, int value); 148 | void sendProgramChange(int channel, int value); 149 | void sendPitchBend(int channel, int value); 150 | void sendPitchBend(int channel, unsigned char lsb, unsigned char msb); 151 | void sendAftertouch(int channel, int value); 152 | void sendPolyAftertouch(int channel, int pitch, int value); 153 | 154 | /// raw MIDI bytes 155 | /// 156 | void sendMidiByte(unsigned char byte); 157 | void sendMidiBytes(std::vector &bytes); 158 | 159 | /// \section Sending Stream Interface 160 | 161 | /// MIDI events 162 | /// 163 | /// midiOut << NoteOn(1, 64, 64) << NoteOff(1, 64); 164 | /// midiOut << ControlChange(1, 100, 64) << ProgramChange(1, 100); 165 | /// midiOut << << PitchBend(1, 2000); 166 | /// midiOut << Aftertouch(1, 127) << PolyAftertouch(1, 64, 127); 167 | /// 168 | ofxMidiOut& operator<<(const NoteOn &var); 169 | ofxMidiOut& operator<<(const NoteOff &var); 170 | ofxMidiOut& operator<<(const ControlChange &var); 171 | ofxMidiOut& operator<<(const ProgramChange &var); 172 | ofxMidiOut& operator<<(const PitchBend &var); 173 | ofxMidiOut& operator<<(const Aftertouch &var); 174 | ofxMidiOut& operator<<(const PolyAftertouch &var); 175 | 176 | /// compound raw MIDI byte stream 177 | /// 178 | /// midiOut << StartMidi() << 0x90 << 0x3C << 0x40 << FinishMidi(); 179 | /// 180 | /// build a raw MIDI byte message and send it with FinishMidi() 181 | /// 182 | /// note: other MIDI messages (except raw MIDI bytes) cannot be sent while 183 | /// the stream is in progress 184 | /// 185 | /// warning: this is not thread safe, use sendMidiBytes() in a shared context 186 | // 187 | ofxMidiOut& operator<<(const StartMidi &var); 188 | ofxMidiOut& operator<<(const FinishMidi &var); 189 | ofxMidiOut& operator<<(const unsigned char var); 190 | 191 | private: 192 | 193 | std::shared_ptr midiOut; 194 | }; 195 | -------------------------------------------------------------------------------- /midiInputExample/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofApp.h" 12 | 13 | //-------------------------------------------------------------- 14 | void ofApp::setup() { 15 | ofSetVerticalSync(true); 16 | ofBackground(255, 255, 255); 17 | ofSetLogLevel(OF_LOG_VERBOSE); 18 | 19 | // print input ports to console 20 | midiIn.listInPorts(); 21 | 22 | // open port by number (you may need to change this) 23 | midiIn.openPort(1); 24 | //midiIn.openPort("IAC Pure Data In"); // by name 25 | //midiIn.openVirtualPort("ofxMidiIn Input"); // open a virtual port 26 | 27 | // don't ignore sysex, timing, & active sense messages, 28 | // these are ignored by default 29 | midiIn.ignoreTypes(false, false, false); 30 | 31 | // add ofApp as a listener and enable direct message handling 32 | // comment this to use queued message handling 33 | midiIn.addListener(this); 34 | 35 | // print received messages to the console 36 | midiIn.setVerbose(true); 37 | } 38 | 39 | //-------------------------------------------------------------- 40 | void ofApp::update() { 41 | 42 | /// queued message handling 43 | if(midiIn.hasWaitingMessages()) { 44 | ofxMidiMessage message; 45 | 46 | // add the latest message to the message queue 47 | while(midiIn.getNextMessage(message)) { 48 | midiMessages.push_back(message); 49 | } 50 | 51 | // remove any old messages if we have too many 52 | while(midiMessages.size() > maxMessages) { 53 | midiMessages.erase(midiMessages.begin()); 54 | } 55 | } 56 | } 57 | 58 | //-------------------------------------------------------------- 59 | void ofApp::draw() { 60 | 61 | for(unsigned int i = 0; i < midiMessages.size(); ++i) { 62 | 63 | ofxMidiMessage &message = midiMessages[i]; 64 | int x = 10; 65 | int y = i*40 + 40; 66 | 67 | // draw the last received message contents to the screen, 68 | // this doesn't print all the data from every status type 69 | // but you should get the general idea 70 | stringstream text; 71 | text << ofxMidiMessage::getStatusString(message.status); 72 | while(text.str().length() < 16) { // pad status width 73 | text << " "; 74 | } 75 | 76 | ofSetColor(127); 77 | if(message.status < MIDI_SYSEX) { 78 | text << "chan: " << message.channel; 79 | if(message.status == MIDI_NOTE_ON || 80 | message.status == MIDI_NOTE_OFF) { 81 | text << "\tpitch: " << message.pitch; 82 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 83 | ofMap(message.pitch, 0, 127, 0, ofGetWidth()*0.2), 10); 84 | text << "\tvel: " << message.velocity; 85 | ofDrawRectangle(x + (ofGetWidth()*0.2 * 2), y + 12, 86 | ofMap(message.velocity, 0, 127, 0, ofGetWidth()*0.2), 10); 87 | } 88 | if(message.status == MIDI_CONTROL_CHANGE) { 89 | text << "\tctl: " << message.control; 90 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 91 | ofMap(message.control, 0, 127, 0, ofGetWidth()*0.2), 10); 92 | text << "\tval: " << message.value; 93 | ofDrawRectangle(x + ofGetWidth()*0.2 * 2, y + 12, 94 | ofMap(message.value, 0, 127, 0, ofGetWidth()*0.2), 10); 95 | } 96 | else if(message.status == MIDI_PROGRAM_CHANGE) { 97 | text << "\tpgm: " << message.value; 98 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 99 | ofMap(message.value, 0, 127, 0, ofGetWidth()*0.2), 10); 100 | } 101 | else if(message.status == MIDI_PITCH_BEND) { 102 | text << "\tval: " << message.value; 103 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 104 | ofMap(message.value, 0, MIDI_MAX_BEND, 0, ofGetWidth()*0.2), 10); 105 | } 106 | else if(message.status == MIDI_AFTERTOUCH) { 107 | text << "\tval: " << message.value; 108 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 109 | ofMap(message.value, 0, 127, 0, ofGetWidth()*0.2), 10); 110 | } 111 | else if(message.status == MIDI_POLY_AFTERTOUCH) { 112 | text << "\tpitch: " << message.pitch; 113 | ofDrawRectangle(x + ofGetWidth()*0.2, y + 12, 114 | ofMap(message.pitch, 0, 127, 0, ofGetWidth()*0.2), 10); 115 | text << "\tval: " << message.value; 116 | ofDrawRectangle(x + ofGetWidth()*0.2 * 2, y + 12, 117 | ofMap(message.value, 0, 127, 0, ofGetWidth()*0.2), 10); 118 | } 119 | text << " "; // pad for delta print 120 | } 121 | else { 122 | text << message.bytes.size() << " bytes "; 123 | } 124 | 125 | text << "delta: " << message.deltatime; 126 | ofSetColor(0); 127 | ofDrawBitmapString(text.str(), x, y); 128 | text.str(""); // clear 129 | } 130 | } 131 | 132 | //-------------------------------------------------------------- 133 | void ofApp::exit() { 134 | 135 | // clean up 136 | midiIn.closePort(); 137 | midiIn.removeListener(this); 138 | } 139 | 140 | //-------------------------------------------------------------- 141 | /// direct message handling 142 | /// note: this is called on the MIDI thread, so copy message contents 143 | void ofApp::newMidiMessage(ofxMidiMessage &message) { 144 | 145 | // add the latest message to the message queue 146 | midiMessages.push_back(message); 147 | 148 | // remove any old messages if we have too many 149 | while(midiMessages.size() > maxMessages) { 150 | midiMessages.erase(midiMessages.begin()); 151 | } 152 | } 153 | 154 | //-------------------------------------------------------------- 155 | void ofApp::keyPressed(int key) { 156 | switch(key) { 157 | case '?': 158 | midiIn.listInPorts(); 159 | break; 160 | } 161 | } 162 | 163 | //-------------------------------------------------------------- 164 | void ofApp::keyReleased(int key) { 165 | } 166 | 167 | //-------------------------------------------------------------- 168 | void ofApp::mouseMoved(int x, int y) { 169 | } 170 | 171 | //-------------------------------------------------------------- 172 | void ofApp::mouseDragged(int x, int y, int button) { 173 | } 174 | 175 | //-------------------------------------------------------------- 176 | void ofApp::mousePressed(int x, int y, int button) { 177 | } 178 | 179 | //-------------------------------------------------------------- 180 | void ofApp::mouseReleased() { 181 | } 182 | -------------------------------------------------------------------------------- /src/ios/ofxPGMidiIn.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxPGMidiIn.h" 12 | 13 | #include "ofLog.h" 14 | 15 | #import "ofxPGMidiContext.h" 16 | #import "ofxPGMidiSourceDelegate.h" 17 | 18 | // PIMPL wrapper from http://stackoverflow.com/questions/7132755/wrapping-objective-c-in-objective-c-c 19 | struct ofxPGMidiIn::InputDelegate { 20 | ofxPGMidiSourceDelegate *d; ///< Obj-C input delegate 21 | }; 22 | 23 | // ----------------------------------------------------------------------------- 24 | ofxPGMidiIn::ofxPGMidiIn(const std::string name, ofxMidiApi api) : ofxBaseMidiIn(name, MIDI_API_COREMIDI) { 25 | 26 | // setup global midi instance 27 | ofxPGMidiContext::setup(); 28 | 29 | // setup Obj-C interface to PGMidi 30 | @autoreleasepool { 31 | inputDelegate = new InputDelegate; 32 | inputDelegate->d = [[ofxPGMidiSourceDelegate alloc] init]; 33 | [inputDelegate->d setInputPtr:(void *)this]; 34 | } 35 | } 36 | 37 | // ----------------------------------------------------------------------------- 38 | ofxPGMidiIn::~ofxPGMidiIn() { 39 | @autoreleasepool { 40 | inputDelegate->d = nil; 41 | } 42 | delete inputDelegate; 43 | } 44 | 45 | // ----------------------------------------------------------------------------- 46 | void ofxPGMidiIn::listInPorts() { 47 | PGMidi *midi = ofxPGMidiContext::getMidi(); 48 | int count = [midi.sources count]; 49 | ofLogNotice("ofxMidiIn") << count << " ports available"; 50 | for(NSUInteger i = 0; i < count; ++i) { 51 | PGMidiSource *source = [midi.sources objectAtIndex:i]; 52 | ofLogNotice("ofxMidiIn") << i << ": " << [source.name UTF8String]; 53 | } 54 | } 55 | 56 | // ----------------------------------------------------------------------------- 57 | std::vector ofxPGMidiIn::getInPortList() { 58 | PGMidi *midi = ofxPGMidiContext::getMidi(); 59 | std::vector portList; 60 | for(PGMidiSource *source in midi.sources) { 61 | portList.push_back([source.name UTF8String]); 62 | } 63 | return portList; 64 | } 65 | 66 | // ----------------------------------------------------------------------------- 67 | int ofxPGMidiIn::getNumInPorts() { 68 | return [ofxPGMidiContext::getMidi().sources count]; 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | std::string ofxPGMidiIn::getInPortName(unsigned int portNumber) { 73 | 74 | PGMidi *midi = ofxPGMidiContext::getMidi(); 75 | 76 | // handle OBJ-C exceptions 77 | @try { 78 | PGMidiSource *source = [midi.sources objectAtIndex:portNumber]; 79 | return [source.name UTF8String]; 80 | } 81 | @catch(NSException *ex) { 82 | ofLogError("ofxMidiIn") << "couldn't get name for port " << portNumber 83 | << " " << [ex.name UTF8String] << ": " << [ex.reason UTF8String]; 84 | } 85 | return ""; 86 | } 87 | 88 | // ----------------------------------------------------------------------------- 89 | bool ofxPGMidiIn::openPort(unsigned int portNumber) { 90 | 91 | PGMidi *midi = ofxPGMidiContext::getMidi(); 92 | PGMidiSource *source = nil; 93 | 94 | // handle OBJ-C exceptions 95 | @try { 96 | source = [midi.sources objectAtIndex:portNumber]; 97 | } 98 | @catch(NSException *ex) { 99 | ofLogError("ofxMidiIn") << "couldn't open port " << portNumber 100 | << " " << [ex.name UTF8String] << ": " << [ex.reason UTF8String]; 101 | return false; 102 | } 103 | [source addDelegate:inputDelegate->d]; 104 | portNum = portNumber; 105 | portName = [source.name UTF8String]; 106 | bOpen = true; 107 | ofLogVerbose("ofxMidiIn") << "opened port " << portNum << " " << portName; 108 | return true; 109 | } 110 | 111 | // ----------------------------------------------------------------------------- 112 | bool ofxPGMidiIn::openPort(std::string deviceName) { 113 | 114 | PGMidi *midi = ofxPGMidiContext::getMidi(); 115 | 116 | // iterate through MIDI ports, find requested device 117 | int port = -1; 118 | for(NSUInteger i = 0; i < [midi.sources count]; ++i) { 119 | PGMidiSource *source = [midi.sources objectAtIndex:i]; 120 | if([source.name UTF8String] == deviceName) { 121 | port = i; 122 | break; 123 | } 124 | } 125 | 126 | // bail if not found 127 | if(port == -1) { 128 | ofLogError("ofxMidiIn") << "port \"" << deviceName << "\" is not available";; 129 | return false; 130 | } 131 | 132 | return openPort(port); 133 | } 134 | 135 | // ----------------------------------------------------------------------------- 136 | bool ofxPGMidiIn::openVirtualPort(std::string portName) { 137 | ofLogWarning("ofxMidiIn") << "couldn't open virtual port \"" << portName << "\""; 138 | ofLogWarning("ofxMidiIn") << "virtual ports are currently not supported on iOS"; 139 | return false; 140 | } 141 | 142 | // ----------------------------------------------------------------------------- 143 | void ofxPGMidiIn::closePort() { 144 | 145 | if(bOpen) { 146 | ofLogVerbose("ofxMidiIn") << "closing port " << portNum << " " << portName; 147 | 148 | // sometimes the source may already have been removed in PGMidi, so make 149 | // sure we have a valid index otherwise the app will crash 150 | PGMidi *midi = ofxPGMidiContext::getMidi(); 151 | if(portNum < midi.sources.count) { 152 | PGMidiSource *source = [midi.sources objectAtIndex:portNum]; 153 | [source removeDelegate:inputDelegate->d]; 154 | } 155 | } 156 | 157 | portNum = -1; 158 | portName = ""; 159 | bOpen = false; 160 | bVirtual = false; 161 | } 162 | 163 | // ----------------------------------------------------------------------------- 164 | void ofxPGMidiIn::ignoreTypes(bool midiSysex, bool midiTiming, bool midiSense) { 165 | 166 | inputDelegate->d.bIgnoreSysex = midiSysex; 167 | inputDelegate->d.bIgnoreTiming = midiTiming; 168 | inputDelegate->d.bIgnoreSense = midiSense; 169 | 170 | ofLogVerbose("ofxMidiIn") << "ignore types on " << portName << ": sysex: " << midiSysex 171 | << " timing: " << midiTiming << " sense: " << midiSense; 172 | } 173 | 174 | // ----------------------------------------------------------------------------- 175 | void ofxPGMidiIn::messageReceived(double deltatime, std::vector *message) { 176 | manageNewMessage(deltatime, message); 177 | } 178 | 179 | // ----------------------------------------------------------------------------- 180 | void ofxPGMidiIn::setConnectionListener(ofxMidiConnectionListener * listener) { 181 | ofxPGMidiContext::setConnectionListener(listener); 182 | } 183 | 184 | // ----------------------------------------------------------------------------- 185 | void ofxPGMidiIn::clearConnectionListener() { 186 | ofxPGMidiContext::clearConnectionListener(); 187 | } 188 | 189 | // ----------------------------------------------------------------------------- 190 | void ofxPGMidiIn::enableNetworking() { 191 | ofxPGMidiContext::enableNetwork(); 192 | } 193 | -------------------------------------------------------------------------------- /midiTimingExample/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofApp.h" 12 | 13 | //-------------------------------------------------------------- 14 | void ofApp::setup() { 15 | ofSetVerticalSync(true); 16 | ofBackground(255); 17 | 18 | ofSetLogLevel(OF_LOG_VERBOSE); 19 | ofSetLogLevel("ofxMidiClock", OF_LOG_NOTICE); 20 | ofSetLogLevel("ofxMidiTimecode", OF_LOG_NOTICE); 21 | 22 | // open port by number (you may need to change this) 23 | midiIn.openPort(0); 24 | midiIn.ignoreTypes(false, // sysex <-- don't ignore timecode messages! 25 | false, // timing <-- don't ignore clock messages! 26 | true); // sensing 27 | 28 | // add ofApp as a listener 29 | midiIn.addListener(this); 30 | } 31 | 32 | //-------------------------------------------------------------- 33 | void ofApp::update() { 34 | 35 | // MIDI TIMECODE 36 | 37 | // if we haven't received a quarter frame message from the timecode master, 38 | // assume playback has stopped 39 | if(timecodeRunning && ofGetElapsedTimeMillis() - timecodeTimestamp > 100) { 40 | ofLog() << "timecode stopped"; 41 | timecodeRunning = false; 42 | } 43 | } 44 | 45 | //-------------------------------------------------------------- 46 | void ofApp::draw() { 47 | ofSetColor(0); 48 | 49 | // MIDI CLOCK 50 | 51 | ofDrawBitmapString((clockRunning ? "MIDI clock: running" : "MIDI clock: stopped"), 20, 30); 52 | ofDrawBitmapString("pos MIDI beats: "+ofToString(beats), 20, 58); 53 | ofDrawBitmapString("pos seconds: "+ofToString(seconds), 20, 74); 54 | ofDrawBitmapString("bpm: "+ofToString(round(bpm)), 20, 90); 55 | 56 | // a MIDI beat is a 16th note, so do a little math to convert to a time signature: 57 | // 4/4 -> 4 notes per bar & quarter note = 1 beat, add 1 to count from 1 instead of 0 58 | int quarters = beats / 4; // convert total # beats to # quarters 59 | int bars = (quarters / 4) + 1; // compute # of bars 60 | int beatsInBar = (quarters % 4) + 1; // compute remainder as # notes within the current bar 61 | ofDrawBitmapString("4/4 bars: "+ofToString(bars)+" beat: "+ofToString(beatsInBar), 20, 106); 62 | 63 | // MIDI TIMECODE 64 | 65 | ofDrawBitmapString((timecodeRunning ? "MIDI timecode: running" : "MIDI timecode: stopped"), 20, 160); 66 | ofDrawBitmapString("pos time: "+frame.toString(), 20, 188); 67 | ofDrawBitmapString("pos seconds: "+ofToString(frame.toSeconds()), 20, 204); 68 | ofDrawBitmapString("framerate: "+ofToString(ofxMidiTimecode::rateToFps(frame.rate)), 20, 220); 69 | 70 | // if you are working with a timecode master which uses an offset 71 | // simply add or subtract the time, ie. Logic defaults to +1 hour 72 | //ofDrawBitmapString("pos seconds (no offset): "+ofToString(frame.toSeconds() - 3600), 20, 236); 73 | } 74 | 75 | //-------------------------------------------------------------- 76 | void ofApp::exit() { 77 | 78 | // clean up 79 | midiIn.closePort(); 80 | midiIn.removeListener(this); 81 | } 82 | 83 | //-------------------------------------------------------------- 84 | void ofApp::keyPressed(int key) { 85 | 86 | // toggle verbose printing 87 | switch(key) { 88 | case 'v': 89 | verbose = !verbose; 90 | if(verbose) { 91 | ofSetLogLevel("ofxMidiClock", OF_LOG_VERBOSE); 92 | ofSetLogLevel("ofxMidiTimecode", OF_LOG_VERBOSE); 93 | } 94 | else { 95 | ofSetLogLevel("ofxMidiClock", OF_LOG_NOTICE); 96 | ofSetLogLevel("ofxMidiTimecode", OF_LOG_NOTICE); 97 | } 98 | ofLog() << (verbose ? "on" : "off"); 99 | } 100 | } 101 | 102 | //-------------------------------------------------------------- 103 | void ofApp::keyReleased(int key) { 104 | 105 | } 106 | 107 | //-------------------------------------------------------------- 108 | void ofApp::mouseMoved(int x, int y) { 109 | 110 | } 111 | 112 | //-------------------------------------------------------------- 113 | void ofApp::mouseDragged(int x, int y, int button) { 114 | 115 | } 116 | 117 | //-------------------------------------------------------------- 118 | void ofApp::mousePressed(int x, int y, int button) { 119 | 120 | } 121 | 122 | //-------------------------------------------------------------- 123 | void ofApp::mouseReleased(int x, int y, int button) { 124 | 125 | } 126 | 127 | //-------------------------------------------------------------- 128 | void ofApp::mouseEntered(int x, int y) { 129 | 130 | } 131 | 132 | //-------------------------------------------------------------- 133 | void ofApp::mouseExited(int x, int y) { 134 | 135 | } 136 | 137 | //-------------------------------------------------------------- 138 | void ofApp::windowResized(int w, int h) { 139 | 140 | } 141 | 142 | //-------------------------------------------------------------- 143 | void ofApp::gotMessage(ofMessage message) { 144 | 145 | } 146 | 147 | //-------------------------------------------------------------- 148 | void ofApp::dragEvent(ofDragInfo dragInfo) { 149 | 150 | } 151 | 152 | //-------------------------------------------------------------- 153 | void ofApp::newMidiMessage(ofxMidiMessage &message) { 154 | 155 | // MIDI CLOCK 156 | 157 | // update the clock length and song pos in beats 158 | if(clock.update(message.bytes)) { 159 | // we got a new song pos 160 | beats = clock.getBeats(); 161 | seconds = clock.getSeconds(); 162 | } 163 | 164 | // compute the seconds and bpm 165 | switch(message.status) { 166 | 167 | // compute seconds and bpm live, you may or may not always need this 168 | // which is why it is not integrated into the ofxMidiClock parser class 169 | case MIDI_TIME_CLOCK: 170 | seconds = clock.getSeconds(); 171 | bpm += (clock.getBpm() - bpm) / 5; // average the last 5 bpm values 172 | // no break here so the next case statement is checked, 173 | // this way we can set clockRunning if we've missed a MIDI_START 174 | // ie. master was running before we started this example 175 | 176 | // transport control 177 | case MIDI_START: case MIDI_CONTINUE: 178 | if(!clockRunning) { 179 | clockRunning = true; 180 | ofLog() << "clock started"; 181 | } 182 | break; 183 | case MIDI_STOP: 184 | if(clockRunning) { 185 | clockRunning = false; 186 | ofLog() << "clock stopped"; 187 | } 188 | break; 189 | 190 | default: 191 | break; 192 | } 193 | 194 | // MIDI TIMECODE 195 | 196 | // update the timecode pos 197 | if(timecode.update(message.bytes)) { 198 | 199 | // we got a new frame pos 200 | frame = timecode.getFrame(); 201 | 202 | // if the last message was a timecode quarter frame message, 203 | // then assume the timecode master has started playback 204 | if(message.status == MIDI_TIME_CODE) { 205 | if(!timecodeRunning) { 206 | timecodeRunning = true; 207 | ofLog() << "timecode started"; 208 | } 209 | timecodeTimestamp = ofGetElapsedTimeMillis(); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/ofxMidiOut.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxMidiOut.h" 12 | 13 | // ----------------------------------------------------------------------------- 14 | ofxMidiOut::ofxMidiOut(const std::string name, ofxMidiApi api) { 15 | midiOut = std::shared_ptr(new OFX_MIDI_OUT_TYPE(name, api)); 16 | } 17 | 18 | // ----------------------------------------------------------------------------- 19 | ofxMidiOut::~ofxMidiOut() {} 20 | 21 | // ----------------------------------------------------------------------------- 22 | void ofxMidiOut::listOutPorts() { 23 | midiOut->listOutPorts(); 24 | } 25 | 26 | // ----------------------------------------------------------------------------- 27 | std::vector ofxMidiOut::getOutPortList() { 28 | return midiOut->getOutPortList(); 29 | } 30 | 31 | // ----------------------------------------------------------------------------- 32 | int ofxMidiOut::getNumOutPorts() { 33 | return midiOut->getNumOutPorts(); 34 | } 35 | 36 | // ----------------------------------------------------------------------------- 37 | std::string ofxMidiOut::getOutPortName(unsigned int portNumber) { 38 | return midiOut->getOutPortName(portNumber); 39 | } 40 | 41 | // ----------------------------------------------------------------------------- 42 | bool ofxMidiOut::openPort(unsigned int portNumber) { 43 | return midiOut->openPort(portNumber); 44 | } 45 | 46 | // ----------------------------------------------------------------------------- 47 | bool ofxMidiOut::openPort(std::string deviceName) { 48 | return midiOut->openPort(deviceName); 49 | } 50 | 51 | // ----------------------------------------------------------------------------- 52 | bool ofxMidiOut::openVirtualPort(std::string portName) { 53 | return midiOut->openVirtualPort(portName); 54 | } 55 | 56 | // ----------------------------------------------------------------------------- 57 | void ofxMidiOut::closePort() { 58 | midiOut->closePort(); 59 | } 60 | 61 | // ----------------------------------------------------------------------------- 62 | int ofxMidiOut::getPort() { 63 | return midiOut->getPort(); 64 | } 65 | 66 | // ----------------------------------------------------------------------------- 67 | std::string ofxMidiOut::getName() { 68 | return midiOut->getName(); 69 | } 70 | 71 | // ----------------------------------------------------------------------------- 72 | bool ofxMidiOut::isOpen() { 73 | return midiOut->isOpen(); 74 | } 75 | 76 | // ----------------------------------------------------------------------------- 77 | bool ofxMidiOut::isVirtual() { 78 | return midiOut->isVirtual(); 79 | } 80 | 81 | // ----------------------------------------------------------------------------- 82 | void ofxMidiOut::sendNoteOn(int channel, int pitch, int velocity) { 83 | midiOut->sendNoteOn(channel, pitch, velocity); 84 | } 85 | 86 | // ----------------------------------------------------------------------------- 87 | void ofxMidiOut::sendNoteOff(int channel, int pitch, int velocity) { 88 | midiOut->sendNoteOff(channel, pitch, velocity); 89 | } 90 | 91 | // ----------------------------------------------------------------------------- 92 | void ofxMidiOut::sendControlChange(int channel, int control, int value) { 93 | midiOut->sendControlChange(channel, control, value); 94 | } 95 | 96 | // ----------------------------------------------------------------------------- 97 | void ofxMidiOut::sendProgramChange(int channel, int value) { 98 | midiOut->sendProgramChange(channel, value); 99 | } 100 | 101 | // ----------------------------------------------------------------------------- 102 | void ofxMidiOut::sendPitchBend(int channel, int value) { 103 | midiOut->sendPitchBend(channel, value); 104 | } 105 | 106 | void ofxMidiOut::sendPitchBend(int channel, unsigned char lsb, unsigned char msb) { 107 | midiOut->sendPitchBend(channel, lsb, msb); 108 | } 109 | 110 | // ----------------------------------------------------------------------------- 111 | void ofxMidiOut::sendAftertouch(int channel, int value) { 112 | midiOut->sendAftertouch(channel, value); 113 | } 114 | 115 | // ----------------------------------------------------------------------------- 116 | void ofxMidiOut::sendPolyAftertouch(int channel, int pitch, int value) { 117 | midiOut->sendPolyAftertouch(channel, pitch, value); 118 | } 119 | 120 | // ----------------------------------------------------------------------------- 121 | void ofxMidiOut::sendMidiByte(unsigned char byte) { 122 | midiOut->sendMidiByte(byte); 123 | } 124 | 125 | //---------------------------------------------------------- 126 | void ofxMidiOut::sendMidiBytes(std::vector &bytes) { 127 | midiOut->sendMidiBytes(bytes); 128 | } 129 | 130 | //---------------------------------------------------------- 131 | ofxMidiOut& ofxMidiOut::operator<<(const NoteOn &var) { 132 | midiOut->sendNoteOn(var.channel, var.pitch, var.velocity); 133 | return *this; 134 | } 135 | 136 | //---------------------------------------------------------- 137 | ofxMidiOut& ofxMidiOut::operator<<(const NoteOff &var) { 138 | midiOut->sendNoteOff(var.channel, var.pitch, var.velocity); 139 | return *this; 140 | } 141 | 142 | //---------------------------------------------------------- 143 | ofxMidiOut& ofxMidiOut::operator<<(const ControlChange &var) { 144 | midiOut->sendControlChange(var.channel, var.control, var.value); 145 | return *this; 146 | } 147 | 148 | //---------------------------------------------------------- 149 | ofxMidiOut& ofxMidiOut::operator<<(const ProgramChange &var) { 150 | midiOut->sendProgramChange(var.channel, var.value); 151 | return *this; 152 | } 153 | 154 | //---------------------------------------------------------- 155 | ofxMidiOut& ofxMidiOut::operator<<(const PitchBend &var) { 156 | midiOut->sendPitchBend(var.channel, var.value); 157 | return *this; 158 | } 159 | 160 | //---------------------------------------------------------- 161 | ofxMidiOut& ofxMidiOut::operator<<(const Aftertouch &var) { 162 | midiOut->sendAftertouch(var.channel, var.value); 163 | return *this; 164 | } 165 | 166 | //---------------------------------------------------------- 167 | ofxMidiOut& ofxMidiOut::operator<<(const PolyAftertouch &var) { 168 | midiOut->sendPolyAftertouch(var.channel, var.pitch, var.value); 169 | return *this; 170 | } 171 | 172 | //---------------------------------------------------------- 173 | ofxMidiOut& ofxMidiOut::operator<<(const StartMidi &var) { 174 | midiOut->startMidiStream(); 175 | return *this; 176 | } 177 | 178 | // --------------------------------------------------------- 179 | ofxMidiOut& ofxMidiOut::operator<<(const FinishMidi &var) { 180 | midiOut->finishMidiStream(); 181 | return *this; 182 | } 183 | 184 | // --------------------------------------------------------- 185 | ofxMidiOut& ofxMidiOut::operator<<(unsigned char var) { 186 | midiOut->sendMidiByte(var); 187 | return *this; 188 | } 189 | -------------------------------------------------------------------------------- /midiOutputExample/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofApp.h" 12 | 13 | //-------------------------------------------------------------- 14 | void ofApp::setup() { 15 | 16 | ofSetVerticalSync(true); 17 | ofBackground(255); 18 | ofSetLogLevel(OF_LOG_VERBOSE); 19 | 20 | // print the available output ports to the console 21 | midiOut.listOutPorts(); 22 | 23 | // connect 24 | midiOut.openPort(0); // by number 25 | //midiOut.openPort("IAC Driver Pure Data In"); // by name 26 | //midiOut.openVirtualPort("ofxMidiOut"); // open a virtual port 27 | 28 | channel = 1; 29 | currentPgm = 0; 30 | note = 0; 31 | velocity = 0; 32 | pan = 0; 33 | bend = 0; 34 | touch = 0; 35 | polytouch = 0; 36 | } 37 | 38 | //-------------------------------------------------------------- 39 | void ofApp::update() {} 40 | 41 | //-------------------------------------------------------------- 42 | void ofApp::draw() { 43 | 44 | // let's see something 45 | ofSetColor(0); 46 | stringstream text; 47 | text << "connected to port " << midiOut.getPort() 48 | << " \"" << midiOut.getName() << "\"" << endl 49 | << "is virtual?: " << midiOut.isVirtual() << endl << endl 50 | << "sending to channel " << channel << endl << endl 51 | << "current program: " << currentPgm << endl << endl 52 | << "note: " << note << endl 53 | << "velocity: " << velocity << endl 54 | << "pan: " << pan << endl 55 | << "bend: " << bend << endl 56 | << "touch: " << touch << endl 57 | << "polytouch: " << polytouch; 58 | ofDrawBitmapString(text.str(), 20, 20); 59 | } 60 | 61 | //-------------------------------------------------------------- 62 | void ofApp::exit() { 63 | 64 | // clean up 65 | midiOut.closePort(); 66 | } 67 | 68 | //-------------------------------------------------------------- 69 | void ofApp::keyPressed(int key) { 70 | 71 | // ignore shift+s since it is used in keyReleased for sysex sending 72 | if(key == 'S') return; 73 | 74 | // send a note on if the key is a letter or a number 75 | if(isalnum((unsigned char) key)) { 76 | 77 | // scale the ascii values to midi velocity range 0-127 78 | // see an ascii table: http://www.asciitable.com/ 79 | note = ofMap(key, 48, 122, 0, 127); 80 | velocity = 64; 81 | midiOut.sendNoteOn(channel, note, velocity); 82 | 83 | // print out both the midi note and the frequency 84 | ofLogNotice() << "note: " << note 85 | << " freq: " << ofxMidi::mtof(note) << " Hz"; 86 | } 87 | } 88 | 89 | //-------------------------------------------------------------- 90 | void ofApp::keyReleased(int key) { 91 | 92 | switch(key) { 93 | 94 | // send pgm change on arrow keys 95 | case OF_KEY_UP: 96 | currentPgm = (int) ofClamp(currentPgm+1, 0, 127); 97 | midiOut.sendProgramChange(channel, currentPgm); 98 | break; 99 | case OF_KEY_DOWN: 100 | currentPgm = (int) ofClamp(currentPgm-1, 0, 127); 101 | midiOut << ProgramChange(channel, currentPgm); // stream interface 102 | break; 103 | 104 | // aftertouch 105 | case '[': 106 | touch = 64; 107 | midiOut.sendAftertouch(channel, touch); 108 | break; 109 | case ']': 110 | touch = 127; 111 | midiOut << Aftertouch(channel, touch); // stream interface 112 | break; 113 | 114 | // poly aftertouch 115 | case '<': 116 | polytouch = 64; 117 | midiOut.sendPolyAftertouch(channel, 64, polytouch); 118 | break; 119 | case '>': 120 | polytouch = 127; 121 | midiOut << PolyAftertouch(channel, 64, polytouch); // stream interface 122 | break; 123 | 124 | // sysex using raw bytes (use shift + s) 125 | case 'S': { 126 | // send a pitch change to Part 1 of a MULTI on an Akai sampler 127 | // from http://troywoodfield.tripod.com/sysex.html 128 | // 129 | // do you have an S2000 to try? 130 | // 131 | // note: this is probably not as efficient as the next two methods 132 | // since it sends only one byte at a time, instead of all 133 | // at once 134 | // 135 | midiOut.sendMidiByte(MIDI_SYSEX); 136 | midiOut.sendMidiByte(0x47); // akai manufacturer code 137 | midiOut.sendMidiByte(0x00); // channel 0 138 | midiOut.sendMidiByte(0x42); // MULTI 139 | midiOut.sendMidiByte(0x48); // using an Akai S2000 140 | midiOut.sendMidiByte(0x00); // Part 1 141 | midiOut.sendMidiByte(0x00); // transpose 142 | midiOut.sendMidiByte(0x01); // Access Multi Parts 143 | midiOut.sendMidiByte(0x4B); // offset 144 | midiOut.sendMidiByte(0x00); // offset 145 | midiOut.sendMidiByte(0x01); // Field size = 1 146 | midiOut.sendMidiByte(0x00); // Field size = 1 147 | midiOut.sendMidiByte(0x04); // pitch value = 4 148 | midiOut.sendMidiByte(0x00); // offset 149 | midiOut.sendMidiByte(MIDI_SYSEX_END); 150 | 151 | // send again using a vector 152 | // 153 | // sends all bytes within one message 154 | // 155 | vector sysexMsg; 156 | sysexMsg.push_back(MIDI_SYSEX); 157 | sysexMsg.push_back(0x47); 158 | sysexMsg.push_back(0x00); 159 | sysexMsg.push_back(0x42); 160 | sysexMsg.push_back(0x48); 161 | sysexMsg.push_back(0x00); 162 | sysexMsg.push_back(0x00); 163 | sysexMsg.push_back(0x01); 164 | sysexMsg.push_back(0x4B); 165 | sysexMsg.push_back(0x00); 166 | sysexMsg.push_back(0x01); 167 | sysexMsg.push_back(0x00); 168 | sysexMsg.push_back(0x04); 169 | sysexMsg.push_back(0x00); 170 | sysexMsg.push_back(MIDI_SYSEX_END); 171 | midiOut.sendMidiBytes(sysexMsg); 172 | 173 | // send again with the byte stream interface 174 | // 175 | // builds the message, then sends it on FinishMidi() 176 | // 177 | midiOut << StartMidi() << MIDI_SYSEX 178 | << 0x47 << 0x00 << 0x42 << 0x48 << 0x00 << 0x00 << 0x01 179 | << 0x4B << 0x00 << 0x01 << 0x00 << 0x04 << 0x00 180 | << MIDI_SYSEX_END << FinishMidi(); 181 | break; 182 | } 183 | 184 | // print the port list 185 | case '?': 186 | midiOut.listOutPorts(); 187 | break; 188 | 189 | // note off using raw bytes 190 | case ' ': 191 | // send with the byte stream interface, noteoff for note 60 192 | midiOut << StartMidi() << 0x80 << 0x3C << 0x40 << FinishMidi(); 193 | break; 194 | 195 | default: 196 | 197 | // send a note off if the key is a letter or a number 198 | if(isalnum(key)) { 199 | note = ofMap(key, 48, 122, 0, 127); 200 | velocity = 0; 201 | midiOut << NoteOff(channel, note, velocity); // stream interface 202 | } 203 | break; 204 | } 205 | } 206 | 207 | //-------------------------------------------------------------- 208 | void ofApp::mouseMoved(int x, int y) { 209 | } 210 | 211 | //-------------------------------------------------------------- 212 | void ofApp::mouseDragged(int x, int y, int button) { 213 | 214 | // x pos controls the pan (ctl = 10) 215 | pan = ofMap(x, 0, ofGetWidth(), 0, 127); 216 | midiOut.sendControlChange(channel, 10, pan); 217 | 218 | // y pos controls the pitch bend 219 | bend = ofMap(y, 0, ofGetHeight(), 0, MIDI_MAX_BEND); 220 | midiOut.sendPitchBend(channel, bend); 221 | } 222 | 223 | //-------------------------------------------------------------- 224 | void ofApp::mousePressed(int x, int y, int button) { 225 | } 226 | 227 | //-------------------------------------------------------------- 228 | void ofApp::mouseReleased() { 229 | } 230 | -------------------------------------------------------------------------------- /midiExampleIOS/src/ofApp.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofApp.h" 12 | 13 | //-------------------------------------------------------------- 14 | void ofApp::setup() { 15 | 16 | // iPhoneAlerts will be sent to this 17 | ofxiOSAlerts.addListener(this); 18 | 19 | // if you want a landscape oreintation 20 | // ofxiOSSetOrientation(OF_ORIENTATION_90_RIGHT); 21 | 22 | // lets see what's going on inside 23 | ofSetLogLevel(OF_LOG_VERBOSE); 24 | 25 | // gray background 26 | ofBackground(127, 127, 127); 27 | 28 | maxMessages = 28; 29 | messages.push_back("nothing yet ..."); 30 | 31 | note = -1; 32 | ctl = -1; 33 | 34 | // enables the network midi session between iOS and Mac OSX on a 35 | // local wifi network 36 | // 37 | // in ofxMidi: open the input/outport network ports named "Session 1" 38 | // 39 | // on OSX: use the Audio MIDI Setup Utility to connect to the iOS device 40 | // 41 | ofxMidi::enableNetworking(); 42 | 43 | // list the number of available input & output ports 44 | ofxMidiIn input; 45 | ofxMidiOut output; 46 | input.listInPorts(); 47 | output.listOutPorts(); 48 | 49 | // create and open input ports 50 | for(int i = 0; i < input.getNumInPorts(); ++i) { 51 | 52 | // new object 53 | inputs.push_back(new ofxMidiIn); 54 | 55 | // set this class to receive incoming midi events 56 | inputs[i]->addListener(this); 57 | 58 | // open input port via port number 59 | inputs[i]->openPort(i); 60 | } 61 | 62 | // create and open output ports 63 | for(int i = 0; i < output.getNumOutPorts(); ++i) { 64 | 65 | // new object 66 | outputs.push_back(new ofxMidiOut); 67 | 68 | // open input port via port number 69 | outputs[i]->openPort(i); 70 | } 71 | 72 | // set this class to receieve midi device (dis)connection events 73 | ofxMidi::setConnectionListener(this); 74 | } 75 | 76 | //-------------------------------------------------------------- 77 | void ofApp::update() {} 78 | 79 | //-------------------------------------------------------------- 80 | void ofApp::draw() { 81 | 82 | ofSetColor(0); 83 | 84 | ofDrawBitmapString("Input:", 10, 20); 85 | int x = 10, y = 34; 86 | messageMutex.lock(); 87 | std::deque::iterator iter = messages.begin(); 88 | for(; iter != messages.end(); ++iter) { 89 | ofDrawBitmapString((*iter), x, y); 90 | y += 14; 91 | } 92 | messageMutex.unlock(); 93 | 94 | ofDrawBitmapString("Output:", 10, ofGetHeight()-42); 95 | if(note > 0) { 96 | ofDrawBitmapString("note "+ofToString(note), 10, ofGetHeight()-28); 97 | ofDrawRectangle(80, ofGetHeight()-38, ofMap(note, 0, 127, 0, ofGetWidth()-10), 12); 98 | } 99 | if(ctl > 0) { 100 | ofDrawBitmapString("pan "+ofToString(ctl), 10, ofGetHeight()-14); 101 | ofDrawRectangle(80, ofGetHeight()-24, ofMap(ctl, 0, 127, 0, ofGetWidth()-10), 12); 102 | } 103 | } 104 | 105 | //-------------------------------------------------------------- 106 | void ofApp::exit() { 107 | 108 | // clean up 109 | 110 | for(int i = 0; i < inputs.size(); ++i) { 111 | inputs[i]->closePort(); 112 | inputs[i]->removeListener(this); 113 | delete inputs[i]; 114 | } 115 | 116 | for(int i = 0; i < outputs.size(); ++i) { 117 | outputs[i]->closePort(); 118 | delete outputs[i]; 119 | } 120 | } 121 | 122 | //-------------------------------------------------------------- 123 | void ofApp::touchDown(ofTouchEventArgs &touch) { 124 | 125 | // send note on 126 | note = (int) ofMap(touch.y, ofGetHeight(), 0, 0, 127); 127 | for(int i = 0; i < outputs.size(); ++i) { 128 | outputs[i]->sendNoteOn(1, note); 129 | } 130 | } 131 | 132 | //-------------------------------------------------------------- 133 | void ofApp::touchMoved(ofTouchEventArgs &touch) { 134 | 135 | // send ctl change 136 | ctl = (int) ofMap(touch.x, 0, ofGetWidth(), 0, 127); 137 | for(int i = 0; i < outputs.size(); ++i) { 138 | outputs[i]->sendControlChange(1, 10, ctl); // 10: pan 139 | } 140 | } 141 | 142 | //-------------------------------------------------------------- 143 | void ofApp::touchUp(ofTouchEventArgs &touch) { 144 | 145 | // send note off 146 | for(int i = 0; i < outputs.size(); ++i) { 147 | outputs[i]->sendNoteOff(1, note); 148 | } 149 | note = -1; 150 | ctl = -1; 151 | } 152 | 153 | //-------------------------------------------------------------- 154 | void ofApp::touchDoubleTap(ofTouchEventArgs &touch) { 155 | 156 | } 157 | 158 | //-------------------------------------------------------------- 159 | void ofApp::lostFocus() { 160 | 161 | } 162 | 163 | //-------------------------------------------------------------- 164 | void ofApp::gotFocus() { 165 | 166 | } 167 | 168 | //-------------------------------------------------------------- 169 | void ofApp::gotMemoryWarning() { 170 | 171 | } 172 | 173 | //-------------------------------------------------------------- 174 | void ofApp::deviceOrientationChanged(int newOrientation) { 175 | 176 | } 177 | 178 | //-------------------------------------------------------------- 179 | void ofApp::touchCancelled(ofTouchEventArgs &args) { 180 | 181 | } 182 | 183 | //-------------------------------------------------------------- 184 | void ofApp::addMessage(std::string message) { 185 | messageMutex.lock(); 186 | std::cout << message << std::endl; 187 | messages.push_back(message); 188 | while(messages.size() > maxMessages) { 189 | messages.pop_front(); 190 | } 191 | messageMutex.unlock(); 192 | } 193 | 194 | //-------------------------------------------------------------- 195 | void ofApp::newMidiMessage(ofxMidiMessage &message) { 196 | addMessage(message.toString()); 197 | } 198 | 199 | //-------------------------------------------------------------- 200 | void ofApp::midiInputAdded(std::string name, bool isNetwork) { 201 | std::stringstream msg; 202 | msg << "ofxMidi: input added: " << name << " network: " << isNetwork; 203 | addMessage(msg.str()); 204 | 205 | // create and open a new input port 206 | ofxMidiIn *newInput = new ofxMidiIn; 207 | newInput->openPort(name); 208 | newInput->addListener(this); 209 | inputs.push_back(newInput); 210 | } 211 | 212 | //-------------------------------------------------------------- 213 | void ofApp::midiInputRemoved(std::string name, bool isNetwork) { 214 | std::stringstream msg; 215 | msg << "ofxMidi: input removed: " << name << " network: " << isNetwork << endl; 216 | addMessage(msg.str()); 217 | 218 | // close and remove input port 219 | std::vector::iterator iter; 220 | for(iter = inputs.begin(); iter != inputs.end(); ++iter) { 221 | ofxMidiIn *input = (*iter); 222 | if(input->getName() == name) { 223 | input->closePort(); 224 | input->removeListener(this); 225 | delete input; 226 | inputs.erase(iter); 227 | break; 228 | } 229 | } 230 | } 231 | 232 | //-------------------------------------------------------------- 233 | void ofApp::midiOutputAdded(std::string name, bool isNetwork) { 234 | std::stringstream msg; 235 | msg << "ofxMidi: output added: " << name << " network: " << isNetwork << endl; 236 | addMessage(msg.str()); 237 | 238 | // create and open new output port 239 | ofxMidiOut *newOutput = new ofxMidiOut; 240 | newOutput->openPort(name); 241 | outputs.push_back(newOutput); 242 | } 243 | 244 | //-------------------------------------------------------------- 245 | void ofApp::midiOutputRemoved(std::string name, bool isNetwork) { 246 | std::stringstream msg; 247 | msg << "ofxMidi: output removed: " << name << " network: " << isNetwork << endl; 248 | addMessage(msg.str()); 249 | 250 | // close and remove output port 251 | std::vector::iterator iter; 252 | for(iter = outputs.begin(); iter != outputs.end(); ++iter) { 253 | ofxMidiOut *output = (*iter); 254 | if(output->getName() == name) { 255 | output->closePort(); 256 | delete output; 257 | outputs.erase(iter); 258 | break; 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/ofxMidiTimecode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | * Adapted from code written in Swift for the Hertz-Lab @ ZKM | Karlsruhe 11 | * 12 | */ 13 | #include "ofxMidiTimecode.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include "ofLog.h" 19 | #include "ofxMidi.h" 20 | 21 | // ofxMidiTimecodeFrame 22 | 23 | // ----------------------------------------------------------------------------- 24 | double ofxMidiTimecodeFrame::getFps() const { 25 | return ofxMidiTimecode::rateToFps(rate); 26 | } 27 | 28 | // ----------------------------------------------------------------------------- 29 | std::string ofxMidiTimecodeFrame::toString() const { 30 | std::stringstream stream; 31 | stream << std::setw(2) << std::setfill('0') << hours << ":" 32 | << std::setw(2) << std::setfill('0') << minutes << ":" 33 | << std::setw(2) << std::setfill('0') << seconds << ":" 34 | << std::setw(2) << std::setfill('0') << frames; 35 | return stream.str(); 36 | } 37 | 38 | // ----------------------------------------------------------------------------- 39 | double ofxMidiTimecodeFrame::toSeconds() const { 40 | double time = (double)hours * 3600.0; // 60.0 * 60.0 41 | time += (double)minutes * 60.0; 42 | time += (double)seconds; 43 | time += (double)ofxMidiTimecode::framesToMs(frames, rate) / 1000; 44 | return time; 45 | } 46 | 47 | // ----------------------------------------------------------------------------- 48 | void ofxMidiTimecodeFrame::fromSeconds(double s) { 49 | return fromSeconds(s, 0x0); 50 | } 51 | 52 | // ----------------------------------------------------------------------------- 53 | void ofxMidiTimecodeFrame::fromSeconds(double s, unsigned char r) { 54 | seconds = (int)s % 60; 55 | minutes = (int)((s - seconds) * 0.016666667) % 60; 56 | hours = (int)(s * 0.00027777778) % 60; 57 | 58 | // round fractional part of seconds for ms 59 | double ms = (int)(floor((s - floor(s)) * 1000.0) + 0.5); 60 | frames = ofxMidiTimecode::msToFrames(ms, r); 61 | rate = r; 62 | } 63 | 64 | // ofxMidiTimecode 65 | 66 | // ----------------------------------------------------------------------------- 67 | bool ofxMidiTimecode::update(std::vector &message) { 68 | unsigned char statusByte = message[0]; 69 | if(statusByte == MIDI_TIME_CODE) { 70 | return decodeQuarterFrame(message); 71 | } 72 | else if(statusByte == MIDI_SYSEX) { 73 | return decodeFullFrame(message); 74 | } 75 | return false; 76 | } 77 | 78 | // ----------------------------------------------------------------------------- 79 | void ofxMidiTimecode::reset() { 80 | frame = ofxMidiTimecodeFrame(); 81 | quarterFrame = QuarterFrame(); 82 | } 83 | 84 | /// Util 85 | 86 | // ----------------------------------------------------------------------------- 87 | int ofxMidiTimecode::framesToMs(int frames, unsigned char rate) { 88 | return (int)(rateToMultiplier(rate) * (double)frames * 1000.0); 89 | } 90 | 91 | // ----------------------------------------------------------------------------- 92 | int ofxMidiTimecode::msToFrames(int ms, unsigned char rate) { 93 | return (int)((double)ms / rateToMultiplier(rate) / 1000.0); 94 | } 95 | 96 | // ----------------------------------------------------------------------------- 97 | double ofxMidiTimecode::rateToFps(unsigned char rate) { 98 | switch(rate) { 99 | default: 100 | case FRAMERATE_24: 101 | return 24.0; 102 | case FRAMERATE_25: 103 | return 25.0; 104 | case FRAMERATE_30_DROP: 105 | return 29.97; 106 | case FRAMERATE_30: 107 | return 30.0; 108 | } 109 | } 110 | 111 | // ----------------------------------------------------------------------------- 112 | unsigned char ofxMidiTimecode::fpsToRate(double fps) { 113 | if(fps < 25) {return FRAMERATE_24;} 114 | if(fps < 29) {return FRAMERATE_25;} 115 | if(fps < 30) {return FRAMERATE_30_DROP;} 116 | return FRAMERATE_30; // 30 fps 117 | } 118 | 119 | // ----------------------------------------------------------------------------- 120 | double ofxMidiTimecode::rateToMultiplier(unsigned char rate) { 121 | switch(rate) { 122 | case FRAMERATE_24: 123 | return 0.04166666667; // 1.0 / 24.0 124 | case FRAMERATE_25: 125 | return 0.04000000000; // 1.0 / 25.0 126 | case FRAMERATE_30_DROP: 127 | return 0.03336670003; // 1.0 / 29.97 128 | case FRAMERATE_30: 129 | default: 130 | return 0.03333333333; // 1.0 / 30.0 131 | 132 | } 133 | } 134 | 135 | /// PROTECTED 136 | 137 | // ----------------------------------------------------------------------------- 138 | bool ofxMidiTimecode::decodeQuarterFrame(std::vector &message) { 139 | 140 | ofLogVerbose("ofxMidiTimecode") << "Quarter Frame " << ofxMidi::bytesToString(message); 141 | 142 | bool complete = false; 143 | unsigned char dataByte = message[1]; 144 | unsigned char msgType = dataByte & 0xF0; 145 | 146 | if(quarterFrame.direction == QuarterFrame::UNKNOWN && quarterFrame.count > 1) { 147 | unsigned char lastMsgType = quarterFrame.lastDataByte & 0xF0; 148 | if(lastMsgType < msgType) { 149 | quarterFrame.direction = QuarterFrame::FORWARDS; 150 | } 151 | else if(lastMsgType > msgType) { 152 | quarterFrame.direction = QuarterFrame::BACKWARDS; 153 | } 154 | } 155 | quarterFrame.lastDataByte = dataByte; 156 | 157 | switch(msgType) { 158 | case 0x00: // frame LSB 159 | quarterFrame.frame.frames = (int)(dataByte & 0x0F); 160 | quarterFrame.count += 1; 161 | quarterFrame.receivedFirst = true; 162 | if(quarterFrame.count >= QUARTERFRAME_LEN && 163 | quarterFrame.direction == QuarterFrame::BACKWARDS && 164 | quarterFrame.receivedLast) { 165 | complete = true; 166 | } 167 | break; 168 | case 0x10: // frame MSB 169 | quarterFrame.frame.frames |= (int)((dataByte & 0x01) << 4); 170 | quarterFrame.count += 1; 171 | break; 172 | case 0x20: // second LSB 173 | quarterFrame.frame.seconds = (int)(dataByte & 0x0F); 174 | quarterFrame.count += 1; 175 | break; 176 | case 0x30: // second MSB 177 | quarterFrame.frame.seconds |= (int)((dataByte & 0x03) << 4); 178 | quarterFrame.count += 1; 179 | break; 180 | case 0x40: // minute LSB 181 | quarterFrame.frame.minutes = (int)(dataByte & 0x0F); 182 | quarterFrame.count += 1; 183 | break; 184 | case 0x50: // minute MSB 185 | quarterFrame.frame.minutes |= (int)((dataByte & 0x03) << 4); 186 | quarterFrame.count += 1; 187 | break; 188 | case 0x60: // hours LSB 189 | quarterFrame.frame.hours = (int)(dataByte & 0x0F); 190 | quarterFrame.count += 1; 191 | break; 192 | case 0x70: // hours MSB & framerate 193 | quarterFrame.frame.hours |= (int)((dataByte & 0x01) << 4); 194 | quarterFrame.frame.rate = (dataByte & 0x06) >> 1; 195 | quarterFrame.count += 1; 196 | quarterFrame.receivedLast = true; 197 | if(quarterFrame.count >= QUARTERFRAME_LEN && 198 | quarterFrame.direction == QuarterFrame::FORWARDS && 199 | quarterFrame.receivedFirst) { 200 | complete = true; 201 | } 202 | break; 203 | default: 204 | return false; 205 | } 206 | 207 | // update time using the (hopefully) complete message 208 | if(complete) { 209 | // add 2 frames to compensate for time it takes to receive 8 QF messages 210 | quarterFrame.frame.frames += 2; 211 | frame = quarterFrame.frame; 212 | ofLogVerbose("ofxMidiTimecode") << frame.toString(); 213 | quarterFrame = QuarterFrame(); 214 | return true; 215 | } 216 | 217 | return false; 218 | } 219 | 220 | // ----------------------------------------------------------------------------- 221 | bool ofxMidiTimecode::decodeFullFrame(std::vector &message) { 222 | if(message.size() == FULLFRAME_LEN && isFullFrame(message)) { 223 | ofLogVerbose("ofxMidiTimecode") << "Full Frame " << ofxMidi::bytesToString(message); 224 | frame.hours = (int)(message[5] & 0x1F); 225 | frame.rate = (int)((message[5] & 0x60) >> 5); 226 | frame.minutes = (int)(message[6]); 227 | frame.seconds = (int)(message[7]); 228 | frame.frames = (int)(message[8]); 229 | ofLogVerbose("ofxMidiTimecode") << frame.toString(); 230 | return true; 231 | } 232 | return false; 233 | } 234 | 235 | // ----------------------------------------------------------------------------- 236 | bool ofxMidiTimecode::isFullFrame(std::vector &message) { 237 | return 238 | (message[1] == 0x7F) && // universal message 239 | (message[2] == 0x7F) && // global broadcast 240 | (message[3] == 0x01) && // time code 241 | (message[4] == 0x01) && // full frame 242 | (message[9] == 0xF7); // end of sysex 243 | } 244 | -------------------------------------------------------------------------------- /src/ofxBaseMidi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2023 Dan Wilcox 3 | * 4 | * BSD Simplified License. 5 | * For information on usage and redistribution, and for a DISCLAIMER OF ALL 6 | * WARRANTIES, see the file, "LICENSE.txt," in this distribution. 7 | * 8 | * See https://github.com/danomatika/ofxMidi for documentation 9 | * 10 | */ 11 | #include "ofxBaseMidi.h" 12 | 13 | #include "ofLog.h" 14 | 15 | // MIDI IN 16 | 17 | // ----------------------------------------------------------------------------- 18 | ofxBaseMidiIn::ofxBaseMidiIn(const std::string name, ofxMidiApi api) { 19 | portNum = -1; 20 | portName = ""; 21 | bOpen = false; 22 | bVerbose = false; 23 | bVirtual = false; 24 | this->api = api; 25 | messagesChannel = std::make_unique>(); 26 | } 27 | // ----------------------------------------------------------------------------- 28 | int ofxBaseMidiIn::getPort() { 29 | return portNum; 30 | } 31 | 32 | // ----------------------------------------------------------------------------- 33 | std::string ofxBaseMidiIn::getName() { 34 | return portName; 35 | } 36 | 37 | // ----------------------------------------------------------------------------- 38 | bool ofxBaseMidiIn::isOpen() { 39 | return bOpen; 40 | } 41 | 42 | // ----------------------------------------------------------------------------- 43 | bool ofxBaseMidiIn::isVirtual() { 44 | return bVirtual; 45 | } 46 | 47 | // ----------------------------------------------------------------------------- 48 | bool ofxBaseMidiIn::isQueued() { 49 | return (messagesChannel != nullptr); 50 | } 51 | 52 | // ----------------------------------------------------------------------------- 53 | ofxMidiApi ofxBaseMidiIn::getApi() { 54 | return api; 55 | } 56 | 57 | // ----------------------------------------------------------------------------- 58 | void ofxBaseMidiIn::addListener(ofxMidiListener *listener) { 59 | ofAddListener(newMessageEvent, listener, &ofxMidiListener::newMidiMessage); 60 | if(messagesChannel) { // disable thread channel 61 | messagesChannel.reset(); 62 | } 63 | } 64 | 65 | // ----------------------------------------------------------------------------- 66 | void ofxBaseMidiIn::removeListener(ofxMidiListener *listener) { 67 | ofRemoveListener(newMessageEvent, listener, &ofxMidiListener::newMidiMessage); 68 | if(newMessageEvent.size() == 0) { 69 | if(!messagesChannel) { // re-enable thread channel 70 | messagesChannel = std::make_unique>(); 71 | } 72 | } 73 | } 74 | 75 | //------------------------------------------------------------------------------ 76 | bool ofxBaseMidiIn::hasWaitingMessages() const { 77 | return (messagesChannel ? !messagesChannel->empty() : false); 78 | } 79 | 80 | //------------------------------------------------------------------------------ 81 | bool ofxBaseMidiIn::getNextMessage(ofxMidiMessage &message) { 82 | return (messagesChannel ? messagesChannel->tryReceive(message) : false); 83 | } 84 | 85 | // ----------------------------------------------------------------------------- 86 | void ofxBaseMidiIn::setVerbose(bool verbose) { 87 | bVerbose = verbose; 88 | } 89 | 90 | // PRIVATE 91 | // ----------------------------------------------------------------------------- 92 | void ofxBaseMidiIn::manageNewMessage(double deltatime, std::vector *message) { 93 | 94 | // parse message and fill event 95 | ofxMidiMessage midiMessage(message); 96 | midiMessage.deltatime = deltatime; 97 | midiMessage.portNum = portNum; 98 | midiMessage.portName = portName; 99 | if(bVerbose) { 100 | ofLogVerbose("ofxMidiIn") << midiMessage.toString(); 101 | } 102 | 103 | // send event to listeners or push onto thread channel 104 | if(messagesChannel) { 105 | messagesChannel->send(std::move(midiMessage)); 106 | } 107 | else { 108 | ofNotifyEvent(newMessageEvent, midiMessage, this); 109 | } 110 | } 111 | 112 | // MIDI OUT 113 | 114 | // ----------------------------------------------------------------------------- 115 | ofxBaseMidiOut::ofxBaseMidiOut(const std::string name, ofxMidiApi api) { 116 | portNum = -1; 117 | portName = ""; 118 | bOpen = false; 119 | bStreamInProgress = false; 120 | bVirtual = false; 121 | this->api = api; 122 | } 123 | 124 | // ----------------------------------------------------------------------------- 125 | int ofxBaseMidiOut::getPort() { 126 | return portNum; 127 | } 128 | 129 | // ----------------------------------------------------------------------------- 130 | std::string ofxBaseMidiOut::getName() { 131 | return portName; 132 | } 133 | 134 | // ----------------------------------------------------------------------------- 135 | bool ofxBaseMidiOut::isOpen() { 136 | return bOpen; 137 | } 138 | 139 | // ----------------------------------------------------------------------------- 140 | bool ofxBaseMidiOut::isVirtual() { 141 | return bVirtual; 142 | } 143 | 144 | // ----------------------------------------------------------------------------- 145 | ofxMidiApi ofxBaseMidiOut::getApi() { 146 | return api; 147 | } 148 | 149 | // ----------------------------------------------------------------------------- 150 | void ofxBaseMidiOut::sendNoteOn(int channel, int pitch, int velocity) { 151 | std::vector message; 152 | message.push_back(MIDI_NOTE_ON+(channel-1)); 153 | message.push_back(pitch); 154 | message.push_back(velocity); 155 | sendMessage(message); 156 | } 157 | 158 | // ----------------------------------------------------------------------------- 159 | void ofxBaseMidiOut::sendNoteOff(int channel, int pitch, int velocity) { 160 | std::vector message; 161 | message.push_back(MIDI_NOTE_OFF+(channel-1)); 162 | message.push_back(pitch); 163 | message.push_back(velocity); 164 | sendMessage(message); 165 | } 166 | 167 | // ----------------------------------------------------------------------------- 168 | void ofxBaseMidiOut::sendControlChange(int channel, int control, int value) { 169 | std::vector message; 170 | message.push_back(MIDI_CONTROL_CHANGE+(channel-1)); 171 | message.push_back(control); 172 | message.push_back(value); 173 | sendMessage(message); 174 | } 175 | 176 | // ----------------------------------------------------------------------------- 177 | void ofxBaseMidiOut::sendProgramChange(int channel, int value) { 178 | std::vector message; 179 | message.push_back(MIDI_PROGRAM_CHANGE+(channel-1)); 180 | message.push_back(value); 181 | sendMessage(message); 182 | } 183 | 184 | // ----------------------------------------------------------------------------- 185 | void ofxBaseMidiOut::sendPitchBend(int channel, int value) { 186 | std::vector message; 187 | message.push_back(MIDI_PITCH_BEND+(channel-1)); 188 | message.push_back(value & 0x7F); // lsb 7bit 189 | message.push_back((value >> 7) & 0x7F); // msb 7bit 190 | sendMessage(message); 191 | } 192 | 193 | void ofxBaseMidiOut::sendPitchBend(int channel, unsigned char lsb, unsigned char msb) { 194 | std::vector message; 195 | message.push_back(MIDI_PITCH_BEND+(channel-1)); 196 | message.push_back(lsb); 197 | message.push_back(msb); 198 | sendMessage(message); 199 | } 200 | 201 | // ----------------------------------------------------------------------------- 202 | void ofxBaseMidiOut::sendAftertouch(int channel, int value) { 203 | std::vector message; 204 | message.push_back(MIDI_AFTERTOUCH+(channel-1)); 205 | message.push_back(value); 206 | sendMessage(message); 207 | } 208 | 209 | // ----------------------------------------------------------------------------- 210 | void ofxBaseMidiOut::sendPolyAftertouch(int channel, int pitch, int value) { 211 | std::vector message; 212 | message.push_back(MIDI_POLY_AFTERTOUCH+(channel-1)); 213 | message.push_back(pitch); 214 | message.push_back(value); 215 | sendMessage(message); 216 | } 217 | 218 | // ----------------------------------------------------------------------------- 219 | void ofxBaseMidiOut::sendMidiByte(unsigned char byte) { 220 | if(bStreamInProgress) { 221 | stream.push_back(byte); 222 | } 223 | else { 224 | std::vector message; 225 | message.push_back(byte); 226 | sendMessage(message); 227 | } 228 | } 229 | 230 | //---------------------------------------------------------- 231 | void ofxBaseMidiOut::sendMidiBytes(std::vector &bytes) { 232 | if(bStreamInProgress) { 233 | stream.insert(stream.end(), bytes.begin(), bytes.end()); 234 | } 235 | else { 236 | sendMessage(bytes); 237 | } 238 | } 239 | 240 | //---------------------------------------------------------- 241 | void ofxBaseMidiOut::startMidiStream() { 242 | if(bStreamInProgress) { 243 | ofLogWarning("ofxMidiOut") << "calling StartMidi when byte stream in progress"; 244 | return; 245 | } 246 | stream.clear(); 247 | bStreamInProgress = true; 248 | } 249 | 250 | // ----------------------------------------------------------------------------- 251 | void ofxBaseMidiOut::finishMidiStream() { 252 | if(!bStreamInProgress) { 253 | ofLogWarning("ofxMidiOut") << "can not finish midi byte stream, stream not in progress"; 254 | return; 255 | } 256 | sendMessage(stream); 257 | bStreamInProgress = false; 258 | } 259 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ofxMidi 2 | ======= 3 |

4 | 5 |

6 | 7 | MIDI input and output addon for openFrameworks 8 | 9 | Copyright (c) [Dan Wilcox](danomatika.com) 2011-2023 10 | (original implementation by Chris O'Shea, Arturo Castro, Kyle McDonald) 11 | 12 | BSD Simplified License. 13 | 14 | For information on usage and redistribution, and for a DISCLAIMER OF ALL 15 | WARRANTIES, see the file, "LICENSE.txt," in this distribution. 16 | 17 | See Documentation on [Github](https://github.com/danomatika/ofxMidi) and the [openFrameworks Forum post](https://forum.openframeworks.cc/t/ofxmidi-updates/2435). 18 | 19 | openFrameworks is a cross platform open source toolkit for creative coding in C++. 20 | 21 | [http://www.openframeworks.cc](http://www.openframeworks.cc) 22 | 23 | This project has been supported by the CMU [Frank-Ratchye STUDIO for Creative Inquiry](http://studioforcreativeinquiry.org), the DU [Emergent Digital Practices Program](https://www.du.edu/ahss/edp/), and my time at the [ZKM | Hertz-Lab](https://zkm.de/en/about-the-zkm/organisation/hertz-lab). 24 | 25 | Description 26 | ----------- 27 | 28 | ofxMidi provides [Music Instrument Digital Interface](http://en.wikipedia.org/wiki/Musical_Instrument_Digital_Interface) IO capability to an openFrameworks app 29 | 30 | * ofxMidiIn: a single MIDI input port, derive from the ofxMidiListener class to receive messages 31 | * ofxMidiMessage: a received MIDI message 32 | * ofxMidiOut: a single MIDI output port, includes a stream << interface 33 | 34 | This project utilizes [RtMidi](http://www.music.mcgill.ca/~gary/rtmidi/) for Mac, Windows, & Linux and [PGMidi](https://github.com/petegoodliffe/PGMidi) on iOS. 35 | 36 | Installation 37 | ------------ 38 | 39 | To use ofxMidi, first you need to download and install openFrameworks. ofxMidi is currently developed against the current stable version of openFrameworks on github. 40 | 41 | To get a copy of the repository you can download the source from [http://github.com/danomatika/ofxMidi/zipball/master](http://github.com/danomatika/ofxMidi/zipball/master) or, alternatively, you can use git clone: 42 | 43 | git clone git://github.com/danomatika/ofxMidi.git 44 | 45 | The addon should sit in `openFrameworks/addons/ofxMidi/`. 46 | 47 | ### Which version to use? 48 | 49 | The master branch of ofxMidi will work with the current stable version of openFrameworks and can be considered *relatively* stable. 50 | 51 | Previous versions are tagged using [Semantic Versioning](http://semver.org) with the updates to newer versions of openFrameworks and MIDI libraries noted in the changelog, CHANGES.txt. You can select the tag in the Github "Current Branch" menu or clone and check it out using git. 52 | 53 | If you want to use ofxMidi with a previous version of openFrameworks, checkout the corresponding version tag after cloning: 54 | 55 | git clone git://github.com/danomatika/ofxMidi.git 56 | cd ofxMidi 57 | git checkout 1.0.5 58 | 59 | MIDI Routing 60 | ------------ 61 | 62 | ### macOS 63 | 64 | Checkout a useful app for MIDI port routing called [MIDI Patchbay](http://notahat.com/midi_patchbay). 65 | 66 | ### Linux 67 | 68 | Check out the Alsa utility apps aconnect & aconnectgui as well as the qjackctl gui for MIDI port routing control. 69 | 70 | ### Windows 71 | 72 | Windows does not come with a virtual MIDI routing system like Linux (ALSA) and macOS (CoreMIDI). 73 | 74 | If you want to connect your ofxMidi app to other software (synths, DAWs, etc) check out [loopMIDI](http://www.tobias-erichsen.de/loopMIDI.html). Run the app and create a few virtual ports which you can then connect to within your software. 75 | 76 | Running an Example Projects 77 | --------------------------- 78 | 79 | The example projects are in the `ofxMidi/midiInputExample`, `ofxMidi/midiOutputExample`, & `ofxMidi/midiExampleIOS` folders. 80 | 81 | Project files for the examples are not included so you will need to generate the project files for your operating system and development environment using the OF ProjectGenerator which is included with the openFrameworks distribution. 82 | 83 | To (re)generate project files for an *existing* project: 84 | 85 | * Click the "Import" button in the ProjectGenerator 86 | * Navigate to the project's parent folder ie. "ofxMidi", select the base folder for the example project ie. "midiOutputExample", and click the Open button 87 | * Click the "Update" button 88 | 89 | If everything went ok, you should now be able to open the generated project and build/run the example. 90 | 91 | ### macOS 92 | 93 | Open the Xcode project, select the Debug scheme, and hit "Run". 94 | 95 | ### Linux 96 | 97 | Open the QT Creator project file and/or build the example with the Makefile. 98 | 99 | To build and run via Makefile on the terminal: 100 | ~~~ 101 | make 102 | make run 103 | ~~~ 104 | 105 | ### Windows 106 | 107 | Open the Visual Studio project, build and run. 108 | 109 | Creating a New ofxMidi Project 110 | ------------------------------ 111 | 112 | _Note: These instructions are for manually creating a new project. You do not need to follow these steps if you use the ProjecGenerator app._ 113 | 114 | To develop your own project based on ofxMidi, simply copy an example project and rename it. You probably want to put it in your apps folder, for example, after copying: 115 | 116 | openFrameworks/addons/ofxMidi/midiExampleInput/ => openFrameworks/apps/myApps/midiExampleInput/ 117 | 118 | It must be 3 levels down in the openFrameworks folder structure. 119 | 120 | Then rename the folder: 121 | 122 | openFrameworks/apps/myApps/myMidiProject/ 123 | 124 | #### Xcode 125 | 126 | Rename the project in Xcode (do not rename the .xcodeproj file in Finder!): 127 | 128 | * Xcode Menu->Project->Rename 129 | 130 | #### Visual Studio 131 | 132 | * Rename the \*.sln, \*.vcxproj, & \*.vcxproj.filters files 133 | * Open the solution and delete the old project from the projects tree 134 | * Go to File->Add->Existing Projects/Solutions and select the \*.vcxproj file 135 | * Right-click on the project in the projects tree and rename it 136 | 137 | Adding ofxMidi to an Existing Project 138 | ------------------------------------- 139 | 140 | _Note: These instructions are for manually adding ofxMidi to a project. You do not need to follow these steps if you use the ProjecGenerator app to regenerate your project files._ 141 | 142 | ### Xcode 143 | 144 | * Create a new group "ofxMidi" in the "addons" group 145 | * Drag these directories from ofxMidi into this new group: `ofxMidi/src` & `ofxMidi/libs` 146 | - In the Add dialog: add to your current project target, uncheck "Copy items if needed" & select "Create groups" 147 | - If building for macOS, remove the src/ios & libs/pgmidi folder references 148 | - If building for iOS, remove the src/desktop & libs/rtmidi folder references 149 | * Add the CoreMIDI framework to your project 150 | - Click on your project in the sidebar 151 | - Select the Summary tab 152 | - Click the + under Linked Frameworks & Libraries 153 | - Search for and select the CoreMIDI.framework from the list 154 | * Add the following directories to your search path in your project's Project.xconfig file (see the Project.xconfig of the example project): 155 |
156 | ../../../addons/ofxMidi/src
157 | ../../../addons/ofxMidi/libs/rtmidi
158 | 
159 | 160 | ### Linux Makefiles/CodeBlocks 161 | 162 | Edit addons.make in your project folder and add the following line to the end of the file: 163 |
164 | ofxMidi
165 | 
166 | 167 | ### Win Codeblocks & Visual Studio 168 | 169 | * Add the ofxMidi sources to the project tree 170 | ofxMidi/src 171 | ofxMidi/libs/rtmidi 172 | * Visual Studio: drag the ofxMidi/src & ofxMidi/libs/rtmidi folder onto the project tree 173 | * Add the following search paths: 174 |
175 | ..\\..\\..\addons\ofxMidi\src
176 | ..\\..\\..\addons\ofxMidi\libs\rtmidi
177 | 
178 | * Visual Studio 179 | - Right click on the project in the project tree and select Properties 180 | - Set the Configuration to All Configurations 181 | - Configuration Properties->C/C++->General->Additional Directories 182 | 183 | KNOWN ISSUES 184 | ------------ 185 | 186 | ### Help, app crashes when receiving MIDI messages 187 | 188 | _As of ofxMidi 1.3.0, queued message passing is available for ofxMidiIn via 189 | getNextMessage()._ 190 | 191 | If you are using direct message passing by sub-classing `ofxMidiListener` and 192 | receiving MIDI messages via the `newMidiMessage()` callback function, there is a 193 | chance of segmentation faults (and crashes) if you share the received messages 194 | between multiple threads (ie. main GUI, OSC receiver, etc). 195 | 196 | Depending upon the design of your application, you may need to place a mutex 197 | object or shared lock around access to these resources shared between threads. 198 | 199 | For example, in ofApp.h: 200 | ```cpp 201 | class ofApp : public ofBaseApp, public ofxMidiListener { 202 | ... 203 | ofxMidiIn midiIn; 204 | std::vector midiMessages; //< received messages 205 | ofMutex midiMutex; //< MIDI message access mutex 206 | ``` 207 | 208 | and ofApp.cpp: 209 | ```cpp 210 | void draw() { 211 | // draw current messages, lock in case of incoming messages 212 | midiMutex.lock(); 213 | // do something with midiMessages 214 | midiMutex.unlock(); 215 | } 216 | 217 | void newMidiMessage(ofxMidiMessage& msg) { 218 | // lock and add new messages 219 | midiMutex.lock(); 220 | midiMessages.push_back(msg); 221 | midiMutex.unlock(); 222 | } 223 | ``` 224 | 225 | This should stop multiple-thread access crashes, however may stutter incoming 226 | message timing as adding new messages will have to wait until the current frame 227 | is done. Another option is to use a lock-free design using a ring-buffer. 228 | 229 | ### Undefined symbols for architecture x86_64 on iOS 230 | 231 | _Steps contributed by Zach Lee._ 232 | 233 | There is a bug with the OF ProjectGenerator in 0.10 and 0.11 which may result in 234 | an iOS project using ofxMidi which will have build errors like the following: 235 | 236 | ~~~ 237 | Undefined symbols for architecture x86_64: 238 | "_glBufferSubData", referenced from: 239 | ofBufferObject::updateData(long, long, void const*) in libofxiOS_iphonesimulator_Debug.a(ofBufferObject.o) 240 | "_glBufferData", referenced from: 241 | ofBufferObject::setData(long, void const*, unsigned int) in libofxiOS_iphonesimulator_Debug.a(ofBufferObject.o) 242 | "_glDeleteBuffers", referenced from: 243 | ofBufferObject::Data::~Data() in libofxiOS_iphonesimulator_Debug.a(ofBufferObject.o) 244 | "_glUniformMatrix4fv", referenced from: 245 | ... 246 | "_OBJC_CLASS_$_EAGLContext", referenced from: 247 | objc-class-ref in libofxiOS_iphonesimulator_Debug.a(ES1Renderer.o) 248 | objc-class-ref in libofxiOS_iphonesimulator_Debug.a(ES2Renderer.o) 249 | objc-class-ref in libofxiOS_iphonesimulator_Debug.a(EAGLKView.o) 250 | "_glCheckFramebufferStatus", referenced from: 251 | -[ES2Renderer createFramebuffer:] in libofxiOS_iphonesimulator_Debug.a(ES2Renderer.o) 252 | (maybe you meant: _glCheckFramebufferStatusFunc) 253 | "_OBJC_CLASS_$_MIDINetworkSession", referenced from: 254 | objc-class-ref in PGMidi.o 255 | "_AVAudioSessionInterruptionTypeKey", referenced from: 256 | -[SoundStream handleInterruption:] in libofxiOS_iphonesimulator_Debug.a(SoundStream.o) 257 | ld: symbol(s) not found for architecture x86_64 258 | clang: error: linker command failed with exit code 1 (use -v to see invocation) 259 | Showing first 200 notices only 260 | Showing first 200 errors only 261 | ~~~ 262 | 263 | The fix is to remove a search path added by the PG which is causing the problem: 264 | 265 | 1. click on your project in the sidebar 266 | 2. select the project under TARGETS 267 | 3. select the Build Settings tab 268 | 4. scroll down to or find Framework Search Paths and remove the last entry: 269 | 270 | ![iOS Framework search paths](res/ios-framework-search-paths.png) 271 | 272 | ### Using static ofxMidi objects on Linux causes segmentation faults 273 | 274 | Avoid creating static ofxMidiIn / ofxMidiOut objects on Linux as the compiler seems to set creation order so they are created *before* ALSA is ready. This leads to a confirmed segmentation fault on Ubuntu and probably all other flavors of Linux using ALSA. The midi apis on Windows and macOS do not share this problem. 275 | 276 | Instead, create a static shared_ptr and initialize it later. For example, in .h: 277 | ```cpp 278 | class MyClass { 279 | 280 | ... 281 | 282 | static std::shared_ptr s_midiOut; 283 | } 284 | ``` 285 | 286 | and in .cpp: 287 | ```cpp 288 | std::shared_ptr MyClass::s_midiOut; 289 | 290 | ... 291 | 292 | // initialize somewhere else 293 | void MyClass::setup() { 294 | if(s_midiOut == NULL) { 295 | s_midiOut = std::shared_ptr(new ofxMidiOut("ofxMidi Client")); 296 | } 297 | } 298 | ``` 299 | 300 | ### ofxMidi classes created in constructors don't seem to work 301 | 302 | This is related to the issue above, in that the ofxMidi classes are being created too early in the app startup process and the back end MIDI library is not being set up correctly. The easiest & best solution is to call the ofxMidi class setup code as part of your ofApp's setup() function, whether there directly or within a subclass. This way you have direct control over when things are happening as opposed to within a constructor which may be called at an arbitrarily early point. 303 | 304 | DEVELOPING 305 | ---------- 306 | 307 | You can help develop ofxMidi on GitHub: [https://github.com/danomatika/ofxMidi](https://github.com/danomatika/ofxMidi) 308 | 309 | Create an account, clone or fork the repo, then request a push/merge. Please use the *develop* branch of updates and pull requests. 310 | 311 | If you find any bugs or suggestions please log them to GitHub as well. 312 | 313 | ### Adding a Midi Back End 314 | 315 | If you want to add a new midi back end (Android, Jack, etc), you'll need two classes derived from ofxBaseMidiIn & ofxBaseMidiOut. 316 | 317 | Place your source files in a new folder named after your platform/library and add new include #ifdef flags to ofxMidiIn.h & ofxMidiIn.cpp. 318 | 319 | Last, you'll need to add specific #ifdef flags to the static port info ofxMidiIn/Out functions (listPorts, getPortName, etc). 320 | 321 | ### Updating Midi Libraries 322 | 323 | RtMidi & PGMidi can be updated by running the `update_rtmidi.sh` or `update_pgmidi.sh` shell scripts in the scripts folder. 324 | 325 | For RtMidi, edit the version setting in the script header and run the script to download and place the RtMidi sources into `libs/rtmidi`. 326 | 327 | PGMidi sources are placed in `libs/pgmidi`. 328 | 329 | #### RtMidi.cpp include 330 | 331 | Next, make sure to add the following include to `RtMidi.cpp` at around line 38 or there will be link errors: 332 | 333 | #include "ofxMidiConstants.h" 334 | --------------------------------------------------------------------------------