midi-mmc 0.1

Sign up to get free protection for your applications and to get access to all the features.
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