Skip to content
This repository has been archived by the owner on Apr 5, 2023. It is now read-only.

Commit

Permalink
Add multi-device support and device management features; add requirem…
Browse files Browse the repository at this point in the history
…ents.txt; update readme
  • Loading branch information
lebaston100 committed Sep 14, 2019
1 parent 8af63e9 commit d1a646b
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 178 deletions.
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Midi OBS what???
This script let's you use a MIDI controller like the Novation Launchpad, Ableton Push, Akai LPD or the Korg nanoPAD to switch scenes, start/stop recording/streaming, control volume/delay/transition time and more in [obs-studio](https://github.com/obsproject/obs-studio).
This script let's you use one or multiple MIDI controller like the Novation Launchpad, Ableton Push, Akai LPD or the Korg nanoPAD to switch scenes, start/stop recording/streaming, control volume/delay/transition time and more in [obs-studio](https://github.com/obsproject/obs-studio).

**Important**: If you upgrade to this version, your old configuration file is no longer usable and you have to re-create the entire mapping!! This is because of the new multi-device feature. (If this is a huge problem for you let me know and i'll tell you the steps to migrate by hand)

## Requirements

Expand All @@ -14,30 +16,35 @@ This script let's you use a MIDI controller like the Novation Launchpad, Ableton

## Setup Part 1

- Install Python 3.x.x
- Install Python 3.x.x (whatever the latest version is)
- On Windows: Make sure you trick "Add Python 3.x to PATH" in the setup
- Make sure you also install pip
- For instructions how to install TinyDB click [here](https://tinydb.readthedocs.io/en/latest/getting-started.html#installing-tinydb)
- For instructions how to install mido and python-rtmidi click [here](https://github.com/olemb/mido#installing)
- For instructions how to install websocket-client click [here](https://github.com/websocket-client/websocket-client#installation)

If you want to install all packages in one go, run "pip install -r requirements.txt"

## Setup Part 2 OBS Websocket

- Download the installer and run it
- Start OBS, open the "Tools" menu and select "websocket server settings"
- Make sure that "Enable Websocket server" is checked, "Server Port" is 4444 and "Enable authentification" is unchecked
- Make sure that "Enable Websocket server" is checked, "Server Port" is 4444, "Enable authentification" is unchecked and "Enable System Tray Alerts" is unchecked(trust me, you don't want that on)

## Setup Part 3

- [Download the repository](https://github.com/lebaston100/MIDItoOBS/archive/master.zip) or clone it
- Connect your MIDI controller
- Launch obs-studio
- Launch the setup.py (Try double click or the "Run Setup.bat" if you are on Windows)
- You will get a list of available MIDI devices. Type the number you want to select and press Enter
- If you run the setup for the first time and have no yet setup a device yet, it will automatically start the device configuration:
- You will get a list of available MIDI devices. Type the number you want to select and press Enter
- You will be asked if you want to ad another device.
- If you only have a single device choose 2 and press enter, otherwise select 1 and you will get a list with remaining devices.
- Now you will be asked to press a button or move a fader on your MIDI controller, do that
- If your midi controller sends control change messages, you will also be asked for the type of the input(fader or button)
- Select an action from the list and press enter. The names represent the request-type in obs-websocket
- Depending on the action, you will also be asked for the scene and source name (selecting always works by typing in the number and pressing enter)
- Depending on the action, you will also be asked for the scene and source name (selecting always works by typing in the number and pressing enter). If no source of that type is available and you are promted to "select 0--1:" then you know that is no such source available in obs and the script will crash trying to select anything. Just add the required object and restart the setup script in this case. (This is already on the todo list for a further update)
- Available for buttons:
- SetCurrentScene: Switches to the scene
- SetPreviewScene: Puts a scene into preview when in studio mode
Expand Down Expand Up @@ -66,15 +73,25 @@ This script let's you use a MIDI controller like the Novation Launchpad, Ableton
- ReloadBrowserSource: Reloads a BrowserSource

- Available for faders
- SetVolume: Sets the volume of a source
- SetVolume: Sets the volume of a source (unlike other solutions this will actually make the fader move in a visual linear way inside obs(Like a % slider))
- SetSyncOffset: Sets the sync offset of a source(in ns)
- SetSourcePosition: Sets the x or y position of a source (in px)
- SetSourceRotation: Sets the rotation of a source (in degree)
- SetSourceScale: Sets the scale for x/y or both of a source (For the scaling 1 = original scale)
- SetTransitionDuration: Sets the length of the currently selected transistion if supported(fade)(in ms)
- Now you can either setup another button/fader by repeating the steps above(except starting the script again) or just close the window to exit the configuration

For a detailed description see the [obs-websocket protocol documentation](https://github.com/Palakis/obs-websocket/blob/master/docs/generated/protocol.md)
For a detailed description of most of the commands see the [obs-websocket protocol documentation](https://github.com/Palakis/obs-websocket/blob/master/docs/generated/protocol.md)

### Device Management

If you run the setup another time after the inital configuration you will get a dialog at startup where you can select if you want to go to the device management (1) or just continue adding new button/fader assignments with the already configured devices.

If you select 1 you have a few options:
- 1: Delete all devices from the database without removing their mapping. This does exactly that and be warned, will cause a device mixup when you add more devices later. You'll be better of using option 2
- 2: Remove a single device and their assignments.
- 3: Add a new device. This allows you do add more devices.
- 4: Skip device configuration. This exits the device management without changing anything and continues with the assignment dialog.

### Understanding input scaling

Expand All @@ -94,22 +111,22 @@ You can assign unlimited different actions to the same button. This requires edi

- Setup the functions as described above on different buttons
- Now stop the setup.py and open the config(config.json) with a text editor.
- Change the "msgNoC" value of the buttons you want to combine to the value of the button you want to use.
- Change the "msgNoC" value of the buttons you want to combine to the value of the button you want to use. Make sure you have the entry with the right device ID.
- Here are some pictures for better understanding: [Step 1](https://cdn.lebaston100.de/git/midiobs/miditoobs_1.png) [Step 2](https://cdn.lebaston100.de/git/midiobs/miditoobs_2.png)
- Now save and close the config file
- Start main.py and verify that it works

## Using it (!Very important!)

- If you're a first time use make sure to follow setup steps 1-3
- You can launch setup.py anytime(as long as main.py is not running) to change the configuration of a single button/fader without reconfiguring the whole controller even if you get asked for the controller every time
- You can launch setup.py anytime(as long as main.py is not running) to change the configuration of a single button/fader without reconfiguring the whole controller.
- Always make sure that obs is running before launching any of the scripts
- Launch the main.py file (Try double click or the "Run Main.bat" if you are on Windows)
- The console gives you information when it successfully connects to OBS
- Also, if there is an error it will be printed out(If you ignore case sensitive fields or the scene doesn't exist)
- Third, it prints out a message every time you press a button that is setup
- Now just leave it running in the background
- To stop the program simply close the window (on linux kill the task or CTRL + C)
- To stop the program simply close the window (or CTRL + C)

## Troubleshooting

Expand All @@ -127,12 +144,16 @@ Special thanks to:
- [asquelt](https://github.com/asquelt) (making it work in python2)
- [Alex-Dash](https://github.com/Alex-Dash) (make the volume control linear)
- [imcrazytwkr](https://github.com/imcrazytwkr) (completly refactoring the main.py)
- [juliscrazy](https://github.com/juliscrazy) (fix typo in readme)


### Tested on/with:

- Win 10 Build 18362
- Python 3.7.0:1bf9cc5093
- obs-studio 23.2.1
- obs-websocket 4.6.1
- KORG nanoPAD
- Hercules DJ Control MP3

Let me know if you had success with your device.
78 changes: 49 additions & 29 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
from sys import exit, stdout
from os import path

import logging
import json
import mido
import logging, json, mido

TEMPLATES = {
"ToggleSourceVisibility": """{
Expand Down Expand Up @@ -43,6 +41,29 @@ def get_logger(name, level=logging.INFO):
logger.addHandler(std_output)
return logger

class DeviceHandler:
def __init__(self, device, deviceid):
self.log = get_logger("midi_to_obs_device")
self._id = deviceid
self._devicename = device["devicename"]
self._port = 0

try:
self.log.debug("Attempting to open midi port `%s`" % self._devicename)
self._port = mido.open_input(name=self._devicename, callback=self.callback)
except:
self.log.critical("\nCould not open", self._devicename)
self.log.critical("The midi device is used by another application, isnot connected or has a different name.")
self.log.critical("Please plug the device in or run setup.py again and restart this script\n")
# EIO 5 (Input/output error)
exit(5)

def callback(self, msg):
handler.handle_midi_input(msg, self._id, self._devicename)

def close(self):
self._port_close()

class MidiHandler:
# Initializes the handler class
def __init__(self, config_path="config.json", ws_server="localhost", ws_port=4444):
Expand All @@ -52,32 +73,29 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444
# Internal service variables
self._action_buffer = []
self._action_counter = 2
self._portobjects = []

self.log.debug("Trying to load config file from %s" % config_path)
self.db = TinyDB(config_path, indent=4)
result = self.db.search(Query().type.exists())
self.database = TinyDB(config_path, indent=4)
self.db = self.database.table("keys", cache_size=0)
self.devdb = self.database.table("devices", cache_size=0)

result = self.devdb.all()
if not result:
self.log.critical("Config file %s doesn't exist or is damaged" % config_path)
# ENOENT (No such file or directory)
exit(2)

self.log.info("Successfully parsed config file")
port_name = str(result[0]["value"])

self.log.debug("Retrieved MIDI port name `%s`" % port_name)
del result
self.log.debug("Retrieved MIDI port name(s) `%s`" % result)
#create new class with handler and open from there, just create new instances
for device in result:
self._portobjects.append(DeviceHandler(device, device.doc_id))

try:
self.log.debug("Attempting to open midi port")
self.port = mido.open_input(name=port_name, callback=self.handle_midi_input)
except:
self.log.critical("The midi device %s is not connected or has a different name" % port_name)
self.log.critical("Please plug the device in or run setup.py again and restart this script")
# EIO 5 (Input/output error)
exit(5)
del result

self.log.info("Successfully initialized midi port `%s`" % port_name)
del port_name
self.log.info("Successfully initialized midi port(s)")

# Properly setting up a Websocket client
self.log.debug("Attempting to connect to OBS using websocket protocol")
Expand All @@ -87,24 +105,24 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444
self.obs_socket.on_close = self.handle_obs_close
self.obs_socket.on_open = self.handle_obs_open

def handle_midi_input(self, message):
self.log.debug("Received %s message from midi: %s" % (message.type, message))
def handle_midi_input(self, message, deviceID, deviceName):
self.log.debug("Received", message, "from device", deviceID, "/", deviceName)

if message.type == "note_on":
return self.handle_midi_button(message.type, message.note)
return self.handle_midi_button(deviceID, message.type, message.note)

# `program_change` messages can be only used as regular buttons since
# they have no extra value, unlike faders (`control_change`)
if message.type == "program_change":
return self.handle_midi_button(message.type, message.program)
return self.handle_midi_button(deviceID, message.type, message.program)

if message.type == "control_change":
return self.handle_midi_fader(message.control, message.value)
return self.handle_midi_fader(deviceID, message.control, message.value)


def handle_midi_button(self, type, note):
def handle_midi_button(self, deviceID, type, note):
query = Query()
results = self.db.search((query.msg_type == type) & (query.msgNoC == note))
results = self.db.search((query.msg_type == type) & (query.msgNoC == note) & (query.deviceID == deviceID))

if not results:
self.log.debug("Cound not find action for note %s", note)
Expand All @@ -114,9 +132,9 @@ def handle_midi_button(self, type, note):
if self.send_action(result):
pass

def handle_midi_fader(self, control, value):
def handle_midi_fader(self, deviceID, control, value):
query = Query()
results = self.db.search((query.msg_type == "control_change") & (query.msgNoC == control))
results = self.db.search((query.msg_type == "control_change") & (query.msgNoC == control) & (query.deviceID == deviceID))

if not results:
self.log.debug("Cound not find action for fader %s", control)
Expand Down Expand Up @@ -237,8 +255,10 @@ def start(self):
self.obs_socket.run_forever()

def close(self, teardown=False):
self.log.debug("Attempting to close midi port")
self.port.close()
self.log.debug("Attempting to close midi port(s)")
result = self.devdb.all()
for device in result:
device.close()

self.log.info("Midi connection has been closed successfully")

Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mido==1.2.9
tinydb==3.14.1
websocket_client==0.56.0
python-rtmidi==1.3.0
Loading

0 comments on commit d1a646b

Please sign in to comment.