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 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
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .DS_Store
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/images
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ Bundler.require
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/*test.rb']
7
+ end
8
+
9
+ task :default => :test
data/TODO.md ADDED
@@ -0,0 +1,4 @@
1
+ # TODO
2
+
3
+ - Implement other MMC commands.
4
+ - Implement monitoring/reading, to get responses.
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