jack-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +205 -0
- data/Rakefile +8 -0
- data/examples/audio_passthrough.rb +31 -0
- data/examples/meter.rb +29 -0
- data/examples/midi_generator.rb +35 -0
- data/examples/midi_monitor.rb +29 -0
- data/examples/port_connector.rb +22 -0
- data/examples/ringbuffer_recorder.rb +35 -0
- data/examples/simple_client.rb +20 -0
- data/examples/sine_generator.rb +30 -0
- data/examples/transport_control.rb +21 -0
- data/lib/jack/audio_port.rb +19 -0
- data/lib/jack/callback_manager.rb +29 -0
- data/lib/jack/client.rb +371 -0
- data/lib/jack/error.rb +41 -0
- data/lib/jack/ffi/lib_jack.rb +306 -0
- data/lib/jack/ffi/structs.rb +74 -0
- data/lib/jack/ffi/types.rb +123 -0
- data/lib/jack/metadata.rb +50 -0
- data/lib/jack/midi/event.rb +82 -0
- data/lib/jack/midi_port.rb +71 -0
- data/lib/jack/port.rb +200 -0
- data/lib/jack/ring_buffer.rb +83 -0
- data/lib/jack/session.rb +63 -0
- data/lib/jack/transport.rb +138 -0
- data/lib/jack/uuid.rb +54 -0
- data/lib/jack/version.rb +5 -0
- data/lib/jack.rb +64 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: deb5b3c37204593146aa576d510d9c62a32c9849fd764ccf2a7ef445c1b6bdee
|
|
4
|
+
data.tar.gz: 81274b63cc67a48af0acd4666eaa5b0d504f743c6852822585049452d94de9c8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 95cd674bef67eca949b8c949f36bffddb06ac71d8a107b09dac04402e4b852b98d54586893a136eb5c772bea13deff2d0335e9f79c0c7f0fb612950560fb16aa
|
|
7
|
+
data.tar.gz: d54bf327e2f5bb61a648847c56a43bf2bb3f55c6eda2b878592c253ae26e04d80e0c0dff2cda9f32899406a4ded797b4c74a8a95f1d01f5e7558856cf0206a07
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Complete FFI bindings for JACK Audio Connection Kit (JACK1 and JACK2)
|
|
15
|
+
- FFI type definitions, structs, and callback types (`Jack::FFI::Types`, `Jack::FFI::Structs`)
|
|
16
|
+
- Library detection for Linux, macOS, and Windows (`Jack::FFI::LibJack`)
|
|
17
|
+
- High-level `Jack::Client` with open/close/activate lifecycle management
|
|
18
|
+
- `Jack::AudioPort` for audio buffer read/write
|
|
19
|
+
- `Jack::MidiPort` for MIDI event read/write
|
|
20
|
+
- `Jack::Midi::Event` with message parsing helpers (note on/off, CC, pitch bend, etc.)
|
|
21
|
+
- `Jack::Transport` for transport control (start/stop/locate/query)
|
|
22
|
+
- `Jack::Session` for session management
|
|
23
|
+
- `Jack::RingBuffer` for lock-free inter-thread communication
|
|
24
|
+
- `Jack::Metadata` for JACK2 metadata API (optional)
|
|
25
|
+
- `Jack::CallbackManager` for GC-safe callback reference management
|
|
26
|
+
- Comprehensive error classes (`ClientOpenFailed`, `ConnectionFailed`, etc.)
|
|
27
|
+
- JACK1/JACK2 compatibility via `attach_optional` for version-specific APIs
|
|
28
|
+
- Example scripts for common use cases
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Jack
|
|
2
|
+
|
|
3
|
+
Ruby FFI bindings for the core [JACK Audio Connection Kit](https://jackaudio.org/) client APIs.
|
|
4
|
+
|
|
5
|
+
This gem targets `libjack` from both JACK1 and JACK2. It covers client lifecycle, audio and MIDI ports, callbacks, transport, session management, metadata, ring buffers, UUID helpers, thread/statistics helpers, and internal client APIs.
|
|
6
|
+
|
|
7
|
+
`net.h` / NetJACK (`libjacknet`) is a separate library and is not currently bound by this gem.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add the gem with Bundler:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add jack-ruby
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install it directly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install jack-ruby
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
JACK itself must already be installed:
|
|
24
|
+
|
|
25
|
+
- macOS: `brew install jack`
|
|
26
|
+
- Ubuntu/Debian: `sudo apt-get install jackd2 libjack-jackd2-dev`
|
|
27
|
+
- Fedora: `sudo dnf install jack-audio-connection-kit-devel`
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
require "jack"
|
|
33
|
+
|
|
34
|
+
puts "JACK #{Jack.version_string}"
|
|
35
|
+
|
|
36
|
+
Jack::Client.open("simple") do |client|
|
|
37
|
+
puts "Client name: #{client.name}"
|
|
38
|
+
puts "Sample rate: #{client.sample_rate}"
|
|
39
|
+
puts "Buffer size: #{client.buffer_size}"
|
|
40
|
+
puts "Realtime: #{client.realtime?}"
|
|
41
|
+
|
|
42
|
+
client.activate
|
|
43
|
+
|
|
44
|
+
client.get_ports.each do |port_name|
|
|
45
|
+
puts port_name
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Examples
|
|
51
|
+
|
|
52
|
+
### Audio Passthrough
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require "jack"
|
|
56
|
+
|
|
57
|
+
Jack::Client.open("passthrough") do |client|
|
|
58
|
+
input = client.register_audio_input("in")
|
|
59
|
+
output = client.register_audio_output("out")
|
|
60
|
+
|
|
61
|
+
client.on_process do |nframes|
|
|
62
|
+
in_buf = input.buffer_pointer(nframes)
|
|
63
|
+
out_buf = output.buffer_pointer(nframes)
|
|
64
|
+
out_buf.write_bytes(in_buf.read_bytes(nframes * 4))
|
|
65
|
+
0
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
client.activate
|
|
69
|
+
|
|
70
|
+
capture = client.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsOutput).first
|
|
71
|
+
playback = client.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsInput).first
|
|
72
|
+
client.connect(capture, "passthrough:in") if capture
|
|
73
|
+
client.connect("passthrough:out", playback) if playback
|
|
74
|
+
|
|
75
|
+
sleep
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### MIDI Monitor
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
require "jack"
|
|
83
|
+
|
|
84
|
+
Jack::Client.open("midi_monitor") do |client|
|
|
85
|
+
midi_in = client.register_midi_input("in")
|
|
86
|
+
|
|
87
|
+
client.on_process do |nframes|
|
|
88
|
+
midi_in.read_all_events(nframes).each do |event|
|
|
89
|
+
puts "Note ON #{event.note}" if event.note_on?
|
|
90
|
+
puts "Note OFF #{event.note}" if event.note_off?
|
|
91
|
+
end
|
|
92
|
+
0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
client.activate
|
|
96
|
+
sleep
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Transport Control
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
require "jack"
|
|
104
|
+
|
|
105
|
+
Jack::Client.open("transport_ctl") do |client|
|
|
106
|
+
transport = client.transport
|
|
107
|
+
|
|
108
|
+
client.activate
|
|
109
|
+
transport.locate(0)
|
|
110
|
+
transport.start
|
|
111
|
+
|
|
112
|
+
sleep 2
|
|
113
|
+
|
|
114
|
+
position = transport.query
|
|
115
|
+
puts "State: #{transport.state}"
|
|
116
|
+
puts "Frame: #{position[:frame]}"
|
|
117
|
+
|
|
118
|
+
transport.stop
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
More runnable examples live in `examples/`:
|
|
123
|
+
|
|
124
|
+
- `audio_passthrough.rb`
|
|
125
|
+
- `meter.rb`
|
|
126
|
+
- `midi_generator.rb`
|
|
127
|
+
- `midi_monitor.rb`
|
|
128
|
+
- `port_connector.rb`
|
|
129
|
+
- `ringbuffer_recorder.rb`
|
|
130
|
+
- `simple_client.rb`
|
|
131
|
+
- `sine_generator.rb`
|
|
132
|
+
- `transport_control.rb`
|
|
133
|
+
|
|
134
|
+
## API Overview
|
|
135
|
+
|
|
136
|
+
| API | Description |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `Jack` | Library-wide helpers like `version`, `version_string`, `client_pid`, `on_error`, `on_info` |
|
|
139
|
+
| `Jack::Client` | Client open/close, activation, port registration, connection management, callbacks, stats, RT scheduling, internal clients |
|
|
140
|
+
| `Jack::Port` | Common port metadata, aliases, monitor control, latency helpers, legacy tie/untie support |
|
|
141
|
+
| `Jack::AudioPort` | Raw audio buffer access |
|
|
142
|
+
| `Jack::MidiPort` | MIDI event read/write, buffer reset/clear helpers |
|
|
143
|
+
| `Jack::Midi::Event` | MIDI message inspection helpers |
|
|
144
|
+
| `Jack::Transport` | Transport query/control, sync callback, timebase callback, legacy transport info helpers |
|
|
145
|
+
| `Jack::Session` | Session callback, notify/reply, UUID and reservation helpers |
|
|
146
|
+
| `Jack::Metadata` | Property set/get/remove and property change callback |
|
|
147
|
+
| `Jack::RingBuffer` | Lock-free ring buffer, vectors, mlock, reset/reset_size |
|
|
148
|
+
| `Jack::UUID` | UUID generation, parse/unparse, compare/copy/clear helpers |
|
|
149
|
+
|
|
150
|
+
## Optional APIs and Compatibility
|
|
151
|
+
|
|
152
|
+
JACK exports some symbols conditionally depending on the server and library version. When a method relies on an optional symbol and that symbol is not present, this gem raises `Jack::NotImplementedError`.
|
|
153
|
+
|
|
154
|
+
Common examples:
|
|
155
|
+
|
|
156
|
+
- `Jack::Metadata` methods on JACK installations without metadata support
|
|
157
|
+
- `Jack::Port#rename` on older JACK versions
|
|
158
|
+
- `Jack::RingBuffer#reset_size`
|
|
159
|
+
- Legacy compatibility helpers such as `Jack::Port#latency`, `Jack::Port#total_latency`, and `Jack::Transport#legacy_info`
|
|
160
|
+
|
|
161
|
+
The gem supports both JACK1 and JACK2 `libjack`, but some helpers are only available on JACK2 or newer JACK releases.
|
|
162
|
+
|
|
163
|
+
## Top-Level Helpers
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
require "jack"
|
|
167
|
+
|
|
168
|
+
Jack.on_error { |message| warn "JACK error: #{message}" }
|
|
169
|
+
Jack.on_info { |message| puts "JACK info: #{message}" }
|
|
170
|
+
|
|
171
|
+
p Jack.version
|
|
172
|
+
puts Jack.version_string
|
|
173
|
+
puts Jack.client_pid("system")
|
|
174
|
+
|
|
175
|
+
uuid = Jack::UUID.generate_client
|
|
176
|
+
puts Jack::UUID.unparse(uuid)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
Install dependencies and run the test suite:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
bundle install
|
|
185
|
+
bundle exec rspec
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Integration tests require a running JACK server and are only enabled when `JACK_TEST=1` is set:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
jackd -d dummy &
|
|
192
|
+
JACK_TEST=1 bundle exec rspec spec/integration/
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/jack.
|
|
198
|
+
|
|
199
|
+
## Environment Variables
|
|
200
|
+
|
|
201
|
+
- `JACK_LIB_PATH`: override the `libjack` path loaded by FFI
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("passthrough") do |jack|
|
|
7
|
+
input = jack.register_audio_input("in")
|
|
8
|
+
output = jack.register_audio_output("out")
|
|
9
|
+
|
|
10
|
+
jack.on_process do |nframes|
|
|
11
|
+
in_buf = input.buffer_pointer(nframes)
|
|
12
|
+
out_buf = output.buffer_pointer(nframes)
|
|
13
|
+
out_buf.write_bytes(in_buf.read_bytes(nframes * 4))
|
|
14
|
+
0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
jack.on_shutdown do |_code, reason|
|
|
18
|
+
puts "JACK shutdown: #{reason}"
|
|
19
|
+
exit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
jack.activate
|
|
23
|
+
|
|
24
|
+
capture = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsOutput).first
|
|
25
|
+
playback = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsInput).first
|
|
26
|
+
jack.connect(capture, "passthrough:in") if capture
|
|
27
|
+
jack.connect("passthrough:out", playback) if playback
|
|
28
|
+
|
|
29
|
+
puts "Audio passthrough running... Press Ctrl-C to quit"
|
|
30
|
+
sleep
|
|
31
|
+
end
|
data/examples/meter.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("meter") do |jack|
|
|
7
|
+
input = jack.register_audio_input("in")
|
|
8
|
+
|
|
9
|
+
jack.on_process do |nframes|
|
|
10
|
+
buf = input.buffer_pointer(nframes)
|
|
11
|
+
samples = buf.read_array_of_float(nframes)
|
|
12
|
+
peak = samples.map(&:abs).max || 0.0
|
|
13
|
+
db = peak > 0 ? 20.0 * Math.log10(peak) : -Float::INFINITY
|
|
14
|
+
|
|
15
|
+
bar_len = [(peak * 50).to_i, 50].min
|
|
16
|
+
bar = "#" * bar_len + " " * (50 - bar_len)
|
|
17
|
+
$stdout.write "\r[#{bar}] #{db.round(1)} dB "
|
|
18
|
+
$stdout.flush
|
|
19
|
+
0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
jack.activate
|
|
23
|
+
|
|
24
|
+
capture = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsOutput).first
|
|
25
|
+
jack.connect(capture, "meter:in") if capture
|
|
26
|
+
|
|
27
|
+
puts "Level meter running... Press Ctrl-C to quit"
|
|
28
|
+
sleep
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("midi_gen") do |jack|
|
|
7
|
+
midi_out = jack.register_midi_output("out")
|
|
8
|
+
|
|
9
|
+
note = 60
|
|
10
|
+
note_on = true
|
|
11
|
+
frame_count = 0
|
|
12
|
+
interval = jack.sample_rate / 2 # Toggle every 0.5 seconds
|
|
13
|
+
|
|
14
|
+
jack.on_process do |nframes|
|
|
15
|
+
midi_out.clear_buffer(nframes)
|
|
16
|
+
|
|
17
|
+
if frame_count >= interval
|
|
18
|
+
if note_on
|
|
19
|
+
midi_out.write_event(0, [0x90, note, 100], nframes)
|
|
20
|
+
else
|
|
21
|
+
midi_out.write_event(0, [0x80, note, 0], nframes)
|
|
22
|
+
note = (note + 1) % 128
|
|
23
|
+
end
|
|
24
|
+
note_on = !note_on
|
|
25
|
+
frame_count = 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
frame_count += nframes
|
|
29
|
+
0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
jack.activate
|
|
33
|
+
puts "MIDI Generator running... Press Ctrl-C to quit"
|
|
34
|
+
sleep
|
|
35
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("midi_monitor") do |jack|
|
|
7
|
+
midi_in = jack.register_midi_input("in")
|
|
8
|
+
|
|
9
|
+
jack.on_process do |nframes|
|
|
10
|
+
midi_in.read_all_events(nframes).each do |event|
|
|
11
|
+
if event.note_on?
|
|
12
|
+
puts "Note ON: ch=#{event.channel} note=#{event.note} vel=#{event.velocity}"
|
|
13
|
+
elsif event.note_off?
|
|
14
|
+
puts "Note OFF: ch=#{event.channel} note=#{event.note}"
|
|
15
|
+
elsif event.control_change?
|
|
16
|
+
puts "CC: ch=#{event.channel} cc=#{event.controller} val=#{event.cc_value}"
|
|
17
|
+
elsif event.pitch_bend?
|
|
18
|
+
puts "Pitch Bend: ch=#{event.channel} val=#{event.pitch_bend_value}"
|
|
19
|
+
elsif event.program_change?
|
|
20
|
+
puts "Program Change: ch=#{event.channel} prog=#{event.note}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
jack.activate
|
|
27
|
+
puts "MIDI Monitor running... Press Ctrl-C to quit"
|
|
28
|
+
sleep
|
|
29
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("connector") do |jack|
|
|
7
|
+
jack.activate
|
|
8
|
+
|
|
9
|
+
outputs = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsOutput)
|
|
10
|
+
inputs = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsInput)
|
|
11
|
+
|
|
12
|
+
puts "Physical outputs: #{outputs}"
|
|
13
|
+
puts "Physical inputs: #{inputs}"
|
|
14
|
+
|
|
15
|
+
# Connect first output to first input (loopback)
|
|
16
|
+
if outputs.first && inputs.first
|
|
17
|
+
jack.connect(outputs.first, inputs.first)
|
|
18
|
+
puts "Connected #{outputs.first} -> #{inputs.first}"
|
|
19
|
+
else
|
|
20
|
+
puts "No physical ports found"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
RECORD_SECONDS = 5
|
|
7
|
+
|
|
8
|
+
Jack::Client.open("recorder") do |jack|
|
|
9
|
+
input = jack.register_audio_input("in")
|
|
10
|
+
rb = Jack::RingBuffer.new(jack.sample_rate * 4 * RECORD_SECONDS * 2)
|
|
11
|
+
|
|
12
|
+
jack.on_process do |nframes|
|
|
13
|
+
buf = input.buffer_pointer(nframes)
|
|
14
|
+
data = buf.read_bytes(nframes * 4)
|
|
15
|
+
rb.write(data)
|
|
16
|
+
0
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
jack.activate
|
|
20
|
+
|
|
21
|
+
capture = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsOutput).first
|
|
22
|
+
jack.connect(capture, "recorder:in") if capture
|
|
23
|
+
|
|
24
|
+
puts "Recording #{RECORD_SECONDS} seconds..."
|
|
25
|
+
sleep RECORD_SECONDS
|
|
26
|
+
|
|
27
|
+
total = rb.read_space
|
|
28
|
+
puts "Recorded #{total} bytes (#{total / 4} samples)"
|
|
29
|
+
|
|
30
|
+
data = rb.read(total)
|
|
31
|
+
File.binwrite("recording.raw", data)
|
|
32
|
+
puts "Saved to recording.raw (32-bit float, #{jack.sample_rate}Hz, mono)"
|
|
33
|
+
|
|
34
|
+
rb.free
|
|
35
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("simple") do |jack|
|
|
7
|
+
puts "Client name: #{jack.name}"
|
|
8
|
+
puts "Sample rate: #{jack.sample_rate}"
|
|
9
|
+
puts "Buffer size: #{jack.buffer_size}"
|
|
10
|
+
puts "Realtime: #{jack.realtime?}"
|
|
11
|
+
|
|
12
|
+
jack.activate
|
|
13
|
+
|
|
14
|
+
ports = jack.get_ports
|
|
15
|
+
puts "\nAvailable ports:"
|
|
16
|
+
ports.each { |p| puts " #{p}" }
|
|
17
|
+
|
|
18
|
+
puts "\nPress Ctrl-C to quit"
|
|
19
|
+
sleep
|
|
20
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
FREQUENCY = 440.0
|
|
7
|
+
|
|
8
|
+
Jack::Client.open("sine_gen") do |jack|
|
|
9
|
+
output = jack.register_audio_output("out")
|
|
10
|
+
phase = 0.0
|
|
11
|
+
phase_inc = 2.0 * Math::PI * FREQUENCY / jack.sample_rate
|
|
12
|
+
|
|
13
|
+
jack.on_process do |nframes|
|
|
14
|
+
buf = output.buffer_pointer(nframes)
|
|
15
|
+
nframes.times do |i|
|
|
16
|
+
buf.put_float32(i * 4, Math.sin(phase))
|
|
17
|
+
phase += phase_inc
|
|
18
|
+
phase -= 2.0 * Math::PI if phase >= 2.0 * Math::PI
|
|
19
|
+
end
|
|
20
|
+
0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
jack.activate
|
|
24
|
+
|
|
25
|
+
playback = jack.get_ports(flags: Jack::JackPortIsPhysical | Jack::JackPortIsInput).first
|
|
26
|
+
jack.connect("sine_gen:out", playback) if playback
|
|
27
|
+
|
|
28
|
+
puts "Generating #{FREQUENCY}Hz sine wave... Press Ctrl-C to quit"
|
|
29
|
+
sleep
|
|
30
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "jack"
|
|
5
|
+
|
|
6
|
+
Jack::Client.open("transport_ctl") do |jack|
|
|
7
|
+
transport = jack.transport
|
|
8
|
+
|
|
9
|
+
jack.activate
|
|
10
|
+
|
|
11
|
+
transport.locate(0)
|
|
12
|
+
transport.start
|
|
13
|
+
sleep 2
|
|
14
|
+
|
|
15
|
+
pos = transport.query
|
|
16
|
+
puts "Position: frame=#{pos[:frame]} rate=#{pos[:frame_rate]}"
|
|
17
|
+
puts "State: #{transport.state}"
|
|
18
|
+
|
|
19
|
+
transport.stop
|
|
20
|
+
puts "Transport stopped"
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class AudioPort < Port
|
|
5
|
+
def read_buffer(nframes)
|
|
6
|
+
ptr = buffer_pointer(nframes)
|
|
7
|
+
ptr.read_array_of_float(nframes)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def write_buffer(samples, nframes)
|
|
11
|
+
ptr = buffer_pointer(nframes)
|
|
12
|
+
ptr.write_array_of_float(samples[0, nframes])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def buffer_pointer(nframes)
|
|
16
|
+
FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class CallbackManager
|
|
5
|
+
def initialize
|
|
6
|
+
@callbacks = {}
|
|
7
|
+
@ffi_procs = {}
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def register(type, proc_obj, ffi_func)
|
|
12
|
+
@mutex.synchronize do
|
|
13
|
+
@callbacks[type] = proc_obj
|
|
14
|
+
@ffi_procs[type] = ffi_func
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(type)
|
|
19
|
+
@mutex.synchronize { @callbacks[type] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear
|
|
23
|
+
@mutex.synchronize do
|
|
24
|
+
@callbacks.clear
|
|
25
|
+
@ffi_procs.clear
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|