mvlc 0.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
+ 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