├── .github └── dependabot.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── binding.gyp ├── midi.js ├── package-lock.json ├── package.json ├── src ├── input.cpp ├── input.h ├── midi.cpp ├── output.cpp └── output.h └── test ├── fixture └── 144-23-81.bin ├── input ├── input-hang-test.js ├── input-list-test.js ├── input-port-status-test.js ├── input-reopen-test.js ├── input-test.js └── virtual-input-test.js ├── output ├── output-list-test.js ├── output-port-status-test.js ├── output-test.js └── virtual-output-test.js ├── unit ├── input.js └── output.js ├── virtual-loopback-test-automated.js ├── virtual-loopback-test.js ├── virtual-read-stream-resume-test.js ├── virtual-read-stream-test.js └── virtual-write-stream-test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "17:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .lock-wscript 2 | build/ 3 | node_modules/ 4 | .vscode/ 5 | .vs/ 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/rtmidi"] 2 | path = vendor/rtmidi 3 | url = https://github.com/justinlatimer/rtmidi.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 12 5 | - 14 6 | - 15 7 | os: 8 | - linux 9 | - osx 10 | - windows 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - libasound2-dev 17 | notifications: 18 | email: false 19 | script: 'true' 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # node-midi Changelog 2 | 3 | ## Version 2.0.0 4 | 5 | * Add tests for listing ports. 6 | * Prevent RtMidi from ensuring unique port names on Windows (Breaking change to behaviour of port names on Windows). 7 | * RtMidi Changes 8 | * Trim whitespace changes from endpoint names (Breaking change to behaviour of port names on macOS). 9 | * Refactor CoreMIDI client usage for stability. 10 | 11 | ## Version 1.0.4 12 | 13 | * Use a git submodule for RtMidi. 14 | * Use a larger sysex message buffer size on Windows. 15 | * Fix links in readme (dzoba) 16 | 17 | ## Version 1.0.3 18 | 19 | * Update RtMidi to d2dd50d. 20 | 21 | ## Version 1.0.2 22 | 23 | * Add a 'send' alias for 'sendMessage'. 24 | * Use the NAN module init. 25 | * Ensure promises can be resolved inside on('message') callbacks (Malvineous) 26 | 27 | ## Version 1.0.1 28 | 29 | * Update supported node versions. 30 | * Update dependencies. 31 | 32 | ## Version 1.0.0 33 | 34 | * Added isPortOpen (nroadley) 35 | * Improve examples in README (Simon Egersand) 36 | * Updated examples to es6 (Amila Welihinda) 37 | * Update mocha (The Repo Nanny) 38 | * Update rtmidi to 4.0.0 (Tim Susa) 39 | * Add license to package.json. 40 | * Use NAN to handle additional differences in modern nodejs versions. 41 | * Change supported nodejs version to 6, 8, 10, 12. 42 | * Better handling of Buffer for stream (jhorology) 43 | * Fixing read stream resume bug (justinjmoses) 44 | * Fix clean up of inputs. 45 | * Exception catching to prevent RtMidi errors crashing the node process (Jeremy Bernstein) 46 | * Split classes into different files. 47 | * Fix capitalisation on the classes. 48 | * Add some documentation about MIDI message formats. 49 | 50 | ## Version 0.9.5 51 | 52 | * Updated RtMidi to most recent version (Szymon Kaliski) 53 | * Updating NAN to the latest version. This allows node 6.2.0 to be used. (Michael Lawrence) 54 | 55 | ## Version 0.9.4 56 | 57 | * Upgrade to nan v2.0 (Julián Duque) 58 | * Call cancelCallback when closing port (Szymon Kaliski) 59 | 60 | ## Version 0.9.3 61 | 62 | * Update NAN version for iojs 2.x support. (Ilkka Myller) 63 | 64 | ## Version 0.9.2 65 | 66 | * More NAN use for broader support (nw.js, iojs). (Andrew Morton) 67 | 68 | ## Version 0.9.1 69 | 70 | * Use NAN to support node 0.8-0.12. (Andrew Morton) 71 | 72 | ## Version 0.9.0 73 | 74 | * Avoid fatal error closing unopened port. (Andrew Morton) 75 | * Upgraded RtMidi to 2.1.0. (Hugo Hromic) 76 | * Fixed compile warnings on Windows. (Hugo Hromic) 77 | 78 | ## Version 0.8.1 79 | 80 | * Fixing crash when `new` is omitted. (Andrew Morton) 81 | 82 | ## Version 0.8.0 83 | 84 | * Update RtMidi to latest upstream. (Andrew Morton) 85 | * Added missing MIDI Clock event case. (Hugo Hromic) 86 | * Upgraded RtMidi library to version 2.0.1. (Hugo Hromic) 87 | 88 | ## Version 0.7.1 89 | 90 | * Remove unmatched uv_unref() causing segfault. (Andrew Morton) 91 | 92 | ## Version 0.7.0 93 | 94 | * Add readable/writable stream support. (Elijah Insua) 95 | 96 | ## Version 0.6.0 97 | 98 | * Upgrade build system to node-gyp bringing Windows support. (Michael Alyn Miller) 99 | * Fix an overzealous delete. 100 | 101 | ## Version 0.5.0 102 | 103 | * Switch from using libev to libuv. (Luc Deschenaux) 104 | * Check a port number is valid before trying to open it. (Luc Deschenaux) 105 | * Remove support for node versions < 0.6.0. 106 | * Code and build system improvements with new supported node versions. 107 | * Update documentation. 108 | 109 | 110 | ## Version 0.4.0 111 | 112 | * Upgrade RtMidi to 1.0.15. (Luc Deschenaux) 113 | * Refactor the EventEmitter inheritance to support node > 0.5.2. (Luc Deschenaux) 114 | * Add support for ignore type settings (Sysex, Timing, Active Sensing) on the input. (Luc Deschenaux) 115 | * List supported node versions in the package.json. 116 | 117 | 118 | ## Version 0.3.0 119 | 120 | * Add support for virtual input and output ports. 121 | 122 | 123 | ## Version 0.2.0 124 | 125 | * Add Linux support to the build script. 126 | 127 | 128 | ## Version 0.1.0 129 | 130 | * Initial release. 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2021 by Justin Latimer. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ♪ ♫ ♩ ♬ 2 | 3 | # node-midi 4 | 5 | A node.js wrapper for the RtMidi C++ library that provides realtime MIDI I/O. 6 | RtMidi supports Linux (ALSA & Jack), Macintosh OS X (CoreMidi), and Windows 7 | (Multimedia). 8 | 9 | [![Build Status](https://travis-ci.com/justinlatimer/node-midi.svg?branch=master)](https://travis-ci.com/github/justinlatimer/node-midi) 10 | 11 | ## Prerequisites 12 | 13 | ### OSX 14 | 15 | * Some version of Xcode (or Command Line Tools) 16 | * Python (for node-gyp) 17 | 18 | ### Windows 19 | 20 | * Microsoft Visual C++ (the Express edition works fine) 21 | * Python (for node-gyp) 22 | 23 | ### Linux 24 | 25 | * A C++ compiler 26 | * You must have installed and configured ALSA. Without it this module will **NOT** build. 27 | * Install the libasound2-dev package. 28 | * Python (for node-gyp) 29 | 30 | ## Installation 31 | 32 | Installation uses node-gyp and requires Python 2.7.2 or higher. 33 | 34 | From npm: 35 | ```bash 36 | $ npm install midi 37 | ``` 38 | 39 | From source: 40 | ```bash 41 | $ git clone https://github.com/justinlatimer/node-midi.git 42 | $ cd node-midi/ 43 | $ npm install 44 | ``` 45 | 46 | ## Usage 47 | 48 | ### MIDI Messages 49 | 50 | This library deals with MIDI messages as JS Arrays for both input and output. For example, `[144,69,127]` is MIDI message with status code 144 which means "Note on" on "Channel 1". 51 | 52 | For list of midi status codes, see http://www.midi.org/techspecs/midimessages.php 53 | 54 | ### Input 55 | 56 | ```js 57 | const midi = require('midi'); 58 | 59 | // Set up a new input. 60 | const input = new midi.Input(); 61 | 62 | // Count the available input ports. 63 | input.getPortCount(); 64 | 65 | // Get the name of a specified input port. 66 | input.getPortName(0); 67 | 68 | // Configure a callback. 69 | input.on('message', (deltaTime, message) => { 70 | // The message is an array of numbers corresponding to the MIDI bytes: 71 | // [status, data1, data2] 72 | // https://www.cs.cf.ac.uk/Dave/Multimedia/node158.html has some helpful 73 | // information interpreting the messages. 74 | console.log(`m: ${message} d: ${deltaTime}`); 75 | }); 76 | 77 | // Open the first available input port. 78 | input.openPort(0); 79 | 80 | // Sysex, timing, and active sensing messages are ignored 81 | // by default. To enable these message types, pass false for 82 | // the appropriate type in the function below. 83 | // Order: (Sysex, Timing, Active Sensing) 84 | // For example if you want to receive only MIDI Clock beats 85 | // you should use 86 | // input.ignoreTypes(true, false, true) 87 | input.ignoreTypes(false, false, false); 88 | 89 | // ... receive MIDI messages ... 90 | 91 | // Close the port when done. 92 | setTimeout(function() { 93 | input.closePort(); 94 | }, 100000); 95 | ``` 96 | 97 | ### Output 98 | 99 | ```js 100 | const midi = require('midi'); 101 | 102 | // Set up a new output. 103 | const output = new midi.Output(); 104 | 105 | // Count the available output ports. 106 | output.getPortCount(); 107 | 108 | // Get the name of a specified output port. 109 | output.getPortName(0); 110 | 111 | // Open the first available output port. 112 | output.openPort(0); 113 | 114 | // Send a MIDI message. 115 | output.sendMessage([176,22,1]); 116 | 117 | // Close the port when done. 118 | output.closePort(); 119 | ``` 120 | 121 | ### Virtual Ports 122 | 123 | Instead of opening a connection to an existing MIDI device, on Mac OS X and 124 | Linux with ALSA you can create a virtual device that other software may 125 | connect to. This can be done simply by calling openVirtualPort(portName) instead 126 | of openPort(portNumber). 127 | 128 | ```js 129 | const midi = require('midi'); 130 | 131 | // Set up a new input. 132 | const input = new midi.Input(); 133 | 134 | // Configure a callback. 135 | input.on('message', (deltaTime, message) => { 136 | console.log(`m: ${message} d: ${deltaTime}`); 137 | }); 138 | 139 | // Create a virtual input port. 140 | input.openVirtualPort("Test Input"); 141 | 142 | // A midi device "Test Input" is now available for other 143 | // software to send messages to. 144 | 145 | // ... receive MIDI messages ... 146 | 147 | // Close the port when done. 148 | input.closePort(); 149 | ``` 150 | 151 | The same can be done with output ports. 152 | 153 | ### Streams 154 | 155 | You can also use this library with streams! Here are the interfaces 156 | 157 | #### Readable Stream 158 | 159 | ```js 160 | // create a readable stream 161 | const stream1 = midi.createReadStream(); 162 | 163 | // createReadStream also accepts an optional `input` param 164 | const input = new midi.Input(); 165 | input.openVirtualPort('hello world'); 166 | 167 | const stream2 = midi.createReadStream(input) 168 | 169 | stream2.pipe(require('fs').createWriteStream('something.bin')); 170 | ``` 171 | 172 | #### Writable Stream 173 | 174 | ```js 175 | // create a writable stream 176 | const stream1 = midi.createWriteStream(); 177 | 178 | // createWriteStream also accepts an optional `output` param 179 | const output = new midi.Output(); 180 | output.openVirtualPort('hello again'); 181 | 182 | const stream2 = midi.createWriteStream(output); 183 | 184 | require('fs').createReadStream('something.bin').pipe(stream2); 185 | ``` 186 | 187 | ## References 188 | 189 | * https://www.music.mcgill.ca/~gary/rtmidi/ 190 | * http://syskall.com/how-to-write-your-own-native-nodejs-extension 191 | 192 | ## Maintainers 193 | 194 | * Justin Latimer - [@justinlatimer](https://github.com/justinlatimer) 195 | * Elijah Insua - [@tmpvar](https://github.com/tmpvar) 196 | * Andrew Morton - [@drewish](https://github.com/drewish) 197 | 198 | ## Contributors 199 | 200 | * Luc Deschenaux - [@luxigo](https://github.com/luxigo) 201 | * Michael Alyn Miller - [@malyn](https://github.com/malyn) 202 | * Hugo Hromic - [@hhromic](https://github.com/hhromic) 203 | 204 | ## License 205 | 206 | Copyright (C) 2011-2021 by Justin Latimer. 207 | 208 | Permission is hereby granted, free of charge, to any person obtaining a copy 209 | of this software and associated documentation files (the "Software"), to deal 210 | in the Software without restriction, including without limitation the rights 211 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 212 | copies of the Software, and to permit persons to whom the Software is 213 | furnished to do so, subject to the following conditions: 214 | 215 | The above copyright notice and this permission notice shall be included in 216 | all copies or substantial portions of the Software. 217 | 218 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 219 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 220 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 221 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 222 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 223 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 224 | THE SOFTWARE. 225 | 226 | A different license may apply to other software included in this package, 227 | including RtMidi. Please consult their respective license files for the 228 | terms of their individual licenses. 229 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'midi', 5 | 'include_dirs': [ 6 | "=10" 38 | }, 39 | "dependencies": { 40 | "bindings": "~1.5.0", 41 | "nan": "^2.14.2" 42 | }, 43 | "devDependencies": { 44 | "mocha": "^9.0.0", 45 | "should": "^13.2.3" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/justinlatimer/node-midi.git" 50 | }, 51 | "files": [ 52 | "midi.js", 53 | "binding.gyp", 54 | "src/", 55 | "test/", 56 | "vendor/" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/input.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "RtMidi.h" 6 | 7 | #include "input.h" 8 | 9 | const char* symbol_emit = "emit"; 10 | const char* symbol_message = "message"; 11 | 12 | void NodeMidiInput::Init(v8::Local target) 13 | { 14 | Nan::HandleScope scope; 15 | 16 | v8::Local t = Nan::New(NodeMidiInput::New); 17 | 18 | s_ct.Reset(t); 19 | t->SetClassName(Nan::New("NodeMidiInput").ToLocalChecked()); 20 | t->InstanceTemplate()->SetInternalFieldCount(1); 21 | 22 | Nan::SetPrototypeMethod(t, "getPortCount", GetPortCount); 23 | Nan::SetPrototypeMethod(t, "getPortName", GetPortName); 24 | 25 | Nan::SetPrototypeMethod(t, "openPort", OpenPort); 26 | Nan::SetPrototypeMethod(t, "openVirtualPort", OpenVirtualPort); 27 | Nan::SetPrototypeMethod(t, "closePort", ClosePort); 28 | Nan::SetPrototypeMethod(t, "isPortOpen", IsPortOpen); 29 | 30 | Nan::SetPrototypeMethod(t, "ignoreTypes", IgnoreTypes); 31 | 32 | Nan::Set(target, Nan::New("Input").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); 33 | } 34 | 35 | NodeMidiInput::NodeMidiInput() 36 | { 37 | try { 38 | in = new RtMidiIn(); 39 | } 40 | catch(RtMidiError& e) { 41 | in = NULL; 42 | } 43 | configured = false; 44 | uv_mutex_init(&message_mutex); 45 | } 46 | 47 | NodeMidiInput::~NodeMidiInput() 48 | { 49 | if (in) { 50 | cleanUp(); 51 | delete in; 52 | } 53 | uv_mutex_destroy(&message_mutex); 54 | } 55 | 56 | void NodeMidiInput::cleanUp() 57 | { 58 | if (this->configured) { 59 | this->Unref(); 60 | try { 61 | this->in->cancelCallback(); 62 | this->in->closePort(); 63 | } 64 | catch(RtMidiError& e) { 65 | ; 66 | } 67 | uv_close((uv_handle_t*)&this->message_async, NULL); 68 | this->configured = false; 69 | } 70 | } 71 | 72 | NAUV_WORK_CB(NodeMidiInput::EmitMessage) 73 | { 74 | Nan::HandleScope scope; 75 | NodeMidiInput *input = static_cast(async->data); 76 | uv_mutex_lock(&input->message_mutex); 77 | v8::Local emitFunction = Nan::Get(input->handle(), Nan::New(symbol_emit).ToLocalChecked()).ToLocalChecked().As(); 78 | while (!input->message_queue.empty()) 79 | { 80 | MidiMessage* message = input->message_queue.front(); 81 | v8::Local info[3]; 82 | info[0] = Nan::New(symbol_message).ToLocalChecked(); 83 | info[1] = Nan::New(message->deltaTime); 84 | int32_t count = (int32_t)message->message.size(); 85 | v8::Local data = Nan::New(count); 86 | for (int32_t i = 0; i < count; ++i) { 87 | Nan::Set(data, Nan::New(i), Nan::New(message->message[i])); 88 | } 89 | info[2] = data; 90 | Nan::Callback callback_emit(emitFunction); 91 | callback_emit.Call(input->handle(), 3, info); 92 | input->message_queue.pop(); 93 | delete message; 94 | } 95 | uv_mutex_unlock(&input->message_mutex); 96 | } 97 | 98 | void NodeMidiInput::Callback(double deltaTime, std::vector *message, void *userData) 99 | { 100 | NodeMidiInput *input = static_cast(userData); 101 | MidiMessage* data = new MidiMessage(); 102 | data->deltaTime = deltaTime; 103 | data->message = *message; 104 | uv_mutex_lock(&input->message_mutex); 105 | input->message_queue.push(data); 106 | uv_mutex_unlock(&input->message_mutex); 107 | uv_async_send(&input->message_async); 108 | } 109 | 110 | NAN_METHOD(NodeMidiInput::New) 111 | { 112 | Nan::HandleScope scope; 113 | 114 | if (!info.IsConstructCall()) { 115 | return Nan::ThrowTypeError("Use the new operator to create instances of this object."); 116 | } 117 | 118 | NodeMidiInput* input = new NodeMidiInput(); 119 | input->message_async.data = input; 120 | input->Wrap(info.This()); 121 | 122 | info.GetReturnValue().Set(info.This()); 123 | } 124 | 125 | NAN_METHOD(NodeMidiInput::GetPortCount) 126 | { 127 | Nan::HandleScope scope; 128 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 129 | v8::Local result = Nan::New(input->in ? input->in->getPortCount() : 0); 130 | info.GetReturnValue().Set(result); 131 | } 132 | 133 | NAN_METHOD(NodeMidiInput::GetPortName) 134 | { 135 | Nan::HandleScope scope; 136 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 137 | if (info.Length() == 0 || !info[0]->IsUint32()) { 138 | return Nan::ThrowTypeError("First argument must be an integer"); 139 | } 140 | 141 | unsigned int portNumber = Nan::To(info[0]).FromJust(); 142 | try { 143 | v8::Local result = Nan::New(input->in ? input->in->getPortName(portNumber).c_str() : "").ToLocalChecked(); 144 | info.GetReturnValue().Set(result); 145 | } 146 | catch(RtMidiError& e) { 147 | info.GetReturnValue().Set(Nan::New("").ToLocalChecked()); 148 | } 149 | } 150 | 151 | NAN_METHOD(NodeMidiInput::OpenPort) 152 | { 153 | Nan::HandleScope scope; 154 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 155 | 156 | if (!input->in) return; 157 | 158 | if (info.Length() == 0 || !info[0]->IsUint32()) { 159 | return Nan::ThrowTypeError("First argument must be an integer"); 160 | } 161 | unsigned int portNumber = Nan::To(info[0]).FromJust(); 162 | if (portNumber >= input->in->getPortCount()) { 163 | return Nan::ThrowRangeError("Invalid MIDI port number"); 164 | } 165 | 166 | input->Ref(); 167 | uv_async_init(uv_default_loop(), &input->message_async, NodeMidiInput::EmitMessage); 168 | try { 169 | input->in->setCallback(&NodeMidiInput::Callback, Nan::ObjectWrap::Unwrap(info.This())); 170 | input->configured = true; 171 | input->in->openPort(portNumber); 172 | } 173 | catch(RtMidiError& e) { 174 | ; 175 | } 176 | return; 177 | } 178 | 179 | NAN_METHOD(NodeMidiInput::OpenVirtualPort) 180 | { 181 | Nan::HandleScope scope; 182 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 183 | 184 | if (!input->in) return; 185 | 186 | if (info.Length() == 0 || !info[0]->IsString()) { 187 | return Nan::ThrowTypeError("First argument must be a string"); 188 | } 189 | 190 | std::string name(*Nan::Utf8String(info[0])); 191 | 192 | input->Ref(); 193 | uv_async_init(uv_default_loop(), &input->message_async, NodeMidiInput::EmitMessage); 194 | try { 195 | input->in->setCallback(&NodeMidiInput::Callback, Nan::ObjectWrap::Unwrap(info.This())); 196 | input->configured = true; 197 | input->in->openVirtualPort(name); 198 | } 199 | catch(RtMidiError& e) { 200 | ; 201 | } 202 | return; 203 | } 204 | 205 | NAN_METHOD(NodeMidiInput::ClosePort) 206 | { 207 | Nan::HandleScope scope; 208 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 209 | 210 | if (!input->in) return; 211 | 212 | input->cleanUp(); 213 | 214 | return; 215 | } 216 | 217 | NAN_METHOD(NodeMidiInput::IsPortOpen) 218 | { 219 | Nan::HandleScope scope; 220 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 221 | 222 | if (!input->in) return; 223 | 224 | v8::Local result = Nan::New(input->in->isPortOpen()); 225 | info.GetReturnValue().Set(result); 226 | } 227 | 228 | NAN_METHOD(NodeMidiInput::IgnoreTypes) 229 | { 230 | Nan::HandleScope scope; 231 | NodeMidiInput* input = Nan::ObjectWrap::Unwrap(info.This()); 232 | 233 | if (!input->in) return; 234 | 235 | if (info.Length() != 3 || !info[0]->IsBoolean() || !info[1]->IsBoolean() || !info[2]->IsBoolean()) { 236 | return Nan::ThrowTypeError("Arguments must be boolean"); 237 | } 238 | 239 | bool filter_sysex = Nan::To(info[0]).FromJust(); 240 | bool filter_timing = Nan::To(info[1]).FromJust(); 241 | bool filter_sensing = Nan::To(info[2]).FromJust(); 242 | input->in->ignoreTypes(filter_sysex, filter_timing, filter_sensing); 243 | return; 244 | } 245 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_MIDI_INPUT_H 2 | #define NODE_MIDI_INPUT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "RtMidi.h" 9 | 10 | class NodeMidiInput : public Nan::ObjectWrap 11 | { 12 | private: 13 | RtMidiIn* in; 14 | bool configured; 15 | 16 | public: 17 | uv_async_t message_async; 18 | uv_mutex_t message_mutex; 19 | 20 | struct MidiMessage 21 | { 22 | double deltaTime; 23 | std::vector message; 24 | }; 25 | std::queue message_queue; 26 | 27 | static Nan::Persistent s_ct; 28 | static void Init(v8::Local target); 29 | 30 | NodeMidiInput(); 31 | ~NodeMidiInput(); 32 | void cleanUp(); 33 | 34 | static NAUV_WORK_CB(EmitMessage); 35 | static void Callback(double deltaTime, std::vector *message, void *userData); 36 | 37 | static NAN_METHOD(New); 38 | 39 | static NAN_METHOD(GetPortCount); 40 | static NAN_METHOD(GetPortName); 41 | 42 | static NAN_METHOD(OpenPort); 43 | static NAN_METHOD(OpenVirtualPort); 44 | static NAN_METHOD(ClosePort); 45 | static NAN_METHOD(IsPortOpen); 46 | 47 | static NAN_METHOD(IgnoreTypes); 48 | }; 49 | 50 | #endif // NODE_MIDI_INPUT_H 51 | -------------------------------------------------------------------------------- /src/midi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "input.h" 4 | #include "output.h" 5 | 6 | Nan::Persistent NodeMidiInput::s_ct; 7 | Nan::Persistent NodeMidiOutput::s_ct; 8 | 9 | NAN_MODULE_INIT(InitAll) { 10 | NodeMidiOutput::Init(target); 11 | NodeMidiInput::Init(target); 12 | } 13 | 14 | NODE_MODULE(midi, InitAll) 15 | -------------------------------------------------------------------------------- /src/output.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "RtMidi.h" 4 | 5 | #include "output.h" 6 | 7 | void NodeMidiOutput::Init(v8::Local target) 8 | { 9 | Nan::HandleScope scope; 10 | 11 | v8::Local t = Nan::New(NodeMidiOutput::New); 12 | 13 | s_ct.Reset(t); 14 | t->SetClassName(Nan::New("NodeMidiOutput").ToLocalChecked()); 15 | t->InstanceTemplate()->SetInternalFieldCount(1); 16 | 17 | Nan::SetPrototypeMethod(t, "getPortCount", GetPortCount); 18 | Nan::SetPrototypeMethod(t, "getPortName", GetPortName); 19 | 20 | Nan::SetPrototypeMethod(t, "openPort", OpenPort); 21 | Nan::SetPrototypeMethod(t, "openVirtualPort", OpenVirtualPort); 22 | Nan::SetPrototypeMethod(t, "closePort", ClosePort); 23 | Nan::SetPrototypeMethod(t, "isPortOpen", IsPortOpen); 24 | 25 | Nan::SetPrototypeMethod(t, "sendMessage", Send); 26 | Nan::SetPrototypeMethod(t, "send", Send); 27 | 28 | Nan::Set(target, Nan::New("Output").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); 29 | } 30 | 31 | NodeMidiOutput::NodeMidiOutput() 32 | { 33 | try { 34 | out = new RtMidiOut(); 35 | } 36 | catch(RtMidiError &e) { 37 | out = NULL; 38 | } 39 | } 40 | 41 | NodeMidiOutput::~NodeMidiOutput() 42 | { 43 | if (out) { 44 | delete out; 45 | } 46 | } 47 | 48 | NAN_METHOD(NodeMidiOutput::New) 49 | { 50 | Nan::HandleScope scope; 51 | 52 | if (!info.IsConstructCall()) { 53 | return Nan::ThrowTypeError("Use the new operator to create instances of this object."); 54 | } 55 | 56 | NodeMidiOutput* output = new NodeMidiOutput(); 57 | output->Wrap(info.This()); 58 | 59 | info.GetReturnValue().Set(info.This()); 60 | } 61 | 62 | NAN_METHOD(NodeMidiOutput::GetPortCount) 63 | { 64 | Nan::HandleScope scope; 65 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 66 | v8::Local result = Nan::New(output-> out ? output->out->getPortCount() : 0); 67 | info.GetReturnValue().Set(result); 68 | } 69 | 70 | NAN_METHOD(NodeMidiOutput::GetPortName) 71 | { 72 | Nan::HandleScope scope; 73 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 74 | if (info.Length() == 0 || !info[0]->IsUint32()) { 75 | return Nan::ThrowTypeError("First argument must be an integer"); 76 | } 77 | 78 | unsigned int portNumber = Nan::To(info[0]).FromJust(); 79 | try { 80 | v8::Local result = Nan::New(output->out ? output->out->getPortName(portNumber).c_str() : "").ToLocalChecked(); 81 | info.GetReturnValue().Set(result); 82 | } 83 | catch(RtMidiError& e) { 84 | info.GetReturnValue().Set(Nan::New("").ToLocalChecked()); 85 | } 86 | } 87 | 88 | NAN_METHOD(NodeMidiOutput::OpenPort) 89 | { 90 | Nan::HandleScope scope; 91 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 92 | 93 | if (!output->out) return; 94 | 95 | if (info.Length() == 0 || !info[0]->IsUint32()) { 96 | return Nan::ThrowTypeError("First argument must be an integer"); 97 | } 98 | unsigned int portNumber = Nan::To(info[0]).FromJust(); 99 | if (portNumber >= output->out->getPortCount()) { 100 | return Nan::ThrowRangeError("Invalid MIDI port number"); 101 | } 102 | 103 | try { 104 | output->out->openPort(portNumber); 105 | } 106 | catch(RtMidiError& e) { 107 | ; 108 | } 109 | return; 110 | } 111 | 112 | NAN_METHOD(NodeMidiOutput::OpenVirtualPort) 113 | { 114 | Nan::HandleScope scope; 115 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 116 | 117 | if (!output->out) return; 118 | 119 | if (info.Length() == 0 || !info[0]->IsString()) { 120 | return Nan::ThrowTypeError("First argument must be a string"); 121 | } 122 | 123 | std::string name(*Nan::Utf8String(info[0])); 124 | 125 | try { 126 | output->out->openVirtualPort(name); 127 | } 128 | catch(RtMidiError& e) { 129 | ; 130 | } 131 | return; 132 | } 133 | 134 | NAN_METHOD(NodeMidiOutput::ClosePort) 135 | { 136 | Nan::HandleScope scope; 137 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 138 | 139 | if (!output->out) return; 140 | 141 | output->out->closePort(); 142 | return; 143 | } 144 | 145 | NAN_METHOD(NodeMidiOutput::IsPortOpen) 146 | { 147 | Nan::HandleScope scope; 148 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 149 | 150 | if (!output->out) return; 151 | 152 | v8::Local result = Nan::New(output->out->isPortOpen()); 153 | info.GetReturnValue().Set(result); 154 | } 155 | 156 | NAN_METHOD(NodeMidiOutput::Send) 157 | { 158 | Nan::HandleScope scope; 159 | NodeMidiOutput* output = Nan::ObjectWrap::Unwrap(info.This()); 160 | 161 | if (!output->out) return; 162 | 163 | if (info.Length() == 0 || !info[0]->IsArray()) { 164 | return Nan::ThrowTypeError("First argument must be an array"); 165 | } 166 | 167 | v8::Local message = Nan::To(info[0]).ToLocalChecked(); 168 | int32_t messageLength = Nan::To(Nan::Get(message, Nan::New("length").ToLocalChecked()).ToLocalChecked()).FromJust(); 169 | std::vector messageOutput; 170 | for (int32_t i = 0; i != messageLength; ++i) { 171 | messageOutput.push_back(Nan::To(Nan::Get(message, Nan::New(i)).ToLocalChecked()).FromJust()); 172 | } 173 | try { 174 | output->out->sendMessage(&messageOutput); 175 | } 176 | catch(RtMidiError& e) { 177 | ; 178 | } 179 | return; 180 | } 181 | -------------------------------------------------------------------------------- /src/output.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_MIDI_OUTPUT_H 2 | #define NODE_MIDI_OUTPUT_H 3 | 4 | #include 5 | 6 | #include "RtMidi.h" 7 | 8 | class NodeMidiOutput : public Nan::ObjectWrap 9 | { 10 | private: 11 | RtMidiOut* out; 12 | public: 13 | static Nan::Persistent s_ct; 14 | static void Init(v8::Local target); 15 | 16 | NodeMidiOutput(); 17 | ~NodeMidiOutput(); 18 | 19 | static NAN_METHOD(New); 20 | 21 | static NAN_METHOD(GetPortCount); 22 | static NAN_METHOD(GetPortName); 23 | 24 | static NAN_METHOD(OpenPort); 25 | static NAN_METHOD(OpenVirtualPort); 26 | static NAN_METHOD(ClosePort); 27 | static NAN_METHOD(IsPortOpen); 28 | 29 | static NAN_METHOD(Send); 30 | }; 31 | 32 | #endif // NODE_MIDI_OUTPUT_H 33 | -------------------------------------------------------------------------------- /test/fixture/144-23-81.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinlatimer/node-midi/1356985b1fc0aef919dc0e7abb8918ebe83522be/test/fixture/144-23-81.bin -------------------------------------------------------------------------------- /test/input/input-hang-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | var input = new midi.Input(); 3 | -------------------------------------------------------------------------------- /test/input/input-list-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var input = new midi.Input(); 4 | console.log('Input ports: ' + input.getPortCount()); 5 | 6 | for (var i = 0; i < input.getPortCount(); ++i) { 7 | console.log('Port ' + i + ' name: ' + input.getPortName(i)); 8 | } 9 | -------------------------------------------------------------------------------- /test/input/input-port-status-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var input = new midi.Input(); 4 | 5 | console.log('Is open ', input.isPortOpen()); 6 | input.openPort(0); 7 | console.log('Is open ', input.isPortOpen()); 8 | 9 | input.closePort(); 10 | -------------------------------------------------------------------------------- /test/input/input-reopen-test.js: -------------------------------------------------------------------------------- 1 | var midi = require('../../midi.js'); 2 | var input = new midi.Input(); 3 | input.on('message', function(deltaTime, message) { 4 | console.log('m:' + message + ' d:' + deltaTime); 5 | }); 6 | 7 | var newInput = function(port) { 8 | if (input) { 9 | input.closePort(); 10 | } 11 | 12 | setTimeout(function() { 13 | console.log('new input', port); 14 | input.openPort(port); 15 | }, 100); 16 | }; 17 | 18 | newInput(0); 19 | 20 | setTimeout(function() { 21 | newInput(0); 22 | }, 5000); 23 | 24 | setTimeout(function() { 25 | input.closePort(); 26 | }, 10000); 27 | -------------------------------------------------------------------------------- /test/input/input-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var input = new midi.Input(); 4 | console.log(input.getPortCount()); 5 | console.log(input.getPortName(0)); 6 | input.on('message', function(deltaTime, message) { 7 | console.log('m:' + message + ' d:' + deltaTime); 8 | }); 9 | input.openPort(0); 10 | setTimeout(function() { 11 | input.closePort(); 12 | }, 10000); 13 | -------------------------------------------------------------------------------- /test/input/virtual-input-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var input = new midi.Input(); 4 | input.on('message', function(deltaTime, message) { 5 | console.log('m:' + message + ' d:' + deltaTime); 6 | }); 7 | input.openVirtualPort("node-midi Virtual Input"); 8 | setTimeout(function() { 9 | input.closePort(); 10 | }, 20000); 11 | -------------------------------------------------------------------------------- /test/output/output-list-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var output = new midi.Output(); 4 | console.log('Output ports: ' + output.getPortCount()); 5 | 6 | for (var i = 0; i < output.getPortCount(); ++i) { 7 | console.log('Port ' + i + ' name: ' + output.getPortName(i)); 8 | } 9 | -------------------------------------------------------------------------------- /test/output/output-port-status-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var output = new midi.Output(); 4 | 5 | console.log('Is open ', output.isPortOpen()); 6 | output.openPort(0); 7 | console.log('Is open ', output.isPortOpen()); 8 | 9 | output.closePort(); 10 | -------------------------------------------------------------------------------- /test/output/output-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var output = new midi.Output(); 4 | console.log(output.getPortCount()); 5 | console.log(output.getPortName(0)); 6 | output.openPort(0); 7 | output.sendMessage([176,22,1]); 8 | output.sendMessage([176,22,99]); 9 | output.closePort(); 10 | 11 | -------------------------------------------------------------------------------- /test/output/virtual-output-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../../midi.js"); 2 | 3 | var output = new midi.Output(); 4 | output.openVirtualPort("node-midi Virtual Output"); 5 | setTimeout(function() { 6 | output.sendMessage([144,64,90]); 7 | }, 10000); 8 | setTimeout(function() { 9 | output.sendMessage([128,64,40]); 10 | }, 15000); 11 | setTimeout(function() { 12 | output.closePort(); 13 | }, 20000); 14 | -------------------------------------------------------------------------------- /test/unit/input.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Midi = require('../../midi'); 4 | describe('midi.Input', function() { 5 | 6 | var input; 7 | beforeEach(()=>{ 8 | input = new Midi.Input();; 9 | }) 10 | 11 | afterEach(()=>{ 12 | input.closePort(); 13 | }) 14 | 15 | it('should raise when not called with new', function() { 16 | (function() { 17 | Midi.Input(); 18 | }).should.throw('Use the new operator to create instances of this object.'); 19 | }); 20 | 21 | it('should be an emitter', function() { 22 | input.should.be.an.instanceOf(EventEmitter); 23 | }); 24 | 25 | 26 | describe('.getPortCount', function() { 27 | it('.getPortCoun should return an integer', function() { 28 | // I feel like having more than 255 ports indicates a problem. 29 | input.getPortCount().should.be.within(0, 255); 30 | }); 31 | }); 32 | 33 | describe('.getPortName', function() { 34 | it('requires an argument', function() { 35 | (function() { 36 | input.getPortName(); 37 | }).should.throw('First argument must be an integer'); 38 | }); 39 | 40 | it('requires an integer', function() { 41 | (function() { 42 | input.getPortName('asdf'); 43 | }).should.throw('First argument must be an integer'); 44 | }); 45 | 46 | it('returns an empty string for unknown port', function() { 47 | input.getPortName(999).should.eql(''); 48 | }); 49 | }); 50 | 51 | 52 | describe('.openPort', function() { 53 | it('requires an argument', function() { 54 | (function() { 55 | input.openPort(); 56 | }).should.throw('First argument must be an integer'); 57 | }); 58 | 59 | it('requires an integer', function() { 60 | (function() { 61 | input.openPort('asdf'); 62 | }).should.throw('First argument must be an integer'); 63 | }); 64 | 65 | it('requires a valid port', function() { 66 | (function() { 67 | input.openPort(999); 68 | }).should.throw('Invalid MIDI port number'); 69 | }); 70 | }); 71 | 72 | 73 | describe('.openVirtualPort', function() { 74 | it('requires an argument', function() { 75 | (function() { 76 | input.openVirtualPort(); 77 | }).should.throw('First argument must be a string'); 78 | }); 79 | 80 | it('requires a string', function() { 81 | (function() { 82 | input.openVirtualPort(999); 83 | }).should.throw('First argument must be a string'); 84 | }); 85 | }); 86 | 87 | 88 | describe(".on('message')", function() { 89 | it('allows promises to resolve', async function() { 90 | const portName = 'node-midi Virtual Loopback'; 91 | 92 | // Create a promise we can use to pass/fail the whole test. 93 | let resolveTestPromise, rejectTestPromise; 94 | const testPromise = new Promise((resolve, reject) => { 95 | resolveTestPromise = resolve; 96 | rejectTestPromise = reject; 97 | }); 98 | 99 | // Create a promise to resolve in the on('message') callback. 100 | let resolvePendingPromise, rejectPendingPromise; 101 | const promise = new Promise((resolve, reject) => { 102 | resolvePendingPromise = resolve; 103 | rejectPendingPromise = reject; 104 | }); 105 | 106 | // Create a virtual loopback MIDI port. 107 | var input = new Midi.Input(); 108 | input.on('message', function(deltaTime, message) { 109 | // Resolve the promise now we have received a MIDI message. 110 | resolvePendingPromise(message); 111 | }); 112 | input.openVirtualPort(portName); 113 | 114 | let awaitComplete = false; 115 | setTimeout(function() { 116 | input.closePort(); 117 | if (!awaitComplete) { 118 | // If the `await` statement below has not yet returned, then this is 119 | // a failure. 120 | rejectTestPromise(new Error('Await did not return in time')); 121 | } 122 | }, 1500); // Must be under 2000 or Mocha fails us for being too slow. 123 | 124 | // Find the other end of the virtual MIDI port we created above. 125 | const output = new Midi.Output(); 126 | for (var i = 0; i < output.getPortCount(); ++i) { 127 | if (output.getPortName(i).includes(portName)) { 128 | output.openPort(i); 129 | } 130 | } 131 | if (!output.isPortOpen()) { 132 | throw Error('Unable to find virtual loopback port'); 133 | } 134 | 135 | // Send a message, which should end up calling the on('message') handler. 136 | output.sendMessage([176, 22, 1]); 137 | output.closePort(); 138 | 139 | // Wait for the promise resolved by on('message'). If everything is 140 | // working, this promise has already been resolved and will complete 141 | // immediately. However if the event loop is starved, it will block 142 | // until the next event arrives, resulting in a timeout. 143 | const result = await promise; 144 | awaitComplete = true; 145 | 146 | // Resolve the promise for this test, which will have no effect if the 147 | // promise has already been rejected because the `await` took too long. 148 | resolveTestPromise(); 149 | 150 | // Wait for the test promise, which will cause an exception to be thrown 151 | // if the test promise was rejected. 152 | await testPromise; 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /test/unit/output.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var Midi = require('../../midi'); 4 | 5 | describe('midi.Output', function() { 6 | var output; 7 | 8 | beforeEach(()=>{ 9 | output = new Midi.Output();; 10 | }); 11 | 12 | afterEach(()=>{ 13 | output.closePort();; 14 | }); 15 | 16 | it('should raise when not called with new', function() { 17 | (function() { 18 | Midi.Output(); 19 | }).should.throw('Use the new operator to create instances of this object.'); 20 | }); 21 | 22 | it('should not be an emitter', function() { 23 | var output = new Midi.Output(); 24 | output.should.not.be.an.instanceOf(EventEmitter); 25 | }); 26 | 27 | describe('.getPortCount', function() { 28 | it('should return an integer', function() { 29 | // I feel like having more than 255 ports indicates a problem. 30 | output.getPortCount().should.be.within(0, 255); 31 | }); 32 | }); 33 | 34 | describe('.getPortName', function() { 35 | it('requires an argument', function() { 36 | (function() { 37 | output.getPortName(); 38 | }).should.throw('First argument must be an integer'); 39 | }); 40 | 41 | it('requires an integer', function() { 42 | (function() { 43 | output.getPortName('asdf'); 44 | }).should.throw('First argument must be an integer'); 45 | }); 46 | 47 | it('returns an empty string for unknown port', function() { 48 | output.getPortName(999).should.eql(''); 49 | }); 50 | }); 51 | 52 | describe('.openPort', function() { 53 | it('requires an argument', function() { 54 | (function() { 55 | output.openPort(); 56 | }).should.throw('First argument must be an integer'); 57 | }); 58 | 59 | it('requires an integer', function() { 60 | (function() { 61 | output.openPort('asdf'); 62 | }).should.throw('First argument must be an integer'); 63 | }); 64 | 65 | it('requires a valid port', function() { 66 | (function() { 67 | output.openPort(999); 68 | }).should.throw('Invalid MIDI port number'); 69 | }); 70 | }); 71 | 72 | describe('.openVirtualPort', function() { 73 | it('requires an argument', function() { 74 | (function() { 75 | output.openVirtualPort(); 76 | }).should.throw('First argument must be a string'); 77 | }); 78 | 79 | it('requires a string', function() { 80 | (function() { 81 | output.openVirtualPort(999); 82 | }).should.throw('First argument must be a string'); 83 | }); 84 | }); 85 | 86 | describe('.closePort', function() { 87 | var output = new Midi.Output(); 88 | 89 | it('allows you to close a port that was not opened', function() { 90 | output.closePort(); 91 | }); 92 | }); 93 | 94 | describe('.send', function() { 95 | var output = new Midi.Output(); 96 | 97 | it('should require an array argument', function() { 98 | (function() { 99 | output.send(); 100 | }).should.throw('First argument must be an array'); 101 | }); 102 | }); 103 | 104 | describe('.sendMessage', function() { 105 | var output = new Midi.Output(); 106 | 107 | it('should require an array argument', function() { 108 | (function() { 109 | output.sendMessage(); 110 | }).should.throw('First argument must be an array'); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/virtual-loopback-test-automated.js: -------------------------------------------------------------------------------- 1 | var midi = require("../midi.js"); 2 | 3 | var outputName = "node-midi Virtual Output"; 4 | var inputName = "node-midi Virtual Input"; 5 | 6 | var virtualOutput = new midi.Output(); 7 | var virtualInput = new midi.Input(); 8 | 9 | virtualOutput.openVirtualPort(outputName); 10 | virtualInput.on('message', function(deltaTime, message) { 11 | console.log('Virtual input recieved m:' + message + ' d:' + deltaTime); 12 | virtualOutput.sendMessage([ 13 | message[0], 14 | message[1] + 10, 15 | message[2] 16 | ]); 17 | }); 18 | virtualInput.openVirtualPort(inputName); 19 | 20 | setTimeout(function() { 21 | var output = new midi.Output(); 22 | var input = new midi.Input(); 23 | 24 | input.on('message', function(deltaTime, message) { 25 | console.log('Input recieved m:' + message + ' d:' + deltaTime); 26 | }); 27 | 28 | console.log('Enumerating inputs'); 29 | for (var i = 0; i < input.getPortCount(); ++i) { 30 | console.log('Input found: ' + input.getPortName(i)); 31 | if (input.getPortName(i) == outputName) { 32 | console.log('Opening ' + input.getPortName(i)); 33 | input.openPort(i); 34 | } 35 | } 36 | 37 | console.log('Enumerating outputs'); 38 | for (var i = 0; i < output.getPortCount(); ++i) { 39 | console.log('Output found: ' + input.getPortName(i)); 40 | if (output.getPortName(i) == inputName) { 41 | console.log('Opening ' + output.getPortName(i)); 42 | output.openPort(i); 43 | } 44 | } 45 | 46 | var id = setInterval(function() { 47 | console.log('Sending message'); 48 | output.sendMessage([144, 23, 81]); 49 | }, 1000); 50 | 51 | setTimeout(function() { 52 | clearInterval(id); 53 | 54 | setTimeout(function() { 55 | input.closePort(); 56 | output.closePort(); 57 | virtualInput.closePort(); 58 | virtualOutput.closePort(); 59 | }, 100); 60 | }, 10000); 61 | }, 500); 62 | -------------------------------------------------------------------------------- /test/virtual-loopback-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../midi.js"); 2 | 3 | var output = new midi.Output(); 4 | var input = new midi.Input(); 5 | 6 | output.openVirtualPort("node-midi Virtual Output"); 7 | input.on('message', function(deltaTime, message) { 8 | console.log('m:' + message + ' d:' + deltaTime); 9 | output.sendMessage([ 10 | message[0], 11 | message[1] + 10, 12 | message[2] 13 | ]); 14 | }); 15 | input.openVirtualPort("node-midi Virtual Input"); 16 | setTimeout(function() { 17 | input.closePort(); 18 | output.closePort(); 19 | }, 60000); 20 | -------------------------------------------------------------------------------- /test/virtual-read-stream-resume-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../midi.js"); 2 | var virtualInput = new midi.Input(); 3 | var output = new midi.Output(); 4 | var assert = require('assert'); 5 | var payload = [144, 23, 81]; 6 | var called = false; 7 | 8 | virtualInput.openVirtualPort("node-midi"); 9 | 10 | var reader = midi.createReadStream(virtualInput); 11 | reader.pipe(process.stdout); 12 | reader.on('data', function(buffer) { 13 | assert.deepEqual(buffer, new Buffer.from(payload)); 14 | called = true; 15 | }); 16 | 17 | for (var i = 0; i < output.getPortCount(); ++i) { 18 | if (output.getPortName(i) === 'node-midi') { 19 | output.openPort(i); 20 | break; 21 | } 22 | } 23 | reader.pause() 24 | output.sendMessage(payload); 25 | 26 | setTimeout(function() { reader.resume(); }, 10) 27 | 28 | setTimeout(function() { 29 | assert(called); 30 | process.exit(0); 31 | }, 100); 32 | -------------------------------------------------------------------------------- /test/virtual-read-stream-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../midi.js"); 2 | var virtualInput = new midi.Input(); 3 | var output = new midi.Output(); 4 | var assert = require('assert'); 5 | var payload = [144, 23, 81]; 6 | var called = false; 7 | 8 | virtualInput.openVirtualPort("node-midi"); 9 | 10 | var reader = midi.createReadStream(virtualInput); 11 | reader.pipe(process.stdout); 12 | reader.on('data', function(buffer) { 13 | assert.deepEqual(buffer, new Buffer.from(payload)); 14 | called = true; 15 | }); 16 | 17 | for (var i = 0; i < output.getPortCount(); ++i) { 18 | if (output.getPortName(i) === 'node-midi') { 19 | output.openPort(i); 20 | break; 21 | } 22 | } 23 | 24 | output.sendMessage(payload); 25 | 26 | setTimeout(function() { 27 | assert(called); 28 | process.exit(0); 29 | }, 10); 30 | -------------------------------------------------------------------------------- /test/virtual-write-stream-test.js: -------------------------------------------------------------------------------- 1 | var midi = require("../midi.js"); 2 | var virtualInput = new midi.Input(); 3 | var output = new midi.Output(); 4 | var assert = require('assert'); 5 | var fs = require('fs'); 6 | var expect = [144, 23, 81]; 7 | var called = false; 8 | var writer = midi.createWriteStream(output); 9 | 10 | virtualInput.openVirtualPort("node-midi"); 11 | virtualInput.on('message', function(deltaTime, buffer) { 12 | assert.deepEqual(buffer, expect); 13 | called = true; 14 | }); 15 | 16 | 17 | for (var i = 0; i < output.getPortCount(); ++i) { 18 | if (output.getPortName(i) === 'node-midi') { 19 | output.openPort(i); 20 | break; 21 | } 22 | } 23 | 24 | fs.createReadStream(__dirname + '/fixture/144-23-81.bin').pipe(writer); 25 | 26 | setTimeout(function() { 27 | assert(called); 28 | output.closePort(); 29 | virtualInput.closePort(); 30 | process.exit(0); 31 | }, 100); 32 | --------------------------------------------------------------------------------