Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Mirco Lukas committed Jun 22, 2018
1 parent 5c965bc commit 374c73c
Show file tree
Hide file tree
Showing 9 changed files with 604 additions and 2 deletions.
2 changes: 0 additions & 2 deletions README.md

This file was deleted.

27 changes: 27 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Installation:

Move all JavaScript files into the controller folder of Bitwig like i.e. "Bitwig Studio/Controller Scripts/Steinberg/CC121".
The files are:

- SteinbergCC121.control.js (starting point of the script)
- SteinbergCC121.ai.js (AI knob, zoom, jog)
- SteinbergCC121.channel.js (channel functions)
- SteinbergCC121.device.js (device functions)
- SteinbergCC121.function.js (function buttons, shortcuts)
- SteinbergCC121.transport.js (transport functions)

When using Linux, the script should automaticly find and subscribe the controller. It can be necessary for other operating systems to manually choose the controller script and select the right MIDI-IN and MIDI-OUT devices in the Bitwig settings.

Information:

Script was tested in Linux Mint 18 64-bit with Bitwig 2 and uses API version 5. There is an explanation what each knob and button does (assignment.pdf).

Known issues:

When changing a device parameter on parameter page 1, the LEDs of the parameter buttons (parameters 5-8 on page 2) light up like the parameters 5-8 on page 1. This seems inevitable with the current API due to the face that one can only set a callback for a parameter of the parameter view. When the view changes its page, the callback is activated. Moveover, increasing the amount of parameters per page seems not to work for values greater than 8, so that page scrolling is necessary.
However, the button function itself works fine - it is only the LEDs.

About:

Author: Philipp Winniewski
Date: 22/06/2018
51 changes: 51 additions & 0 deletions SteinbergCC121.ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Keep selected AI knob function
var isLockActive = false;

// Factor to speed up scrolling by with job button
var JOGFACTOR_POSITION = 4;

function initAi() {
// Creating views
application = host.createApplication();
transport = host.createTransport();

return onMidiAi;
}

function onMidiAi(status, data1, data2) {
// Check if message is MIDI CC from AI knob
if (isChannelController(status) && data1 == CC.AIKNOB) {
var knob_value = midiCcValueToInteger(data2);
if (isLockActive) {
if (knob_value < 0) {
application.zoomOut();
} else {
application.zoomIn();
}
} else {
if (isJogActive) {
knob_value = JOGFACTOR_POSITION * knob_value;
}
transport.incPosition(knob_value, true);
}
} else if (isNoteOn(status) && data2 > 0) {
switch (data1) {
case NOTE.LOCK:
isLockActive = !isLockActive;
if (isLockActive) {
sendMidi(144, NOTE.LOCK, LIGHT_ON);
} else {
sendMidi(144, NOTE.LOCK, LIGHT_OFF);
}
break;
case NOTE.JOG:
isJogActive = !isJogActive;
if (isJogActive) {
sendMidi(144, NOTE.JOG, LIGHT_ON);
} else {
sendMidi(144, NOTE.JOG, LIGHT_OFF);
}
break;
}
}
}
117 changes: 117 additions & 0 deletions SteinbergCC121.channel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Factor to speed up panning by with job button
var JOGFACTOR_PANNING = 4;

function initChannel() {
// Creating views
trackBank = host.createMainTrackBank(1, 1, 1);
arrangerCursorTrack = host.createArrangerCursorTrack(1, 1);
trackBank.followCursorTrack(arrangerCursorTrack);
transport = host.createTransport();
application = host.createApplication();

// Publishing observers
trackBank.getChannel(0).mute().addValueObserver(function(isMuted) {
if (isMuted) {
sendMidi(144, NOTE.MUTE, LIGHT_ON);
} else {
sendMidi(144, NOTE.MUTE, LIGHT_OFF);
}
});

trackBank.getChannel(0).solo().addValueObserver(function(isSolo) {
if (isSolo) {
sendMidi(144, NOTE.SOLO, LIGHT_ON);
} else {
sendMidi(144, NOTE.SOLO, LIGHT_OFF);
}
});

transport.isArrangerAutomationWriteEnabled().addValueObserver(function(isAutomationWrite) {
if (isAutomationWrite) {
sendMidi(144, NOTE.AUTOMATIONWRITE, LIGHT_ON);
} else {
sendMidi(144, NOTE.AUTOMATIONWRITE, LIGHT_OFF);
}
});

transport.isAutomationOverrideActive().addValueObserver(function(isAutomationOverride) {
if (isAutomationOverride) {
sendMidi(144, NOTE.AUTOMATIONREAD, LIGHT_ON);
} else {
sendMidi(144, NOTE.AUTOMATIONREAD, LIGHT_OFF);
}
});

trackBank.getTrack(0).arm().addValueObserver(function(isArmed) {
if (isArmed) {
sendMidi(144, NOTE.RECORDARM, LIGHT_ON);
} else {
sendMidi(144, NOTE.RECORDARM, LIGHT_OFF);
}
});

trackBank.getTrack(0).monitor().addValueObserver(function(isMonitored) {
if (isMonitored) {
sendMidi(144, NOTE.INPUTMONITOR, LIGHT_ON);
} else {
sendMidi(144, NOTE.INPUTMONITOR, LIGHT_OFF);
}
});

trackBank.getChannel(0).volume().value().addValueObserver(function(volumeValue) {
var LSB = (volumeValue * 16383) % 128;
var MSB = (volumeValue * 16383) / 128;
sendMidi(224, LSB, MSB);
});

return onMidiChannel;
}

