├── .gitignore ├── makefile ├── startm.sh ├── midicloro.cfg ├── python ├── mconfig.py └── midicloro.py ├── LICENSE ├── rtmidi ├── readme ├── release.txt ├── RtMidi.h └── RtMidi.cpp ├── README.md └── midicloro.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | midicloro 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: 2 | g++ -Wall -D__LINUX_ALSA__ -o midicloro midicloro.cpp rtmidi/RtMidi.cpp -lasound -lpthread -lboost_system -lboost_program_options -lboost_regex 3 | 4 | run: all 5 | ./midicloro 6 | -------------------------------------------------------------------------------- /startm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $1 4 | while true; do 5 | if ! ps aux | grep -v 'grep' | grep -v 'startm' | grep 'midicloro' ; then 6 | ./midicloro & 7 | else 8 | exit 0 9 | fi 10 | sleep 5s 11 | done 12 | -------------------------------------------------------------------------------- /midicloro.cfg: -------------------------------------------------------------------------------- 1 | input1 = 2 | input2 = 3 | input3 = 4 | input4 = 5 | output = 6 | enableClock = true 7 | startMidiCC = 13 8 | stopMidiCC = 14 9 | ignoreProgramChanges = false 10 | initialBpm = 142 11 | tapTempoMinBpm = 80 12 | tapTempoMaxBpm = 200 13 | bpmOffsetForMidiCC = 70 14 | velocityRandomOffset = -40 15 | velocityMultiDeviceCtrl = true 16 | velocityMidiCC = 7 17 | tempoMidiCC = 10 18 | chordMidiCC = 11 19 | routeMidiCC = 12 20 | -------------------------------------------------------------------------------- /python/mconfig.py: -------------------------------------------------------------------------------- 1 | inputs = [{"name": "E-MU XMidi1X1", "number": 1, "mono": True}, {"name": "iRig Keys", "number": 2, "mono": False}] 2 | output = "E-MU XMidi1X1" 3 | enable_clock = True 4 | ignore_program_changes = False 5 | initial_bpm = 142 6 | tap_tempo_min_bpm = 80 7 | tap_tempo_max_bpm = 200 8 | bpm_offset_for_midi_cc = 70 9 | velocity_random_offset = -40 10 | velocity_multi_device_ctrl = True 11 | velocity_midi_cc = 7 12 | tempo_midi_cc = 10 13 | chord_midi_cc = 11 14 | route_midi_cc = 12 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 David Ramström 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /rtmidi/readme: -------------------------------------------------------------------------------- 1 | RtMidi - a set of C++ classes that provide a common API for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMidi & JACK) and Windows (Multimedia). 2 | 3 | By Gary P. Scavone, 2003-2014. 4 | 5 | This distribution of RtMidi contains the following: 6 | 7 | doc: RtMidi documentation (see doc/html/index.html) 8 | tests: example RtMidi programs 9 | 10 | 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. 11 | 12 | If you checked out the code from git, please run "autoconf" before "./configure". 13 | 14 | OVERVIEW: 15 | 16 | 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: 17 | 18 | - object oriented C++ design 19 | - simple, common API across all supported platforms 20 | - only one header and one source file for easy inclusion in programming projects 21 | - MIDI device enumeration 22 | 23 | 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. 24 | 25 | FURTHER READING: 26 | 27 | For complete documentation on RtMidi, see the doc directory of the distribution or surf to http://music.mcgill.ca/~gary/rtmidi/. 28 | 29 | 30 | LEGAL AND ETHICAL: 31 | 32 | The RtMidi license is similar to the the MIT License, with the added "feature" that modifications be sent to the developer. 33 | 34 | RtMidi: realtime MIDI i/o C++ classes 35 | Copyright (c) 2003-2014 Gary P. Scavone 36 | 37 | Permission is hereby granted, free of charge, to any person 38 | obtaining a copy of this software and associated documentation files 39 | (the "Software"), to deal in the Software without restriction, 40 | including without limitation the rights to use, copy, modify, merge, 41 | publish, distribute, sublicense, and/or sell copies of the Software, 42 | and to permit persons to whom the Software is furnished to do so, 43 | subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be 46 | included in all copies or substantial portions of the Software. 47 | 48 | Any person wishing to distribute modifications to the Software is 49 | asked to send the modifications to the original developer so that 50 | they can be incorporated into the canonical version. This is, 51 | however, not a binding provision of this license. 52 | 53 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 54 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 55 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 56 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 57 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 58 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 59 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 60 | -------------------------------------------------------------------------------- /python/midicloro.py: -------------------------------------------------------------------------------- 1 | import time 2 | import rtmidi 3 | import logging 4 | import sys 5 | import threading 6 | import collections 7 | import mconfig 8 | from rtmidi.midiutil import open_midiport 9 | 10 | 11 | log = logging.getLogger("midicloro") 12 | 13 | send_clock = False 14 | clock_interval = 60 / (mconfig.initial_bpm * 24) 15 | 16 | class Mode(): 17 | def __init__(self, channel): 18 | self.last_note = -1 19 | self.channel_routing = channel 20 | self.chord = 0 21 | self.velocity_mode = 0 22 | self.velocity = 100 23 | self.mono_legato = False 24 | 25 | class InputPort(): 26 | def __init__(self, midiin, number, mono): 27 | self.midiin = midiin 28 | self.number = number 29 | self.mono = mono 30 | self.modes = [Mode(x) for x in range(0, 16)] 31 | 32 | class ClockTimer(threading.Thread): 33 | def __init__(self): 34 | super(ClockTimer, self).__init__() 35 | self.running = True 36 | 37 | def run(self): 38 | global send_clock 39 | while True: 40 | if not self.running: 41 | break 42 | send_clock = True 43 | time.sleep(clock_interval) 44 | 45 | def stop(self): 46 | global send_clock 47 | send_clock = False 48 | self.running = False 49 | 50 | 51 | class MidiDispatcher(threading.Thread): 52 | def __init__(self, input_ports, midiout): 53 | super(MidiDispatcher, self).__init__() 54 | self.input_ports = input_ports 55 | self.midiout = midiout 56 | #self.last_clock = 0 57 | self.tap_tempo_times = collections.deque(maxlen=4) 58 | self.running = True 59 | 60 | def run(self): 61 | global send_clock 62 | while True: 63 | if not self.running: 64 | break 65 | if send_clock: 66 | self.midiout.send_message([248]) 67 | send_clock = False 68 | #now = time.time() 69 | #log.debug("Clock: delta=%0.6f", now - self.last_clock) 70 | #self.last_clock = now 71 | for ip in self.input_ports: 72 | data = ip.midiin.get_message() 73 | if not data: 74 | continue 75 | 76 | msg, deltatime = data 77 | log.debug("Input%s: @%0.6f %r", str(ip.number), deltatime, msg) 78 | 79 | # Tap-tempo 80 | if (msg[0] & 0b11110000) == 0b10110000 and msg[1] == mconfig.tempo_midi_cc: 81 | log.debug("TAP") 82 | 83 | self.midiout.send_message(msg) 84 | 85 | def stop(self): 86 | self.running = False 87 | 88 | 89 | 90 | def main(args=None): 91 | logging.basicConfig(format="%(name)s: %(levelname)s - %(message)s", level=logging.DEBUG) 92 | 93 | try: 94 | input_ports = [] 95 | for ip in mconfig.inputs: 96 | midiin, inport_name = open_midiport(ip["name"], "input") 97 | input_ports.append(InputPort(midiin, ip["number"], ip["mono"])) 98 | 99 | midiout, outport_name = open_midiport(mconfig.output, "output") 100 | except IOError as exc: 101 | print(exc) 102 | return 1 103 | except (EOFError, KeyboardInterrupt): 104 | return 0 105 | 106 | dispatcher = MidiDispatcher(input_ports, midiout) 107 | clock = ClockTimer() 108 | 109 | print("Entering main loop. Press Control-C to exit.") 110 | try: 111 | dispatcher.start() 112 | clock.start() 113 | while True: 114 | time.sleep(1) 115 | except KeyboardInterrupt: 116 | clock.stop() 117 | clock.join() 118 | dispatcher.stop() 119 | dispatcher.join() 120 | print('') 121 | finally: 122 | print("Exit.") 123 | 124 | for ip in input_ports: 125 | ip.midiin.close_port() 126 | del ip.midiin 127 | 128 | midiout.close_port() 129 | del midiout 130 | 131 | return 0 132 | 133 | 134 | if __name__ == '__main__': 135 | sys.exit(main(sys.argv[1:]) or 0) 136 | 137 | -------------------------------------------------------------------------------- /rtmidi/release.txt: -------------------------------------------------------------------------------- 1 | RtMidi - a set of C++ classes that provides a common API for realtime MIDI input/output across Linux (ALSA & JACK), Macintosh OS X (CoreMidi & JACK), and Windows (Multimedia, Kernel Streaming). 2 | 3 | By Gary P. Scavone, 2003-2014 4 | 5 | v2.1.0: (30 March 2014) 6 | - renamed RtError class to RtMidiError and embedded it in RtMidi.h (and deleted RtError.h) 7 | - fix to CoreMidi implementation to support dynamic port changes 8 | - removed global ALSA sequencer objects because they were not thread safe (Martin Koegler) 9 | - fix for ALSA timing ignore flag (Devin Anderson) 10 | - fix for ALSA incorrect use of snd_seq_create_port() function (Tobias Schlemmer) 11 | - fix for international character support in CoreMidi (Martin Finke) 12 | - fix for unicode conversion in WinMM (Dan Wilcox) 13 | - added custom error hook that allows the client to capture an RtMidi error outside of the RtMidi code (Pavel Mogilevskiy) 14 | - added RtMidi::isPortOpen function (Pavel Mogilevskiy) 15 | - updated OS-X sysex sending mechanism to use normal message sending, which fixes a problem where virtual ports didn't receive sysex messages 16 | - Windows update to avoid lockups when shutting down while sending/receiving sysex messages (ptarabbia) 17 | - OS-X fix to avoid empty messages in callbacks when ignoring sysex messages and split sysexes are received (codepainters) 18 | - ALSA openPort fix to better distinguish sender and receiver (Russell Smyth) 19 | - Windows Kernel Streaming support removed because it was uncompilable and incomplete 20 | 21 | v2.0.1: (26 July 2012) 22 | - small fixes for problems reported by Chris Arndt (scoping, preprocessor, and include) 23 | 24 | v2.0.0: (18 June 2012) 25 | - revised structure to support multiple simultaneous compiled APIs 26 | - revised ALSA client hierarchy so subsequent instances share same client (thanks to Dan Wilcox) 27 | - added beta Windows kernel streaming support (thanks to Sebastien Alaiwan) 28 | - updates to compile as a shared library or dll 29 | - updated license 30 | - various memory-leak fixes (thanks to Sebastien Alaiwan and Martin Koegler) 31 | - fix for continue sysex problem (thanks to Luc Deschenaux) 32 | - removed SGI (IRIX) support 33 | 34 | v1.0.15: (11 August 2011) 35 | - updates for wide character support in Windows 36 | - stopped using std::queue and implemented internal MIDI ring buffer (for thread safety ... thanks to Michael Behrman) 37 | - removal of the setQueueSizeLimit() function ... queue size limit now an optional arguement to constructor 38 | 39 | v1.0.14: (17 April 2011) 40 | - bug fix to Jack MIDI support (thanks to Alexander Svetalkin and Pedro Lopez-Cabanillas) 41 | 42 | v1.0.13: (7 April 2011) 43 | - updated RtError.h to the same version as in RtAudio 44 | - new Jack MIDI support in Linux (thanks to Alexander Svetalkin) 45 | 46 | v1.0.12: (17 February 2011) 47 | - Windows 64-bit pointer fixes (thanks to Ward Kockelkorn) 48 | - removed possible exceptions from getPortName() functions 49 | - changed sysex sends in OS-X to use MIDISendSysex() function (thanks to Casey Tucker) 50 | - bug fixes to time code parsing in OS-X and ALSA (thanks to Greg) 51 | - added MSW project file to build as library (into lib/ directory ... thanks to Jason Champion) 52 | 53 | v1.0.11: (29 January 2010) 54 | - added CoreServices/CoreServices.h include for OS-X 10.6 and gcc4.2 compile (thanks to Jon McCormack) 55 | - various increment optimizations (thanks to Paul Dean) 56 | - fixed incorrectly located snd_seq_close() function in ALSA API (thanks to Pedro Lopez-Cabanillas) 57 | - updates to Windows sysex code to better deal with possible delivery problems (thanks to Bastiaan Verreijt) 58 | 59 | v1.0.10: (3 June 2009) 60 | - fix adding timestamp to OS-X sendMessage() function (thanks to John Dey) 61 | 62 | v1.0.9: (30 April 2009) 63 | - added #ifdef AVOID_TIMESTAMPING to conditionally compile support for event timestamping of ALSA sequencer events. This is useful for programs not needing timestamps, saving valuable system resources. 64 | - updated functionality in OSX_CORE for getting driver name (thanks to Casey Tucker) 65 | 66 | v1.0.8: (29 January 2009) 67 | - bug fixes for concatenating segmented sysex messages in ALSA (thanks to Christoph Eckert) 68 | - update to ALSA sequencer port enumeration (thanks to Pedro Lopez-Cabonillas) 69 | - bug fixes for concatenating segmented sysex messages in OS-X (thanks to Emmanuel Litzroth) 70 | - added functionality for naming clients (thanks to Pedro Lopez-Cabonillas and Axel Schmidt) 71 | - bug fix in Windows when receiving sysex messages if the ignore flag was set (thanks to Pedro Lopez-Cabonillas) 72 | 73 | v1.0.7: (7 December 2007) 74 | - configure and Makefile changes for MinGW 75 | - renamed midiinfo.cpp to midiprobe.cpp and updated VC++ project/workspace 76 | 77 | v1.0.6: (9 March 2006) 78 | - bug fix for timestamp problem in ALSA (thanks to Pedro Lopez-Cabanillas) 79 | 80 | v1.0.5: (18 November 2005) 81 | - added optional port name to openVirtualPort() functions 82 | - fixed UNICODE problem in Windows getting device names (thanks Eduardo Coutinho!). 83 | - fixed bug in Windows with respect to getting Sysex data (thanks Jean-Baptiste Berruchon!) 84 | 85 | v1.0.4: (14 October 2005) 86 | - added check for status byte == 0xF8 if ignoring timing messages 87 | - changed pthread attribute to SCHED_OTHER (from SCHED_RR) to avoid thread problem when realtime cababilities are not enabled. 88 | - now using ALSA sequencer time stamp information (thanks to Pedro Lopez-Cabanillas) 89 | - fixed memory leak in ALSA implementation 90 | - now concatenate segmented sysex messages in ALSA 91 | 92 | v1.0.3: (22 November 2004) 93 | - added common pure virtual functions to RtMidi abstract base class 94 | 95 | v1.0.2: (21 September 2004) 96 | - added warning messages to openVirtualPort() functions in Windows and Irix (where it can't be implemented) 97 | 98 | v1.0.1: (20 September 2004) 99 | - changed ALSA preprocessor definition to __LINUX_ALSASEQ__ 100 | 101 | v1.0.0: (17 September 2004) 102 | - first release of new independent class with both input and output functionality 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIDIcloro - MIDI clock generator and router 2 | By David Ramström 3 | 4 | ## Introduction 5 | MIDIcloro turns a Raspberry Pi into a little box of MIDI handling goodness! This is what it does: 6 | * Listens to MIDI data from up to 4 input devices (e.g. sequencers, keyboards, interfaces) and merges it into 1 output. 7 | * Sends MIDI clock (tempo is controlled via MIDI CC). 8 | * Adds effects to MIDI notes (polyphonic chords, velocity and changing of channels - all controlled via MIDI CC). 9 | * Runs stand-alone on the Raspberry Pi. No need for a mouse, keyboard or monitor. Autostart script is included. 10 | * Plain old MIDI connectors (5-pin DIN) are supported by using a USB MIDI interface. Home-made MIDI ports connected to the GPIO will also work as long as ALSA is used. 11 | 12 | 13 | ## Example use-cases 14 | * **Sequencer/keyboard rig**: Add clock, chords, velocity and channel routing to the data sent from a MIDI sequencer. The settings are controlled via knobs on the sequencer. Connect a MIDI keyboard as well to mix notes from the sequencer with some live jamming on the keys. 15 | * **Gameboy as sequencer**: Same scenario as above, but using a Gameboy + Arduinoboy as MIDI sequencer. 16 | * Use LSDJ MIDI out mode and connect Gameboy - Arduinoboy - MIDIcloro input port. 17 | * Clock and other functions can easily be controlled from LSDJ. See *Gameboy examples*. 18 | * **Clock only**: Use MIDIcloro as a master clock. You can connect a MIDI controller to set the tempo with a knob. 19 | 20 | 21 | ## Settings 22 | The config file below gives a good overview of the functions of MIDIcloro. The file is called *midicloro.cfg* and is created when running the initial configuration `./midicloro -c` (see *Initial configuration*). 23 | 24 | ``` 25 | input1 = (inputs and output are detected automatically when running the configuration) 26 | input1mono = true (mono mode: when notes overlap, note off is sent leaving only the last note playing) 27 | input2 = 28 | input3 = 29 | output = 30 | enableClock = false (enable or disable clock) 31 | ignoreProgramChanges = true (ignore or allow incoming program change messages) 32 | initialBpm = 142 (this is the clock tempo used when starting MIDIcloro) 33 | tapTempoMinBpm = 80 (lower limit for tempoMidiCC tapping) 34 | tapTempoMaxBpm = 200 (upper limit for tempoMidiCC tapping) 35 | bpmOffsetForMidiCC = 70 (this offset is added to the tempoMidiCC value to set the tempo) 36 | velocityRandomOffset = -40 (in random velocity mode, notes get a random velocity between the velocityMidiCC value and this offset - set to 0 to get random velocity between 0-127) 37 | velocityMultiDeviceCtrl = true (mirrors the velocity setting of the current input to inputs with lower number) 38 | velocityMidiCC = 7 (MIDI CC number for setting the velocity mode) 39 | tempoMidiCC = 10 (MIDI CC number for setting the tempo) 40 | chordMidiCC = 11 (MIDI CC number for setting the chord mode) 41 | routeMidiCC = 12 (MIDI CC number for setting the channel routing) 42 | ``` 43 | 44 | 45 | ## MIDI clock 46 | There are two ways of setting the clock tempo: 47 | 48 | 1. Send a single *tempo MIDI CC* message to one of the input ports: new tempo = configured offset + *tempo MIDI CC* value. 49 | 2. Send *tempo MIDI CC* messages every beat at the desired tempo (a.k.a. "tapping"), 4 to 8 taps should be enough. The first tap will set the tempo instantly and each of the following taps will recalculate and set the clock tempo to the tapped BPM. 50 | 51 | 52 | ## Chord mode 53 | Each device and MIDI channel has its individual chord mode setting. By setting a chord mode, every incoming note will generate other notes, creating a chord. Chord mode is set via the *chord mode MIDI CC*. The CC value range 0-127 is divided into intervals of 8 to set the following modes: 54 | * OFF 55 | * MINOR3 56 | * MAJOR3 57 | * MINOR3_LO 58 | * MAJOR3_LO 59 | * MINOR2 60 | * MAJOR2 61 | * M7 62 | * MAJ7 63 | * M9 64 | * MAJ9 65 | * SUS4 66 | * POWER2 67 | * POWER3 68 | * OCTAVE2 69 | * OCTAVE3 70 | 71 | 72 | ## Channel routing 73 | Each device and MIDI channel has its own routing setting. Set channel routing via the *routing MIDI CC*. The MIDI CC value range (0-127) is divided into intervals of 8, where 0-7 sets channel 1, 8-15 channel 2 and so on. By changing the channel routing, the current channel is changed to another of the 16 MIDI channels. Notes, CC messages and other MIDI data with channel will be routed to the new target channel. 74 | 75 | 76 | ## Velocity mode 77 | Each device and MIDI channel has a velocity setting set via the *velocity MIDI CC* as follows: 78 | * CC value=0 => OFF (velocity from the input device is unchanged) 79 | * CC value=1-126 (all notes get velocity=value) 80 | * CC value=127 (toggles random velocity mode on/off) 81 | 82 | **Random velocity mode**: Send CC value=127 once to activate, and again to disable. When active, all notes get a random velocity between the CC value and the *velocityRandomOffset*. 83 | 84 | **Scaling**: Velocity is scaled to squeeze the whole 0-127 range in between CC values 8-120 to allow reaching max/min velocity without accidentally toggling ON/OFF/RANDOM. 85 | 86 | **Mirroring**: The velocity setting is mirrored to all other input ports with a number lower than the current port. The mirroring can be turned off in the settings by setting *velocityMultiDeviceCtrl* to false. 87 | 88 | 89 | ## Mono mode 90 | By enabling mono mode for an input port (see *Settings*) only 1 note can play at the same time. MIDIcloro sends note-off when notes overlap. 91 | * Retrig is the default mono mode behavior (note-off first, then note-on). 92 | * Legato (note-on first, then note-off for the old note) can be enabled by sending *chord mode MIDI CC* value 0-7 when chord mode already is OFF (another value 0-7 toggles back to retrig). The *chord mode CC* is used here to spare another CC from being occupied by MIDIcloro. 93 | 94 | 95 | ## Gameboy examples 96 | 97 | MIDIcloro is controlled from LSDJ via CC (the X command). Examples: 98 | 99 | ``` 100 | X32 - Activates velocity mode: all MIDI notes on the current channel will get a low velocity. 101 | X36 - Increases the velocity. 102 | X3F - Enables random velocity mode: every note gets a random velocity between the last velocity setting (X36 above) and the configured offset (-40 by default). 103 | X3D - High velocity, random velocity mode still active. 104 | X31 - Low velocity, random velocity mode still active. 105 | X3F - Disables random velocity mode. 106 | X30 - Disables velocity mode: velocity is not changed by MIDIcloro anymore (velocity from the Gameboy/Arduinoboy is 100 by default). 107 | 108 | X4A - If sent once: sets clock tempo to 150 (offset 70 + value 80 = 150). 109 | X4A - If sent for e.g. 4 beats in a row (RECOMMENDED): sets clock tempo to 150 the first time and calculates the tempo according to the interval between the messages the following times. 110 | 111 | X51 - Every note sent on the current channel will generate three MIDI notes creating a minor chord. 112 | X52 - Same as above but generating a major chord. 113 | X53 - Same as above but generating a minor-low chord. 114 | X54 - Same as above but generating a major-low chord. 115 | X50 - Disables the chord mode. 116 | 117 | X60 - Route MIDI data sent on the current channel to MIDI channel 1. 118 | X64 - Route MIDI data sent on the current channel to MIDI channel 5. 119 | X6F - Route MIDI data sent on the current channel to MIDI channel 16. 120 | ``` 121 | 122 | 123 | ## Installation 124 | Prepare Raspberry Pi: 125 | * Flash a "Lite" version of Raspbian to an SD card (no desktop or GUI functions are needed). 126 | * Start the Raspberry Pi and log in as the default user "pi" (use SSH or monitor+keyboard). 127 | 128 | 129 | Install dependencies: 130 | 131 | ``` 132 | sudo apt-get install libasound2-dev 133 | sudo apt-get install libboost-system-dev 134 | sudo apt-get install libboost-program-options-dev 135 | sudo apt-get install libboost-regex-dev 136 | ``` 137 | 138 | Download the MIDIcloro binary and make it executable: 139 | 140 | ``` 141 | wget https://github.com/ledfyr/midicloro/releases/download/v1.5/midicloro 142 | chmod +x midicloro 143 | ``` 144 | 145 | 146 | ## Initial configuration 147 | Connect your devices and start the configuration: 148 | 149 | `./midicloro -c` 150 | 151 | Available input and output ports will be listed and you will be prompted to select which ones to use. You will then be prompted for the rest of the parameters listed under *Settings*. 152 | 153 | When the configuration is finished, a config file is created (see *Settings*). This file can be edited manually (e.g. using nano). Changes take effect when restarting MIDIcloro. 154 | 155 | It is also possible to change settings by running the configuration again using `./midicloro -c`. However, if the MIDIcloro process is running in the background, shut it down before running the configuration. Find the process ID with `ps aux | grep midicloro` and shut it down with `sudo kill process_id_goes_here`. 156 | 157 | 158 | ## Run 159 | To run MIDIcloro with the current settings: 160 | 161 | `./midicloro` 162 | 163 | 164 | ## Autostart 165 | Follow these instructions if you want to start MIDIcloro automatically when the Raspberry Pi starts up. 166 | 167 | Download the auto-start script and make it executable: 168 | 169 | ``` 170 | wget https://github.com/ledfyr/midicloro/releases/download/v1.5/startm.sh 171 | chmod +x startm.sh 172 | ``` 173 | 174 | If you haven't done so already, configure MIDIcloro with the inputs/output and settings you want to use and verify that it works (see *Initial configuration* and *Run*). 175 | 176 | In the file `/etc/rc.local`, add a call to the startm.sh script with the path to midicloro as parameter. Place it before the last exit command. IMPORTANT - add a `&` to let startm.sh run as a background process, otherwise your system may hang on boot. 177 | 178 | Example (midicloro and startm.sh are downloaded to /home/pi): 179 | 180 | `sudo nano /etc/rc.local` 181 | 182 | Add the following line above `exit 0` in the bottom of the file. Do not forget the `&`. 183 | 184 | `/home/pi/startm.sh /home/pi &` 185 | 186 | MIDIcloro will now start when booting up, without the need of logging in. You are now running MIDIcloro stand-alone! 187 | 188 | If the program fails to start automatically, try to run it as: 189 | 190 | `sudo ./midicloro` 191 | 192 | If you get errors regarding missing libraries, make sure that the `libasound2-dev` and `libboost` libraries exist in `/usr/lib/`. 193 | 194 | 195 | ## Build and compile 196 | Install the dependencies (see *Installation*). Also make sure g++ is installed. 197 | 198 | Compile MIDIcloro with `make` or the following command: 199 | 200 | `g++ -Wall -D__LINUX_ALSA__ -o midicloro midicloro.cpp rtmidi/RtMidi.cpp -lasound -lpthread -lboost_system -lboost_program_options -lboost_regex` 201 | 202 | 203 | ## Supported USB MIDI devices 204 | Any class compliant device should work. Please contact me if you find any working/non-working device not listed here and I will update the list. 205 | 206 | Known working devices: 207 | * IK Multimedia iRig Keys 208 | * E-MU XMidi 1X1 209 | 210 | Known problematic devices (not working out-of-the-box): 211 | * M-Audio Midisport Uno 212 | 213 | 214 | ## Other info 215 | MIDIcloro is built and tested on a Raspberry Pi 1 Model B+ running Raspbian Jessie Lite. 216 | 217 | MIDIcloro uses RtMidi to handle the MIDI communication. Many thanks to Gary P. Scavone for creating this great framework. See rtmidi/readme for license and other info regarding RtMidi. 218 | 219 | 220 | ## Contact 221 | Ledfyr at chipmusic.org 222 | 223 | david.ramstrom (at) gmail \_dot\_ com 224 | 225 | 226 | ## License 227 | MIDIcloro is licensed under the MIT license, see the LICENSE file for details. 228 | 229 | -------------------------------------------------------------------------------- /rtmidi/RtMidi.h: -------------------------------------------------------------------------------- 1 | /**********************************************************************/ 2 | /*! \class RtMidi 3 | \brief An abstract base class for realtime MIDI input/output. 4 | 5 | This class implements some common functionality for the realtime 6 | MIDI input/output subclasses RtMidiIn and RtMidiOut. 7 | 8 | RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ 9 | 10 | RtMidi: realtime MIDI i/o C++ classes 11 | Copyright (c) 2003-2014 Gary P. Scavone 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation files 15 | (the "Software"), to deal in the Software without restriction, 16 | including without limitation the rights to use, copy, modify, merge, 17 | publish, distribute, sublicense, and/or sell copies of the Software, 18 | and to permit persons to whom the Software is furnished to do so, 19 | subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | Any person wishing to distribute modifications to the Software is 25 | asked to send the modifications to the original developer so that 26 | they can be incorporated into the canonical version. This is, 27 | however, not a binding provision of this license. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 31 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 32 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 33 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 34 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 35 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 36 | */ 37 | /**********************************************************************/ 38 | 39 | /*! 40 | \file RtMidi.h 41 | */ 42 | 43 | #ifndef RTMIDI_H 44 | #define RTMIDI_H 45 | 46 | #define RTMIDI_VERSION "2.1.0" 47 | 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | /************************************************************************/ 54 | /*! \class RtMidiError 55 | \brief Exception handling class for RtMidi. 56 | 57 | The RtMidiError class is quite simple but it does allow errors to be 58 | "caught" by RtMidiError::Type. See the RtMidi documentation to know 59 | which methods can throw an RtMidiError. 60 | */ 61 | /************************************************************************/ 62 | 63 | class RtMidiError : public std::exception 64 | { 65 | public: 66 | //! Defined RtMidiError types. 67 | enum Type { 68 | WARNING, /*!< A non-critical error. */ 69 | DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ 70 | UNSPECIFIED, /*!< The default, unspecified error type. */ 71 | NO_DEVICES_FOUND, /*!< No devices found on system. */ 72 | INVALID_DEVICE, /*!< An invalid device ID was specified. */ 73 | MEMORY_ERROR, /*!< An error occured during memory allocation. */ 74 | INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ 75 | INVALID_USE, /*!< The function was called incorrectly. */ 76 | DRIVER_ERROR, /*!< A system driver error occured. */ 77 | SYSTEM_ERROR, /*!< A system error occured. */ 78 | THREAD_ERROR /*!< A thread error occured. */ 79 | }; 80 | 81 | //! The constructor. 82 | RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {} 83 | 84 | //! The destructor. 85 | virtual ~RtMidiError( void ) throw() {} 86 | 87 | //! Prints thrown error message to stderr. 88 | virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } 89 | 90 | //! Returns the thrown error message type. 91 | virtual const Type& getType(void) const throw() { return type_; } 92 | 93 | //! Returns the thrown error message string. 94 | virtual const std::string& getMessage(void) const throw() { return message_; } 95 | 96 | //! Returns the thrown error message as a c-style string. 97 | virtual const char* what( void ) const throw() { return message_.c_str(); } 98 | 99 | protected: 100 | std::string message_; 101 | Type type_; 102 | }; 103 | 104 | //! RtMidi error callback function prototype. 105 | /*! 106 | \param type Type of error. 107 | \param errorText Error description. 108 | 109 | Note that class behaviour is undefined after a critical error (not 110 | a warning) is reported. 111 | */ 112 | typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText ); 113 | 114 | class MidiApi; 115 | 116 | class RtMidi 117 | { 118 | public: 119 | 120 | //! MIDI API specifier arguments. 121 | enum Api { 122 | UNSPECIFIED, /*!< Search for a working compiled API. */ 123 | MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */ 124 | LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ 125 | UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ 126 | WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ 127 | RTMIDI_DUMMY /*!< A compilable but non-functional API. */ 128 | }; 129 | 130 | //! A static function to determine the current RtMidi version. 131 | static std::string getVersion( void ) throw(); 132 | 133 | //! A static function to determine the available compiled MIDI APIs. 134 | /*! 135 | The values returned in the std::vector can be compared against 136 | the enumerated list values. Note that there can be more than one 137 | API compiled for certain operating systems. 138 | */ 139 | static void getCompiledApi( std::vector &apis ) throw(); 140 | 141 | //! Pure virtual openPort() function. 142 | virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0; 143 | 144 | //! Pure virtual openVirtualPort() function. 145 | virtual void openVirtualPort( const std::string portName = std::string( "RtMidi" ) ) = 0; 146 | 147 | //! Pure virtual getPortCount() function. 148 | virtual unsigned int getPortCount() = 0; 149 | 150 | //! Pure virtual getPortName() function. 151 | virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; 152 | 153 | //! Pure virtual closePort() function. 154 | virtual void closePort( void ) = 0; 155 | 156 | //! Returns true if a port is open and false if not. 157 | virtual bool isPortOpen( void ) const = 0; 158 | 159 | //! Set an error callback function to be invoked when an error has occured. 160 | /*! 161 | The callback function will be called whenever an error has occured. It is best 162 | to set the error callback function before opening a port. 163 | */ 164 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ) = 0; 165 | 166 | protected: 167 | 168 | RtMidi(); 169 | virtual ~RtMidi(); 170 | 171 | MidiApi *rtapi_; 172 | }; 173 | 174 | /**********************************************************************/ 175 | /*! \class RtMidiIn 176 | \brief A realtime MIDI input class. 177 | 178 | This class provides a common, platform-independent API for 179 | realtime MIDI input. It allows access to a single MIDI input 180 | port. Incoming MIDI messages are either saved to a queue for 181 | retrieval using the getMessage() function or immediately passed to 182 | a user-specified callback function. Create multiple instances of 183 | this class to connect to more than one MIDI device at the same 184 | time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also 185 | possible to open a virtual input port to which other MIDI software 186 | clients can connect. 187 | 188 | by Gary P. Scavone, 2003-2014. 189 | */ 190 | /**********************************************************************/ 191 | 192 | // **************************************************************** // 193 | // 194 | // RtMidiIn and RtMidiOut class declarations. 195 | // 196 | // RtMidiIn / RtMidiOut are "controllers" used to select an available 197 | // MIDI input or output interface. They present common APIs for the 198 | // user to call but all functionality is implemented by the classes 199 | // MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut 200 | // each create an instance of a MidiInApi or MidiOutApi subclass based 201 | // on the user's API choice. If no choice is made, they attempt to 202 | // make a "logical" API selection. 203 | // 204 | // **************************************************************** // 205 | 206 | class RtMidiIn : public RtMidi 207 | { 208 | public: 209 | 210 | //! User callback function type definition. 211 | typedef void (*RtMidiCallback)( double timeStamp, std::vector *message, void *userData); 212 | 213 | //! Default constructor that allows an optional api, client name and queue size. 214 | /*! 215 | An exception will be thrown if a MIDI system initialization 216 | error occurs. The queue size defines the maximum number of 217 | messages that can be held in the MIDI queue (when not using a 218 | callback function). If the queue size limit is reached, 219 | incoming messages will be ignored. 220 | 221 | If no API argument is specified and multiple API support has been 222 | compiled, the default order of use is ALSA, JACK (Linux) and CORE, 223 | JACK (OS-X). 224 | 225 | \param api An optional API id can be specified. 226 | \param clientName An optional client name can be specified. This 227 | will be used to group the ports that are created 228 | by the application. 229 | \param queueSizeLimit An optional size of the MIDI input queue can be specified. 230 | */ 231 | RtMidiIn( RtMidi::Api api=UNSPECIFIED, 232 | const std::string clientName = std::string( "RtMidi Input Client"), 233 | unsigned int queueSizeLimit = 100 ); 234 | 235 | //! If a MIDI connection is still open, it will be closed by the destructor. 236 | ~RtMidiIn ( void ) throw(); 237 | 238 | //! Returns the MIDI API specifier for the current instance of RtMidiIn. 239 | RtMidi::Api getCurrentApi( void ) throw(); 240 | 241 | //! Open a MIDI input connection given by enumeration number. 242 | /*! 243 | \param portNumber An optional port number greater than 0 can be specified. 244 | Otherwise, the default or first port found is opened. 245 | \param portName An optional name for the application port that is used to connect to portId can be specified. 246 | */ 247 | void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) ); 248 | 249 | //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). 250 | /*! 251 | This function creates a virtual MIDI input port to which other 252 | software applications can connect. This type of functionality 253 | is currently only supported by the Macintosh OS-X, any JACK, 254 | and Linux ALSA APIs (the function returns an error for the other APIs). 255 | 256 | \param portName An optional name for the application port that is 257 | used to connect to portId can be specified. 258 | */ 259 | void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) ); 260 | 261 | //! Set a callback function to be invoked for incoming MIDI messages. 262 | /*! 263 | The callback function will be called whenever an incoming MIDI 264 | message is received. While not absolutely necessary, it is best 265 | to set the callback function before opening a MIDI port to avoid 266 | leaving some messages in the queue. 267 | 268 | \param callback A callback function must be given. 269 | \param userData Optionally, a pointer to additional data can be 270 | passed to the callback function whenever it is called. 271 | */ 272 | void setCallback( RtMidiCallback callback, void *userData = 0 ); 273 | 274 | //! Cancel use of the current callback function (if one exists). 275 | /*! 276 | Subsequent incoming MIDI messages will be written to the queue 277 | and can be retrieved with the \e getMessage function. 278 | */ 279 | void cancelCallback(); 280 | 281 | //! Close an open MIDI connection (if one exists). 282 | void closePort( void ); 283 | 284 | //! Returns true if a port is open and false if not. 285 | virtual bool isPortOpen() const; 286 | 287 | //! Return the number of available MIDI input ports. 288 | /*! 289 | \return This function returns the number of MIDI ports of the selected API. 290 | */ 291 | unsigned int getPortCount(); 292 | 293 | //! Return a string identifier for the specified MIDI input port number. 294 | /*! 295 | \return The name of the port with the given Id is returned. 296 | \retval An empty string is returned if an invalid port specifier is provided. 297 | */ 298 | std::string getPortName( unsigned int portNumber = 0 ); 299 | 300 | //! Specify whether certain MIDI message types should be queued or ignored during input. 301 | /*! 302 | By default, MIDI timing and active sensing messages are ignored 303 | during message input because of their relative high data rates. 304 | MIDI sysex messages are ignored by default as well. Variable 305 | values of "true" imply that the respective message type will be 306 | ignored. 307 | */ 308 | void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true ); 309 | 310 | //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. 311 | /*! 312 | This function returns immediately whether a new message is 313 | available or not. A valid message is indicated by a non-zero 314 | vector size. An exception is thrown if an error occurs during 315 | message retrieval or an input connection was not previously 316 | established. 317 | */ 318 | double getMessage( std::vector *message ); 319 | 320 | //! Set an error callback function to be invoked when an error has occured. 321 | /*! 322 | The callback function will be called whenever an error has occured. It is best 323 | to set the error callback function before opening a port. 324 | */ 325 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ); 326 | 327 | protected: 328 | void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ); 329 | 330 | }; 331 | 332 | /**********************************************************************/ 333 | /*! \class RtMidiOut 334 | \brief A realtime MIDI output class. 335 | 336 | This class provides a common, platform-independent API for MIDI 337 | output. It allows one to probe available MIDI output ports, to 338 | connect to one such port, and to send MIDI bytes immediately over 339 | the connection. Create multiple instances of this class to 340 | connect to more than one MIDI device at the same time. With the 341 | OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a 342 | virtual port to which other MIDI software clients can connect. 343 | 344 | by Gary P. Scavone, 2003-2014. 345 | */ 346 | /**********************************************************************/ 347 | 348 | class RtMidiOut : public RtMidi 349 | { 350 | public: 351 | 352 | //! Default constructor that allows an optional client name. 353 | /*! 354 | An exception will be thrown if a MIDI system initialization error occurs. 355 | 356 | If no API argument is specified and multiple API support has been 357 | compiled, the default order of use is ALSA, JACK (Linux) and CORE, 358 | JACK (OS-X). 359 | */ 360 | RtMidiOut( RtMidi::Api api=UNSPECIFIED, 361 | const std::string clientName = std::string( "RtMidi Output Client") ); 362 | 363 | //! The destructor closes any open MIDI connections. 364 | ~RtMidiOut( void ) throw(); 365 | 366 | //! Returns the MIDI API specifier for the current instance of RtMidiOut. 367 | RtMidi::Api getCurrentApi( void ) throw(); 368 | 369 | //! Open a MIDI output connection. 370 | /*! 371 | An optional port number greater than 0 can be specified. 372 | Otherwise, the default or first port found is opened. An 373 | exception is thrown if an error occurs while attempting to make 374 | the port connection. 375 | */ 376 | void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) ); 377 | 378 | //! Close an open MIDI connection (if one exists). 379 | void closePort( void ); 380 | 381 | //! Returns true if a port is open and false if not. 382 | virtual bool isPortOpen() const; 383 | 384 | //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). 385 | /*! 386 | This function creates a virtual MIDI output port to which other 387 | software applications can connect. This type of functionality 388 | is currently only supported by the Macintosh OS-X, Linux ALSA 389 | and JACK APIs (the function does nothing with the other APIs). 390 | An exception is thrown if an error occurs while attempting to 391 | create the virtual port. 392 | */ 393 | void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) ); 394 | 395 | //! Return the number of available MIDI output ports. 396 | unsigned int getPortCount( void ); 397 | 398 | //! Return a string identifier for the specified MIDI port type and number. 399 | /*! 400 | An empty string is returned if an invalid port specifier is provided. 401 | */ 402 | std::string getPortName( unsigned int portNumber = 0 ); 403 | 404 | //! Immediately send a single message out an open MIDI output port. 405 | /*! 406 | An exception is thrown if an error occurs during output or an 407 | output connection was not previously established. 408 | */ 409 | void sendMessage( std::vector *message ); 410 | 411 | //! Set an error callback function to be invoked when an error has occured. 412 | /*! 413 | The callback function will be called whenever an error has occured. It is best 414 | to set the error callback function before opening a port. 415 | */ 416 | virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL ); 417 | 418 | protected: 419 | void openMidiApi( RtMidi::Api api, const std::string clientName ); 420 | }; 421 | 422 | 423 | // **************************************************************** // 424 | // 425 | // MidiInApi / MidiOutApi class declarations. 426 | // 427 | // Subclasses of MidiInApi and MidiOutApi contain all API- and 428 | // OS-specific code necessary to fully implement the RtMidi API. 429 | // 430 | // Note that MidiInApi and MidiOutApi are abstract base classes and 431 | // cannot be explicitly instantiated. RtMidiIn and RtMidiOut will 432 | // create instances of a MidiInApi or MidiOutApi subclass. 433 | // 434 | // **************************************************************** // 435 | 436 | class MidiApi 437 | { 438 | public: 439 | 440 | MidiApi(); 441 | virtual ~MidiApi(); 442 | virtual RtMidi::Api getCurrentApi( void ) = 0; 443 | virtual void openPort( unsigned int portNumber, const std::string portName ) = 0; 444 | virtual void openVirtualPort( const std::string portName ) = 0; 445 | virtual void closePort( void ) = 0; 446 | 447 | virtual unsigned int getPortCount( void ) = 0; 448 | virtual std::string getPortName( unsigned int portNumber ) = 0; 449 | 450 | inline bool isPortOpen() const { return connected_; } 451 | void setErrorCallback( RtMidiErrorCallback errorCallback ); 452 | 453 | //! A basic error reporting function for RtMidi classes. 454 | void error( RtMidiError::Type type, std::string errorString ); 455 | 456 | protected: 457 | virtual void initialize( const std::string& clientName ) = 0; 458 | 459 | void *apiData_; 460 | bool connected_; 461 | std::string errorString_; 462 | RtMidiErrorCallback errorCallback_; 463 | }; 464 | 465 | class MidiInApi : public MidiApi 466 | { 467 | public: 468 | 469 | MidiInApi( unsigned int queueSizeLimit ); 470 | virtual ~MidiInApi( void ); 471 | void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); 472 | void cancelCallback( void ); 473 | virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); 474 | double getMessage( std::vector *message ); 475 | 476 | // A MIDI structure used internally by the class to store incoming 477 | // messages. Each message represents one and only one MIDI message. 478 | struct MidiMessage { 479 | std::vector bytes; 480 | double timeStamp; 481 | 482 | // Default constructor. 483 | MidiMessage() 484 | :bytes(0), timeStamp(0.0) {} 485 | }; 486 | 487 | struct MidiQueue { 488 | unsigned int front; 489 | unsigned int back; 490 | unsigned int size; 491 | unsigned int ringSize; 492 | MidiMessage *ring; 493 | 494 | // Default constructor. 495 | MidiQueue() 496 | :front(0), back(0), size(0), ringSize(0) {} 497 | }; 498 | 499 | // The RtMidiInData structure is used to pass private class data to 500 | // the MIDI input handling function or thread. 501 | struct RtMidiInData { 502 | MidiQueue queue; 503 | MidiMessage message; 504 | unsigned char ignoreFlags; 505 | bool doInput; 506 | bool firstMessage; 507 | void *apiData; 508 | bool usingCallback; 509 | RtMidiIn::RtMidiCallback userCallback; 510 | void *userData; 511 | bool continueSysex; 512 | 513 | // Default constructor. 514 | RtMidiInData() 515 | : ignoreFlags(7), doInput(false), firstMessage(true), 516 | apiData(0), usingCallback(false), userCallback(0), userData(0), 517 | continueSysex(false) {} 518 | }; 519 | 520 | protected: 521 | RtMidiInData inputData_; 522 | }; 523 | 524 | class MidiOutApi : public MidiApi 525 | { 526 | public: 527 | 528 | MidiOutApi( void ); 529 | virtual ~MidiOutApi( void ); 530 | virtual void sendMessage( std::vector *message ) = 0; 531 | }; 532 | 533 | // **************************************************************** // 534 | // 535 | // Inline RtMidiIn and RtMidiOut definitions. 536 | // 537 | // **************************************************************** // 538 | 539 | inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } 540 | inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } 541 | inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } 542 | inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } 543 | inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } 544 | inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); } 545 | inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); } 546 | inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } 547 | inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } 548 | inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } 549 | inline double RtMidiIn :: getMessage( std::vector *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); } 550 | inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } 551 | 552 | inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } 553 | inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); } 554 | inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); } 555 | inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } 556 | inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } 557 | inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } 558 | inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } 559 | inline void RtMidiOut :: sendMessage( std::vector *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); } 560 | inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback ) { rtapi_->setErrorCallback(errorCallback); } 561 | 562 | // **************************************************************** // 563 | // 564 | // MidiInApi and MidiOutApi subclass prototypes. 565 | // 566 | // **************************************************************** // 567 | 568 | #if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) 569 | #define __RTMIDI_DUMMY__ 570 | #endif 571 | 572 | #if defined(__MACOSX_CORE__) 573 | 574 | class MidiInCore: public MidiInApi 575 | { 576 | public: 577 | MidiInCore( const std::string clientName, unsigned int queueSizeLimit ); 578 | ~MidiInCore( void ); 579 | RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 580 | void openPort( unsigned int portNumber, const std::string portName ); 581 | void openVirtualPort( const std::string portName ); 582 | void closePort( void ); 583 | unsigned int getPortCount( void ); 584 | std::string getPortName( unsigned int portNumber ); 585 | 586 | protected: 587 | void initialize( const std::string& clientName ); 588 | }; 589 | 590 | class MidiOutCore: public MidiOutApi 591 | { 592 | public: 593 | MidiOutCore( const std::string clientName ); 594 | ~MidiOutCore( void ); 595 | RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; 596 | void openPort( unsigned int portNumber, const std::string portName ); 597 | void openVirtualPort( const std::string portName ); 598 | void closePort( void ); 599 | unsigned int getPortCount( void ); 600 | std::string getPortName( unsigned int portNumber ); 601 | void sendMessage( std::vector *message ); 602 | 603 | protected: 604 | void initialize( const std::string& clientName ); 605 | }; 606 | 607 | #endif 608 | 609 | #if defined(__UNIX_JACK__) 610 | 611 | class MidiInJack: public MidiInApi 612 | { 613 | public: 614 | MidiInJack( const std::string clientName, unsigned int queueSizeLimit ); 615 | ~MidiInJack( void ); 616 | RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 617 | void openPort( unsigned int portNumber, const std::string portName ); 618 | void openVirtualPort( const std::string portName ); 619 | void closePort( void ); 620 | unsigned int getPortCount( void ); 621 | std::string getPortName( unsigned int portNumber ); 622 | 623 | protected: 624 | std::string clientName; 625 | 626 | void connect( void ); 627 | void initialize( const std::string& clientName ); 628 | }; 629 | 630 | class MidiOutJack: public MidiOutApi 631 | { 632 | public: 633 | MidiOutJack( const std::string clientName ); 634 | ~MidiOutJack( void ); 635 | RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; 636 | void openPort( unsigned int portNumber, const std::string portName ); 637 | void openVirtualPort( const std::string portName ); 638 | void closePort( void ); 639 | unsigned int getPortCount( void ); 640 | std::string getPortName( unsigned int portNumber ); 641 | void sendMessage( std::vector *message ); 642 | 643 | protected: 644 | std::string clientName; 645 | 646 | void connect( void ); 647 | void initialize( const std::string& clientName ); 648 | }; 649 | 650 | #endif 651 | 652 | #if defined(__LINUX_ALSA__) 653 | 654 | class MidiInAlsa: public MidiInApi 655 | { 656 | public: 657 | MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ); 658 | ~MidiInAlsa( void ); 659 | RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 660 | void openPort( unsigned int portNumber, const std::string portName ); 661 | void openVirtualPort( const std::string portName ); 662 | void closePort( void ); 663 | unsigned int getPortCount( void ); 664 | std::string getPortName( unsigned int portNumber ); 665 | 666 | protected: 667 | void initialize( const std::string& clientName ); 668 | }; 669 | 670 | class MidiOutAlsa: public MidiOutApi 671 | { 672 | public: 673 | MidiOutAlsa( const std::string clientName ); 674 | ~MidiOutAlsa( void ); 675 | RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; 676 | void openPort( unsigned int portNumber, const std::string portName ); 677 | void openVirtualPort( const std::string portName ); 678 | void closePort( void ); 679 | unsigned int getPortCount( void ); 680 | std::string getPortName( unsigned int portNumber ); 681 | void sendMessage( std::vector *message ); 682 | 683 | protected: 684 | void initialize( const std::string& clientName ); 685 | }; 686 | 687 | #endif 688 | 689 | #if defined(__WINDOWS_MM__) 690 | 691 | class MidiInWinMM: public MidiInApi 692 | { 693 | public: 694 | MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ); 695 | ~MidiInWinMM( void ); 696 | RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 697 | void openPort( unsigned int portNumber, const std::string portName ); 698 | void openVirtualPort( const std::string portName ); 699 | void closePort( void ); 700 | unsigned int getPortCount( void ); 701 | std::string getPortName( unsigned int portNumber ); 702 | 703 | protected: 704 | void initialize( const std::string& clientName ); 705 | }; 706 | 707 | class MidiOutWinMM: public MidiOutApi 708 | { 709 | public: 710 | MidiOutWinMM( const std::string clientName ); 711 | ~MidiOutWinMM( void ); 712 | RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; 713 | void openPort( unsigned int portNumber, const std::string portName ); 714 | void openVirtualPort( const std::string portName ); 715 | void closePort( void ); 716 | unsigned int getPortCount( void ); 717 | std::string getPortName( unsigned int portNumber ); 718 | void sendMessage( std::vector *message ); 719 | 720 | protected: 721 | void initialize( const std::string& clientName ); 722 | }; 723 | 724 | #endif 725 | 726 | #if defined(__RTMIDI_DUMMY__) 727 | 728 | class MidiInDummy: public MidiInApi 729 | { 730 | public: 731 | MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 732 | RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 733 | void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} 734 | void openVirtualPort( const std::string /*portName*/ ) {} 735 | void closePort( void ) {} 736 | unsigned int getPortCount( void ) { return 0; } 737 | std::string getPortName( unsigned int portNumber ) { return ""; } 738 | 739 | protected: 740 | void initialize( const std::string& /*clientName*/ ) {} 741 | }; 742 | 743 | class MidiOutDummy: public MidiOutApi 744 | { 745 | public: 746 | MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } 747 | RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } 748 | void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {} 749 | void openVirtualPort( const std::string /*portName*/ ) {} 750 | void closePort( void ) {} 751 | unsigned int getPortCount( void ) { return 0; } 752 | std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } 753 | void sendMessage( std::vector * /*message*/ ) {} 754 | 755 | protected: 756 | void initialize( const std::string& /*clientName*/ ) {} 757 | }; 758 | 759 | #endif 760 | 761 | #endif 762 | -------------------------------------------------------------------------------- /midicloro.cpp: -------------------------------------------------------------------------------- 1 | //************** MIDIcloro ************** 2 | // 3 | // MIDI clock generator and router 4 | // 5 | // Copyright (c) 2015 David Ramstrom 6 | // This project is licensed under the terms of the MIT license 7 | // 8 | // Run: 9 | // ./midicloro [-c to start interactive configuration] 10 | // 11 | //*************************************** 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include "rtmidi/RtMidi.h" 29 | 30 | using namespace std; 31 | 32 | namespace po = boost::program_options; 33 | namespace convert { 34 | template string to_string(const T& n) { 35 | ostringstream stm; 36 | stm << n; 37 | return stm.str(); 38 | } 39 | } 40 | 41 | enum Chord { 42 | CHORD_OFF = 0, 43 | MINOR3, 44 | MAJOR3, 45 | MINOR3_LO, 46 | MAJOR3_LO, 47 | MINOR2, 48 | MAJOR2, 49 | M7, 50 | MAJ7, 51 | M9, 52 | MAJ9, 53 | SUS4, 54 | POWER2, 55 | POWER3, 56 | OCTAVE2, 57 | OCTAVE3 58 | }; 59 | 60 | enum Velo { 61 | VEL_OFF, 62 | VEL_ON, 63 | VEL_RDM 64 | }; 65 | 66 | RtMidiIn *midiin1 = 0; 67 | RtMidiIn *midiin2 = 0; 68 | RtMidiIn *midiin3 = 0; 69 | RtMidiIn *midiin4 = 0; 70 | RtMidiOut *midiout = 0; 71 | bool done; 72 | bool enableClock; 73 | bool resetClock; 74 | bool ignoreProgramChanges; 75 | int tempoMidiCC; 76 | int chordMidiCC; 77 | int routeMidiCC; 78 | int startMidiCC; 79 | int stopMidiCC; 80 | int velocityMidiCC; 81 | int bpmOffsetForMidiCC; 82 | long clockInterval; // Clock interval in ns 83 | long tapTempoMinInterval; // Tap-tempo min interval in ns 84 | long tapTempoMaxInterval; // Tap-tempo max interval in ns 85 | int velocityRandomOffset; 86 | bool velocityMultiDeviceCtrl; 87 | boost::mt19937 *randomGenerator; 88 | vector *clockMessage; 89 | vector *clockStartMessage; 90 | vector *clockStopMessage; 91 | vector *noteOffMessage; 92 | int lastNote[4][16] = {{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, 93 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, 94 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, 95 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}}; 96 | int channelRouting[4][16] = {{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, 97 | {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, 98 | {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}, 99 | {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}}; 100 | int chordModes[4][16] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 101 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 102 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 103 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; 104 | int velocityModes[4][16] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 105 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 106 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, 107 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}; 108 | int velocity[4][16] = {{100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100}, 109 | {100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100}, 110 | {100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100}, 111 | {100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100}}; 112 | bool mono[4] = {false,false,false,false}; 113 | bool monoLegato[4][16] = {{false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}, 114 | {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}, 115 | {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}, 116 | {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false}}; 117 | boost::circular_buffer *tapTempoTimes; 118 | const char *CONFIG_FILE = "midicloro.cfg"; 119 | 120 | void usage(void); 121 | static void finish( int /*ignore*/ ){ done = true; } 122 | double random01(); 123 | bool ignoreMessage(unsigned char msgByte); 124 | void transposeAndSend(vector *message, int semiNotes); 125 | void sendNoteOrChord(vector *message, int source); 126 | void sendNoteOffAndNote(vector *message, int source); 127 | void setChordMode(int source, int channel, int value); 128 | void routeChannel(vector *message, int source); 129 | void setChannelRouting(int source, int channel, int newChannel); 130 | void applyVelocity(vector *message, int source); 131 | void setVelocityMode(int source, int channel, int value); 132 | void setVelocityModeMulti(int source, int channel, int value); 133 | int scaleUp(int value); 134 | long tapTempo(); 135 | void handleMessage(vector *message, int source); 136 | void messageAtIn1(double deltatime, vector *message, void */*userData*/); 137 | void messageAtIn2(double deltatime, vector *message, void */*userData*/); 138 | void messageAtIn3(double deltatime, vector *message, void */*userData*/); 139 | void messageAtIn4(double deltatime, vector *message, void */*userData*/); 140 | string trimPort(bool doTrim, const string& str); 141 | bool openInputPort(RtMidiIn *in, string port); 142 | bool openOutputPort(RtMidiIn *in, string port); 143 | bool openPorts(string i1, string i2, string i3, string i4, string o); 144 | void cleanUp(); 145 | void runInteractiveConfiguration(); 146 | 147 | int main(int argc, char *argv[]) { 148 | try { 149 | if (argc == 2 && string(argv[1]) == "-c") 150 | runInteractiveConfiguration(); 151 | else if (argc > 1) 152 | usage(); 153 | 154 | // Handle configuration 155 | string input1, input2, input3, input4, output; 156 | bool input1mono, input2mono, input3mono, input4mono; 157 | int initialBpm, tapTempoMinBpm, tapTempoMaxBpm; 158 | 159 | po::options_description desc("Options"); 160 | desc.add_options() 161 | ("input1", po::value(&input1), "input1") 162 | ("input1mono", po::value(&input1mono)->default_value(false), "input1mono") 163 | ("input2", po::value(&input2), "input2") 164 | ("input2mono", po::value(&input2mono)->default_value(false), "input2mono") 165 | ("input3", po::value(&input3), "input3") 166 | ("input3mono", po::value(&input3mono)->default_value(false), "input3mono") 167 | ("input4", po::value(&input4), "input4") 168 | ("input4mono", po::value(&input4mono)->default_value(false), "input4mono") 169 | ("output", po::value(&output), "output") 170 | ("enableClock", po::value(&enableClock)->default_value(true), "enableClock") 171 | ("startMidiCC", po::value(&startMidiCC)->default_value(13), "startMidiCC") 172 | ("stopMidiCC", po::value(&stopMidiCC)->default_value(14), "stopMidiCC") 173 | ("ignoreProgramChanges", po::value(&ignoreProgramChanges)->default_value(false), "ignoreProgramChanges") 174 | ("initialBpm", po::value(&initialBpm)->default_value(142), "initialBpm") 175 | ("tapTempoMinBpm", po::value(&tapTempoMinBpm)->default_value(80), "tapTempoMinBpm") 176 | ("tapTempoMaxBpm", po::value(&tapTempoMaxBpm)->default_value(200), "tapTempoMaxBpm") 177 | ("bpmOffsetForMidiCC", po::value(&bpmOffsetForMidiCC)->default_value(70), "bpmOffsetForMidiCC") 178 | ("velocityRandomOffset", po::value(&velocityRandomOffset)->default_value(-40), "velocityRandomOffset") 179 | ("velocityMultiDeviceCtrl", po::value(&velocityMultiDeviceCtrl)->default_value(true), "velocityMultiDeviceCtrl") 180 | ("velocityMidiCC", po::value(&velocityMidiCC)->default_value(7), "velocityMidiCC") 181 | ("tempoMidiCC", po::value(&tempoMidiCC)->default_value(10), "tempoMidiCC") 182 | ("chordMidiCC", po::value(&chordMidiCC)->default_value(11), "chordMidiCC") 183 | ("routeMidiCC", po::value(&routeMidiCC)->default_value(12), "routeMidiCC"); 184 | po::variables_map vm; 185 | 186 | ifstream file(CONFIG_FILE); 187 | po::store(po::parse_config_file(file, desc), vm); 188 | po::notify(vm); 189 | file.close(); 190 | 191 | clockInterval = 60000000000/(initialBpm*24); 192 | tapTempoMaxInterval = 60000000000/tapTempoMinBpm; 193 | tapTempoMinInterval = 60000000000/tapTempoMaxBpm; 194 | 195 | mono[0] = input1mono; 196 | mono[1] = input2mono; 197 | mono[2] = input3mono; 198 | mono[3] = input4mono; 199 | 200 | struct timespec lastClock, now; 201 | clock_gettime(CLOCK_MONOTONIC, &now); 202 | randomGenerator = new boost::mt19937(now.tv_nsec); 203 | 204 | midiin1 = new RtMidiIn(); 205 | midiin2 = new RtMidiIn(); 206 | midiin3 = new RtMidiIn(); 207 | midiin4 = new RtMidiIn(); 208 | midiout = new RtMidiOut(); 209 | 210 | // Assign MIDI ports 211 | if (!openPorts(input1, input2, input3, input4, output)) { 212 | cout << "Exiting" << endl; 213 | cleanUp(); 214 | exit(0); 215 | } 216 | 217 | // Note off message 218 | vector offMsg; 219 | offMsg.push_back(BOOST_BINARY(10000000)); 220 | offMsg.push_back(42); 221 | offMsg.push_back(100); 222 | noteOffMessage = &offMsg; 223 | 224 | // Clock messages 225 | vector clkMsg; 226 | clkMsg.push_back(BOOST_BINARY(11111000)); 227 | clockMessage = &clkMsg; 228 | 229 | // Midi clock start 230 | vector clkStartMsg; 231 | clkStartMsg.push_back(BOOST_BINARY(11111010)); 232 | clockStartMessage = &clkStartMsg; 233 | 234 | // Midi clock stop 235 | vector clkStopMsg; 236 | clkStopMsg.push_back(BOOST_BINARY(11111100)); 237 | clockStopMessage = &clkStopMsg; 238 | 239 | // Tap-tempo 240 | boost::circular_buffer taps(4); 241 | taps.push_front(now); 242 | tapTempoTimes = &taps; 243 | 244 | map midiins; 245 | if (midiin1->isPortOpen()) midiins[0] = midiin1; 246 | if (midiin2->isPortOpen()) midiins[1] = midiin2; 247 | if (midiin3->isPortOpen()) midiins[2] = midiin3; 248 | if (midiin4->isPortOpen()) midiins[3] = midiin4; 249 | 250 | done = false; 251 | resetClock = false; 252 | (void) signal(SIGINT, finish); 253 | std::vector incomingMsg; 254 | 255 | cout << "Starting" << endl; 256 | midiout->sendMessage(clockMessage); 257 | clock_gettime(CLOCK_MONOTONIC, &lastClock); 258 | 259 | while (!done) { 260 | for(map::iterator iter = midiins.begin(); iter != midiins.end(); ++iter) { 261 | iter->second->getMessage(&incomingMsg); 262 | if (incomingMsg.size() > 0) handleMessage(&incomingMsg, iter->first); 263 | } 264 | clock_gettime(CLOCK_MONOTONIC, &now); 265 | if(resetClock || ((now.tv_nsec-lastClock.tv_nsec)+((now.tv_sec-lastClock.tv_sec)*1000000000)) >= clockInterval) { 266 | midiout->sendMessage(clockMessage); 267 | clock_gettime(CLOCK_MONOTONIC, &lastClock); 268 | resetClock = false; 269 | } 270 | } 271 | cout << endl; 272 | } 273 | catch (RtMidiError &error) { 274 | error.printMessage(); 275 | } 276 | catch (exception& e) { 277 | cout << "Error occurred while reading configuration: " << e.what() << endl; 278 | return 1; 279 | } 280 | 281 | cleanUp(); 282 | return 0; 283 | } 284 | 285 | void usage(void) { 286 | cout << "Usage: ./midicloro [-c to start interactive configuration]" << endl; 287 | exit(0); 288 | } 289 | 290 | double random01() { 291 | static boost::uniform_01 dist(*randomGenerator); 292 | return dist(); 293 | } 294 | 295 | bool ignoreMessage(unsigned char msgByte) { 296 | if ((enableClock && (msgByte == BOOST_BINARY(11111000))) || // MIDI clock 297 | (ignoreProgramChanges && ((msgByte & BOOST_BINARY(11110000)) == BOOST_BINARY(11000000)))) // Program change 298 | return true; 299 | return false; 300 | } 301 | 302 | void transposeAndSend(vector *message, int semiNotes) { 303 | // Verify that the note will end up withing the permitted range 304 | int note = (int)(*message)[1] + semiNotes; 305 | if (note >= 0 && note <= 127){ 306 | // This changes the message - keep in mind for the next note in the chord 307 | (*message)[1] = note; 308 | midiout->sendMessage(message); 309 | } 310 | } 311 | 312 | void sendNoteOrChord(vector *message, int source) { 313 | int channel = (int)((*message)[0] & BOOST_BINARY(00001111)); 314 | // Handle chord mode 315 | switch(chordModes[source][channel]) { 316 | case CHORD_OFF: 317 | midiout->sendMessage(message); 318 | break; 319 | case MINOR3: 320 | midiout->sendMessage(message); 321 | transposeAndSend(message, 3); 322 | transposeAndSend(message, 4); 323 | break; 324 | case MAJOR3: 325 | midiout->sendMessage(message); 326 | transposeAndSend(message, 4); 327 | transposeAndSend(message, 3); 328 | break; 329 | case MINOR3_LO: 330 | transposeAndSend(message, -5); 331 | transposeAndSend(message, 5); 332 | transposeAndSend(message, 3); 333 | break; 334 | case MAJOR3_LO: 335 | transposeAndSend(message, -5); 336 | transposeAndSend(message, 5); 337 | transposeAndSend(message, 4); 338 | break; 339 | case MINOR2: 340 | midiout->sendMessage(message); 341 | transposeAndSend(message, 3); 342 | break; 343 | case MAJOR2: 344 | midiout->sendMessage(message); 345 | transposeAndSend(message, 4); 346 | break; 347 | case M7: 348 | midiout->sendMessage(message); 349 | transposeAndSend(message, 3); 350 | transposeAndSend(message, 4); 351 | transposeAndSend(message, 3); 352 | break; 353 | case MAJ7: 354 | midiout->sendMessage(message); 355 | transposeAndSend(message, 4); 356 | transposeAndSend(message, 3); 357 | transposeAndSend(message, 4); 358 | break; 359 | case M9: 360 | midiout->sendMessage(message); 361 | transposeAndSend(message, 3); 362 | transposeAndSend(message, 4); 363 | transposeAndSend(message, 3); 364 | transposeAndSend(message, 4); 365 | break; 366 | case MAJ9: 367 | midiout->sendMessage(message); 368 | transposeAndSend(message, 4); 369 | transposeAndSend(message, 3); 370 | transposeAndSend(message, 4); 371 | transposeAndSend(message, 3); 372 | break; 373 | case SUS4: 374 | midiout->sendMessage(message); 375 | transposeAndSend(message, 5); 376 | transposeAndSend(message, 2); 377 | break; 378 | case POWER2: 379 | midiout->sendMessage(message); 380 | transposeAndSend(message, 7); 381 | break; 382 | case POWER3: 383 | midiout->sendMessage(message); 384 | transposeAndSend(message, 7); 385 | transposeAndSend(message, 5); 386 | break; 387 | case OCTAVE2: 388 | midiout->sendMessage(message); 389 | transposeAndSend(message, 12); 390 | break; 391 | case OCTAVE3: 392 | midiout->sendMessage(message); 393 | transposeAndSend(message, 12); 394 | transposeAndSend(message, 12); 395 | break; 396 | default: 397 | midiout->sendMessage(message); 398 | break; 399 | } 400 | } 401 | 402 | void sendNoteOffAndNote(vector *message, int source) { 403 | int channel = (int)((*message)[0] & BOOST_BINARY(00001111)); 404 | bool thisIsNoteOn = ((*message)[0] & BOOST_BINARY(10010000)) == BOOST_BINARY(10010000); 405 | if (!monoLegato[source][channel]) { 406 | if (thisIsNoteOn && lastNote[source][channel] != -1) { 407 | (*noteOffMessage)[0] = 128 + channel; 408 | (*noteOffMessage)[1] = lastNote[source][channel]; 409 | sendNoteOrChord(noteOffMessage, source); 410 | } 411 | lastNote[source][channel] = thisIsNoteOn ? (*message)[1] : -1; 412 | sendNoteOrChord(message, source); 413 | } 414 | else { 415 | unsigned char currNote = (*message)[1]; 416 | sendNoteOrChord(message, source); 417 | if (thisIsNoteOn && lastNote[source][channel] != -1) { 418 | (*noteOffMessage)[0] = 128 + channel; 419 | (*noteOffMessage)[1] = lastNote[source][channel]; 420 | sendNoteOrChord(noteOffMessage, source); 421 | } 422 | lastNote[source][channel] = thisIsNoteOn ? currNote : -1; 423 | } 424 | } 425 | 426 | 427 | void setChordMode(int source, int channel, int value) { 428 | if (chordModes[source][channel] == 0 && value == 0) 429 | monoLegato[source][channel] = !monoLegato[source][channel]; 430 | 431 | chordModes[source][channel] = value/8; 432 | } 433 | 434 | void routeChannel(vector *message, int source) { 435 | int channel = (int)((*message)[0] & BOOST_BINARY(00001111)); 436 | (*message)[0] = ((*message)[0] & BOOST_BINARY(11110000)) + channelRouting[source][channel]; 437 | } 438 | 439 | void setChannelRouting(int source, int channel, int newChannel) { 440 | if (newChannel >= 0 && newChannel <= 127) 441 | channelRouting[source][channel] = newChannel/8; 442 | } 443 | 444 | void applyVelocity(vector *message, int source) { 445 | int channel = (int)((*message)[0] & BOOST_BINARY(00001111)); 446 | if (velocityModes[source][channel] == VEL_OFF || message->size() < 3) 447 | return; 448 | 449 | if (velocityModes[source][channel] == VEL_RDM) { 450 | if (velocityRandomOffset < 0) 451 | (*message)[2] = max(velocity[source][channel]+(int)(velocityRandomOffset*random01()), 1); 452 | else if (velocityRandomOffset > 0) 453 | (*message)[2] = min(velocity[source][channel]+(int)(velocityRandomOffset*random01()), 126) + 1; 454 | else 455 | (*message)[2] = max((int)(random01()*127), 1); 456 | } 457 | else { 458 | (*message)[2] = max(velocity[source][channel], 1); 459 | } 460 | } 461 | 462 | void setVelocityMode(int source, int channel, int value) { 463 | if (value == 127) { 464 | velocityModes[source][channel] = (velocityModes[source][channel] == VEL_RDM) ? VEL_ON : VEL_RDM; 465 | } 466 | else if (value == 0) { 467 | velocityModes[source][channel] = VEL_OFF; 468 | } 469 | else { 470 | value = scaleUp(value); 471 | velocity[source][channel] = value; 472 | if (velocityModes[source][channel] == VEL_OFF) 473 | velocityModes[source][channel] = VEL_ON; 474 | } 475 | } 476 | 477 | void setVelocityModeMulti(int source, int channel, int value) { 478 | if (value == 127) { 479 | int newMode = (velocityModes[source][channel] == VEL_RDM) ? VEL_ON : VEL_RDM; 480 | for (int i=source; i>=0; i--) 481 | velocityModes[i][channel] = newMode; 482 | } 483 | else if (value == 0) { 484 | for (int i=source; i>=0; i--) 485 | velocityModes[i][channel] = VEL_OFF; 486 | } 487 | else { 488 | value = scaleUp(value); 489 | for (int i=source; i>=0; i--) 490 | velocity[i][channel] = value; 491 | if (velocityModes[source][channel] == VEL_OFF) 492 | for (int i=source; i>=0; i--) 493 | velocityModes[i][channel] = VEL_ON; 494 | } 495 | } 496 | 497 | int scaleUp(int value) { 498 | // Scale value to let 8-120 contain the whole range 0-127 499 | if (value > 64) { 500 | value += 8*(value - 64)/56; 501 | value = min(value, 127); 502 | } 503 | else if (value < 64) { 504 | value -= 8*(64 - value)/56; 505 | value = max(value, 0); 506 | } 507 | return value; 508 | } 509 | 510 | long tapTempo() { 511 | long diff = 0; 512 | long accumulatedDiffs = 0; 513 | unsigned int i = 0; 514 | struct timespec now, curr, prev; 515 | clock_gettime(CLOCK_MONOTONIC, &now); 516 | tapTempoTimes->push_front(now); 517 | do { 518 | curr = (*tapTempoTimes)[i]; 519 | prev = (*tapTempoTimes)[i+1]; 520 | diff = (curr.tv_nsec - prev.tv_nsec) + (curr.tv_sec - prev.tv_sec) * 1000000000; 521 | accumulatedDiffs += diff; 522 | i++; 523 | } 524 | while (diff >= tapTempoMinInterval && diff <= tapTempoMaxInterval && i < tapTempoTimes->size()-1); 525 | if (i > 1) 526 | return accumulatedDiffs/i; // Interval in ns 527 | else 528 | return 0; 529 | } 530 | 531 | void handleMessage(vector *message, int source) { 532 | // Handle mono mode 533 | if (mono[source] && ((*message)[0] & BOOST_BINARY(11100000)) == BOOST_BINARY(10000000)) { 534 | routeChannel(message, source); 535 | applyVelocity(message, source); 536 | sendNoteOffAndNote(message, source); 537 | } 538 | // Note on/off: send note or chord 539 | else if (((*message)[0] & BOOST_BINARY(11100000)) == BOOST_BINARY(10000000)) { 540 | routeChannel(message, source); 541 | applyVelocity(message, source); 542 | sendNoteOrChord(message, source); 543 | } 544 | // Start message: pass it through and reset clock 545 | else if (enableClock && ((*message)[0] == BOOST_BINARY(11111010))) { 546 | midiout->sendMessage(message); 547 | resetClock = true; 548 | } 549 | // Stop message: reset last notes 550 | else if (enableClock && ((*message)[0] == BOOST_BINARY(11111100))) { 551 | midiout->sendMessage(message); 552 | for (int i=0; i<4; i++) 553 | for (int j=0; j<16; j++) 554 | lastNote[i][j] = -1; 555 | } 556 | // Tap-tempo MIDI CC: use tap-tempo or tempo from MIDI message 557 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == tempoMidiCC) { 558 | long tapInterval = tapTempo(); 559 | if (tapInterval != 0) 560 | clockInterval = tapInterval/24; 561 | else 562 | clockInterval = 60000000000/((bpmOffsetForMidiCC+(*message)[2])*24); 563 | 564 | resetClock = true; 565 | } 566 | // Chord mode MIDI CC: set chord mode 567 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == chordMidiCC) { 568 | routeChannel(message, source); 569 | setChordMode(source, (*message)[0] & BOOST_BINARY(00001111), (*message)[2]); 570 | } 571 | // Channel routing MIDI CC: set channel routing 572 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == routeMidiCC) { 573 | setChannelRouting(source, (*message)[0] & BOOST_BINARY(00001111), (*message)[2]); 574 | } 575 | // Velocity MIDI CC: set velocity mode 576 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == velocityMidiCC) { 577 | routeChannel(message, source); 578 | if (velocityMultiDeviceCtrl) 579 | setVelocityModeMulti(source, (*message)[0] & BOOST_BINARY(00001111), (*message)[2]); 580 | else 581 | setVelocityMode(source, (*message)[0] & BOOST_BINARY(00001111), (*message)[2]); 582 | } 583 | // Start message CC: Send midi clock start 584 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == startMidiCC && (*message)[2] >= 64) { 585 | midiout->sendMessage(clockStartMessage); 586 | } 587 | // Stop message CC: Send midi clock stop 588 | else if (((*message)[0] & BOOST_BINARY(11110000)) == BOOST_BINARY(10110000) && message->size() > 2 && (*message)[1] == stopMidiCC && (*message)[2] >= 64) { 589 | midiout->sendMessage(clockStopMessage); 590 | } 591 | // Other MIDI messages 592 | else if (!ignoreMessage((*message)[0])) { 593 | if ((((*message)[0] & BOOST_BINARY(11110000)) >= BOOST_BINARY(10000000)) && 594 | (((*message)[0] & BOOST_BINARY(11110000)) <= BOOST_BINARY(11100000))) { 595 | routeChannel(message, source); 596 | } 597 | midiout->sendMessage(message); 598 | } 599 | } 600 | 601 | void messageAtIn1(double deltatime, vector *message, void */*userData*/) { 602 | handleMessage(message, 0); 603 | } 604 | 605 | void messageAtIn2(double deltatime, vector *message, void */*userData*/) { 606 | handleMessage(message, 1); 607 | } 608 | 609 | void messageAtIn3(double deltatime, vector *message, void */*userData*/) { 610 | handleMessage(message, 2); 611 | } 612 | 613 | void messageAtIn4(double deltatime, vector *message, void */*userData*/) { 614 | handleMessage(message, 3); 615 | } 616 | 617 | string trimPort(bool doTrim, const string& str) { 618 | if (doTrim) 619 | return str.substr(0,str.find_last_of(" ")); 620 | return str; 621 | } 622 | 623 | bool openInputPort(RtMidiIn *in, string port) { 624 | if (port.empty()) 625 | return false; 626 | 627 | // Match full name if port contains hardware id (example: 11:0), otherwise remove the hardware id before matching 628 | bool doTrim = !boost::regex_match(port, boost::regex("(.+)\\s([0-9]+):([0-9]+)")); 629 | string portName; 630 | unsigned int i = 0, nPorts = in->getPortCount(); 631 | for (i=0; igetPortName(i); 633 | if (trimPort(doTrim, portName) == port) { 634 | cout << "Opening input port: " << portName << endl; 635 | in->openPort(i); 636 | in->ignoreTypes(false, false, false); 637 | return true; 638 | } 639 | } 640 | cout << "Couldn't find input port: " << port << endl; 641 | return false; 642 | } 643 | 644 | bool openOutputPort(RtMidiOut *out, string port) { 645 | // Match full name if port contains hardware id (example: 11:0), otherwise remove the hardware id before matching 646 | bool doTrim = !boost::regex_match(port, boost::regex("(.+)\\s([0-9]+):([0-9]+)")); 647 | string portName; 648 | unsigned int i = 0, nPorts = out->getPortCount(); 649 | for (i=0; igetPortName(i); 651 | if (trimPort(doTrim, portName) == port) { 652 | cout << "Opening output port: " << portName << endl; 653 | out->openPort(i); 654 | return true; 655 | } 656 | } 657 | cout << "Couldn't find output port: " << port << endl; 658 | return false; 659 | } 660 | 661 | bool openPorts(string i1, string i2, string i3, string i4, string o) { 662 | openInputPort(midiin1, i1); 663 | openInputPort(midiin2, i2); 664 | openInputPort(midiin3, i3); 665 | openInputPort(midiin4, i4); 666 | return openOutputPort(midiout, o); 667 | } 668 | 669 | void cleanUp() { 670 | delete midiin1; 671 | delete midiin2; 672 | delete midiin3; 673 | delete midiin4; 674 | delete midiout; 675 | } 676 | 677 | void runInteractiveConfiguration() { 678 | cout << "This will clear and reconfigure the settings. Continue? (y/N): "; 679 | string keyHit; 680 | getline(cin, keyHit); 681 | if (keyHit != "y") { 682 | cout << "Exiting" << endl; 683 | exit(0); 684 | } 685 | RtMidiIn *cfgMidiIn = new RtMidiIn(); 686 | RtMidiOut *cfgMidiOut = new RtMidiOut(); 687 | string cfg = ""; 688 | vector inputs; 689 | vector outputs; 690 | string portName; 691 | int userIn; 692 | int addedIns = 0; 693 | cout << endl << 694 | "Note about hardware id (HWid, example: 11:0). " 695 | "The HWid for a port might change if you connect it to another USB port." 696 | << endl << 697 | "Never store HWid except for ports which have the same name (storing HWid is required in that case)." 698 | << endl << endl; 699 | // Input 700 | int nPorts = cfgMidiIn->getPortCount(); 701 | cout << "Available input ports:" << endl; 702 | for (int i=0; igetPortName(i); 704 | inputs.push_back(portName); 705 | cout << i << ". " << portName << endl; 706 | } 707 | for (int i=0; i<4; i++) { 708 | cout << "Enter port number for input " << i+1 << " (press enter to disable)" << ": "; 709 | if (addedIns>=nPorts) { 710 | cout << endl << "Disabling input" << i+1 << endl; 711 | cfg += string("input") + convert::to_string(i+1) + string(" =\n"); 712 | continue; 713 | } 714 | else if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>=nPorts || inputs[userIn]=="") { 715 | cout << "Disabling input " << i+1 << endl; 716 | cfg += string("input") + convert::to_string(i+1) + string(" =\n"); 717 | cin.clear(); 718 | cin.ignore(numeric_limits::max(), '\n'); 719 | continue; 720 | } 721 | cin.clear(); 722 | cin.ignore(numeric_limits::max(), '\n'); 723 | 724 | cout << "Store hardware id for input " << i+1 << "? (y/N): "; 725 | getline(cin, keyHit); 726 | if (keyHit == "y") 727 | cfg += string("input") + convert::to_string(i+1) + string(" = ") + inputs[userIn] + "\n"; 728 | else 729 | cfg += string("input") + convert::to_string(i+1) + string(" = ") + trimPort(true, inputs[userIn]) + "\n"; 730 | 731 | cout << "Enable mono mode for input " << i+1 << "? (y/N): "; 732 | getline(cin, keyHit); 733 | if (keyHit == "y") 734 | cfg += string("input") + convert::to_string(i+1) + string("mono = true") + "\n"; 735 | 736 | addedIns++; 737 | inputs[userIn] = ""; 738 | } 739 | // Output 740 | nPorts = cfgMidiOut->getPortCount(); 741 | cout << endl << "Available output ports:" << endl; 742 | for (int i=0; igetPortName(i); 744 | outputs.push_back(portName); 745 | cout << i << ". " << portName << endl; 746 | } 747 | cout << "Enter port number for output: "; 748 | while (!(cin >> userIn) || userIn < 0 || userIn >= nPorts) { 749 | cout << "Incorrect port number, try again: "; 750 | cin.clear(); 751 | cin.ignore(numeric_limits::max(), '\n'); 752 | } 753 | cin.clear(); 754 | cin.ignore(numeric_limits::max(), '\n'); 755 | cout << "Store hardware id? (y/N): "; 756 | getline(cin, keyHit); 757 | if (keyHit == "y") 758 | cfg += string("output = ") + outputs[userIn] + "\n"; 759 | else 760 | cfg += string("output = ") + trimPort(true, outputs[userIn]) + "\n"; 761 | 762 | cout << endl << "Enable MIDI clock? (Y/n): "; 763 | getline(cin, keyHit); 764 | if (keyHit == "n") 765 | cfg += string("enableClock = false") + "\n"; 766 | else 767 | cfg += string("enableClock = true") + "\n"; 768 | 769 | cout << "Enter start clock MIDI CC number (default 13): "; 770 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 771 | cfg += string("startMidiCC = 13") + "\n"; 772 | else 773 | cfg += string("startMidiCC = ") + convert::to_string(userIn) + "\n"; 774 | 775 | cin.clear(); 776 | cin.ignore(numeric_limits::max(), '\n'); 777 | 778 | cout << "Enter stop clock MIDI CC number (default 14): "; 779 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 780 | cfg += string("stopMidiCC = 14") + "\n"; 781 | else 782 | cfg += string("stopMidiCC = ") + convert::to_string(userIn) + "\n"; 783 | 784 | cin.clear(); 785 | cin.ignore(numeric_limits::max(), '\n'); 786 | 787 | cout << "Ignore incoming program change messages? (y/N): "; 788 | getline(cin, keyHit); 789 | if (keyHit == "y") 790 | cfg += string("ignoreProgramChanges = true") + "\n"; 791 | else 792 | cfg += string("ignoreProgramChanges = false") + "\n"; 793 | 794 | cout << "Enter initial MIDI clock BPM (1-300, default 142): "; 795 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<1 || userIn>300) 796 | cfg += string("initialBpm = 142") + "\n"; 797 | else 798 | cfg += string("initialBpm = ") + convert::to_string(userIn) + "\n"; 799 | 800 | cin.clear(); 801 | cin.ignore(numeric_limits::max(), '\n'); 802 | 803 | cout << "Enter tap-tempo minimum BPM (default 80): "; 804 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0) 805 | cfg += string("tapTempoMinBpm = 80") + "\n"; 806 | else 807 | cfg += string("tapTempoMinBpm = ") + convert::to_string(userIn) + "\n"; 808 | 809 | cin.clear(); 810 | cin.ignore(numeric_limits::max(), '\n'); 811 | 812 | cout << "Enter tap-tempo maximum BPM (default 200): "; 813 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0) 814 | cfg += string("tapTempoMaxBpm = 200") + "\n"; 815 | else 816 | cfg += string("tapTempoMaxBpm = ") + convert::to_string(userIn) + "\n"; 817 | 818 | cin.clear(); 819 | cin.ignore(numeric_limits::max(), '\n'); 820 | 821 | cout << "Enter BPM offset (offset + MIDI CC value [0-127] = BPM, default 70): "; 822 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0) 823 | cfg += string("bpmOffsetForMidiCC = 70") + "\n"; 824 | else 825 | cfg += string("bpmOffsetForMidiCC = ") + convert::to_string(userIn) + "\n"; 826 | 827 | cin.clear(); 828 | cin.ignore(numeric_limits::max(), '\n'); 829 | 830 | cout << "Enter velocity random offset (default -40): "; 831 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn < -127 || userIn > 127) 832 | cfg += string("velocityRandomOffset = -40") + "\n"; 833 | else 834 | cfg += string("velocityRandomOffset = ") + convert::to_string(userIn) + "\n"; 835 | 836 | cin.clear(); 837 | cin.ignore(numeric_limits::max(), '\n'); 838 | 839 | cout << "Enable velocity multi device control (e.g. input 3 controls the velocity setting for input 3, 2 and 1)? (Y/n): "; 840 | getline(cin, keyHit); 841 | if (keyHit == "n") 842 | cfg += string("velocityMultiDeviceCtrl = false") + "\n"; 843 | else 844 | cfg += string("velocityMultiDeviceCtrl = true") + "\n"; 845 | 846 | cout << "Enter velocity MIDI CC number (default 7): "; 847 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 848 | cfg += string("velocityMidiCC = 7") + "\n"; 849 | else 850 | cfg += string("velocityMidiCC = ") + convert::to_string(userIn) + "\n"; 851 | 852 | cin.clear(); 853 | cin.ignore(numeric_limits::max(), '\n'); 854 | 855 | cout << "Enter tempo MIDI CC number (default 10): "; 856 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 857 | cfg += string("tempoMidiCC = 10") + "\n"; 858 | else 859 | cfg += string("tempoMidiCC = ") + convert::to_string(userIn) + "\n"; 860 | 861 | cin.clear(); 862 | cin.ignore(numeric_limits::max(), '\n'); 863 | 864 | cout << "Enter chord mode MIDI CC number (default 11): "; 865 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 866 | cfg += string("chordMidiCC = 11") + "\n"; 867 | else 868 | cfg += string("chordMidiCC = ") + convert::to_string(userIn) + "\n"; 869 | 870 | cin.clear(); 871 | cin.ignore(numeric_limits::max(), '\n'); 872 | 873 | cout << "Enter channel routing MIDI CC number (default 12): "; 874 | if (cin.peek()=='\n' || !(cin >> userIn) || userIn<0 || userIn>127) 875 | cfg += string("routeMidiCC = 12") + "\n"; 876 | else 877 | cfg += string("routeMidiCC = ") + convert::to_string(userIn) + "\n"; 878 | 879 | cin.clear(); 880 | cin.ignore(numeric_limits::max(), '\n'); 881 | 882 | cout << endl; 883 | 884 | ofstream file(CONFIG_FILE); 885 | file << cfg; 886 | 887 | delete cfgMidiIn; 888 | delete cfgMidiOut; 889 | } 890 | -------------------------------------------------------------------------------- /rtmidi/RtMidi.cpp: -------------------------------------------------------------------------------- 1 | /**********************************************************************/ 2 | /*! \class RtMidi 3 | \brief An abstract base class for realtime MIDI input/output. 4 | 5 | This class implements some common functionality for the realtime 6 | MIDI input/output subclasses RtMidiIn and RtMidiOut. 7 | 8 | RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/ 9 | 10 | RtMidi: realtime MIDI i/o C++ classes 11 | Copyright (c) 2003-2014 Gary P. Scavone 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation files 15 | (the "Software"), to deal in the Software without restriction, 16 | including without limitation the rights to use, copy, modify, merge, 17 | publish, distribute, sublicense, and/or sell copies of the Software, 18 | and to permit persons to whom the Software is furnished to do so, 19 | subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be 22 | included in all copies or substantial portions of the Software. 23 | 24 | Any person wishing to distribute modifications to the Software is 25 | asked to send the modifications to the original developer so that 26 | they can be incorporated into the canonical version. This is, 27 | however, not a binding provision of this license. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 30 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 31 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 32 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 33 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 34 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 35 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 36 | */ 37 | /**********************************************************************/ 38 | 39 | #include "RtMidi.h" 40 | #include 41 | 42 | //*********************************************************************// 43 | // RtMidi Definitions 44 | //*********************************************************************// 45 | 46 | RtMidi :: RtMidi() 47 | : rtapi_(0) 48 | { 49 | } 50 | 51 | RtMidi :: ~RtMidi() 52 | { 53 | if ( rtapi_ ) 54 | delete rtapi_; 55 | rtapi_ = 0; 56 | } 57 | 58 | std::string RtMidi :: getVersion( void ) throw() 59 | { 60 | return std::string( RTMIDI_VERSION ); 61 | } 62 | 63 | void RtMidi :: getCompiledApi( std::vector &apis ) throw() 64 | { 65 | apis.clear(); 66 | 67 | // The order here will control the order of RtMidi's API search in 68 | // the constructor. 69 | #if defined(__MACOSX_CORE__) 70 | apis.push_back( MACOSX_CORE ); 71 | #endif 72 | #if defined(__LINUX_ALSA__) 73 | apis.push_back( LINUX_ALSA ); 74 | #endif 75 | #if defined(__UNIX_JACK__) 76 | apis.push_back( UNIX_JACK ); 77 | #endif 78 | #if defined(__WINDOWS_MM__) 79 | apis.push_back( WINDOWS_MM ); 80 | #endif 81 | #if defined(__RTMIDI_DUMMY__) 82 | apis.push_back( RTMIDI_DUMMY ); 83 | #endif 84 | } 85 | 86 | //*********************************************************************// 87 | // RtMidiIn Definitions 88 | //*********************************************************************// 89 | 90 | void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) 91 | { 92 | if ( rtapi_ ) 93 | delete rtapi_; 94 | rtapi_ = 0; 95 | 96 | #if defined(__UNIX_JACK__) 97 | if ( api == UNIX_JACK ) 98 | rtapi_ = new MidiInJack( clientName, queueSizeLimit ); 99 | #endif 100 | #if defined(__LINUX_ALSA__) 101 | if ( api == LINUX_ALSA ) 102 | rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); 103 | #endif 104 | #if defined(__WINDOWS_MM__) 105 | if ( api == WINDOWS_MM ) 106 | rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); 107 | #endif 108 | #if defined(__MACOSX_CORE__) 109 | if ( api == MACOSX_CORE ) 110 | rtapi_ = new MidiInCore( clientName, queueSizeLimit ); 111 | #endif 112 | #if defined(__RTMIDI_DUMMY__) 113 | if ( api == RTMIDI_DUMMY ) 114 | rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); 115 | #endif 116 | } 117 | 118 | RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit ) 119 | : RtMidi() 120 | { 121 | if ( api != UNSPECIFIED ) { 122 | // Attempt to open the specified API. 123 | openMidiApi( api, clientName, queueSizeLimit ); 124 | if ( rtapi_ ) return; 125 | 126 | // No compiled support for specified API value. Issue a warning 127 | // and continue as if no API was specified. 128 | std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; 129 | } 130 | 131 | // Iterate through the compiled APIs and return as soon as we find 132 | // one with at least one port or we reach the end of the list. 133 | std::vector< RtMidi::Api > apis; 134 | getCompiledApi( apis ); 135 | for ( unsigned int i=0; igetPortCount() ) break; 138 | } 139 | 140 | if ( rtapi_ ) return; 141 | 142 | // It should not be possible to get here because the preprocessor 143 | // definition __RTMIDI_DUMMY__ is automatically defined if no 144 | // API-specific definitions are passed to the compiler. But just in 145 | // case something weird happens, we'll throw an error. 146 | std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; 147 | throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 148 | } 149 | 150 | RtMidiIn :: ~RtMidiIn() throw() 151 | { 152 | } 153 | 154 | 155 | //*********************************************************************// 156 | // RtMidiOut Definitions 157 | //*********************************************************************// 158 | 159 | void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName ) 160 | { 161 | if ( rtapi_ ) 162 | delete rtapi_; 163 | rtapi_ = 0; 164 | 165 | #if defined(__UNIX_JACK__) 166 | if ( api == UNIX_JACK ) 167 | rtapi_ = new MidiOutJack( clientName ); 168 | #endif 169 | #if defined(__LINUX_ALSA__) 170 | if ( api == LINUX_ALSA ) 171 | rtapi_ = new MidiOutAlsa( clientName ); 172 | #endif 173 | #if defined(__WINDOWS_MM__) 174 | if ( api == WINDOWS_MM ) 175 | rtapi_ = new MidiOutWinMM( clientName ); 176 | #endif 177 | #if defined(__MACOSX_CORE__) 178 | if ( api == MACOSX_CORE ) 179 | rtapi_ = new MidiOutCore( clientName ); 180 | #endif 181 | #if defined(__RTMIDI_DUMMY__) 182 | if ( api == RTMIDI_DUMMY ) 183 | rtapi_ = new MidiOutDummy( clientName ); 184 | #endif 185 | } 186 | 187 | RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName ) 188 | { 189 | if ( api != UNSPECIFIED ) { 190 | // Attempt to open the specified API. 191 | openMidiApi( api, clientName ); 192 | if ( rtapi_ ) return; 193 | 194 | // No compiled support for specified API value. Issue a warning 195 | // and continue as if no API was specified. 196 | std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; 197 | } 198 | 199 | // Iterate through the compiled APIs and return as soon as we find 200 | // one with at least one port or we reach the end of the list. 201 | std::vector< RtMidi::Api > apis; 202 | getCompiledApi( apis ); 203 | for ( unsigned int i=0; igetPortCount() ) break; 206 | } 207 | 208 | if ( rtapi_ ) return; 209 | 210 | // It should not be possible to get here because the preprocessor 211 | // definition __RTMIDI_DUMMY__ is automatically defined if no 212 | // API-specific definitions are passed to the compiler. But just in 213 | // case something weird happens, we'll thrown an error. 214 | std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; 215 | throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); 216 | } 217 | 218 | RtMidiOut :: ~RtMidiOut() throw() 219 | { 220 | } 221 | 222 | //*********************************************************************// 223 | // Common MidiApi Definitions 224 | //*********************************************************************// 225 | 226 | MidiApi :: MidiApi( void ) 227 | : apiData_( 0 ), connected_( false ), errorCallback_(0) 228 | { 229 | } 230 | 231 | MidiApi :: ~MidiApi( void ) 232 | { 233 | } 234 | 235 | void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback ) 236 | { 237 | errorCallback_ = errorCallback; 238 | } 239 | 240 | void MidiApi :: error( RtMidiError::Type type, std::string errorString ) 241 | { 242 | if ( errorCallback_ ) { 243 | static bool firstErrorOccured = false; 244 | 245 | if ( firstErrorOccured ) 246 | return; 247 | 248 | firstErrorOccured = true; 249 | const std::string errorMessage = errorString; 250 | 251 | errorCallback_( type, errorMessage ); 252 | firstErrorOccured = false; 253 | return; 254 | } 255 | 256 | if ( type == RtMidiError::WARNING ) { 257 | std::cerr << '\n' << errorString << "\n\n"; 258 | } 259 | else if ( type == RtMidiError::DEBUG_WARNING ) { 260 | #if defined(__RTMIDI_DEBUG__) 261 | std::cerr << '\n' << errorString << "\n\n"; 262 | #endif 263 | } 264 | else { 265 | std::cerr << '\n' << errorString << "\n\n"; 266 | throw RtMidiError( errorString, type ); 267 | } 268 | } 269 | 270 | //*********************************************************************// 271 | // Common MidiInApi Definitions 272 | //*********************************************************************// 273 | 274 | MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) 275 | : MidiApi() 276 | { 277 | // Allocate the MIDI queue. 278 | inputData_.queue.ringSize = queueSizeLimit; 279 | if ( inputData_.queue.ringSize > 0 ) 280 | inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; 281 | } 282 | 283 | MidiInApi :: ~MidiInApi( void ) 284 | { 285 | // Delete the MIDI queue. 286 | if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; 287 | } 288 | 289 | void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) 290 | { 291 | if ( inputData_.usingCallback ) { 292 | errorString_ = "MidiInApi::setCallback: a callback function is already set!"; 293 | error( RtMidiError::WARNING, errorString_ ); 294 | return; 295 | } 296 | 297 | if ( !callback ) { 298 | errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; 299 | error( RtMidiError::WARNING, errorString_ ); 300 | return; 301 | } 302 | 303 | inputData_.userCallback = callback; 304 | inputData_.userData = userData; 305 | inputData_.usingCallback = true; 306 | } 307 | 308 | void MidiInApi :: cancelCallback() 309 | { 310 | if ( !inputData_.usingCallback ) { 311 | errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; 312 | error( RtMidiError::WARNING, errorString_ ); 313 | return; 314 | } 315 | 316 | inputData_.userCallback = 0; 317 | inputData_.userData = 0; 318 | inputData_.usingCallback = false; 319 | } 320 | 321 | void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) 322 | { 323 | inputData_.ignoreFlags = 0; 324 | if ( midiSysex ) inputData_.ignoreFlags = 0x01; 325 | if ( midiTime ) inputData_.ignoreFlags |= 0x02; 326 | if ( midiSense ) inputData_.ignoreFlags |= 0x04; 327 | } 328 | 329 | double MidiInApi :: getMessage( std::vector *message ) 330 | { 331 | message->clear(); 332 | 333 | if ( inputData_.usingCallback ) { 334 | errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; 335 | error( RtMidiError::WARNING, errorString_ ); 336 | return 0.0; 337 | } 338 | 339 | if ( inputData_.queue.size == 0 ) return 0.0; 340 | 341 | // Copy queued message to the vector pointer argument and then "pop" it. 342 | std::vector *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes); 343 | message->assign( bytes->begin(), bytes->end() ); 344 | double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp; 345 | inputData_.queue.size--; 346 | inputData_.queue.front++; 347 | if ( inputData_.queue.front == inputData_.queue.ringSize ) 348 | inputData_.queue.front = 0; 349 | 350 | return deltaTime; 351 | } 352 | 353 | //*********************************************************************// 354 | // Common MidiOutApi Definitions 355 | //*********************************************************************// 356 | 357 | MidiOutApi :: MidiOutApi( void ) 358 | : MidiApi() 359 | { 360 | } 361 | 362 | MidiOutApi :: ~MidiOutApi( void ) 363 | { 364 | } 365 | 366 | // *************************************************** // 367 | // 368 | // OS/API-specific methods. 369 | // 370 | // *************************************************** // 371 | 372 | #if defined(__MACOSX_CORE__) 373 | 374 | // The CoreMIDI API is based on the use of a callback function for 375 | // MIDI input. We convert the system specific time stamps to delta 376 | // time values. 377 | 378 | // OS-X CoreMIDI header files. 379 | #include 380 | #include 381 | #include 382 | 383 | // A structure to hold variables related to the CoreMIDI API 384 | // implementation. 385 | struct CoreMidiData { 386 | MIDIClientRef client; 387 | MIDIPortRef port; 388 | MIDIEndpointRef endpoint; 389 | MIDIEndpointRef destinationId; 390 | unsigned long long lastTime; 391 | MIDISysexSendRequest sysexreq; 392 | }; 393 | 394 | //*********************************************************************// 395 | // API: OS-X 396 | // Class Definitions: MidiInCore 397 | //*********************************************************************// 398 | 399 | static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) 400 | { 401 | MidiInApi::RtMidiInData *data = static_cast (procRef); 402 | CoreMidiData *apiData = static_cast (data->apiData); 403 | 404 | unsigned char status; 405 | unsigned short nBytes, iByte, size; 406 | unsigned long long time; 407 | 408 | bool& continueSysex = data->continueSysex; 409 | MidiInApi::MidiMessage& message = data->message; 410 | 411 | const MIDIPacket *packet = &list->packet[0]; 412 | for ( unsigned int i=0; inumPackets; ++i ) { 413 | 414 | // My interpretation of the CoreMIDI documentation: all message 415 | // types, except sysex, are complete within a packet and there may 416 | // be several of them in a single packet. Sysex messages can be 417 | // broken across multiple packets and PacketLists but are bundled 418 | // alone within each packet (these packets do not contain other 419 | // message types). If sysex messages are split across multiple 420 | // MIDIPacketLists, they must be handled by multiple calls to this 421 | // function. 422 | 423 | nBytes = packet->length; 424 | if ( nBytes == 0 ) continue; 425 | 426 | // Calculate time stamp. 427 | 428 | if ( data->firstMessage ) { 429 | message.timeStamp = 0.0; 430 | data->firstMessage = false; 431 | } 432 | else { 433 | time = packet->timeStamp; 434 | if ( time == 0 ) { // this happens when receiving asynchronous sysex messages 435 | time = AudioGetCurrentHostTime(); 436 | } 437 | time -= apiData->lastTime; 438 | time = AudioConvertHostTimeToNanos( time ); 439 | if ( !continueSysex ) 440 | message.timeStamp = time * 0.000000001; 441 | } 442 | apiData->lastTime = packet->timeStamp; 443 | if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages 444 | apiData->lastTime = AudioGetCurrentHostTime(); 445 | } 446 | //std::cout << "TimeStamp = " << packet->timeStamp << std::endl; 447 | 448 | iByte = 0; 449 | if ( continueSysex ) { 450 | // We have a continuing, segmented sysex message. 451 | if ( !( data->ignoreFlags & 0x01 ) ) { 452 | // If we're not ignoring sysex messages, copy the entire packet. 453 | for ( unsigned int j=0; jdata[j] ); 455 | } 456 | continueSysex = packet->data[nBytes-1] != 0xF7; 457 | 458 | if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { 459 | // If not a continuing sysex message, invoke the user callback function or queue the message. 460 | if ( data->usingCallback ) { 461 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 462 | callback( message.timeStamp, &message.bytes, data->userData ); 463 | } 464 | else { 465 | // As long as we haven't reached our queue size limit, push the message. 466 | if ( data->queue.size < data->queue.ringSize ) { 467 | data->queue.ring[data->queue.back++] = message; 468 | if ( data->queue.back == data->queue.ringSize ) 469 | data->queue.back = 0; 470 | data->queue.size++; 471 | } 472 | else 473 | std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 474 | } 475 | message.bytes.clear(); 476 | } 477 | } 478 | else { 479 | while ( iByte < nBytes ) { 480 | size = 0; 481 | // We are expecting that the next byte in the packet is a status byte. 482 | status = packet->data[iByte]; 483 | if ( !(status & 0x80) ) break; 484 | // Determine the number of bytes in the MIDI message. 485 | if ( status < 0xC0 ) size = 3; 486 | else if ( status < 0xE0 ) size = 2; 487 | else if ( status < 0xF0 ) size = 3; 488 | else if ( status == 0xF0 ) { 489 | // A MIDI sysex 490 | if ( data->ignoreFlags & 0x01 ) { 491 | size = 0; 492 | iByte = nBytes; 493 | } 494 | else size = nBytes - iByte; 495 | continueSysex = packet->data[nBytes-1] != 0xF7; 496 | } 497 | else if ( status == 0xF1 ) { 498 | // A MIDI time code message 499 | if ( data->ignoreFlags & 0x02 ) { 500 | size = 0; 501 | iByte += 2; 502 | } 503 | else size = 2; 504 | } 505 | else if ( status == 0xF2 ) size = 3; 506 | else if ( status == 0xF3 ) size = 2; 507 | else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { 508 | // A MIDI timing tick message and we're ignoring it. 509 | size = 0; 510 | iByte += 1; 511 | } 512 | else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { 513 | // A MIDI active sensing message and we're ignoring it. 514 | size = 0; 515 | iByte += 1; 516 | } 517 | else size = 1; 518 | 519 | // Copy the MIDI data to our vector. 520 | if ( size ) { 521 | message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); 522 | if ( !continueSysex ) { 523 | // If not a continuing sysex message, invoke the user callback function or queue the message. 524 | if ( data->usingCallback ) { 525 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 526 | callback( message.timeStamp, &message.bytes, data->userData ); 527 | } 528 | else { 529 | // As long as we haven't reached our queue size limit, push the message. 530 | if ( data->queue.size < data->queue.ringSize ) { 531 | data->queue.ring[data->queue.back++] = message; 532 | if ( data->queue.back == data->queue.ringSize ) 533 | data->queue.back = 0; 534 | data->queue.size++; 535 | } 536 | else 537 | std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; 538 | } 539 | message.bytes.clear(); 540 | } 541 | iByte += size; 542 | } 543 | } 544 | } 545 | packet = MIDIPacketNext(packet); 546 | } 547 | } 548 | 549 | MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) 550 | { 551 | initialize( clientName ); 552 | } 553 | 554 | MidiInCore :: ~MidiInCore( void ) 555 | { 556 | // Close a connection if it exists. 557 | closePort(); 558 | 559 | // Cleanup. 560 | CoreMidiData *data = static_cast (apiData_); 561 | MIDIClientDispose( data->client ); 562 | if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 563 | delete data; 564 | } 565 | 566 | void MidiInCore :: initialize( const std::string& clientName ) 567 | { 568 | // Set up our client. 569 | MIDIClientRef client; 570 | OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); 571 | if ( result != noErr ) { 572 | errorString_ = "MidiInCore::initialize: error creating OS-X MIDI client object."; 573 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 574 | return; 575 | } 576 | 577 | // Save our api-specific connection information. 578 | CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 579 | data->client = client; 580 | data->endpoint = 0; 581 | apiData_ = (void *) data; 582 | inputData_.apiData = (void *) data; 583 | } 584 | 585 | void MidiInCore :: openPort( unsigned int portNumber, const std::string portName ) 586 | { 587 | if ( connected_ ) { 588 | errorString_ = "MidiInCore::openPort: a valid connection already exists!"; 589 | error( RtMidiError::WARNING, errorString_ ); 590 | return; 591 | } 592 | 593 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 594 | unsigned int nSrc = MIDIGetNumberOfSources(); 595 | if (nSrc < 1) { 596 | errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; 597 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 598 | return; 599 | } 600 | 601 | if ( portNumber >= nSrc ) { 602 | std::ostringstream ost; 603 | ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 604 | errorString_ = ost.str(); 605 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 606 | return; 607 | } 608 | 609 | MIDIPortRef port; 610 | CoreMidiData *data = static_cast (apiData_); 611 | OSStatus result = MIDIInputPortCreate( data->client, 612 | CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), 613 | midiInputCallback, (void *)&inputData_, &port ); 614 | if ( result != noErr ) { 615 | MIDIClientDispose( data->client ); 616 | errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; 617 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 618 | return; 619 | } 620 | 621 | // Get the desired input source identifier. 622 | MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); 623 | if ( endpoint == 0 ) { 624 | MIDIPortDispose( port ); 625 | MIDIClientDispose( data->client ); 626 | errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; 627 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 628 | return; 629 | } 630 | 631 | // Make the connection. 632 | result = MIDIPortConnectSource( port, endpoint, NULL ); 633 | if ( result != noErr ) { 634 | MIDIPortDispose( port ); 635 | MIDIClientDispose( data->client ); 636 | errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; 637 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 638 | return; 639 | } 640 | 641 | // Save our api-specific port information. 642 | data->port = port; 643 | 644 | connected_ = true; 645 | } 646 | 647 | void MidiInCore :: openVirtualPort( const std::string portName ) 648 | { 649 | CoreMidiData *data = static_cast (apiData_); 650 | 651 | // Create a virtual MIDI input destination. 652 | MIDIEndpointRef endpoint; 653 | OSStatus result = MIDIDestinationCreate( data->client, 654 | CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), 655 | midiInputCallback, (void *)&inputData_, &endpoint ); 656 | if ( result != noErr ) { 657 | errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; 658 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 659 | return; 660 | } 661 | 662 | // Save our api-specific connection information. 663 | data->endpoint = endpoint; 664 | } 665 | 666 | void MidiInCore :: closePort( void ) 667 | { 668 | if ( connected_ ) { 669 | CoreMidiData *data = static_cast (apiData_); 670 | MIDIPortDispose( data->port ); 671 | connected_ = false; 672 | } 673 | } 674 | 675 | unsigned int MidiInCore :: getPortCount() 676 | { 677 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 678 | return MIDIGetNumberOfSources(); 679 | } 680 | 681 | // This function was submitted by Douglas Casey Tucker and apparently 682 | // derived largely from PortMidi. 683 | CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) 684 | { 685 | CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 686 | CFStringRef str; 687 | 688 | // Begin with the endpoint's name. 689 | str = NULL; 690 | MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); 691 | if ( str != NULL ) { 692 | CFStringAppend( result, str ); 693 | CFRelease( str ); 694 | } 695 | 696 | MIDIEntityRef entity = 0; 697 | MIDIEndpointGetEntity( endpoint, &entity ); 698 | if ( entity == 0 ) 699 | // probably virtual 700 | return result; 701 | 702 | if ( CFStringGetLength( result ) == 0 ) { 703 | // endpoint name has zero length -- try the entity 704 | str = NULL; 705 | MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); 706 | if ( str != NULL ) { 707 | CFStringAppend( result, str ); 708 | CFRelease( str ); 709 | } 710 | } 711 | // now consider the device's name 712 | MIDIDeviceRef device = 0; 713 | MIDIEntityGetDevice( entity, &device ); 714 | if ( device == 0 ) 715 | return result; 716 | 717 | str = NULL; 718 | MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); 719 | if ( CFStringGetLength( result ) == 0 ) { 720 | CFRelease( result ); 721 | return str; 722 | } 723 | if ( str != NULL ) { 724 | // if an external device has only one entity, throw away 725 | // the endpoint name and just use the device name 726 | if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { 727 | CFRelease( result ); 728 | return str; 729 | } else { 730 | if ( CFStringGetLength( str ) == 0 ) { 731 | CFRelease( str ); 732 | return result; 733 | } 734 | // does the entity name already start with the device name? 735 | // (some drivers do this though they shouldn't) 736 | // if so, do not prepend 737 | if ( CFStringCompareWithOptions( result, /* endpoint name */ 738 | str /* device name */, 739 | CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { 740 | // prepend the device name to the entity name 741 | if ( CFStringGetLength( result ) > 0 ) 742 | CFStringInsert( result, 0, CFSTR(" ") ); 743 | CFStringInsert( result, 0, str ); 744 | } 745 | CFRelease( str ); 746 | } 747 | } 748 | return result; 749 | } 750 | 751 | // This function was submitted by Douglas Casey Tucker and apparently 752 | // derived largely from PortMidi. 753 | static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) 754 | { 755 | CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); 756 | CFStringRef str; 757 | OSStatus err; 758 | int i; 759 | 760 | // Does the endpoint have connections? 761 | CFDataRef connections = NULL; 762 | int nConnected = 0; 763 | bool anyStrings = false; 764 | err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); 765 | if ( connections != NULL ) { 766 | // It has connections, follow them 767 | // Concatenate the names of all connected devices 768 | nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); 769 | if ( nConnected ) { 770 | const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); 771 | for ( i=0; i= MIDIGetNumberOfSources() ) { 814 | std::ostringstream ost; 815 | ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 816 | errorString_ = ost.str(); 817 | error( RtMidiError::WARNING, errorString_ ); 818 | return stringName; 819 | } 820 | 821 | portRef = MIDIGetSource( portNumber ); 822 | nameRef = ConnectedEndpointName(portRef); 823 | CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); 824 | CFRelease( nameRef ); 825 | 826 | return stringName = name; 827 | } 828 | 829 | //*********************************************************************// 830 | // API: OS-X 831 | // Class Definitions: MidiOutCore 832 | //*********************************************************************// 833 | 834 | MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi() 835 | { 836 | initialize( clientName ); 837 | } 838 | 839 | MidiOutCore :: ~MidiOutCore( void ) 840 | { 841 | // Close a connection if it exists. 842 | closePort(); 843 | 844 | // Cleanup. 845 | CoreMidiData *data = static_cast (apiData_); 846 | MIDIClientDispose( data->client ); 847 | if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); 848 | delete data; 849 | } 850 | 851 | void MidiOutCore :: initialize( const std::string& clientName ) 852 | { 853 | // Set up our client. 854 | MIDIClientRef client; 855 | OSStatus result = MIDIClientCreate( CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ), NULL, NULL, &client ); 856 | if ( result != noErr ) { 857 | errorString_ = "MidiOutCore::initialize: error creating OS-X MIDI client object."; 858 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 859 | return; 860 | } 861 | 862 | // Save our api-specific connection information. 863 | CoreMidiData *data = (CoreMidiData *) new CoreMidiData; 864 | data->client = client; 865 | data->endpoint = 0; 866 | apiData_ = (void *) data; 867 | } 868 | 869 | unsigned int MidiOutCore :: getPortCount() 870 | { 871 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 872 | return MIDIGetNumberOfDestinations(); 873 | } 874 | 875 | std::string MidiOutCore :: getPortName( unsigned int portNumber ) 876 | { 877 | CFStringRef nameRef; 878 | MIDIEndpointRef portRef; 879 | char name[128]; 880 | 881 | std::string stringName; 882 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 883 | if ( portNumber >= MIDIGetNumberOfDestinations() ) { 884 | std::ostringstream ost; 885 | ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 886 | errorString_ = ost.str(); 887 | error( RtMidiError::WARNING, errorString_ ); 888 | return stringName; 889 | } 890 | 891 | portRef = MIDIGetDestination( portNumber ); 892 | nameRef = ConnectedEndpointName(portRef); 893 | CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding()); 894 | CFRelease( nameRef ); 895 | 896 | return stringName = name; 897 | } 898 | 899 | void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName ) 900 | { 901 | if ( connected_ ) { 902 | errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; 903 | error( RtMidiError::WARNING, errorString_ ); 904 | return; 905 | } 906 | 907 | CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); 908 | unsigned int nDest = MIDIGetNumberOfDestinations(); 909 | if (nDest < 1) { 910 | errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; 911 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 912 | return; 913 | } 914 | 915 | if ( portNumber >= nDest ) { 916 | std::ostringstream ost; 917 | ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 918 | errorString_ = ost.str(); 919 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 920 | return; 921 | } 922 | 923 | MIDIPortRef port; 924 | CoreMidiData *data = static_cast (apiData_); 925 | OSStatus result = MIDIOutputPortCreate( data->client, 926 | CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), 927 | &port ); 928 | if ( result != noErr ) { 929 | MIDIClientDispose( data->client ); 930 | errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; 931 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 932 | return; 933 | } 934 | 935 | // Get the desired output port identifier. 936 | MIDIEndpointRef destination = MIDIGetDestination( portNumber ); 937 | if ( destination == 0 ) { 938 | MIDIPortDispose( port ); 939 | MIDIClientDispose( data->client ); 940 | errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; 941 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 942 | return; 943 | } 944 | 945 | // Save our api-specific connection information. 946 | data->port = port; 947 | data->destinationId = destination; 948 | connected_ = true; 949 | } 950 | 951 | void MidiOutCore :: closePort( void ) 952 | { 953 | if ( connected_ ) { 954 | CoreMidiData *data = static_cast (apiData_); 955 | MIDIPortDispose( data->port ); 956 | connected_ = false; 957 | } 958 | } 959 | 960 | void MidiOutCore :: openVirtualPort( std::string portName ) 961 | { 962 | CoreMidiData *data = static_cast (apiData_); 963 | 964 | if ( data->endpoint ) { 965 | errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; 966 | error( RtMidiError::WARNING, errorString_ ); 967 | return; 968 | } 969 | 970 | // Create a virtual MIDI output source. 971 | MIDIEndpointRef endpoint; 972 | OSStatus result = MIDISourceCreate( data->client, 973 | CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ), 974 | &endpoint ); 975 | if ( result != noErr ) { 976 | errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; 977 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 978 | return; 979 | } 980 | 981 | // Save our api-specific connection information. 982 | data->endpoint = endpoint; 983 | } 984 | 985 | // Not necessary if we don't treat sysex messages any differently than 986 | // normal messages ... see below. 987 | //static void sysexCompletionProc( MIDISysexSendRequest *sreq ) 988 | //{ 989 | // free( sreq ); 990 | //} 991 | 992 | void MidiOutCore :: sendMessage( std::vector *message ) 993 | { 994 | // We use the MIDISendSysex() function to asynchronously send sysex 995 | // messages. Otherwise, we use a single CoreMidi MIDIPacket. 996 | unsigned int nBytes = message->size(); 997 | if ( nBytes == 0 ) { 998 | errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; 999 | error( RtMidiError::WARNING, errorString_ ); 1000 | return; 1001 | } 1002 | 1003 | // unsigned int packetBytes, bytesLeft = nBytes; 1004 | // unsigned int messageIndex = 0; 1005 | MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); 1006 | CoreMidiData *data = static_cast (apiData_); 1007 | OSStatus result; 1008 | 1009 | /* 1010 | // I don't think this code is necessary. We can send sysex 1011 | // messages through the normal mechanism. In addition, this avoids 1012 | // the problem of virtual ports not receiving sysex messages. 1013 | 1014 | if ( message->at(0) == 0xF0 ) { 1015 | 1016 | // Apple's fantastic API requires us to free the allocated data in 1017 | // the completion callback but trashes the pointer and size before 1018 | // we get a chance to free it!! This is a somewhat ugly hack 1019 | // submitted by ptarabbia that puts the sysex buffer data right at 1020 | // the end of the MIDISysexSendRequest structure. This solution 1021 | // does not require that we wait for a previous sysex buffer to be 1022 | // sent before sending a new one, which was the old way we did it. 1023 | MIDISysexSendRequest *newRequest = (MIDISysexSendRequest *) malloc(sizeof(struct MIDISysexSendRequest) + nBytes); 1024 | char * sysexBuffer = ((char *) newRequest) + sizeof(struct MIDISysexSendRequest); 1025 | 1026 | // Copy data to buffer. 1027 | for ( unsigned int i=0; iat(i); 1028 | 1029 | newRequest->destination = data->destinationId; 1030 | newRequest->data = (Byte *)sysexBuffer; 1031 | newRequest->bytesToSend = nBytes; 1032 | newRequest->complete = 0; 1033 | newRequest->completionProc = sysexCompletionProc; 1034 | newRequest->completionRefCon = newRequest; 1035 | 1036 | result = MIDISendSysex(newRequest); 1037 | if ( result != noErr ) { 1038 | free( newRequest ); 1039 | errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; 1040 | error( RtMidiError::WARNING, errorString_ ); 1041 | return; 1042 | } 1043 | return; 1044 | } 1045 | else if ( nBytes > 3 ) { 1046 | errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; 1047 | error( RtMidiError::WARNING, errorString_ ); 1048 | return; 1049 | } 1050 | */ 1051 | 1052 | MIDIPacketList packetList; 1053 | MIDIPacket *packet = MIDIPacketListInit( &packetList ); 1054 | packet = MIDIPacketListAdd( &packetList, sizeof(packetList), packet, timeStamp, nBytes, (const Byte *) &message->at( 0 ) ); 1055 | if ( !packet ) { 1056 | errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; 1057 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1058 | return; 1059 | } 1060 | 1061 | // Send to any destinations that may have connected to us. 1062 | if ( data->endpoint ) { 1063 | result = MIDIReceived( data->endpoint, &packetList ); 1064 | if ( result != noErr ) { 1065 | errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; 1066 | error( RtMidiError::WARNING, errorString_ ); 1067 | } 1068 | } 1069 | 1070 | // And send to an explicit destination port if we're connected. 1071 | if ( connected_ ) { 1072 | result = MIDISend( data->port, data->destinationId, &packetList ); 1073 | if ( result != noErr ) { 1074 | errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; 1075 | error( RtMidiError::WARNING, errorString_ ); 1076 | } 1077 | } 1078 | } 1079 | 1080 | #endif // __MACOSX_CORE__ 1081 | 1082 | 1083 | //*********************************************************************// 1084 | // API: LINUX ALSA SEQUENCER 1085 | //*********************************************************************// 1086 | 1087 | // API information found at: 1088 | // - http://www.alsa-project.org/documentation.php#Library 1089 | 1090 | #if defined(__LINUX_ALSA__) 1091 | 1092 | // The ALSA Sequencer API is based on the use of a callback function for 1093 | // MIDI input. 1094 | // 1095 | // Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer 1096 | // time stamps and other assorted fixes!!! 1097 | 1098 | // If you don't need timestamping for incoming MIDI events, define the 1099 | // preprocessor definition AVOID_TIMESTAMPING to save resources 1100 | // associated with the ALSA sequencer queues. 1101 | 1102 | #include 1103 | #include 1104 | 1105 | // ALSA header file. 1106 | #include 1107 | 1108 | // A structure to hold variables related to the ALSA API 1109 | // implementation. 1110 | struct AlsaMidiData { 1111 | snd_seq_t *seq; 1112 | unsigned int portNum; 1113 | int vport; 1114 | snd_seq_port_subscribe_t *subscription; 1115 | snd_midi_event_t *coder; 1116 | unsigned int bufferSize; 1117 | unsigned char *buffer; 1118 | pthread_t thread; 1119 | pthread_t dummy_thread_id; 1120 | unsigned long long lastTime; 1121 | int queue_id; // an input queue is needed to get timestamped events 1122 | int trigger_fds[2]; 1123 | }; 1124 | 1125 | #define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) 1126 | 1127 | //*********************************************************************// 1128 | // API: LINUX ALSA 1129 | // Class Definitions: MidiInAlsa 1130 | //*********************************************************************// 1131 | 1132 | static void *alsaMidiHandler( void *ptr ) 1133 | { 1134 | MidiInApi::RtMidiInData *data = static_cast (ptr); 1135 | AlsaMidiData *apiData = static_cast (data->apiData); 1136 | 1137 | long nBytes; 1138 | unsigned long long time, lastTime; 1139 | bool continueSysex = false; 1140 | bool doDecode = false; 1141 | MidiInApi::MidiMessage message; 1142 | int poll_fd_count; 1143 | struct pollfd *poll_fds; 1144 | 1145 | snd_seq_event_t *ev; 1146 | int result; 1147 | apiData->bufferSize = 32; 1148 | result = snd_midi_event_new( 0, &apiData->coder ); 1149 | if ( result < 0 ) { 1150 | data->doInput = false; 1151 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; 1152 | return 0; 1153 | } 1154 | unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); 1155 | if ( buffer == NULL ) { 1156 | data->doInput = false; 1157 | snd_midi_event_free( apiData->coder ); 1158 | apiData->coder = 0; 1159 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; 1160 | return 0; 1161 | } 1162 | snd_midi_event_init( apiData->coder ); 1163 | snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages 1164 | 1165 | poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; 1166 | poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); 1167 | snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); 1168 | poll_fds[0].fd = apiData->trigger_fds[0]; 1169 | poll_fds[0].events = POLLIN; 1170 | 1171 | while ( data->doInput ) { 1172 | 1173 | if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { 1174 | // No data pending 1175 | if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { 1176 | if ( poll_fds[0].revents & POLLIN ) { 1177 | bool dummy; 1178 | int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); 1179 | (void) res; 1180 | } 1181 | } 1182 | continue; 1183 | } 1184 | 1185 | // If here, there should be data. 1186 | result = snd_seq_event_input( apiData->seq, &ev ); 1187 | if ( result == -ENOSPC ) { 1188 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; 1189 | continue; 1190 | } 1191 | else if ( result <= 0 ) { 1192 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; 1193 | perror("System reports"); 1194 | continue; 1195 | } 1196 | 1197 | // This is a bit weird, but we now have to decode an ALSA MIDI 1198 | // event (back) into MIDI bytes. We'll ignore non-MIDI types. 1199 | if ( !continueSysex ) message.bytes.clear(); 1200 | 1201 | doDecode = false; 1202 | switch ( ev->type ) { 1203 | 1204 | case SND_SEQ_EVENT_PORT_SUBSCRIBED: 1205 | #if defined(__RTMIDI_DEBUG__) 1206 | std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; 1207 | #endif 1208 | break; 1209 | 1210 | case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: 1211 | #if defined(__RTMIDI_DEBUG__) 1212 | std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; 1213 | std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" 1214 | << (int) ev->data.connect.sender.port 1215 | << ", dest = " << (int) ev->data.connect.dest.client << ":" 1216 | << (int) ev->data.connect.dest.port 1217 | << std::endl; 1218 | #endif 1219 | break; 1220 | 1221 | case SND_SEQ_EVENT_QFRAME: // MIDI time code 1222 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1223 | break; 1224 | 1225 | case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick 1226 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1227 | break; 1228 | 1229 | case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick 1230 | if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; 1231 | break; 1232 | 1233 | case SND_SEQ_EVENT_SENSING: // Active sensing 1234 | if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; 1235 | break; 1236 | 1237 | case SND_SEQ_EVENT_SYSEX: 1238 | if ( (data->ignoreFlags & 0x01) ) break; 1239 | if ( ev->data.ext.len > apiData->bufferSize ) { 1240 | apiData->bufferSize = ev->data.ext.len; 1241 | free( buffer ); 1242 | buffer = (unsigned char *) malloc( apiData->bufferSize ); 1243 | if ( buffer == NULL ) { 1244 | data->doInput = false; 1245 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; 1246 | break; 1247 | } 1248 | } 1249 | 1250 | default: 1251 | doDecode = true; 1252 | } 1253 | 1254 | if ( doDecode ) { 1255 | 1256 | nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); 1257 | if ( nBytes > 0 ) { 1258 | // The ALSA sequencer has a maximum buffer size for MIDI sysex 1259 | // events of 256 bytes. If a device sends sysex messages larger 1260 | // than this, they are segmented into 256 byte chunks. So, 1261 | // we'll watch for this and concatenate sysex chunks into a 1262 | // single sysex message if necessary. 1263 | if ( !continueSysex ) 1264 | message.bytes.assign( buffer, &buffer[nBytes] ); 1265 | else 1266 | message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); 1267 | 1268 | continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); 1269 | if ( !continueSysex ) { 1270 | 1271 | // Calculate the time stamp: 1272 | message.timeStamp = 0.0; 1273 | 1274 | // Method 1: Use the system time. 1275 | //(void)gettimeofday(&tv, (struct timezone *)NULL); 1276 | //time = (tv.tv_sec * 1000000) + tv.tv_usec; 1277 | 1278 | // Method 2: Use the ALSA sequencer event time data. 1279 | // (thanks to Pedro Lopez-Cabanillas!). 1280 | time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 ); 1281 | lastTime = time; 1282 | time -= apiData->lastTime; 1283 | apiData->lastTime = lastTime; 1284 | if ( data->firstMessage == true ) 1285 | data->firstMessage = false; 1286 | else 1287 | message.timeStamp = time * 0.000001; 1288 | } 1289 | else { 1290 | #if defined(__RTMIDI_DEBUG__) 1291 | std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; 1292 | #endif 1293 | } 1294 | } 1295 | } 1296 | 1297 | snd_seq_free_event( ev ); 1298 | if ( message.bytes.size() == 0 || continueSysex ) continue; 1299 | 1300 | if ( data->usingCallback ) { 1301 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 1302 | callback( message.timeStamp, &message.bytes, data->userData ); 1303 | } 1304 | else { 1305 | // As long as we haven't reached our queue size limit, push the message. 1306 | if ( data->queue.size < data->queue.ringSize ) { 1307 | data->queue.ring[data->queue.back++] = message; 1308 | if ( data->queue.back == data->queue.ringSize ) 1309 | data->queue.back = 0; 1310 | data->queue.size++; 1311 | } 1312 | else 1313 | std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; 1314 | } 1315 | } 1316 | 1317 | if ( buffer ) free( buffer ); 1318 | snd_midi_event_free( apiData->coder ); 1319 | apiData->coder = 0; 1320 | apiData->thread = apiData->dummy_thread_id; 1321 | return 0; 1322 | } 1323 | 1324 | MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) 1325 | { 1326 | initialize( clientName ); 1327 | } 1328 | 1329 | MidiInAlsa :: ~MidiInAlsa() 1330 | { 1331 | // Close a connection if it exists. 1332 | closePort(); 1333 | 1334 | // Shutdown the input thread. 1335 | AlsaMidiData *data = static_cast (apiData_); 1336 | if ( inputData_.doInput ) { 1337 | inputData_.doInput = false; 1338 | int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); 1339 | (void) res; 1340 | if ( !pthread_equal(data->thread, data->dummy_thread_id) ) 1341 | pthread_join( data->thread, NULL ); 1342 | } 1343 | 1344 | // Cleanup. 1345 | close ( data->trigger_fds[0] ); 1346 | close ( data->trigger_fds[1] ); 1347 | if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 1348 | #ifndef AVOID_TIMESTAMPING 1349 | snd_seq_free_queue( data->seq, data->queue_id ); 1350 | #endif 1351 | snd_seq_close( data->seq ); 1352 | delete data; 1353 | } 1354 | 1355 | void MidiInAlsa :: initialize( const std::string& clientName ) 1356 | { 1357 | // Set up the ALSA sequencer client. 1358 | snd_seq_t *seq; 1359 | int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); 1360 | if ( result < 0 ) { 1361 | errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; 1362 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1363 | return; 1364 | } 1365 | 1366 | // Set client name. 1367 | snd_seq_set_client_name( seq, clientName.c_str() ); 1368 | 1369 | // Save our api-specific connection information. 1370 | AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 1371 | data->seq = seq; 1372 | data->portNum = -1; 1373 | data->vport = -1; 1374 | data->subscription = 0; 1375 | data->dummy_thread_id = pthread_self(); 1376 | data->thread = data->dummy_thread_id; 1377 | data->trigger_fds[0] = -1; 1378 | data->trigger_fds[1] = -1; 1379 | apiData_ = (void *) data; 1380 | inputData_.apiData = (void *) data; 1381 | 1382 | if ( pipe(data->trigger_fds) == -1 ) { 1383 | errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; 1384 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1385 | return; 1386 | } 1387 | 1388 | // Create the input queue 1389 | #ifndef AVOID_TIMESTAMPING 1390 | data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue"); 1391 | // Set arbitrary tempo (mm=100) and resolution (240) 1392 | snd_seq_queue_tempo_t *qtempo; 1393 | snd_seq_queue_tempo_alloca(&qtempo); 1394 | snd_seq_queue_tempo_set_tempo(qtempo, 600000); 1395 | snd_seq_queue_tempo_set_ppq(qtempo, 240); 1396 | snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo); 1397 | snd_seq_drain_output(data->seq); 1398 | #endif 1399 | } 1400 | 1401 | // This function is used to count or get the pinfo structure for a given port number. 1402 | unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) 1403 | { 1404 | snd_seq_client_info_t *cinfo; 1405 | int client; 1406 | int count = 0; 1407 | snd_seq_client_info_alloca( &cinfo ); 1408 | 1409 | snd_seq_client_info_set_client( cinfo, -1 ); 1410 | while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { 1411 | client = snd_seq_client_info_get_client( cinfo ); 1412 | if ( client == 0 ) continue; 1413 | // Reset query info 1414 | snd_seq_port_info_set_client( pinfo, client ); 1415 | snd_seq_port_info_set_port( pinfo, -1 ); 1416 | while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { 1417 | unsigned int atyp = snd_seq_port_info_get_type( pinfo ); 1418 | if ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) continue; 1419 | unsigned int caps = snd_seq_port_info_get_capability( pinfo ); 1420 | if ( ( caps & type ) != type ) continue; 1421 | if ( count == portNumber ) return 1; 1422 | ++count; 1423 | } 1424 | } 1425 | 1426 | // If a negative portNumber was used, return the port count. 1427 | if ( portNumber < 0 ) return count; 1428 | return 0; 1429 | } 1430 | 1431 | unsigned int MidiInAlsa :: getPortCount() 1432 | { 1433 | snd_seq_port_info_t *pinfo; 1434 | snd_seq_port_info_alloca( &pinfo ); 1435 | 1436 | AlsaMidiData *data = static_cast (apiData_); 1437 | return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); 1438 | } 1439 | 1440 | std::string MidiInAlsa :: getPortName( unsigned int portNumber ) 1441 | { 1442 | snd_seq_client_info_t *cinfo; 1443 | snd_seq_port_info_t *pinfo; 1444 | snd_seq_client_info_alloca( &cinfo ); 1445 | snd_seq_port_info_alloca( &pinfo ); 1446 | 1447 | std::string stringName; 1448 | AlsaMidiData *data = static_cast (apiData_); 1449 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { 1450 | int cnum = snd_seq_port_info_get_client( pinfo ); 1451 | snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 1452 | std::ostringstream os; 1453 | os << snd_seq_client_info_get_name( cinfo ); 1454 | os << " "; // These lines added to make sure devices are listed 1455 | os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 1456 | os << ":"; 1457 | os << snd_seq_port_info_get_port( pinfo ); 1458 | stringName = os.str(); 1459 | return stringName; 1460 | } 1461 | 1462 | // If we get here, we didn't find a match. 1463 | errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; 1464 | error( RtMidiError::WARNING, errorString_ ); 1465 | return stringName; 1466 | } 1467 | 1468 | void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName ) 1469 | { 1470 | if ( connected_ ) { 1471 | errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; 1472 | error( RtMidiError::WARNING, errorString_ ); 1473 | return; 1474 | } 1475 | 1476 | unsigned int nSrc = this->getPortCount(); 1477 | if ( nSrc < 1 ) { 1478 | errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; 1479 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1480 | return; 1481 | } 1482 | 1483 | snd_seq_port_info_t *src_pinfo; 1484 | snd_seq_port_info_alloca( &src_pinfo ); 1485 | AlsaMidiData *data = static_cast (apiData_); 1486 | if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { 1487 | std::ostringstream ost; 1488 | ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1489 | errorString_ = ost.str(); 1490 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1491 | return; 1492 | } 1493 | 1494 | snd_seq_addr_t sender, receiver; 1495 | sender.client = snd_seq_port_info_get_client( src_pinfo ); 1496 | sender.port = snd_seq_port_info_get_port( src_pinfo ); 1497 | 1498 | snd_seq_port_info_t *pinfo; 1499 | snd_seq_port_info_alloca( &pinfo ); 1500 | if ( data->vport < 0 ) { 1501 | snd_seq_port_info_set_client( pinfo, 0 ); 1502 | snd_seq_port_info_set_port( pinfo, 0 ); 1503 | snd_seq_port_info_set_capability( pinfo, 1504 | SND_SEQ_PORT_CAP_WRITE | 1505 | SND_SEQ_PORT_CAP_SUBS_WRITE ); 1506 | snd_seq_port_info_set_type( pinfo, 1507 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | 1508 | SND_SEQ_PORT_TYPE_APPLICATION ); 1509 | snd_seq_port_info_set_midi_channels(pinfo, 16); 1510 | #ifndef AVOID_TIMESTAMPING 1511 | snd_seq_port_info_set_timestamping(pinfo, 1); 1512 | snd_seq_port_info_set_timestamp_real(pinfo, 1); 1513 | snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); 1514 | #endif 1515 | snd_seq_port_info_set_name(pinfo, portName.c_str() ); 1516 | data->vport = snd_seq_create_port(data->seq, pinfo); 1517 | 1518 | if ( data->vport < 0 ) { 1519 | errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; 1520 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1521 | return; 1522 | } 1523 | data->vport = snd_seq_port_info_get_port(pinfo); 1524 | } 1525 | 1526 | receiver.client = snd_seq_port_info_get_client( pinfo ); 1527 | receiver.port = data->vport; 1528 | 1529 | if ( !data->subscription ) { 1530 | // Make subscription 1531 | if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { 1532 | errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; 1533 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1534 | return; 1535 | } 1536 | snd_seq_port_subscribe_set_sender(data->subscription, &sender); 1537 | snd_seq_port_subscribe_set_dest(data->subscription, &receiver); 1538 | if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { 1539 | snd_seq_port_subscribe_free( data->subscription ); 1540 | data->subscription = 0; 1541 | errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; 1542 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1543 | return; 1544 | } 1545 | } 1546 | 1547 | if ( inputData_.doInput == false ) { 1548 | // Start the input queue 1549 | #ifndef AVOID_TIMESTAMPING 1550 | snd_seq_start_queue( data->seq, data->queue_id, NULL ); 1551 | snd_seq_drain_output( data->seq ); 1552 | #endif 1553 | // Start our MIDI input thread. 1554 | pthread_attr_t attr; 1555 | pthread_attr_init(&attr); 1556 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 1557 | pthread_attr_setschedpolicy(&attr, SCHED_OTHER); 1558 | 1559 | inputData_.doInput = true; 1560 | int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); 1561 | pthread_attr_destroy(&attr); 1562 | if ( err ) { 1563 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 1564 | snd_seq_port_subscribe_free( data->subscription ); 1565 | data->subscription = 0; 1566 | inputData_.doInput = false; 1567 | errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 1568 | error( RtMidiError::THREAD_ERROR, errorString_ ); 1569 | return; 1570 | } 1571 | } 1572 | 1573 | connected_ = true; 1574 | } 1575 | 1576 | void MidiInAlsa :: openVirtualPort( std::string portName ) 1577 | { 1578 | AlsaMidiData *data = static_cast (apiData_); 1579 | if ( data->vport < 0 ) { 1580 | snd_seq_port_info_t *pinfo; 1581 | snd_seq_port_info_alloca( &pinfo ); 1582 | snd_seq_port_info_set_capability( pinfo, 1583 | SND_SEQ_PORT_CAP_WRITE | 1584 | SND_SEQ_PORT_CAP_SUBS_WRITE ); 1585 | snd_seq_port_info_set_type( pinfo, 1586 | SND_SEQ_PORT_TYPE_MIDI_GENERIC | 1587 | SND_SEQ_PORT_TYPE_APPLICATION ); 1588 | snd_seq_port_info_set_midi_channels(pinfo, 16); 1589 | #ifndef AVOID_TIMESTAMPING 1590 | snd_seq_port_info_set_timestamping(pinfo, 1); 1591 | snd_seq_port_info_set_timestamp_real(pinfo, 1); 1592 | snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id); 1593 | #endif 1594 | snd_seq_port_info_set_name(pinfo, portName.c_str()); 1595 | data->vport = snd_seq_create_port(data->seq, pinfo); 1596 | 1597 | if ( data->vport < 0 ) { 1598 | errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; 1599 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1600 | return; 1601 | } 1602 | data->vport = snd_seq_port_info_get_port(pinfo); 1603 | } 1604 | 1605 | if ( inputData_.doInput == false ) { 1606 | // Wait for old thread to stop, if still running 1607 | if ( !pthread_equal(data->thread, data->dummy_thread_id) ) 1608 | pthread_join( data->thread, NULL ); 1609 | 1610 | // Start the input queue 1611 | #ifndef AVOID_TIMESTAMPING 1612 | snd_seq_start_queue( data->seq, data->queue_id, NULL ); 1613 | snd_seq_drain_output( data->seq ); 1614 | #endif 1615 | // Start our MIDI input thread. 1616 | pthread_attr_t attr; 1617 | pthread_attr_init(&attr); 1618 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 1619 | pthread_attr_setschedpolicy(&attr, SCHED_OTHER); 1620 | 1621 | inputData_.doInput = true; 1622 | int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_); 1623 | pthread_attr_destroy(&attr); 1624 | if ( err ) { 1625 | if ( data->subscription ) { 1626 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 1627 | snd_seq_port_subscribe_free( data->subscription ); 1628 | data->subscription = 0; 1629 | } 1630 | inputData_.doInput = false; 1631 | errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; 1632 | error( RtMidiError::THREAD_ERROR, errorString_ ); 1633 | return; 1634 | } 1635 | } 1636 | } 1637 | 1638 | void MidiInAlsa :: closePort( void ) 1639 | { 1640 | AlsaMidiData *data = static_cast (apiData_); 1641 | 1642 | if ( connected_ ) { 1643 | if ( data->subscription ) { 1644 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 1645 | snd_seq_port_subscribe_free( data->subscription ); 1646 | data->subscription = 0; 1647 | } 1648 | // Stop the input queue 1649 | #ifndef AVOID_TIMESTAMPING 1650 | snd_seq_stop_queue( data->seq, data->queue_id, NULL ); 1651 | snd_seq_drain_output( data->seq ); 1652 | #endif 1653 | connected_ = false; 1654 | } 1655 | 1656 | // Stop thread to avoid triggering the callback, while the port is intended to be closed 1657 | if ( inputData_.doInput ) { 1658 | inputData_.doInput = false; 1659 | int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) ); 1660 | (void) res; 1661 | if ( !pthread_equal(data->thread, data->dummy_thread_id) ) 1662 | pthread_join( data->thread, NULL ); 1663 | } 1664 | } 1665 | 1666 | //*********************************************************************// 1667 | // API: LINUX ALSA 1668 | // Class Definitions: MidiOutAlsa 1669 | //*********************************************************************// 1670 | 1671 | MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi() 1672 | { 1673 | initialize( clientName ); 1674 | } 1675 | 1676 | MidiOutAlsa :: ~MidiOutAlsa() 1677 | { 1678 | // Close a connection if it exists. 1679 | closePort(); 1680 | 1681 | // Cleanup. 1682 | AlsaMidiData *data = static_cast (apiData_); 1683 | if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); 1684 | if ( data->coder ) snd_midi_event_free( data->coder ); 1685 | if ( data->buffer ) free( data->buffer ); 1686 | snd_seq_close( data->seq ); 1687 | delete data; 1688 | } 1689 | 1690 | void MidiOutAlsa :: initialize( const std::string& clientName ) 1691 | { 1692 | // Set up the ALSA sequencer client. 1693 | snd_seq_t *seq; 1694 | int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); 1695 | if ( result1 < 0 ) { 1696 | errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; 1697 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1698 | return; 1699 | } 1700 | 1701 | // Set client name. 1702 | snd_seq_set_client_name( seq, clientName.c_str() ); 1703 | 1704 | // Save our api-specific connection information. 1705 | AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; 1706 | data->seq = seq; 1707 | data->portNum = -1; 1708 | data->vport = -1; 1709 | data->bufferSize = 32; 1710 | data->coder = 0; 1711 | data->buffer = 0; 1712 | int result = snd_midi_event_new( data->bufferSize, &data->coder ); 1713 | if ( result < 0 ) { 1714 | delete data; 1715 | errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; 1716 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1717 | return; 1718 | } 1719 | data->buffer = (unsigned char *) malloc( data->bufferSize ); 1720 | if ( data->buffer == NULL ) { 1721 | delete data; 1722 | errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 1723 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 1724 | return; 1725 | } 1726 | snd_midi_event_init( data->coder ); 1727 | apiData_ = (void *) data; 1728 | } 1729 | 1730 | unsigned int MidiOutAlsa :: getPortCount() 1731 | { 1732 | snd_seq_port_info_t *pinfo; 1733 | snd_seq_port_info_alloca( &pinfo ); 1734 | 1735 | AlsaMidiData *data = static_cast (apiData_); 1736 | return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); 1737 | } 1738 | 1739 | std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) 1740 | { 1741 | snd_seq_client_info_t *cinfo; 1742 | snd_seq_port_info_t *pinfo; 1743 | snd_seq_client_info_alloca( &cinfo ); 1744 | snd_seq_port_info_alloca( &pinfo ); 1745 | 1746 | std::string stringName; 1747 | AlsaMidiData *data = static_cast (apiData_); 1748 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { 1749 | int cnum = snd_seq_port_info_get_client(pinfo); 1750 | snd_seq_get_any_client_info( data->seq, cnum, cinfo ); 1751 | std::ostringstream os; 1752 | os << snd_seq_client_info_get_name(cinfo); 1753 | os << " "; // These lines added to make sure devices are listed 1754 | os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names 1755 | os << ":"; 1756 | os << snd_seq_port_info_get_port(pinfo); 1757 | stringName = os.str(); 1758 | return stringName; 1759 | } 1760 | 1761 | // If we get here, we didn't find a match. 1762 | errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; 1763 | error( RtMidiError::WARNING, errorString_ ); 1764 | return stringName; 1765 | } 1766 | 1767 | void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName ) 1768 | { 1769 | if ( connected_ ) { 1770 | errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; 1771 | error( RtMidiError::WARNING, errorString_ ); 1772 | return; 1773 | } 1774 | 1775 | unsigned int nSrc = this->getPortCount(); 1776 | if (nSrc < 1) { 1777 | errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; 1778 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 1779 | return; 1780 | } 1781 | 1782 | snd_seq_port_info_t *pinfo; 1783 | snd_seq_port_info_alloca( &pinfo ); 1784 | AlsaMidiData *data = static_cast (apiData_); 1785 | if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { 1786 | std::ostringstream ost; 1787 | ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 1788 | errorString_ = ost.str(); 1789 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 1790 | return; 1791 | } 1792 | 1793 | snd_seq_addr_t sender, receiver; 1794 | receiver.client = snd_seq_port_info_get_client( pinfo ); 1795 | receiver.port = snd_seq_port_info_get_port( pinfo ); 1796 | sender.client = snd_seq_client_id( data->seq ); 1797 | 1798 | if ( data->vport < 0 ) { 1799 | data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 1800 | SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 1801 | SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 1802 | if ( data->vport < 0 ) { 1803 | errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; 1804 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1805 | return; 1806 | } 1807 | } 1808 | 1809 | sender.port = data->vport; 1810 | 1811 | // Make subscription 1812 | if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) { 1813 | snd_seq_port_subscribe_free( data->subscription ); 1814 | errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; 1815 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1816 | return; 1817 | } 1818 | snd_seq_port_subscribe_set_sender(data->subscription, &sender); 1819 | snd_seq_port_subscribe_set_dest(data->subscription, &receiver); 1820 | snd_seq_port_subscribe_set_time_update(data->subscription, 1); 1821 | snd_seq_port_subscribe_set_time_real(data->subscription, 1); 1822 | if ( snd_seq_subscribe_port(data->seq, data->subscription) ) { 1823 | snd_seq_port_subscribe_free( data->subscription ); 1824 | errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; 1825 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1826 | return; 1827 | } 1828 | 1829 | connected_ = true; 1830 | } 1831 | 1832 | void MidiOutAlsa :: closePort( void ) 1833 | { 1834 | if ( connected_ ) { 1835 | AlsaMidiData *data = static_cast (apiData_); 1836 | snd_seq_unsubscribe_port( data->seq, data->subscription ); 1837 | snd_seq_port_subscribe_free( data->subscription ); 1838 | connected_ = false; 1839 | } 1840 | } 1841 | 1842 | void MidiOutAlsa :: openVirtualPort( std::string portName ) 1843 | { 1844 | AlsaMidiData *data = static_cast (apiData_); 1845 | if ( data->vport < 0 ) { 1846 | data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), 1847 | SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, 1848 | SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); 1849 | 1850 | if ( data->vport < 0 ) { 1851 | errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; 1852 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1853 | } 1854 | } 1855 | } 1856 | 1857 | void MidiOutAlsa :: sendMessage( std::vector *message ) 1858 | { 1859 | int result; 1860 | AlsaMidiData *data = static_cast (apiData_); 1861 | unsigned int nBytes = message->size(); 1862 | if ( nBytes > data->bufferSize ) { 1863 | data->bufferSize = nBytes; 1864 | result = snd_midi_event_resize_buffer ( data->coder, nBytes); 1865 | if ( result != 0 ) { 1866 | errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; 1867 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 1868 | return; 1869 | } 1870 | free (data->buffer); 1871 | data->buffer = (unsigned char *) malloc( data->bufferSize ); 1872 | if ( data->buffer == NULL ) { 1873 | errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; 1874 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 1875 | return; 1876 | } 1877 | } 1878 | 1879 | snd_seq_event_t ev; 1880 | snd_seq_ev_clear(&ev); 1881 | snd_seq_ev_set_source(&ev, data->vport); 1882 | snd_seq_ev_set_subs(&ev); 1883 | snd_seq_ev_set_direct(&ev); 1884 | for ( unsigned int i=0; ibuffer[i] = message->at(i); 1885 | result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev ); 1886 | if ( result < (int)nBytes ) { 1887 | errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; 1888 | error( RtMidiError::WARNING, errorString_ ); 1889 | return; 1890 | } 1891 | 1892 | // Send the event. 1893 | result = snd_seq_event_output(data->seq, &ev); 1894 | if ( result < 0 ) { 1895 | errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; 1896 | error( RtMidiError::WARNING, errorString_ ); 1897 | return; 1898 | } 1899 | snd_seq_drain_output(data->seq); 1900 | } 1901 | 1902 | #endif // __LINUX_ALSA__ 1903 | 1904 | 1905 | //*********************************************************************// 1906 | // API: Windows Multimedia Library (MM) 1907 | //*********************************************************************// 1908 | 1909 | // API information deciphered from: 1910 | // - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp 1911 | 1912 | // Thanks to Jean-Baptiste Berruchon for the sysex code. 1913 | 1914 | #if defined(__WINDOWS_MM__) 1915 | 1916 | // The Windows MM API is based on the use of a callback function for 1917 | // MIDI input. We convert the system specific time stamps to delta 1918 | // time values. 1919 | 1920 | // Windows MM MIDI header files. 1921 | #include 1922 | #include 1923 | 1924 | #define RT_SYSEX_BUFFER_SIZE 1024 1925 | #define RT_SYSEX_BUFFER_COUNT 4 1926 | 1927 | // A structure to hold variables related to the CoreMIDI API 1928 | // implementation. 1929 | struct WinMidiData { 1930 | HMIDIIN inHandle; // Handle to Midi Input Device 1931 | HMIDIOUT outHandle; // Handle to Midi Output Device 1932 | DWORD lastTime; 1933 | MidiInApi::MidiMessage message; 1934 | LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT]; 1935 | CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo 1936 | }; 1937 | 1938 | //*********************************************************************// 1939 | // API: Windows MM 1940 | // Class Definitions: MidiInWinMM 1941 | //*********************************************************************// 1942 | 1943 | static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, 1944 | UINT inputStatus, 1945 | DWORD_PTR instancePtr, 1946 | DWORD_PTR midiMessage, 1947 | DWORD timestamp ) 1948 | { 1949 | if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; 1950 | 1951 | //MidiInApi::RtMidiInData *data = static_cast (instancePtr); 1952 | MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; 1953 | WinMidiData *apiData = static_cast (data->apiData); 1954 | 1955 | // Calculate time stamp. 1956 | if ( data->firstMessage == true ) { 1957 | apiData->message.timeStamp = 0.0; 1958 | data->firstMessage = false; 1959 | } 1960 | else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; 1961 | apiData->lastTime = timestamp; 1962 | 1963 | if ( inputStatus == MIM_DATA ) { // Channel or system message 1964 | 1965 | // Make sure the first byte is a status byte. 1966 | unsigned char status = (unsigned char) (midiMessage & 0x000000FF); 1967 | if ( !(status & 0x80) ) return; 1968 | 1969 | // Determine the number of bytes in the MIDI message. 1970 | unsigned short nBytes = 1; 1971 | if ( status < 0xC0 ) nBytes = 3; 1972 | else if ( status < 0xE0 ) nBytes = 2; 1973 | else if ( status < 0xF0 ) nBytes = 3; 1974 | else if ( status == 0xF1 ) { 1975 | if ( data->ignoreFlags & 0x02 ) return; 1976 | else nBytes = 2; 1977 | } 1978 | else if ( status == 0xF2 ) nBytes = 3; 1979 | else if ( status == 0xF3 ) nBytes = 2; 1980 | else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) { 1981 | // A MIDI timing tick message and we're ignoring it. 1982 | return; 1983 | } 1984 | else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) { 1985 | // A MIDI active sensing message and we're ignoring it. 1986 | return; 1987 | } 1988 | 1989 | // Copy bytes to our MIDI message. 1990 | unsigned char *ptr = (unsigned char *) &midiMessage; 1991 | for ( int i=0; imessage.bytes.push_back( *ptr++ ); 1992 | } 1993 | else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) 1994 | MIDIHDR *sysex = ( MIDIHDR *) midiMessage; 1995 | if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { 1996 | // Sysex message and we're not ignoring it 1997 | for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) 1998 | apiData->message.bytes.push_back( sysex->lpData[i] ); 1999 | } 2000 | 2001 | // The WinMM API requires that the sysex buffer be requeued after 2002 | // input of each sysex message. Even if we are ignoring sysex 2003 | // messages, we still need to requeue the buffer in case the user 2004 | // decides to not ignore sysex messages in the future. However, 2005 | // it seems that WinMM calls this function with an empty sysex 2006 | // buffer when an application closes and in this case, we should 2007 | // avoid requeueing it, else the computer suddenly reboots after 2008 | // one or two minutes. 2009 | if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { 2010 | //if ( sysex->dwBytesRecorded > 0 ) { 2011 | EnterCriticalSection( &(apiData->_mutex) ); 2012 | MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); 2013 | LeaveCriticalSection( &(apiData->_mutex) ); 2014 | if ( result != MMSYSERR_NOERROR ) 2015 | std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; 2016 | 2017 | if ( data->ignoreFlags & 0x01 ) return; 2018 | } 2019 | else return; 2020 | } 2021 | 2022 | if ( data->usingCallback ) { 2023 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; 2024 | callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); 2025 | } 2026 | else { 2027 | // As long as we haven't reached our queue size limit, push the message. 2028 | if ( data->queue.size < data->queue.ringSize ) { 2029 | data->queue.ring[data->queue.back++] = apiData->message; 2030 | if ( data->queue.back == data->queue.ringSize ) 2031 | data->queue.back = 0; 2032 | data->queue.size++; 2033 | } 2034 | else 2035 | std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n"; 2036 | } 2037 | 2038 | // Clear the vector for the next input message. 2039 | apiData->message.bytes.clear(); 2040 | } 2041 | 2042 | MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) 2043 | { 2044 | initialize( clientName ); 2045 | } 2046 | 2047 | MidiInWinMM :: ~MidiInWinMM() 2048 | { 2049 | // Close a connection if it exists. 2050 | closePort(); 2051 | 2052 | WinMidiData *data = static_cast (apiData_); 2053 | DeleteCriticalSection( &(data->_mutex) ); 2054 | 2055 | // Cleanup. 2056 | delete data; 2057 | } 2058 | 2059 | void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) 2060 | { 2061 | // We'll issue a warning here if no devices are available but not 2062 | // throw an error since the user can plugin something later. 2063 | unsigned int nDevices = midiInGetNumDevs(); 2064 | if ( nDevices == 0 ) { 2065 | errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; 2066 | error( RtMidiError::WARNING, errorString_ ); 2067 | } 2068 | 2069 | // Save our api-specific connection information. 2070 | WinMidiData *data = (WinMidiData *) new WinMidiData; 2071 | apiData_ = (void *) data; 2072 | inputData_.apiData = (void *) data; 2073 | data->message.bytes.clear(); // needs to be empty for first input message 2074 | 2075 | if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) { 2076 | errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; 2077 | error( RtMidiError::WARNING, errorString_ ); 2078 | } 2079 | } 2080 | 2081 | void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) 2082 | { 2083 | if ( connected_ ) { 2084 | errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; 2085 | error( RtMidiError::WARNING, errorString_ ); 2086 | return; 2087 | } 2088 | 2089 | unsigned int nDevices = midiInGetNumDevs(); 2090 | if (nDevices == 0) { 2091 | errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; 2092 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2093 | return; 2094 | } 2095 | 2096 | if ( portNumber >= nDevices ) { 2097 | std::ostringstream ost; 2098 | ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2099 | errorString_ = ost.str(); 2100 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2101 | return; 2102 | } 2103 | 2104 | WinMidiData *data = static_cast (apiData_); 2105 | MMRESULT result = midiInOpen( &data->inHandle, 2106 | portNumber, 2107 | (DWORD_PTR)&midiInputCallback, 2108 | (DWORD_PTR)&inputData_, 2109 | CALLBACK_FUNCTION ); 2110 | if ( result != MMSYSERR_NOERROR ) { 2111 | errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; 2112 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2113 | return; 2114 | } 2115 | 2116 | // Allocate and init the sysex buffers. 2117 | for ( int i=0; isysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; 2119 | data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ]; 2120 | data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE; 2121 | data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator 2122 | data->sysexBuffer[i]->dwFlags = 0; 2123 | 2124 | result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2125 | if ( result != MMSYSERR_NOERROR ) { 2126 | midiInClose( data->inHandle ); 2127 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; 2128 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2129 | return; 2130 | } 2131 | 2132 | // Register the buffer. 2133 | result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); 2134 | if ( result != MMSYSERR_NOERROR ) { 2135 | midiInClose( data->inHandle ); 2136 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; 2137 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2138 | return; 2139 | } 2140 | } 2141 | 2142 | result = midiInStart( data->inHandle ); 2143 | if ( result != MMSYSERR_NOERROR ) { 2144 | midiInClose( data->inHandle ); 2145 | errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; 2146 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2147 | return; 2148 | } 2149 | 2150 | connected_ = true; 2151 | } 2152 | 2153 | void MidiInWinMM :: openVirtualPort( std::string /*portName*/ ) 2154 | { 2155 | // This function cannot be implemented for the Windows MM MIDI API. 2156 | errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2157 | error( RtMidiError::WARNING, errorString_ ); 2158 | } 2159 | 2160 | void MidiInWinMM :: closePort( void ) 2161 | { 2162 | if ( connected_ ) { 2163 | WinMidiData *data = static_cast (apiData_); 2164 | EnterCriticalSection( &(data->_mutex) ); 2165 | midiInReset( data->inHandle ); 2166 | midiInStop( data->inHandle ); 2167 | 2168 | for ( int i=0; iinHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); 2170 | delete [] data->sysexBuffer[i]->lpData; 2171 | delete [] data->sysexBuffer[i]; 2172 | if ( result != MMSYSERR_NOERROR ) { 2173 | midiInClose( data->inHandle ); 2174 | errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; 2175 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2176 | return; 2177 | } 2178 | } 2179 | 2180 | midiInClose( data->inHandle ); 2181 | connected_ = false; 2182 | LeaveCriticalSection( &(data->_mutex) ); 2183 | } 2184 | } 2185 | 2186 | unsigned int MidiInWinMM :: getPortCount() 2187 | { 2188 | return midiInGetNumDevs(); 2189 | } 2190 | 2191 | std::string MidiInWinMM :: getPortName( unsigned int portNumber ) 2192 | { 2193 | std::string stringName; 2194 | unsigned int nDevices = midiInGetNumDevs(); 2195 | if ( portNumber >= nDevices ) { 2196 | std::ostringstream ost; 2197 | ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2198 | errorString_ = ost.str(); 2199 | error( RtMidiError::WARNING, errorString_ ); 2200 | return stringName; 2201 | } 2202 | 2203 | MIDIINCAPS deviceCaps; 2204 | midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); 2205 | 2206 | #if defined( UNICODE ) || defined( _UNICODE ) 2207 | int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; 2208 | stringName.assign( length, 0 ); 2209 | length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); 2210 | #else 2211 | stringName = std::string( deviceCaps.szPname ); 2212 | #endif 2213 | 2214 | // Next lines added to add the portNumber to the name so that 2215 | // the device's names are sure to be listed with individual names 2216 | // even when they have the same brand name 2217 | std::ostringstream os; 2218 | os << " "; 2219 | os << portNumber; 2220 | stringName += os.str(); 2221 | 2222 | return stringName; 2223 | } 2224 | 2225 | //*********************************************************************// 2226 | // API: Windows MM 2227 | // Class Definitions: MidiOutWinMM 2228 | //*********************************************************************// 2229 | 2230 | MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi() 2231 | { 2232 | initialize( clientName ); 2233 | } 2234 | 2235 | MidiOutWinMM :: ~MidiOutWinMM() 2236 | { 2237 | // Close a connection if it exists. 2238 | closePort(); 2239 | 2240 | // Cleanup. 2241 | WinMidiData *data = static_cast (apiData_); 2242 | delete data; 2243 | } 2244 | 2245 | void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) 2246 | { 2247 | // We'll issue a warning here if no devices are available but not 2248 | // throw an error since the user can plug something in later. 2249 | unsigned int nDevices = midiOutGetNumDevs(); 2250 | if ( nDevices == 0 ) { 2251 | errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; 2252 | error( RtMidiError::WARNING, errorString_ ); 2253 | } 2254 | 2255 | // Save our api-specific connection information. 2256 | WinMidiData *data = (WinMidiData *) new WinMidiData; 2257 | apiData_ = (void *) data; 2258 | } 2259 | 2260 | unsigned int MidiOutWinMM :: getPortCount() 2261 | { 2262 | return midiOutGetNumDevs(); 2263 | } 2264 | 2265 | std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) 2266 | { 2267 | std::string stringName; 2268 | unsigned int nDevices = midiOutGetNumDevs(); 2269 | if ( portNumber >= nDevices ) { 2270 | std::ostringstream ost; 2271 | ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2272 | errorString_ = ost.str(); 2273 | error( RtMidiError::WARNING, errorString_ ); 2274 | return stringName; 2275 | } 2276 | 2277 | MIDIOUTCAPS deviceCaps; 2278 | midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS)); 2279 | 2280 | #if defined( UNICODE ) || defined( _UNICODE ) 2281 | int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1; 2282 | stringName.assign( length, 0 ); 2283 | length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL); 2284 | #else 2285 | stringName = std::string( deviceCaps.szPname ); 2286 | #endif 2287 | 2288 | return stringName; 2289 | } 2290 | 2291 | void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ ) 2292 | { 2293 | if ( connected_ ) { 2294 | errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; 2295 | error( RtMidiError::WARNING, errorString_ ); 2296 | return; 2297 | } 2298 | 2299 | unsigned int nDevices = midiOutGetNumDevs(); 2300 | if (nDevices < 1) { 2301 | errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; 2302 | error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); 2303 | return; 2304 | } 2305 | 2306 | if ( portNumber >= nDevices ) { 2307 | std::ostringstream ost; 2308 | ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; 2309 | errorString_ = ost.str(); 2310 | error( RtMidiError::INVALID_PARAMETER, errorString_ ); 2311 | return; 2312 | } 2313 | 2314 | WinMidiData *data = static_cast (apiData_); 2315 | MMRESULT result = midiOutOpen( &data->outHandle, 2316 | portNumber, 2317 | (DWORD)NULL, 2318 | (DWORD)NULL, 2319 | CALLBACK_NULL ); 2320 | if ( result != MMSYSERR_NOERROR ) { 2321 | errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; 2322 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2323 | return; 2324 | } 2325 | 2326 | connected_ = true; 2327 | } 2328 | 2329 | void MidiOutWinMM :: closePort( void ) 2330 | { 2331 | if ( connected_ ) { 2332 | WinMidiData *data = static_cast (apiData_); 2333 | midiOutReset( data->outHandle ); 2334 | midiOutClose( data->outHandle ); 2335 | connected_ = false; 2336 | } 2337 | } 2338 | 2339 | void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ ) 2340 | { 2341 | // This function cannot be implemented for the Windows MM MIDI API. 2342 | errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; 2343 | error( RtMidiError::WARNING, errorString_ ); 2344 | } 2345 | 2346 | void MidiOutWinMM :: sendMessage( std::vector *message ) 2347 | { 2348 | if ( !connected_ ) return; 2349 | 2350 | unsigned int nBytes = static_cast(message->size()); 2351 | if ( nBytes == 0 ) { 2352 | errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; 2353 | error( RtMidiError::WARNING, errorString_ ); 2354 | return; 2355 | } 2356 | 2357 | MMRESULT result; 2358 | WinMidiData *data = static_cast (apiData_); 2359 | if ( message->at(0) == 0xF0 ) { // Sysex message 2360 | 2361 | // Allocate buffer for sysex data. 2362 | char *buffer = (char *) malloc( nBytes ); 2363 | if ( buffer == NULL ) { 2364 | errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; 2365 | error( RtMidiError::MEMORY_ERROR, errorString_ ); 2366 | return; 2367 | } 2368 | 2369 | // Copy data to buffer. 2370 | for ( unsigned int i=0; iat(i); 2371 | 2372 | // Create and prepare MIDIHDR structure. 2373 | MIDIHDR sysex; 2374 | sysex.lpData = (LPSTR) buffer; 2375 | sysex.dwBufferLength = nBytes; 2376 | sysex.dwFlags = 0; 2377 | result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); 2378 | if ( result != MMSYSERR_NOERROR ) { 2379 | free( buffer ); 2380 | errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; 2381 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2382 | return; 2383 | } 2384 | 2385 | // Send the message. 2386 | result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) ); 2387 | if ( result != MMSYSERR_NOERROR ) { 2388 | free( buffer ); 2389 | errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; 2390 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2391 | return; 2392 | } 2393 | 2394 | // Unprepare the buffer and MIDIHDR. 2395 | while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 ); 2396 | free( buffer ); 2397 | } 2398 | else { // Channel or system message. 2399 | 2400 | // Make sure the message size isn't too big. 2401 | if ( nBytes > 3 ) { 2402 | errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; 2403 | error( RtMidiError::WARNING, errorString_ ); 2404 | return; 2405 | } 2406 | 2407 | // Pack MIDI bytes into double word. 2408 | DWORD packet; 2409 | unsigned char *ptr = (unsigned char *) &packet; 2410 | for ( unsigned int i=0; iat(i); 2412 | ++ptr; 2413 | } 2414 | 2415 | // Send the message immediately. 2416 | result = midiOutShortMsg( data->outHandle, packet ); 2417 | if ( result != MMSYSERR_NOERROR ) { 2418 | errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; 2419 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2420 | } 2421 | } 2422 | } 2423 | 2424 | #endif // __WINDOWS_MM__ 2425 | 2426 | 2427 | //*********************************************************************// 2428 | // API: UNIX JACK 2429 | // 2430 | // Written primarily by Alexander Svetalkin, with updates for delta 2431 | // time by Gary Scavone, April 2011. 2432 | // 2433 | // *********************************************************************// 2434 | 2435 | #if defined(__UNIX_JACK__) 2436 | 2437 | // JACK header files 2438 | #include 2439 | #include 2440 | #include 2441 | 2442 | #define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer 2443 | 2444 | struct JackMidiData { 2445 | jack_client_t *client; 2446 | jack_port_t *port; 2447 | jack_ringbuffer_t *buffSize; 2448 | jack_ringbuffer_t *buffMessage; 2449 | jack_time_t lastTime; 2450 | MidiInApi :: RtMidiInData *rtMidiIn; 2451 | }; 2452 | 2453 | //*********************************************************************// 2454 | // API: JACK 2455 | // Class Definitions: MidiInJack 2456 | //*********************************************************************// 2457 | 2458 | static int jackProcessIn( jack_nframes_t nframes, void *arg ) 2459 | { 2460 | JackMidiData *jData = (JackMidiData *) arg; 2461 | MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; 2462 | jack_midi_event_t event; 2463 | jack_time_t time; 2464 | 2465 | // Is port created? 2466 | if ( jData->port == NULL ) return 0; 2467 | void *buff = jack_port_get_buffer( jData->port, nframes ); 2468 | 2469 | // We have midi events in buffer 2470 | int evCount = jack_midi_get_event_count( buff ); 2471 | for (int j = 0; j < evCount; j++) { 2472 | MidiInApi::MidiMessage message; 2473 | message.bytes.clear(); 2474 | 2475 | jack_midi_event_get( &event, buff, j ); 2476 | 2477 | for ( unsigned int i = 0; i < event.size; i++ ) 2478 | message.bytes.push_back( event.buffer[i] ); 2479 | 2480 | // Compute the delta time. 2481 | time = jack_get_time(); 2482 | if ( rtData->firstMessage == true ) 2483 | rtData->firstMessage = false; 2484 | else 2485 | message.timeStamp = ( time - jData->lastTime ) * 0.000001; 2486 | 2487 | jData->lastTime = time; 2488 | 2489 | if ( !rtData->continueSysex ) { 2490 | if ( rtData->usingCallback ) { 2491 | RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; 2492 | callback( message.timeStamp, &message.bytes, rtData->userData ); 2493 | } 2494 | else { 2495 | // As long as we haven't reached our queue size limit, push the message. 2496 | if ( rtData->queue.size < rtData->queue.ringSize ) { 2497 | rtData->queue.ring[rtData->queue.back++] = message; 2498 | if ( rtData->queue.back == rtData->queue.ringSize ) 2499 | rtData->queue.back = 0; 2500 | rtData->queue.size++; 2501 | } 2502 | else 2503 | std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; 2504 | } 2505 | } 2506 | } 2507 | 2508 | return 0; 2509 | } 2510 | 2511 | MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) 2512 | { 2513 | initialize( clientName ); 2514 | } 2515 | 2516 | void MidiInJack :: initialize( const std::string& clientName ) 2517 | { 2518 | JackMidiData *data = new JackMidiData; 2519 | apiData_ = (void *) data; 2520 | 2521 | data->rtMidiIn = &inputData_; 2522 | data->port = NULL; 2523 | data->client = NULL; 2524 | this->clientName = clientName; 2525 | 2526 | connect(); 2527 | } 2528 | 2529 | void MidiInJack :: connect() 2530 | { 2531 | JackMidiData *data = static_cast (apiData_); 2532 | if ( data->client ) 2533 | return; 2534 | 2535 | // Initialize JACK client 2536 | if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { 2537 | errorString_ = "MidiInJack::initialize: JACK server not running?"; 2538 | error( RtMidiError::WARNING, errorString_ ); 2539 | return; 2540 | } 2541 | 2542 | jack_set_process_callback( data->client, jackProcessIn, data ); 2543 | jack_activate( data->client ); 2544 | } 2545 | 2546 | MidiInJack :: ~MidiInJack() 2547 | { 2548 | JackMidiData *data = static_cast (apiData_); 2549 | closePort(); 2550 | 2551 | if ( data->client ) 2552 | jack_client_close( data->client ); 2553 | delete data; 2554 | } 2555 | 2556 | void MidiInJack :: openPort( unsigned int portNumber, const std::string portName ) 2557 | { 2558 | JackMidiData *data = static_cast (apiData_); 2559 | 2560 | connect(); 2561 | 2562 | // Creating new port 2563 | if ( data->port == NULL) 2564 | data->port = jack_port_register( data->client, portName.c_str(), 2565 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 2566 | 2567 | if ( data->port == NULL) { 2568 | errorString_ = "MidiInJack::openPort: JACK error creating port"; 2569 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2570 | return; 2571 | } 2572 | 2573 | // Connecting to the output 2574 | std::string name = getPortName( portNumber ); 2575 | jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); 2576 | } 2577 | 2578 | void MidiInJack :: openVirtualPort( const std::string portName ) 2579 | { 2580 | JackMidiData *data = static_cast (apiData_); 2581 | 2582 | connect(); 2583 | if ( data->port == NULL ) 2584 | data->port = jack_port_register( data->client, portName.c_str(), 2585 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); 2586 | 2587 | if ( data->port == NULL ) { 2588 | errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; 2589 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2590 | } 2591 | } 2592 | 2593 | unsigned int MidiInJack :: getPortCount() 2594 | { 2595 | int count = 0; 2596 | JackMidiData *data = static_cast (apiData_); 2597 | connect(); 2598 | if ( !data->client ) 2599 | return 0; 2600 | 2601 | // List of available ports 2602 | const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 2603 | 2604 | if ( ports == NULL ) return 0; 2605 | while ( ports[count] != NULL ) 2606 | count++; 2607 | 2608 | free( ports ); 2609 | 2610 | return count; 2611 | } 2612 | 2613 | std::string MidiInJack :: getPortName( unsigned int portNumber ) 2614 | { 2615 | JackMidiData *data = static_cast (apiData_); 2616 | std::string retStr(""); 2617 | 2618 | connect(); 2619 | 2620 | // List of available ports 2621 | const char **ports = jack_get_ports( data->client, NULL, 2622 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); 2623 | 2624 | // Check port validity 2625 | if ( ports == NULL ) { 2626 | errorString_ = "MidiInJack::getPortName: no ports available!"; 2627 | error( RtMidiError::WARNING, errorString_ ); 2628 | return retStr; 2629 | } 2630 | 2631 | if ( ports[portNumber] == NULL ) { 2632 | std::ostringstream ost; 2633 | ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2634 | errorString_ = ost.str(); 2635 | error( RtMidiError::WARNING, errorString_ ); 2636 | } 2637 | else retStr.assign( ports[portNumber] ); 2638 | 2639 | free( ports ); 2640 | return retStr; 2641 | } 2642 | 2643 | void MidiInJack :: closePort() 2644 | { 2645 | JackMidiData *data = static_cast (apiData_); 2646 | 2647 | if ( data->port == NULL ) return; 2648 | jack_port_unregister( data->client, data->port ); 2649 | data->port = NULL; 2650 | } 2651 | 2652 | //*********************************************************************// 2653 | // API: JACK 2654 | // Class Definitions: MidiOutJack 2655 | //*********************************************************************// 2656 | 2657 | // Jack process callback 2658 | static int jackProcessOut( jack_nframes_t nframes, void *arg ) 2659 | { 2660 | JackMidiData *data = (JackMidiData *) arg; 2661 | jack_midi_data_t *midiData; 2662 | int space; 2663 | 2664 | // Is port created? 2665 | if ( data->port == NULL ) return 0; 2666 | 2667 | void *buff = jack_port_get_buffer( data->port, nframes ); 2668 | jack_midi_clear_buffer( buff ); 2669 | 2670 | while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) { 2671 | jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) ); 2672 | midiData = jack_midi_event_reserve( buff, 0, space ); 2673 | 2674 | jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space ); 2675 | } 2676 | 2677 | return 0; 2678 | } 2679 | 2680 | MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi() 2681 | { 2682 | initialize( clientName ); 2683 | } 2684 | 2685 | void MidiOutJack :: initialize( const std::string& clientName ) 2686 | { 2687 | JackMidiData *data = new JackMidiData; 2688 | apiData_ = (void *) data; 2689 | 2690 | data->port = NULL; 2691 | data->client = NULL; 2692 | this->clientName = clientName; 2693 | 2694 | connect(); 2695 | } 2696 | 2697 | void MidiOutJack :: connect() 2698 | { 2699 | JackMidiData *data = static_cast (apiData_); 2700 | if ( data->client ) 2701 | return; 2702 | 2703 | // Initialize JACK client 2704 | if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { 2705 | errorString_ = "MidiOutJack::initialize: JACK server not running?"; 2706 | error( RtMidiError::WARNING, errorString_ ); 2707 | return; 2708 | } 2709 | 2710 | jack_set_process_callback( data->client, jackProcessOut, data ); 2711 | data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); 2712 | data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); 2713 | jack_activate( data->client ); 2714 | } 2715 | 2716 | MidiOutJack :: ~MidiOutJack() 2717 | { 2718 | JackMidiData *data = static_cast (apiData_); 2719 | closePort(); 2720 | 2721 | if ( data->client ) { 2722 | // Cleanup 2723 | jack_client_close( data->client ); 2724 | jack_ringbuffer_free( data->buffSize ); 2725 | jack_ringbuffer_free( data->buffMessage ); 2726 | } 2727 | 2728 | delete data; 2729 | } 2730 | 2731 | void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName ) 2732 | { 2733 | JackMidiData *data = static_cast (apiData_); 2734 | 2735 | connect(); 2736 | 2737 | // Creating new port 2738 | if ( data->port == NULL ) 2739 | data->port = jack_port_register( data->client, portName.c_str(), 2740 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 2741 | 2742 | if ( data->port == NULL ) { 2743 | errorString_ = "MidiOutJack::openPort: JACK error creating port"; 2744 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2745 | return; 2746 | } 2747 | 2748 | // Connecting to the output 2749 | std::string name = getPortName( portNumber ); 2750 | jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); 2751 | } 2752 | 2753 | void MidiOutJack :: openVirtualPort( const std::string portName ) 2754 | { 2755 | JackMidiData *data = static_cast (apiData_); 2756 | 2757 | connect(); 2758 | if ( data->port == NULL ) 2759 | data->port = jack_port_register( data->client, portName.c_str(), 2760 | JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); 2761 | 2762 | if ( data->port == NULL ) { 2763 | errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; 2764 | error( RtMidiError::DRIVER_ERROR, errorString_ ); 2765 | } 2766 | } 2767 | 2768 | unsigned int MidiOutJack :: getPortCount() 2769 | { 2770 | int count = 0; 2771 | JackMidiData *data = static_cast (apiData_); 2772 | connect(); 2773 | if ( !data->client ) 2774 | return 0; 2775 | 2776 | // List of available ports 2777 | const char **ports = jack_get_ports( data->client, NULL, 2778 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 2779 | 2780 | if ( ports == NULL ) return 0; 2781 | while ( ports[count] != NULL ) 2782 | count++; 2783 | 2784 | free( ports ); 2785 | 2786 | return count; 2787 | } 2788 | 2789 | std::string MidiOutJack :: getPortName( unsigned int portNumber ) 2790 | { 2791 | JackMidiData *data = static_cast (apiData_); 2792 | std::string retStr(""); 2793 | 2794 | connect(); 2795 | 2796 | // List of available ports 2797 | const char **ports = jack_get_ports( data->client, NULL, 2798 | JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); 2799 | 2800 | // Check port validity 2801 | if ( ports == NULL) { 2802 | errorString_ = "MidiOutJack::getPortName: no ports available!"; 2803 | error( RtMidiError::WARNING, errorString_ ); 2804 | return retStr; 2805 | } 2806 | 2807 | if ( ports[portNumber] == NULL) { 2808 | std::ostringstream ost; 2809 | ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; 2810 | errorString_ = ost.str(); 2811 | error( RtMidiError::WARNING, errorString_ ); 2812 | } 2813 | else retStr.assign( ports[portNumber] ); 2814 | 2815 | free( ports ); 2816 | return retStr; 2817 | } 2818 | 2819 | void MidiOutJack :: closePort() 2820 | { 2821 | JackMidiData *data = static_cast (apiData_); 2822 | 2823 | if ( data->port == NULL ) return; 2824 | jack_port_unregister( data->client, data->port ); 2825 | data->port = NULL; 2826 | } 2827 | 2828 | void MidiOutJack :: sendMessage( std::vector *message ) 2829 | { 2830 | int nBytes = message->size(); 2831 | JackMidiData *data = static_cast (apiData_); 2832 | 2833 | // Write full message to buffer 2834 | jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0], 2835 | message->size() ); 2836 | jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); 2837 | } 2838 | 2839 | #endif // __UNIX_JACK__ 2840 | --------------------------------------------------------------------------------