midi-mmc 0.1
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/.gitignore +19 -0
- data/Gemfile +3 -0
- data/README.md +49 -0
- data/Rakefile +9 -0
- data/TODO.md +4 -0
- data/lib/midi-mmc.rb +190 -0
- data/midi-mmc.gemspec +20 -0
- data/test/test.rb +28 -0
- data/tools/example +20 -0
- data/tools/test-track-arming +27 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0d1f6f968a7a654593b58147b5347d2af79dbac47d35af39d7f7a0c7e1e96395
|
4
|
+
data.tar.gz: 3620e420346df8b561ed93091b350b1fb688887814d3a777d1e74d13b19212d2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '09b2811ad251e3f939eb649cdd578ca8d99032cab1f56a70b823d9f601c564c2c08da980df5bb8063919b3436fe46863b245005b6b5815a7730e7b7176158b85'
|
7
|
+
data.tar.gz: d79f2cae94aaaade3d1f65b0330b0d7cf0e1cf2c87355d481240e664cae77468099042f08a6017dde21577cd8bd20975e6d09226ed61cf479bb5f50afaecb162
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# MIDI::MMC
|
2
|
+
|
3
|
+
This is a Ruby gem to facilitate controlling MIDI devices using the [MIDI Machine Control](https://en.wikipedia.org/wiki/MIDI_Machine_Control) protocol.
|
4
|
+
|
5
|
+
|
6
|
+
## Features
|
7
|
+
|
8
|
+
- Implements the common MMC commands, including transport (stop, play, record, etc.) and track arming.
|
9
|
+
|
10
|
+
|
11
|
+
## Limitations
|
12
|
+
|
13
|
+
- Does not handle any command responses, whether status (like timecode) or errors.
|
14
|
+
- Does not implement full MMC specification.
|
15
|
+
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'midi-mmc'
|
21
|
+
require 'unimidi'
|
22
|
+
|
23
|
+
# set up
|
24
|
+
midi_output = UniMIDI::Output.gets
|
25
|
+
mmc = MIDI::MMC.new(output: midi_output)
|
26
|
+
mmc.reset
|
27
|
+
|
28
|
+
# record
|
29
|
+
mmc.goto_zero
|
30
|
+
mmc.arm_tracks(1)
|
31
|
+
# record something here
|
32
|
+
mmc.stop
|
33
|
+
mmc.arm_tracks(nil)
|
34
|
+
|
35
|
+
# play back
|
36
|
+
mmc.goto_zero
|
37
|
+
mmc.play
|
38
|
+
```
|
39
|
+
|
40
|
+
|
41
|
+
## Requirements
|
42
|
+
|
43
|
+
MIDI::MMC is designed to work with the [unimidi](http://github.com/arirusso/unimidi) gem, although any object passed in the `output` parameter to `MIDI::MMC.new` that responds to the `#puts` method can be used.
|
44
|
+
|
45
|
+
|
46
|
+
## References
|
47
|
+
|
48
|
+
- [Complete MIDI 1.0 Detailed Specification (1996, from archive.org)](https://archive.org/details/Complete_MIDI_1.0_Detailed_Specification_96-1-3)
|
49
|
+
- [MIDI Machine Control (Wikipedia)](https://en.wikipedia.org/wiki/MIDI_Machine_Control)
|
data/Rakefile
ADDED
data/TODO.md
ADDED
data/lib/midi-mmc.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
module MIDI
|
2
|
+
|
3
|
+
class MMC
|
4
|
+
|
5
|
+
attr_accessor :output
|
6
|
+
attr_accessor :device_id
|
7
|
+
|
8
|
+
DEVICE_ID_ALL = 0x7F
|
9
|
+
|
10
|
+
TYPES = {
|
11
|
+
command: 0x06,
|
12
|
+
response: 0x07,
|
13
|
+
}
|
14
|
+
|
15
|
+
COMMANDS = {
|
16
|
+
stop: 0x01,
|
17
|
+
play: 0x02,
|
18
|
+
deferred_play: 0x03,
|
19
|
+
fast_forward: 0x04,
|
20
|
+
rewind: 0x05,
|
21
|
+
record_strobe: 0x06,
|
22
|
+
record_exit: 0x07,
|
23
|
+
record_pause: 0x08,
|
24
|
+
pause: 0x09,
|
25
|
+
eject: 0x0A,
|
26
|
+
chase: 0x0B,
|
27
|
+
reset: 0x0D,
|
28
|
+
write: 0x40,
|
29
|
+
locate: 0x44,
|
30
|
+
shuttle: 0x47,
|
31
|
+
}
|
32
|
+
|
33
|
+
WRITE_MODES = {
|
34
|
+
record_ready: 0x4F,
|
35
|
+
sync_monitor: 0x52,
|
36
|
+
input_monitor: 0x53,
|
37
|
+
mute: 0x62,
|
38
|
+
}
|
39
|
+
|
40
|
+
def initialize(output: nil, device_id: nil, debug: nil)
|
41
|
+
@output = output
|
42
|
+
@device_id = device_id || DEVICE_ID_ALL
|
43
|
+
@debug = debug
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop
|
47
|
+
send_command_message(:stop)
|
48
|
+
end
|
49
|
+
|
50
|
+
def play
|
51
|
+
send_command_message(:play)
|
52
|
+
end
|
53
|
+
|
54
|
+
def deferred_play
|
55
|
+
send_command_message(:deferred_play)
|
56
|
+
end
|
57
|
+
|
58
|
+
def fast_forward
|
59
|
+
send_command_message(:fast_forward)
|
60
|
+
end
|
61
|
+
|
62
|
+
def rewind
|
63
|
+
send_command_message(:rewind)
|
64
|
+
end
|
65
|
+
|
66
|
+
def record_strobe
|
67
|
+
send_command_message(:record_strobe)
|
68
|
+
end
|
69
|
+
alias_method :punch_in, :record_strobe
|
70
|
+
|
71
|
+
def record_exit
|
72
|
+
send_command_message(:record_exit)
|
73
|
+
end
|
74
|
+
alias_method :punch_out, :record_exit
|
75
|
+
|
76
|
+
def record_pause
|
77
|
+
send_command_message(:record_pause)
|
78
|
+
end
|
79
|
+
|
80
|
+
def pause
|
81
|
+
send_command_message(:pause)
|
82
|
+
end
|
83
|
+
|
84
|
+
def eject
|
85
|
+
send_command_message(:eject)
|
86
|
+
end
|
87
|
+
|
88
|
+
def chase
|
89
|
+
send_command_message(:chase)
|
90
|
+
end
|
91
|
+
|
92
|
+
def reset
|
93
|
+
send_command_message(:reset)
|
94
|
+
end
|
95
|
+
|
96
|
+
def arm_tracks(tracks)
|
97
|
+
tracks = case tracks
|
98
|
+
when nil
|
99
|
+
[]
|
100
|
+
when Numeric
|
101
|
+
[tracks]
|
102
|
+
when Range
|
103
|
+
tracks.to_a
|
104
|
+
when Array
|
105
|
+
tracks
|
106
|
+
else
|
107
|
+
raise
|
108
|
+
end
|
109
|
+
bytes = track_bitmap_bytes(tracks)
|
110
|
+
track_bitmap = [bytes.length, *bytes]
|
111
|
+
mode_id = WRITE_MODES[:record_ready] or raise
|
112
|
+
send_command_message(:write, track_bitmap.length, mode_id, *track_bitmap)
|
113
|
+
end
|
114
|
+
alias_method :record_ready, :arm_tracks
|
115
|
+
|
116
|
+
def locate(location)
|
117
|
+
send_command_message(:locate, 0x06, 0x01, *location, wait: 1)
|
118
|
+
end
|
119
|
+
alias_method :goto, :locate
|
120
|
+
|
121
|
+
def locate_zero
|
122
|
+
locate([0, 0, 0, 0, 0])
|
123
|
+
end
|
124
|
+
alias_method :goto_zero, :locate_zero
|
125
|
+
|
126
|
+
def shuffle
|
127
|
+
send_command_message(:shuffle)
|
128
|
+
end
|
129
|
+
|
130
|
+
def message_to_str(msg)
|
131
|
+
msg.map { |b| '%02X' % b }.join(' ')
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def send_command_message(command, *elements, wait: nil)
|
137
|
+
id = COMMANDS[command] or raise
|
138
|
+
msg = [0xF0, 0x7F, @device_id, TYPES[:command], id, *elements, 0xF7]
|
139
|
+
if @debug
|
140
|
+
warn "%-15s >>> %s" % [command, message_to_str(msg)]
|
141
|
+
end
|
142
|
+
@output.puts(msg) if @output
|
143
|
+
sleep(wait) if wait
|
144
|
+
msg
|
145
|
+
end
|
146
|
+
|
147
|
+
TRACK_BITMAP = {
|
148
|
+
1 => [0, 0x20],
|
149
|
+
2 => [0, 0x40],
|
150
|
+
|
151
|
+
3 => [1, 0x01],
|
152
|
+
4 => [1, 0x02],
|
153
|
+
5 => [1, 0x04],
|
154
|
+
6 => [1, 0x08],
|
155
|
+
7 => [1, 0x10],
|
156
|
+
8 => [1, 0x20],
|
157
|
+
9 => [1, 0x40],
|
158
|
+
|
159
|
+
10 => [2, 0x01],
|
160
|
+
11 => [2, 0x02],
|
161
|
+
12 => [2, 0x04],
|
162
|
+
13 => [2, 0x08],
|
163
|
+
14 => [2, 0x10],
|
164
|
+
15 => [2, 0x20],
|
165
|
+
16 => [2, 0x40],
|
166
|
+
|
167
|
+
17 => [3, 0x01],
|
168
|
+
18 => [3, 0x02],
|
169
|
+
19 => [3, 0x04],
|
170
|
+
20 => [3, 0x08],
|
171
|
+
21 => [3, 0x10],
|
172
|
+
22 => [3, 0x20],
|
173
|
+
23 => [3, 0x40],
|
174
|
+
|
175
|
+
24 => [4, 0x01],
|
176
|
+
}
|
177
|
+
|
178
|
+
def track_bitmap_bytes(tracks)
|
179
|
+
bytes = [0, 0, 0, 0, 0]
|
180
|
+
tracks.each do |track|
|
181
|
+
spec = TRACK_BITMAP[track] or raise "Track out of range: #{track.inspect}"
|
182
|
+
byte_idx, bits = *spec
|
183
|
+
bytes[byte_idx] += bits
|
184
|
+
end
|
185
|
+
bytes
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
data/midi-mmc.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'midi-mmc'
|
3
|
+
gem.version = '0.1'
|
4
|
+
gem.authors = 'John Labovitz'
|
5
|
+
gem.email = 'johnl@johnlabovitz.com'
|
6
|
+
gem.summary = %q{Perform MIDI Machine Control (MMC)}
|
7
|
+
gem.description = %q{Perform MIDI Machine Control (MMC).}
|
8
|
+
gem.homepage = 'https://github.com/jslabovitz/midi-mmc.git'
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($/)
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
|
+
gem.require_paths = ['lib']
|
14
|
+
|
15
|
+
gem.add_development_dependency 'bundler', '~> 2.5'
|
16
|
+
gem.add_development_dependency 'minitest', '~> 5.23'
|
17
|
+
gem.add_development_dependency 'minitest-power_assert', '~> 0.3'
|
18
|
+
gem.add_development_dependency 'rake', '~> 13.2'
|
19
|
+
gem.add_development_dependency 'unimidi', '~> 0.5'
|
20
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$VERBOSE = false
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/power_assert'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
7
|
+
require 'midi-mmc'
|
8
|
+
|
9
|
+
class MIDI::MMC::Test < Minitest::Test
|
10
|
+
|
11
|
+
def test_simple
|
12
|
+
mmc = MIDI::MMC.new
|
13
|
+
steps = [
|
14
|
+
[ proc { mmc.reset }, 'F0 7F 7F 06 0D F7' ],
|
15
|
+
[ proc { mmc.goto_zero }, 'F0 7F 7F 06 44 06 01 00 00 00 00 00 F7' ],
|
16
|
+
[ proc { mmc.arm_tracks(1) }, 'F0 7F 7F 06 40 06 4F 05 20 00 00 00 00 F7' ],
|
17
|
+
[ proc { mmc.stop }, 'F0 7F 7F 06 01 F7' ],
|
18
|
+
[ proc { mmc.arm_tracks(nil) }, 'F0 7F 7F 06 40 06 4F 05 00 00 00 00 00 F7' ],
|
19
|
+
[ proc { mmc.goto_zero }, 'F0 7F 7F 06 44 06 01 00 00 00 00 00 F7' ],
|
20
|
+
[ proc { mmc.play }, 'F0 7F 7F 06 02 F7' ],
|
21
|
+
]
|
22
|
+
steps.each do |step, expected_output|
|
23
|
+
output = mmc.message_to_str(step.call)
|
24
|
+
assert { output == expected_output }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/tools/example
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'midi-mmc'
|
4
|
+
require 'unimidi'
|
5
|
+
|
6
|
+
# set up
|
7
|
+
midi_output = UniMIDI::Output.gets
|
8
|
+
mmc = MIDI::MMC.new(output: midi_output)
|
9
|
+
mmc.reset
|
10
|
+
|
11
|
+
# record
|
12
|
+
mmc.goto_zero
|
13
|
+
mmc.arm_tracks(1)
|
14
|
+
# record something here
|
15
|
+
mmc.stop
|
16
|
+
mmc.arm_tracks(nil)
|
17
|
+
|
18
|
+
# play back
|
19
|
+
mmc.goto_zero
|
20
|
+
mmc.play
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# This script exercises the 'arm tracks' MMC command, in order to determine
|
5
|
+
# the proper track bitmap.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'unimidi'
|
9
|
+
|
10
|
+
require_relative '../lib/midi-mmc'
|
11
|
+
|
12
|
+
midi_output = UniMIDI::Output.gets
|
13
|
+
mmc = MIDI::MMC.new(output: midi_output, debug: true)
|
14
|
+
|
15
|
+
mmc.reset
|
16
|
+
|
17
|
+
begin
|
18
|
+
1.upto(24).each do |track|
|
19
|
+
mmc.write(nil)
|
20
|
+
msg = mmc.arm_tracks(track)
|
21
|
+
pp(track => msg)
|
22
|
+
gets
|
23
|
+
end
|
24
|
+
ensure
|
25
|
+
mmc.arm_tracks(nil)
|
26
|
+
mmc.reset
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: midi-mmc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Labovitz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-06-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.23'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.23'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-power_assert
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '13.2'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '13.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: unimidi
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.5'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.5'
|
83
|
+
description: Perform MIDI Machine Control (MMC).
|
84
|
+
email: johnl@johnlabovitz.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- ".gitignore"
|
90
|
+
- Gemfile
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- TODO.md
|
94
|
+
- lib/midi-mmc.rb
|
95
|
+
- midi-mmc.gemspec
|
96
|
+
- test/test.rb
|
97
|
+
- tools/example
|
98
|
+
- tools/test-track-arming
|
99
|
+
homepage: https://github.com/jslabovitz/midi-mmc.git
|
100
|
+
licenses: []
|
101
|
+
metadata: {}
|
102
|
+
post_install_message:
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubygems_version: 3.5.11
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: Perform MIDI Machine Control (MMC)
|
121
|
+
test_files:
|
122
|
+
- test/test.rb
|