function onMidiChannel(status, data1, data2) {
// Check if message is MIDI CC
if (isChannelController(status)) {
var changeValue = midiCcValueToInteger(data2);
if (isJogActive)
changeValue = JOGFACTOR_PANNING * changeValue;
switch (data1) {
case CC.PAN:
trackBank.getChannel(0).pan().value().inc(changeValue, ENCODER_RESOLUTION);
break;
}
} else if (isNoteOn(status) && data2 > 0) {
switch (data1) {
case NOTE.CHANNELSELECTRIGHT:
arrangerCursorTrack.selectNext();
break;
case NOTE.CHANNELSELECTLEFT:
arrangerCursorTrack.selectPrevious();
break;
case NOTE.MUTE:
trackBank.getChannel(0).mute().toggle();
break;
case NOTE.SOLO:
trackBank.getChannel(0).solo().toggle();
break;
case NOTE.AUTOMATIONWRITE:
transport.isArrangerAutomationWriteEnabled().toggle();
break;
case NOTE.AUTOMATIONREAD:
transport.resetAutomationOverrides();
break;
case NOTE.RECORDARM:
trackBank.getTrack(0).arm().toggle();
break;
case NOTE.INPUTMONITOR:
trackBank.getTrack(0).monitor().toggle();
break;
case NOTE.EBUTTON:
application.toggleInspector();
break;
case NOTE.OPENVSTI:
application.toggleDevices();
break;
}
} else if (status == 224) {
trackBank.getChannel(0).volume().value().set(data2 * 128 + data1, 16383);
}
}
127 changes: 127 additions & 0 deletions SteinbergCC121.control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Note: blinking "READY LED" can not be avoided, CC121 needs F043103E150001F7 every second...
loadAPI(5);
load("SteinbergCC121.transport.js");
load("SteinbergCC121.function.js");
load("SteinbergCC121.ai.js");
load("SteinbergCC121.channel.js");
load("SteinbergCC121.device.js");

// Constants and magic numbers

// Encoder resolution in steps
var ENCODER_RESOLUTION = 256;

// Controller does not send absolut values but relative changes
var ENCODER_POS_LO = 1; // + 1
var ENCODER_POS_HI = 15; // + 15
var ENCODER_NEG_LO = 65; // - 1
var ENCODER_NEG_HI = 79; // - 15

// Data values for button LEDs
var LIGHT_ON = 127;
var LIGHT_OFF = 0;

// Midi note number for buttons
var NOTE = {
RECORDARM: 0x00,
SOLO: 0x08,
MUTE: 0x10,
CHANNELSELECTLEFT: 0x30,
CHANNELSELECTRIGHT: 0x31,
EBUTTON: 0x33,
FUNCTION1: 0x36,
FUNCTION2: 0x37,
FUNCTION3: 0x38,
FUNCTION4: 0x39,
VALUEENCODERBUTTON: 0x3A,
FOOTSWITCH: 0x3B,
AUTOMATIONREAD: 0x4A,
AUTOMATIONWRITE: 0x4B,
LOOP: 0x56,
TOSTART: 0x58,
TOEND: 0x5A,
REWIND: 0x5B,
FORWARD: 0x5C,
STOP: 0x5D,
PLAY: 0x5E,
RECORD: 0x5F,
EQENABLE1: 0x70,
EQENABLE2: 0x71,
EQENABLE3: 0x72,
EQENABLE4: 0x73,
EQTYPE: 0x74,
ALLBYPASS: 0x75,
JOG: 0x76,
LOCK: 0x77,
INPUTMONITOR: 0x78,
OPENVSTI: 0x79
};

// Midi CC number for knobs
var CC = {
PAN: 0x10,
EQQ1: 0x20,
EQQ2: 0x21,
EQQ3: 0x22,
EQQ4: 0x23,
EQFREQUENCY1: 0x30,
EQFREQUENCY2: 0x31,
EQFREQUENCY3: 0x32,
EQFREQUENCY4: 0x33,
EQGAIN1: 0x40,
EQGAIN2: 0x41,
EQGAIN3: 0x42,
EQGAIN4: 0x43,
VALUE: 0x50,
AIKNOB: 0x3C
}

// Array of midi listeners
var midiListeners;

// Job button is for parameter acceleration;
var isJogActive = false;

// Company, product, script version, UUID, author
host.defineController("Steinberg", "CC121", "1.0", "A1068940-6B61-11E8-B566-0800200C9A66", "Philipp Winniewski");
host.defineMidiPorts(1, 1);
// For Windows
host.addDeviceNameBasedDiscoveryPair(["Steinberg CC121-1"], ["Steinberg CC121-1"]);
// For Linux
host.addDeviceNameBasedDiscoveryPair(["Steinberg CC121 MIDI 1"], ["Steinberg CC121 MIDI 1"]);

function init() {
// Configuring MIDI device
host.getMidiInPort(0).setMidiCallback(onMidi);

// Initializing controller sections
midiListeners = [
initTransport(),
initFunction(),
initAi(),
initChannel(),
initDevice()
];
}

function exit() {
for (i = 0; i < 128; i++) {
sendMidi(144, i, LIGHT_OFF);
}
}

function onMidi(status, data1, data2) {
for (i = 0; i < midiListeners.length; i++) {
midiListeners[i](status, data1, data2);
}
}

function midiCcValueToInteger(ccValue) {
if (ccValue >= ENCODER_POS_LO && ccValue <= ENCODER_POS_HI) {
return ccValue - ENCODER_POS_LO + 1;
} else if (ccValue >= ENCODER_NEG_LO && ccValue <= ENCODER_NEG_HI) {
return -(ccValue - ENCODER_NEG_LO + 1);
} else {
return 0;
}
}
Loading

0 comments on commit 374c73c

Please sign in to comment.