https://github.com/ydah/mtmidi-ruby
Ruby FFI bindings for RtMidi with Ruby-idiomatic MIDI input/output, typed messages, and low-level C API access.
https://github.com/ydah/mtmidi-ruby
audio bindings ffi gem linux macos music ruby windows
Last synced: 24 days ago
JSON representation
Ruby FFI bindings for RtMidi with Ruby-idiomatic MIDI input/output, typed messages, and low-level C API access.
- Host: GitHub
- URL: https://github.com/ydah/mtmidi-ruby
- Owner: ydah
- License: mit
- Created: 2026-03-06T06:15:54.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-07T05:04:32.000Z (4 months ago)
- Last Synced: 2026-03-07T12:16:17.157Z (4 months ago)
- Topics: audio, bindings, ffi, gem, linux, macos, music, ruby, windows
- Language: Ruby
- Homepage:
- Size: 35.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# rtmidi-ruby
`rtmidi-ruby` is a Ruby FFI binding for the RtMidi C API (`rtmidi_c.h`).
It exposes the low-level C bindings through `Rtmidi::Native` and a higher-level
Ruby API through `Rtmidi::MidiIn`, `Rtmidi::MidiOut`, and `Rtmidi::Message`.
## Features
- `Rtmidi.version`, `Rtmidi.compiled_apis`, and API name helpers
- low-level access to the RtMidi C API via `Rtmidi::Native`
- `Rtmidi::MidiIn` with callback and polling based input
- `Rtmidi::MidiOut` helpers for channel, system common, and realtime messages
- typed MIDI messages through `Rtmidi::Message`
- virtual port support
## Requirements
- Ruby 3.1+
- a system `librtmidi` that provides the RtMidi C API
Install `librtmidi` with your package manager:
- macOS: `brew install rtmidi`
- Ubuntu/Debian: `sudo apt install librtmidi-dev`
- Fedora: `sudo dnf install rtmidi-devel`
- Arch: `sudo pacman -S rtmidi`
- Windows: install an RtMidi DLL and make sure it is on `PATH`
If the library is installed in a non-standard location, set `RTMIDI_LIB_PATH`.
```bash
RTMIDI_LIB_PATH=/path/to/librtmidi.dylib bundle exec ruby your_script.rb
```
Some `librtmidi` builds do not expose `rtmidi_set_error_callback`. In that case,
normal MIDI I/O still works, but `on_error` callbacks are unavailable.
## Installation
Add the gem to your Gemfile:
```ruby
gem "rtmidi-ruby"
```
Then install dependencies:
```bash
bundle install
```
Or install the gem directly:
```bash
gem install rtmidi-ruby
```
## Quick Start
```ruby
require "rtmidi"
puts "RtMidi version: #{Rtmidi.version}"
puts "Compiled APIs: #{Rtmidi.compiled_apis.inspect}"
```
### List Ports
```ruby
require "rtmidi"
out = Rtmidi::MidiOut.new
puts "Output ports:"
out.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
out.close
input = Rtmidi::MidiIn.new
puts "Input ports:"
input.port_names.each_with_index { |name, index| puts " #{index}: #{name}" }
input.close
```
### Send Note On/Off
```ruby
require "rtmidi"
out = Rtmidi::MidiOut.new
if out.port_count.zero?
warn "No output ports available."
else
begin
out.open_port(0)
out.note_on(0, 60, 100)
sleep 0.5
out.note_off(0, 60)
ensure
out.close
end
end
```
### Receive With Callback
```ruby
require "rtmidi"
midi_in = Rtmidi::MidiIn.new
if midi_in.port_count.zero?
warn "No input ports available."
midi_in.close
exit 0
end
begin
midi_in.ignore_types(sysex: false, timing: false, active_sensing: false)
midi_in.open_port(0)
midi_in.on_message do |message, timestamp|
puts "#{timestamp}: #{message.map { |byte| format('%02X', byte) }.join(' ')}"
end
puts "Listening... (Ctrl-C to quit)"
sleep
ensure
midi_in&.close
end
```
### Receive With Polling
```ruby
require "rtmidi"
midi_in = Rtmidi::MidiIn.new
if midi_in.port_count.zero?
warn "No input ports available."
midi_in.close
exit 0
end
begin
midi_in.open_port(0)
loop do
packet = midi_in.get_message
next if packet.nil?
message, timestamp = packet
puts "#{timestamp}: #{message.inspect}"
sleep 0.001
end
ensure
midi_in&.close
end
```
### Typed Messages
`Rtmidi::Message` can parse raw bytes into typed structs and `MidiOut#send_message`
accepts either raw byte arrays or typed messages.
```ruby
require "rtmidi"
message = Rtmidi::Message.parse([0x92, 64, 96])
p message
# => #
out = Rtmidi::MidiOut.new
if out.port_count.zero?
warn "No output ports available."
else
begin
out.open_port(0)
out.send_message(Rtmidi::Message::ProgramChange.new(channel: 1, program: 10))
ensure
out.close
end
end
```
For typed input callbacks:
```ruby
midi_in.on_message(parsed: true) do |message, timestamp|
p [message.class, message, timestamp]
end
```
### System/Common and Realtime Helpers
`Rtmidi::MidiOut` includes helpers for common output messages:
- `sysex`
- `control_change`
- `program_change`
- `pitch_bend`
- `channel_aftertouch`
- `poly_aftertouch`
- `time_code_quarter_frame`
- `song_position_pointer`
- `song_select`
- `tune_request`
- `timing_clock`
- `start`
- `continue`
- `stop`
- `active_sensing`
- `system_reset`
- `nrpn`
Example:
```ruby
require "rtmidi"
out = Rtmidi::MidiOut.new
if out.port_count.zero?
warn "No output ports available."
else
begin
out.open_port(0)
out.sysex([0x7D, 0x01])
out.song_select(3)
out.start
out.nrpn(0, 0x1234, 0x0567)
ensure
out.close
end
end
```
### Virtual Ports
```ruby
require "rtmidi"
midi_in = Rtmidi::MidiIn.new
midi_out = Rtmidi::MidiOut.new
begin
midi_in.open_virtual_port(name: "Rtmidi Ruby Virtual In")
midi_out.open_virtual_port(name: "Rtmidi Ruby Virtual Out")
puts "Virtual ports opened."
sleep
ensure
midi_out&.close
midi_in&.close
end
```
### Low-Level C API
```ruby
require "rtmidi"
handle = nil
begin
handle = Rtmidi::Native.rtmidi_out_create_default
Rtmidi::Native.check_error(handle)
count = Rtmidi::Native.rtmidi_get_port_count(handle)
Rtmidi::Native.check_error(handle)
puts "#{count} output ports found"
ensure
Rtmidi::Native.rtmidi_out_free(handle) if handle && !handle.null?
end
```
## Error Handling
Synchronous operations raise `Rtmidi::Error` subclasses where possible, including:
- `Rtmidi::NoDevicesError`
- `Rtmidi::InvalidPortError`
- `Rtmidi::InvalidUseError`
- `Rtmidi::DriverError`
Validation failures use `ArgumentError`.
If a callback raises, the exception is stored and surfaced on the next locked API call.
## Callback Notes
- Keep callback processing lightweight.
- For heavier work, pass the event to a `Queue` and handle it in another thread.
- Do not call `close` or `close_port` from inside an input callback.
- `parsed: true` yields `Rtmidi::Message::*` structs instead of raw byte arrays.
## Examples
The repository includes runnable examples in [`examples/`](examples):
- `examples/list_ports.rb`
- `examples/send_note.rb`
- `examples/sysex_send.rb`
- `examples/receive_callback.rb`
- `examples/receive_polling.rb`
- `examples/virtual_port.rb`
Run them against the local checkout with:
```bash
bundle exec ruby -Ilib examples/list_ports.rb
```
## Development
```bash
bundle install
bundle exec rspec
bundle exec rake
```
## License
Released under the MIT License. See `LICENSE.txt`.