Skip to content

Commit

Permalink
Doc cleanup into wiki
Browse files Browse the repository at this point in the history
  • Loading branch information
jmkao committed Dec 5, 2020
1 parent 61dd268 commit 1ad5312
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 179 deletions.
186 changes: 7 additions & 179 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,188 +1,16 @@
# OBS MIDI UI (obs-midi-ui)

OBS Stream Festival Control UI with MIDI mappings for an RTMP Hub.
OBS Stream Festival Control UI with MIDI mappings for an RTMP muxing hub.

Uses Electron for cross-platform WebMIDI device support.
## Documentation

## Install the dependencies
```bash
npm install
```
Consult the [wiki](https://github.com/jmkao/obs-midi-ui/wiki)

### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev -m electron
```
## Installation

### Lint the files
```bash
npm run lint
```
Download the relevant Electron package from the [releases page](https://github.com/jmkao/obs-midi-ui/releases)

### Build the app for production
```bash
quasar build
```
Visit the [online SPA](https://aniclover.com/obs-html-control/) in your browser.

## MIDI Input and Output Dispatcher
MIDI input and (optional) output devices can be selected in the dropdown.
Please read the [wiki](https://github.com/jmkao/obs-midi-ui/wiki) for the nuances between the difference versions.

Buttons and faders respond to events, which will be sent based on the return value of user-entered JavaScript snippets. There are two snippets, one for input handling (controls originating from a MIDI device) and one for output handling (commands sent to a MIDI device). The snippets will be saved in Local Storage for use between application restarts, but there is otherwise no open or save behavior other than via copy-paste.
### Simple Fader MIDI Input Recipe
Set FADER_CHANNEL to the MIDI channel your faders are on, and set 4 values into FADER_CCS for the 4 control numbers matching the faders to be matched to the on-screen faders.

```
// Input: midiData
// Output: [ eventName, eventData ]
const FADER_CHANNEL = 1
const FADER_CCS = [11, 12, 13, 14]
var i = FADER_CCS.indexOf(midiData.control)
if (i > -1) {
return ['midiFader' + i, {
index: i,
channel: midiData.channel,
control: midiData.control,
value: midiData.value
}]
}
return [null, null]
```

### Faders and Buttons MIDI Input Recipe
Buttons are mapped to the DDJ-XP1, which has each side fully mapped to buttons 0 to 127 on different channels, thus we use simple index logic to dispatch a button to the corresponding scene or source. A controller with a non-contiguous button mapping (like the Launchpad) would need more row-wise logic to account for the discontinuity in notes.

```
// Input: midiData {channel, control, value} or {channel, note}
// Output: [ eventName, eventData ]
const FADER_CHANNEL = 1;
const FADER_CONTROLS = [11, 12, 13, 14];
const SCENE_BTN_CHANNEL = 8;
const SCENE_ZERO_NOTE = 0;
const SCENE_MAX_NOTE = 100;
const SOURCE_BTN_CHANNEL = 10;
const SOURCE_ZERO_NOTE = 0;
const SOURCE_MAX_NOTE = 100;
const TIMETABLE_FWD_CHANNEL = 1;
const TIMETABLE_FWD_NOTE = 121;
const TIMETABLE_REV_CHANNEL = 1;
const TIMETABLE_REV_NOTE = 10;
const TRANSITION_CHANNEL = 7;
const TRANSITION_NOTE = 70;
var eventName = null;
var eventData = null;
if (midiData.channel === FADER_CHANNEL) {
var i = FADER_CONTROLS.indexOf(midiData.control);
if (i > -1) {
eventName = 'midiFader'+i;
eventData = midiData;
eventData.index = i;
}
}
if (midiData.channel === SCENE_BTN_CHANNEL) {
if (midiData.note >= SCENE_ZERO_NOTE && midiData.note <= SCENE_MAX_NOTE) {
eventName = 'scene' + (midiData.note - SCENE_ZERO_NOTE);
eventData = midiData;
console.log("Midi Emit: "+eventName);
}
}
if (midiData.channel === SOURCE_BTN_CHANNEL) {
if (midiData.note >= SOURCE_ZERO_NOTE && midiData.note <= SOURCE_MAX_NOTE) {
eventName = 'source' + (midiData.note - SOURCE_ZERO_NOTE);
eventData = midiData;
console.log("Midi Emit: "+eventName);
}
}
if (midiData.channel === TIMETABLE_FWD_CHANNEL) {
if (midiData.note === TIMETABLE_FWD_NOTE) {
eventName = 'timetable';
eventData = 'advance';
console.log("Midi Emit: "+eventName);
}
}
if (midiData.channel === TIMETABLE_REV_CHANNEL) {
if (midiData.note === TIMETABLE_REV_NOTE) {
eventName = 'timetable';
eventData = 'retract';
console.log("Midi Emit: "+eventName);
}
}
if (midiData.channel === TRANSITION_CHANNEL) {
if (midiData.note === TRANSITION_NOTE) {
eventName = 'transition';
console.log("Midi Emit: "+eventName);
}
}
return [eventName, eventData];
```

### Button Lights MIDI Output Recipe
Lights are mapped to the DDJ-XP1 pads, which will set colors (not exactly the same colors for clarity) on pads corresponding to the source and scene buttons. The lights are set on the exact same notes and channel as the corresponding inputs, and are contiguous from 0 to 127 for each group of pads, so there is simple index logic. A controllers with discontinuous lights (like the Launchpad) would require more complex logic.

```
// Input 1: eventData { name, index, color }
// Input 2: midiOutput
// Output: none
const SCENE_LIGHT_CHANNEL = 8;
const SCENE_LIGHT_ZERO_NOTE = 0;
const SOURCE_LIGHT_CHANNEL = 10;
const SOURCE_LIGHT_ZERO_NOTE = 0;
console.log(eventData);
if (eventData.name === 'scene') {
const index = eventData.index
const color = eventData.color
// for (index = 0; index < 127; index ++)
// midiOutput.playNote(index+SCENE_LIGHT_ZERO_NOTE, SCENE_LIGHT_CHANNEL, {velocity: index, rawVelocity: true})
switch (color) {
case 'white':
midiOutput.playNote(index+SCENE_LIGHT_ZERO_NOTE, SCENE_LIGHT_CHANNEL, {velocity: 81, rawVelocity: true})
break;
case 'yellow':
midiOutput.playNote(index+SCENE_LIGHT_ZERO_NOTE, SCENE_LIGHT_CHANNEL, {velocity: 24, rawVelocity: true})
break;
case 'red':
midiOutput.playNote(index+SCENE_LIGHT_ZERO_NOTE, SCENE_LIGHT_CHANNEL, {velocity: 48, rawVelocity: true})
break;
}
} else if (eventData.name === 'source') {
const index = eventData.index;
const color = eventData.color;
switch (color) {
case 'white':
midiOutput.playNote(index+SOURCE_LIGHT_ZERO_NOTE, SOURCE_LIGHT_CHANNEL, {velocity: 0, rawVelocity: true})
break;
case 'yellow':
midiOutput.playNote(index+SOURCE_LIGHT_ZERO_NOTE, SOURCE_LIGHT_CHANNEL, {velocity: 24, rawVelocity: true})
break;
case 'red':
midiOutput.playNote(index+SOURCE_LIGHT_ZERO_NOTE, SOURCE_LIGHT_CHANNEL, {velocity: 48, rawVelocity: true})
break;
}
} else if (eventData.name === 'init') {
for (index = 0; index < 128; index ++) {
midiOutput.playNote(index+SCENE_LIGHT_ZERO_NOTE, SCENE_LIGHT_CHANNEL, {velocity: 63, rawVelocity: true})
midiOutput.playNote(index+SOURCE_LIGHT_ZERO_NOTE, SOURCE_LIGHT_CHANNEL, {velocity: 63, rawVelocity: true})
// midiOutput.playNote(index+SOURCE_LIGHT_ZERO_NOTE, SOURCE_LIGHT_CHANNEL, {velocity: index, rawVelocity: true})
}
}
```
2 changes: 2 additions & 0 deletions src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export default {
},
disconnectOBS () {
this.obs.disconnect()
this.obsConnectionPending = false
},
onOBSConnected () {
this.obsBtnLabel = 'Disconnect'
Expand Down Expand Up @@ -214,6 +215,7 @@ export default {
this.obsAddressLabel = err.description
}
this.obsConnectionPending = false
console.error('OBS Websocket Error: ', err)
},
findSceneIndex (sceneName) {
Expand Down

0 comments on commit 1ad5312

Please sign in to comment.