mvlc 0.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
+ SHA1:
3
+ metadata.gz: 052f8904644cacbb9b81cc9c9e2e72eef3150cb8
4
+ data.tar.gz: 88428d64855c4252236e557dd4f68b3748f921a2
5
+ SHA512:
6
+ metadata.gz: e7e0ba57b4fb5f7e33435f6353187dd8ad57c0c282ea5a8fadc769bbfd03914154da75d0e704bc00318517fa3ca5ae970b1e3065a0faa534d9bee845d695cc5c
7
+ data.tar.gz: 7c36b2b70a1042f21dac9e9c1ee257a1249c104893b4b422908e564342fd7e10c061cd548bf81f7b08b531cd7a18faee0c0bad59a65addc83d2ad2857fa2cb33
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2017 Ari Russo
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # mmplayer
2
+
3
+ Control [VLC Media Player](https://en.wikipedia.org/wiki/VLC_media_player) with MIDI
4
+
5
+ This is a fork of [mmplayer](https://github.com/arirusso/mvlc), a gem to control MPlayer with MIDI
6
+
7
+ ## License
8
+
9
+ Apache 2.0, See LICENSE file
10
+
11
+ Copyright (c) 2017 [Ari Russo](http://arirusso.com)
data/lib/mvlc.rb ADDED
@@ -0,0 +1,34 @@
1
+ # MVLC
2
+ # Control VLC media player with MIDI
3
+ #
4
+ # (c)2017 Ari Russo
5
+ # Apache 2.0 License
6
+
7
+ # libs
8
+ require "forwardable"
9
+ require "midi-eye"
10
+ require "scale"
11
+ require "timeout"
12
+ require "unimidi"
13
+ require "vlc-client"
14
+
15
+ # modules
16
+ require "mvlc/helper/numbers"
17
+ require "mvlc/instructions"
18
+ require "mvlc/midi"
19
+ require "mvlc/player"
20
+ require "mvlc/thread"
21
+
22
+ # classes
23
+ require "mvlc/context"
24
+
25
+ module MVLC
26
+
27
+ VERSION = "0.0.1"
28
+
29
+ # Shortcut to Context constructor
30
+ def self.new(*args, &block)
31
+ Context.new(*args, &block)
32
+ end
33
+
34
+ end
@@ -0,0 +1,67 @@
1
+ module MVLC
2
+
3
+ # DSL context for interfacing an instance of MPlayer with MIDI
4
+ class Context
5
+
6
+ include Helper::Numbers
7
+ include Instructions::MIDI
8
+ include Instructions::Player
9
+
10
+ attr_reader :midi, :player
11
+
12
+ # @param [UniMIDI::Input, Array<UniMIDI::Input>] midi_input
13
+ # @param [Hash] options
14
+ # @option options [Integer] :midi_buffer_length Length of MIDI message buffer in seconds
15
+ # @option options [String] :mplayer_flags The command-line flags to invoke MPlayer with
16
+ # @option options [Integer] :receive_channel (also: :rx_channel) A MIDI channel to subscribe to. By default, responds to all
17
+ # @yield
18
+ def initialize(midi_input, options = {}, &block)
19
+ midi_options = {
20
+ :buffer_length => options[:midi_buffer_length],
21
+ :receive_channel => options[:receive_channel] || options[:rx_channel]
22
+ }
23
+ @midi = MIDI.new(midi_input, midi_options)
24
+ @player = Player.new(:flags => options[:mplayer_flags])
25
+ instance_eval(&block) if block_given?
26
+ end
27
+
28
+ # Start listening for MIDI
29
+ # Note that MPlayer will start when Context#play (aka Instructions::Player#play) is called
30
+ # @param [Hash] options
31
+ # @option options [Boolean] :background Whether to run in a background thread
32
+ # @return [Boolean]
33
+ def start(options = {})
34
+ @midi.start
35
+ begin
36
+ @playback_thread = playback_loop
37
+ @playback_thread.join unless !!options[:background]
38
+ rescue SystemExit, Interrupt
39
+ stop
40
+ end
41
+ true
42
+ end
43
+
44
+ # Stop the player
45
+ # @return [Boolean]
46
+ def stop
47
+ @midi.stop
48
+ @player.quit
49
+ @playback_thread.kill
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ # Main playback loop
56
+ def playback_loop
57
+ ::MVLC::Thread.new(:timeout => false) do
58
+ until @player.active?
59
+ sleep(0.1)
60
+ end
61
+ @player.playback_loop
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,26 @@
1
+ module MVLC
2
+
3
+ module Helper
4
+
5
+ # Number conversion
6
+ module Numbers
7
+
8
+ # Converts a percentage to a 7-bit int value eg 50 -> 0x40
9
+ # @param [Integer] num
10
+ # @return [Integer]
11
+ def to_midi_value(num)
12
+ Scale.transform(num).from(0..100).to(0..127.0).round
13
+ end
14
+
15
+ # Converts a MIDI 7-bit int value to a percentage eg 0x40 -> 50
16
+ # @param [Integer] num
17
+ # @return [Integer]
18
+ def to_percent(num)
19
+ Scale.transform(num).from(0..127).to(0..100.0).round
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,9 @@
1
+ require "mvlc/instructions/midi"
2
+ require "mvlc/instructions/player"
3
+
4
+ module MVLC
5
+
6
+ module Instructions
7
+ end
8
+
9
+ end
@@ -0,0 +1,46 @@
1
+ module MVLC
2
+
3
+ module Instructions
4
+
5
+ # Instructions for dealing with MIDI
6
+ module MIDI
7
+
8
+ # Set the MIDI channel to receive messages on
9
+ # @param [Integer, nil] num The channel number 0-15 or nil for all
10
+ def receive_channel(num)
11
+ @midi.channel = num
12
+ end
13
+ alias_method :rx_channel, :receive_channel
14
+
15
+ # Assign a callback for a given MIDI system command
16
+ # @param [String, Symbol] note A MIDI system command eg :start, :continue, :stop
17
+ # @param [Proc] callback The callback to execute when a matching message is received
18
+ # @return [Hash]
19
+ def on_system(command, &callback)
20
+ @midi.add_system_callback(command, &callback)
21
+ end
22
+ alias_method :system, :on_system
23
+
24
+ # Assign a callback for a given MIDI note
25
+ # @param [Integer, String] note A MIDI note eg 64 "F4" or nil for all
26
+ # @param [Proc] callback The callback to execute when a matching message is received
27
+ # @return [Hash]
28
+ def on_note(note = nil, &callback)
29
+ @midi.add_note_callback(note, &callback)
30
+ end
31
+ alias_method :note, :on_note
32
+
33
+ # Assign a callback for the given MIDI control change
34
+ # @param [Integer] index The MIDI control change index to assign the callback for or nil for all
35
+ # @param [Proc] callback The callback to execute when a matching message is received
36
+ # @return [Hash]
37
+ def on_cc(index = nil, &callback)
38
+ @midi.add_cc_callback(index, &callback)
39
+ end
40
+ alias_method :cc, :on_cc
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,50 @@
1
+ module MVLC
2
+
3
+ module Instructions
4
+
5
+ # Instructions dealing with the MPlayer
6
+ module Player
7
+
8
+ # Assign a callback for updating progress
9
+ # @param [Proc] callback The callback to execute when progress is updated
10
+ # @return [Hash]
11
+ def on_progress(&callback)
12
+ @player.add_progress_callback(&callback)
13
+ end
14
+ alias_method :progress, :on_progress
15
+
16
+ # Assign a callback for when a file finishes playback
17
+ # @param [Proc] callback The callback to execute when a file finishes playback
18
+ # @return [Hash]
19
+ def on_end_of_file(&callback)
20
+ @player.add_end_of_file_callback(&callback)
21
+ end
22
+ alias_method :end_of_file, :on_end_of_file
23
+ alias_method :eof, :on_end_of_file
24
+
25
+ private
26
+
27
+ # Add delegators to local player methods
28
+ def self.included(base)
29
+ base.send(:extend, Forwardable)
30
+ base.send(:def_delegators, :@player, :active?, :play)
31
+ end
32
+
33
+ # Add all of the MPlayer::Slave methods to the context as instructions
34
+ def method_missing(method, *args, &block)
35
+ if @player.respond_to?(method)
36
+ @player.send(method, *args, &block)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ # Add all of the MPlayer::Slave methods to the context as instructions
43
+ def respond_to_missing?(method, include_private = false)
44
+ super || @player.respond_to?(method)
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
data/lib/mvlc/midi.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "mvlc/midi/message_handler"
2
+ require "mvlc/midi/wrapper"
3
+
4
+ module MVLC
5
+
6
+ module MIDI
7
+
8
+ def self.new(*args)
9
+ ::MVLC::MIDI::Wrapper.new(*args)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,112 @@
1
+ module MVLC
2
+
3
+ module MIDI
4
+ # Directs what should happen when messages are received
5
+ class MessageHandler
6
+
7
+ attr_reader :callback
8
+
9
+ def initialize
10
+ @callback = {
11
+ :cc => {},
12
+ :note => {},
13
+ :system => {}
14
+ }
15
+ end
16
+
17
+ # Add a callback for a given MIDI message type
18
+ # @param [Symbol] type The MIDI message type (eg :note, :cc)
19
+ # @param [Integer, String] key The ID of the message eg note number/cc index
20
+ # @param [Proc] callback The callback to execute when the given MIDI command is received
21
+ # @return [Hash]
22
+ def add_callback(type, key, &callback)
23
+ @callback[type][key] = callback
24
+ @callback[type]
25
+ end
26
+
27
+ # Add a callback for a given MIDI note
28
+ # @param [Symbol] type The MIDI message type (eg :note, :cc)
29
+ # @param [Integer, String] note
30
+ # @param [Proc] callback The callback to execute when the given MIDI command is received
31
+ # @return [Hash]
32
+ def add_note_callback(note, &callback)
33
+ note = MIDIMessage::Constant.value(:note, note) if note.kind_of?(String)
34
+ add_callback(:note, note, &callback)
35
+ end
36
+
37
+ # Process a message for the given channel
38
+ # @param [Integer, nil] channel
39
+ # @param [MIDIMessage] message
40
+ # @return [Boolean, nil]
41
+ def process(channel, message)
42
+ case message
43
+ when MIDIMessage::SystemCommon, MIDIMessage::SystemExclusive, MIDIMessage::SystemRealtime then system_message(message)
44
+ else
45
+ channel_message(channel, message)
46
+ end
47
+ end
48
+
49
+ # Find and call a note received callback if it exists
50
+ # @param [MIDIMessage] message
51
+ # @return [Boolean, nil]
52
+ def note_message(message)
53
+ call_callback(:note, message.note, message.velocity) |
54
+ call_catch_all_callback(:note, message)
55
+ end
56
+
57
+ # Find and call a cc received callback if it exists
58
+ # @param [MIDIMessage] message
59
+ # @return [Boolean, nil]
60
+ def cc_message(message)
61
+ call_callback(:cc, message.index, message.value) |
62
+ call_catch_all_callback(:cc, message)
63
+ end
64
+
65
+ # Find and call a system message callback if it exists
66
+ # @param [MIDIMessage] message
67
+ # @return [Boolean, nil]
68
+ def system_message(message)
69
+ name = message.name.downcase.to_sym
70
+ call_callback(:system, name)
71
+ end
72
+
73
+ # Find and call a channel message callback if it exists for the given message and channel
74
+ # @param [Integer, nil] channel
75
+ # @param [MIDIMessage] message
76
+ # @return [Boolean, nil]
77
+ def channel_message(channel, message)
78
+ if channel.nil? || message.channel == channel
79
+ case message
80
+ when MIDIMessage::NoteOn then note_message(message)
81
+ when MIDIMessage::ControlChange then cc_message(message)
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Execute the catch-all callback for the given type if it exists
89
+ # @param [Symbol] type
90
+ # @param [MIDIMessage] message
91
+ # @return [Boolean]
92
+ def call_catch_all_callback(type, message)
93
+ call_callback(type, nil, message)
94
+ end
95
+
96
+ # Execute the callback for the given type and key and pass it the given args
97
+ # @param [Symbol] type
98
+ # @param [Object] key
99
+ # @param [*Object] arguments
100
+ # @return [Boolean]
101
+ def call_callback(type, key, *arguments)
102
+ unless (callback = @callback[type][key]).nil?
103
+ callback.call(*arguments)
104
+ true
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,115 @@
1
+ module MVLC
2
+
3
+ module MIDI
4
+
5
+ # Wrapper for MIDI functionality
6
+ class Wrapper
7
+
8
+ attr_reader :channel, :listener, :message_handler
9
+
10
+ # @param [UniMIDI::Input, Array<UniMIDI::Input>] input
11
+ # @param [Hash] options
12
+ # @option options [Integer] :buffer_length Length of MIDI message buffer in seconds
13
+ # @option options [Integer] :receive_channel A MIDI channel to subscribe to. By default, responds to all
14
+ def initialize(input, options = {})
15
+ @buffer_length = options[:buffer_length]
16
+ @channel = options[:receive_channel]
17
+
18
+ @message_handler = MessageHandler.new
19
+ @listener = MIDIEye::Listener.new(input)
20
+ end
21
+
22
+ # Add a callback for a given MIDI system message
23
+ # @param [String, Symbol] command The MIDI system command eg :start, :stop
24
+ # @param [Proc] callback The callback to execute when the given MIDI command is received
25
+ # @return [Hash]
26
+ def add_system_callback(command, &callback)
27
+ @message_handler.add_callback(:system, command, &callback)
28
+ end
29
+
30
+ # Add a callback for a given MIDI note
31
+ # @param [Integer, String, nil] note The MIDI note to add a callback for eg 64 "E4"
32
+ # @param [Proc] callback The callback to execute when the given MIDI note is received
33
+ # @return [Hash]
34
+ def add_note_callback(note, &callback)
35
+ @message_handler.add_note_callback(note, &callback)
36
+ end
37
+
38
+ # Add a callback for a given MIDI control change
39
+ # @param [Integer, nil] index The MIDI control change index to add a callback for eg 10
40
+ # @param [Proc] callback The callback to execute when the given MIDI control change is received
41
+ # @return [Hash]
42
+ def add_cc_callback(index, &callback)
43
+ @message_handler.add_callback(:cc, index, &callback)
44
+ end
45
+
46
+ # Stop the MIDI listener
47
+ # @return [Boolean]
48
+ def stop
49
+ @listener.stop
50
+ end
51
+
52
+ # Change the subscribed MIDI channel (or nil for all)
53
+ # @param [Integer, nil] channel
54
+ # @return [Integer, nil]
55
+ def channel=(channel)
56
+ @listener.event.clear
57
+ @channel = channel
58
+ initialize_listener if @listener.running?
59
+ @channel
60
+ end
61
+
62
+ # Start the MIDI listener
63
+ # @return [Boolean]
64
+ def start
65
+ initialize_listener
66
+ @start_time = Time.now.to_i
67
+ @listener.start(:background => true)
68
+ true
69
+ end
70
+
71
+ # Whether the player is subscribed to all channels
72
+ # @return [Boolean]
73
+ def omni?
74
+ @channel.nil?
75
+ end
76
+
77
+ private
78
+
79
+ # Elapsed time since start in seconds
80
+ # @return [Integer]
81
+ def now
82
+ Time.now.to_i - @start_time
83
+ end
84
+
85
+ # Should the given MIDI event be processed or thrown away?
86
+ # @param [Hash] event
87
+ # @return [Boolean]
88
+ def process_event?(event)
89
+ @buffer_length.nil? ||
90
+ event[:timestamp].nil? ||
91
+ event[:timestamp].to_i >= now - @buffer_length
92
+ end
93
+
94
+ # Handle a new MIDI event received
95
+ # @param [Hash] event
96
+ # @return [Hash]
97
+ def handle_new_event(event)
98
+ if process_event?(event)
99
+ message = event[:message]
100
+ @message_handler.process(@channel, message)
101
+ event
102
+ end
103
+ end
104
+
105
+ # Populate the MIDI listener callback
106
+ # @return [MIDIEye::Listener]
107
+ def initialize_listener
108
+ @listener.on_message { |event| handle_new_event(event) }
109
+ @listener
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end