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
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class MidiPort < Port
|
|
5
|
+
def event_count(nframes)
|
|
6
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
7
|
+
FFI::LibJack.jack_midi_get_event_count(buf)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read_event(index, nframes)
|
|
11
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
12
|
+
event = FFI::Structs::JackMidiEvent.new
|
|
13
|
+
result = FFI::LibJack.jack_midi_event_get(event, buf, index)
|
|
14
|
+
return nil unless result.zero?
|
|
15
|
+
|
|
16
|
+
data = event[:buffer].read_array_of_uint8(event[:size])
|
|
17
|
+
Midi::Event.new(event[:time], data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def read_all_events(nframes)
|
|
21
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
22
|
+
count = FFI::LibJack.jack_midi_get_event_count(buf)
|
|
23
|
+
events = []
|
|
24
|
+
event_struct = FFI::Structs::JackMidiEvent.new
|
|
25
|
+
|
|
26
|
+
count.times do |i|
|
|
27
|
+
next unless FFI::LibJack.jack_midi_event_get(event_struct, buf, i).zero?
|
|
28
|
+
|
|
29
|
+
data = event_struct[:buffer].read_array_of_uint8(event_struct[:size])
|
|
30
|
+
events << Midi::Event.new(event_struct[:time], data)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
events
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clear_buffer(nframes)
|
|
37
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
38
|
+
FFI::LibJack.jack_midi_clear_buffer(buf)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset_buffer(nframes)
|
|
42
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
43
|
+
FFI::LibJack.jack_midi_reset_buffer(buf)
|
|
44
|
+
rescue Jack::NotImplementedError
|
|
45
|
+
FFI::LibJack.jack_midi_clear_buffer(buf)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def reserve_event(time, data_size, nframes)
|
|
49
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
50
|
+
FFI::LibJack.jack_midi_event_reserve(buf, time, data_size)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def write_event(time, data, nframes)
|
|
54
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
55
|
+
data_bytes = data.is_a?(Array) ? data : data.bytes
|
|
56
|
+
ptr = ::FFI::MemoryPointer.new(:uint8, data_bytes.size)
|
|
57
|
+
ptr.write_array_of_uint8(data_bytes)
|
|
58
|
+
FFI::LibJack.jack_midi_event_write(buf, time, ptr, data_bytes.size)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def max_event_size(nframes)
|
|
62
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
63
|
+
FFI::LibJack.jack_midi_max_event_size(buf)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def lost_event_count(nframes)
|
|
67
|
+
buf = FFI::LibJack.jack_port_get_buffer(@handle, nframes)
|
|
68
|
+
FFI::LibJack.jack_midi_get_lost_event_count(buf)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/jack/port.rb
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class Port
|
|
5
|
+
attr_reader :handle, :client
|
|
6
|
+
|
|
7
|
+
def initialize(handle, client)
|
|
8
|
+
@handle = handle
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
FFI::LibJack.jack_port_name(@handle)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def short_name
|
|
17
|
+
FFI::LibJack.jack_port_short_name(@handle)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def full_name
|
|
21
|
+
name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def type
|
|
25
|
+
FFI::LibJack.jack_port_type(@handle)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def flags
|
|
29
|
+
FFI::LibJack.jack_port_flags(@handle)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def uuid
|
|
33
|
+
FFI::LibJack.jack_port_uuid(@handle)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def input?
|
|
37
|
+
(flags & FFI::Types::JackPortIsInput) != 0
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def output?
|
|
41
|
+
(flags & FFI::Types::JackPortIsOutput) != 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def physical?
|
|
45
|
+
(flags & FFI::Types::JackPortIsPhysical) != 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def mine?
|
|
49
|
+
FFI::LibJack.jack_port_is_mine(@client.handle, @handle) != 0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def connections
|
|
53
|
+
ptr = FFI::LibJack.jack_port_get_connections(@handle)
|
|
54
|
+
return [] if ptr.null?
|
|
55
|
+
|
|
56
|
+
result = read_string_array(ptr)
|
|
57
|
+
FFI::LibJack.jack_free(ptr)
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def connected_to?(port_name)
|
|
62
|
+
FFI::LibJack.jack_port_connected_to(@handle, port_name) != 0
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def connected_count
|
|
66
|
+
FFI::LibJack.jack_port_connected(@handle)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def tie(other_port)
|
|
70
|
+
result = FFI::LibJack.jack_port_tie(@handle, other_port.handle)
|
|
71
|
+
raise Error, "Failed to tie port" unless result.zero?
|
|
72
|
+
rescue Jack::NotImplementedError
|
|
73
|
+
raise Jack::NotImplementedError, "Port tying is not available on this JACK installation"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def untie
|
|
77
|
+
result = FFI::LibJack.jack_port_untie(@handle)
|
|
78
|
+
raise Error, "Failed to untie port" unless result.zero?
|
|
79
|
+
rescue Jack::NotImplementedError
|
|
80
|
+
raise Jack::NotImplementedError, "Port untying is not available on this JACK installation"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def rename(new_name)
|
|
84
|
+
result = begin
|
|
85
|
+
FFI::LibJack.jack_port_rename(@client.handle, @handle, new_name)
|
|
86
|
+
rescue Jack::NotImplementedError
|
|
87
|
+
FFI::LibJack.jack_port_set_name(@handle, new_name)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
raise Error, "Failed to rename port" unless result.zero?
|
|
91
|
+
|
|
92
|
+
new_name
|
|
93
|
+
rescue Jack::NotImplementedError
|
|
94
|
+
raise Jack::NotImplementedError, "Port rename is not available on this JACK installation"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def aliases
|
|
98
|
+
ptr1 = ::FFI::MemoryPointer.new(:pointer, 2)
|
|
99
|
+
buf1 = ::FFI::MemoryPointer.new(:char, FFI::LibJack.jack_port_name_size)
|
|
100
|
+
buf2 = ::FFI::MemoryPointer.new(:char, FFI::LibJack.jack_port_name_size)
|
|
101
|
+
ptr1.put_pointer(0, buf1)
|
|
102
|
+
ptr1.put_pointer(::FFI::Pointer.size, buf2)
|
|
103
|
+
|
|
104
|
+
count = FFI::LibJack.jack_port_get_aliases(@handle, ptr1)
|
|
105
|
+
result = []
|
|
106
|
+
result << buf1.read_string if count >= 1
|
|
107
|
+
result << buf2.read_string if count >= 2
|
|
108
|
+
result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def set_alias(alias_name)
|
|
112
|
+
result = FFI::LibJack.jack_port_set_alias(@handle, alias_name)
|
|
113
|
+
raise Error, "Failed to set alias" unless result.zero?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def unset_alias(alias_name)
|
|
117
|
+
result = FFI::LibJack.jack_port_unset_alias(@handle, alias_name)
|
|
118
|
+
raise Error, "Failed to unset alias" unless result.zero?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def capture_latency_range
|
|
122
|
+
range = FFI::Structs::JackLatencyRange.new
|
|
123
|
+
FFI::LibJack.jack_port_get_latency_range(@handle, 0, range)
|
|
124
|
+
{ min: range[:min], max: range[:max] }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def playback_latency_range
|
|
128
|
+
range = FFI::Structs::JackLatencyRange.new
|
|
129
|
+
FFI::LibJack.jack_port_get_latency_range(@handle, 1, range)
|
|
130
|
+
{ min: range[:min], max: range[:max] }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def set_capture_latency_range(min:, max:)
|
|
134
|
+
range = FFI::Structs::JackLatencyRange.new
|
|
135
|
+
range[:min] = min
|
|
136
|
+
range[:max] = max
|
|
137
|
+
FFI::LibJack.jack_port_set_latency_range(@handle, 0, range)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def latency
|
|
141
|
+
FFI::LibJack.jack_port_get_latency(@handle)
|
|
142
|
+
rescue Jack::NotImplementedError
|
|
143
|
+
raise Jack::NotImplementedError, "Legacy port latency is not available on this JACK installation"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def latency=(nframes)
|
|
147
|
+
FFI::LibJack.jack_port_set_latency(@handle, nframes)
|
|
148
|
+
rescue Jack::NotImplementedError
|
|
149
|
+
raise Jack::NotImplementedError, "Legacy port latency is not available on this JACK installation"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def total_latency
|
|
153
|
+
FFI::LibJack.jack_port_get_total_latency(@client.handle, @handle)
|
|
154
|
+
rescue Jack::NotImplementedError
|
|
155
|
+
raise Jack::NotImplementedError, "Legacy total port latency is not available on this JACK installation"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def recompute_total_latency
|
|
159
|
+
result = FFI::LibJack.jack_recompute_total_latency(@client.handle, @handle)
|
|
160
|
+
raise Error, "Failed to recompute port latency" unless result.zero?
|
|
161
|
+
rescue Jack::NotImplementedError
|
|
162
|
+
raise Jack::NotImplementedError, "Legacy total port latency recomputation is not available on this JACK installation"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def set_playback_latency_range(min:, max:)
|
|
166
|
+
range = FFI::Structs::JackLatencyRange.new
|
|
167
|
+
range[:min] = min
|
|
168
|
+
range[:max] = max
|
|
169
|
+
FFI::LibJack.jack_port_set_latency_range(@handle, 1, range)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def request_monitor(enabled)
|
|
173
|
+
FFI::LibJack.jack_port_request_monitor(@handle, enabled ? 1 : 0)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def monitoring_input?
|
|
177
|
+
FFI::LibJack.jack_port_monitoring_input(@handle) != 0
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def unregister
|
|
181
|
+
result = FFI::LibJack.jack_port_unregister(@client.handle, @handle)
|
|
182
|
+
raise Error, "Failed to unregister port" unless result.zero?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
|
|
187
|
+
def read_string_array(ptr)
|
|
188
|
+
result = []
|
|
189
|
+
offset = 0
|
|
190
|
+
loop do
|
|
191
|
+
str_ptr = ptr.get_pointer(offset)
|
|
192
|
+
break if str_ptr.null?
|
|
193
|
+
|
|
194
|
+
result << str_ptr.read_string
|
|
195
|
+
offset += ::FFI::Pointer.size
|
|
196
|
+
end
|
|
197
|
+
result
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class RingBuffer
|
|
5
|
+
def initialize(size)
|
|
6
|
+
@handle = FFI::LibJack.jack_ringbuffer_create(size)
|
|
7
|
+
raise Error, "Failed to create ring buffer" if @handle.null?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def read(count)
|
|
11
|
+
buf = ::FFI::MemoryPointer.new(:char, count)
|
|
12
|
+
bytes_read = FFI::LibJack.jack_ringbuffer_read(@handle, buf, count)
|
|
13
|
+
buf.read_string(bytes_read)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(data)
|
|
17
|
+
ptr = ::FFI::MemoryPointer.from_string(data)
|
|
18
|
+
FFI::LibJack.jack_ringbuffer_write(@handle, ptr, data.bytesize)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def peek(count)
|
|
22
|
+
buf = ::FFI::MemoryPointer.new(:char, count)
|
|
23
|
+
bytes_read = FFI::LibJack.jack_ringbuffer_peek(@handle, buf, count)
|
|
24
|
+
buf.read_string(bytes_read)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read_space
|
|
28
|
+
FFI::LibJack.jack_ringbuffer_read_space(@handle)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write_space
|
|
32
|
+
FFI::LibJack.jack_ringbuffer_write_space(@handle)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def read_advance(count)
|
|
36
|
+
FFI::LibJack.jack_ringbuffer_read_advance(@handle, count)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def write_advance(count)
|
|
40
|
+
FFI::LibJack.jack_ringbuffer_write_advance(@handle, count)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset
|
|
44
|
+
FFI::LibJack.jack_ringbuffer_reset(@handle)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def reset_size(size)
|
|
48
|
+
FFI::LibJack.jack_ringbuffer_reset_size(@handle, size)
|
|
49
|
+
rescue Jack::NotImplementedError
|
|
50
|
+
raise Jack::NotImplementedError, "Ring buffer resizing is not available on this JACK installation"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def mlock
|
|
54
|
+
result = FFI::LibJack.jack_ringbuffer_mlock(@handle)
|
|
55
|
+
raise Error, "Failed to mlock ring buffer" unless result.zero?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_vector
|
|
59
|
+
vec = ::FFI::MemoryPointer.new(FFI::Structs::RingBufferData, 2)
|
|
60
|
+
FFI::LibJack.jack_ringbuffer_get_read_vector(@handle, vec)
|
|
61
|
+
2.times.map do |i|
|
|
62
|
+
data = FFI::Structs::RingBufferData.new(vec + (i * FFI::Structs::RingBufferData.size))
|
|
63
|
+
{ pointer: data[:buf], length: data[:len] }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def write_vector
|
|
68
|
+
vec = ::FFI::MemoryPointer.new(FFI::Structs::RingBufferData, 2)
|
|
69
|
+
FFI::LibJack.jack_ringbuffer_get_write_vector(@handle, vec)
|
|
70
|
+
2.times.map do |i|
|
|
71
|
+
data = FFI::Structs::RingBufferData.new(vec + (i * FFI::Structs::RingBufferData.size))
|
|
72
|
+
{ pointer: data[:buf], length: data[:len] }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def free
|
|
77
|
+
return unless @handle
|
|
78
|
+
|
|
79
|
+
FFI::LibJack.jack_ringbuffer_free(@handle)
|
|
80
|
+
@handle = nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/jack/session.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class Session
|
|
5
|
+
attr_reader :client
|
|
6
|
+
|
|
7
|
+
def initialize(client)
|
|
8
|
+
@client = client
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def on_session(&block)
|
|
12
|
+
ffi_proc = proc { |event_ptr, _arg|
|
|
13
|
+
block.call(event_ptr)
|
|
14
|
+
}
|
|
15
|
+
@client.callback_manager.register(:session, block, ffi_proc)
|
|
16
|
+
FFI::LibJack.jack_set_session_callback(@client.handle, ffi_proc, nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def reply(event_ptr)
|
|
20
|
+
result = FFI::LibJack.jack_session_reply(@client.handle, event_ptr)
|
|
21
|
+
raise Error, "Failed to reply to session event" unless result.zero?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def uuid
|
|
25
|
+
FFI::LibJack.jack_client_get_uuid(@client.handle)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def notify(target: nil, type: :save, path:)
|
|
29
|
+
type_val = case type
|
|
30
|
+
when :save then 1
|
|
31
|
+
when :save_and_quit then 2
|
|
32
|
+
when :save_template then 3
|
|
33
|
+
else type
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ptr = FFI::LibJack.jack_session_notify(@client.handle, target, type_val, path)
|
|
37
|
+
return [] if ptr.null?
|
|
38
|
+
|
|
39
|
+
commands = []
|
|
40
|
+
offset = 0
|
|
41
|
+
cmd_size = FFI::Structs::JackSessionCommand.size
|
|
42
|
+
loop do
|
|
43
|
+
cmd = FFI::Structs::JackSessionCommand.new(ptr + offset)
|
|
44
|
+
break if cmd[:uuid].nil?
|
|
45
|
+
|
|
46
|
+
commands << { uuid: cmd[:uuid], name: cmd[:name], command: cmd[:command], flags: cmd[:flags] }
|
|
47
|
+
offset += cmd_size
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
FFI::LibJack.jack_session_commands_free(ptr)
|
|
51
|
+
commands
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def reserve_name(name, uuid)
|
|
55
|
+
result = FFI::LibJack.jack_reserve_client_name(@client.handle, name, uuid)
|
|
56
|
+
raise Error, "Failed to reserve client name" unless result.zero?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def client_has_callback?(name)
|
|
60
|
+
FFI::LibJack.jack_client_has_session_callback(@client.handle, name) != 0
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
class Transport
|
|
5
|
+
STATES = {
|
|
6
|
+
0 => :stopped,
|
|
7
|
+
1 => :rolling,
|
|
8
|
+
2 => :looping,
|
|
9
|
+
3 => :starting
|
|
10
|
+
}.freeze
|
|
11
|
+
STATE_VALUES = STATES.invert.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :client
|
|
14
|
+
|
|
15
|
+
def initialize(client)
|
|
16
|
+
@client = client
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def state
|
|
20
|
+
pos = FFI::Structs::JackPosition.new
|
|
21
|
+
state_val = FFI::LibJack.jack_transport_query(@client.handle, pos)
|
|
22
|
+
STATES[state_val] || :unknown
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def query
|
|
26
|
+
pos = FFI::Structs::JackPosition.new
|
|
27
|
+
state_val = FFI::LibJack.jack_transport_query(@client.handle, pos)
|
|
28
|
+
{
|
|
29
|
+
state: STATES[state_val] || :unknown,
|
|
30
|
+
frame: pos[:frame],
|
|
31
|
+
frame_rate: pos[:frame_rate],
|
|
32
|
+
usecs: pos[:usecs],
|
|
33
|
+
valid: pos[:valid],
|
|
34
|
+
bar: pos[:bar],
|
|
35
|
+
beat: pos[:beat],
|
|
36
|
+
tick: pos[:tick],
|
|
37
|
+
bar_start_tick: pos[:bar_start_tick],
|
|
38
|
+
beats_per_bar: pos[:beats_per_bar],
|
|
39
|
+
beat_type: pos[:beat_type],
|
|
40
|
+
ticks_per_beat: pos[:ticks_per_beat],
|
|
41
|
+
beats_per_minute: pos[:beats_per_minute]
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def legacy_info
|
|
46
|
+
info = FFI::Structs::JackTransportInfo.new
|
|
47
|
+
FFI::LibJack.jack_get_transport_info(@client.handle, info)
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
frame_rate: info[:frame_rate],
|
|
51
|
+
usecs: info[:usecs],
|
|
52
|
+
valid: info[:valid],
|
|
53
|
+
transport_state: STATES[info[:transport_state]] || :unknown,
|
|
54
|
+
frame: info[:frame],
|
|
55
|
+
loop_start: info[:loop_start],
|
|
56
|
+
loop_end: info[:loop_end],
|
|
57
|
+
smpte_offset: info[:smpte_offset],
|
|
58
|
+
smpte_frame_rate: info[:smpte_frame_rate],
|
|
59
|
+
bar: info[:bar],
|
|
60
|
+
beat: info[:beat],
|
|
61
|
+
tick: info[:tick],
|
|
62
|
+
bar_start_tick: info[:bar_start_tick],
|
|
63
|
+
beats_per_bar: info[:beats_per_bar],
|
|
64
|
+
beat_type: info[:beat_type],
|
|
65
|
+
ticks_per_beat: info[:ticks_per_beat],
|
|
66
|
+
beats_per_minute: info[:beats_per_minute]
|
|
67
|
+
}
|
|
68
|
+
rescue Jack::NotImplementedError
|
|
69
|
+
raise Jack::NotImplementedError, "Legacy transport info is not available on this JACK installation"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def legacy_info=(values)
|
|
73
|
+
info = FFI::Structs::JackTransportInfo.new
|
|
74
|
+
values.each do |key, value|
|
|
75
|
+
next unless info.members.include?(key)
|
|
76
|
+
|
|
77
|
+
info[key] = key == :transport_state && value.is_a?(Symbol) ? STATE_VALUES.fetch(value, value) : value
|
|
78
|
+
end
|
|
79
|
+
FFI::LibJack.jack_set_transport_info(@client.handle, info)
|
|
80
|
+
rescue Jack::NotImplementedError
|
|
81
|
+
raise Jack::NotImplementedError, "Legacy transport info is not available on this JACK installation"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def frame
|
|
85
|
+
FFI::LibJack.jack_get_current_transport_frame(@client.handle)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def start
|
|
89
|
+
FFI::LibJack.jack_transport_start(@client.handle)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def stop
|
|
93
|
+
FFI::LibJack.jack_transport_stop(@client.handle)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def locate(frame_pos)
|
|
97
|
+
result = FFI::LibJack.jack_transport_locate(@client.handle, frame_pos)
|
|
98
|
+
raise Error, "Failed to locate transport" unless result.zero?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def reposition(position_hash)
|
|
102
|
+
pos = FFI::Structs::JackPosition.new
|
|
103
|
+
position_hash.each do |key, value|
|
|
104
|
+
pos[key] = value if pos.members.include?(key)
|
|
105
|
+
end
|
|
106
|
+
result = FFI::LibJack.jack_transport_reposition(@client.handle, pos)
|
|
107
|
+
raise Error, "Failed to reposition transport" unless result.zero?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def on_sync(&block)
|
|
111
|
+
ffi_proc = proc { |state, pos_ptr, _arg|
|
|
112
|
+
block.call(STATES[state] || :unknown, pos_ptr) ? 1 : 0
|
|
113
|
+
}
|
|
114
|
+
@client.callback_manager.register(:sync, block, ffi_proc)
|
|
115
|
+
FFI::LibJack.jack_set_sync_callback(@client.handle, ffi_proc, nil)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def sync_timeout=(usecs)
|
|
119
|
+
FFI::LibJack.jack_set_sync_timeout(@client.handle, usecs)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def set_timebase(conditional: false, &block)
|
|
123
|
+
ffi_proc = proc { |state, nframes, pos_ptr, new_pos, _arg|
|
|
124
|
+
block.call(STATES[state] || :unknown, nframes, pos_ptr, new_pos != 0)
|
|
125
|
+
}
|
|
126
|
+
@client.callback_manager.register(:timebase, block, ffi_proc)
|
|
127
|
+
result = FFI::LibJack.jack_set_timebase_callback(
|
|
128
|
+
@client.handle, conditional ? 1 : 0, ffi_proc, nil
|
|
129
|
+
)
|
|
130
|
+
raise Error, "Failed to set timebase callback" unless result.zero?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def release_timebase
|
|
134
|
+
result = FFI::LibJack.jack_release_timebase(@client.handle)
|
|
135
|
+
raise Error, "Failed to release timebase" unless result.zero?
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/jack/uuid.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jack
|
|
4
|
+
module UUID
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def generate_client
|
|
8
|
+
FFI::LibJack.jack_client_uuid_generate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def generate_port(port_id)
|
|
12
|
+
FFI::LibJack.jack_port_uuid_generate(port_id)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_index(uuid)
|
|
16
|
+
FFI::LibJack.jack_uuid_to_index(uuid)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def compare(left, right)
|
|
20
|
+
FFI::LibJack.jack_uuid_compare(left, right)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def copy(uuid)
|
|
24
|
+
pointer = ::FFI::MemoryPointer.new(:uint64)
|
|
25
|
+
FFI::LibJack.jack_uuid_copy(pointer, uuid)
|
|
26
|
+
pointer.read_uint64
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def clear(uuid)
|
|
30
|
+
pointer = ::FFI::MemoryPointer.new(:uint64)
|
|
31
|
+
pointer.write_uint64(uuid)
|
|
32
|
+
FFI::LibJack.jack_uuid_clear(pointer)
|
|
33
|
+
pointer.read_uint64
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse(value)
|
|
37
|
+
pointer = ::FFI::MemoryPointer.new(:uint64)
|
|
38
|
+
result = FFI::LibJack.jack_uuid_parse(value, pointer)
|
|
39
|
+
raise Error, "Failed to parse UUID '#{value}'" unless result.zero?
|
|
40
|
+
|
|
41
|
+
pointer.read_uint64
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def unparse(uuid)
|
|
45
|
+
pointer = ::FFI::MemoryPointer.new(:char, FFI::Types::JACK_UUID_STRING_SIZE)
|
|
46
|
+
FFI::LibJack.jack_uuid_unparse(uuid, pointer)
|
|
47
|
+
pointer.read_string
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def empty?(uuid)
|
|
51
|
+
FFI::LibJack.jack_uuid_empty(uuid) != 0
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/jack/version.rb
ADDED
data/lib/jack.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
require_relative "jack/version"
|
|
6
|
+
require_relative "jack/error"
|
|
7
|
+
require_relative "jack/ffi/types"
|
|
8
|
+
require_relative "jack/ffi/structs"
|
|
9
|
+
require_relative "jack/ffi/lib_jack"
|
|
10
|
+
require_relative "jack/callback_manager"
|
|
11
|
+
require_relative "jack/port"
|
|
12
|
+
require_relative "jack/audio_port"
|
|
13
|
+
require_relative "jack/midi/event"
|
|
14
|
+
require_relative "jack/midi_port"
|
|
15
|
+
require_relative "jack/transport"
|
|
16
|
+
require_relative "jack/session"
|
|
17
|
+
require_relative "jack/ring_buffer"
|
|
18
|
+
require_relative "jack/metadata"
|
|
19
|
+
require_relative "jack/uuid"
|
|
20
|
+
require_relative "jack/client"
|
|
21
|
+
|
|
22
|
+
module Jack
|
|
23
|
+
# Re-export commonly used constants for convenience
|
|
24
|
+
JackPortIsInput = FFI::Types::JackPortIsInput
|
|
25
|
+
JackPortIsOutput = FFI::Types::JackPortIsOutput
|
|
26
|
+
JackPortIsPhysical = FFI::Types::JackPortIsPhysical
|
|
27
|
+
JackPortCanMonitor = FFI::Types::JackPortCanMonitor
|
|
28
|
+
JackPortIsTerminal = FFI::Types::JackPortIsTerminal
|
|
29
|
+
|
|
30
|
+
JACK_DEFAULT_AUDIO_TYPE = FFI::Types::JACK_DEFAULT_AUDIO_TYPE
|
|
31
|
+
JACK_DEFAULT_MIDI_TYPE = FFI::Types::JACK_DEFAULT_MIDI_TYPE
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def version
|
|
35
|
+
pointers = Array.new(4) { ::FFI::MemoryPointer.new(:int) }
|
|
36
|
+
FFI::LibJack.jack_get_version(*pointers)
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
major: pointers[0].read_int,
|
|
40
|
+
minor: pointers[1].read_int,
|
|
41
|
+
micro: pointers[2].read_int,
|
|
42
|
+
protocol: pointers[3].read_int
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def version_string
|
|
47
|
+
FFI::LibJack.jack_get_version_string
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def client_pid(name)
|
|
51
|
+
FFI::LibJack.jack_get_client_pid(name)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def on_error(&block)
|
|
55
|
+
@error_callback = block && proc { |message| block.call(message) }
|
|
56
|
+
FFI::LibJack.jack_set_error_function(@error_callback)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def on_info(&block)
|
|
60
|
+
@info_callback = block && proc { |message| block.call(message) }
|
|
61
|
+
FFI::LibJack.jack_set_info_function(@info_callback)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|