Browse Source

Finalize XY controller plugin

Signed-off-by: falkTX <falktx@falktx.com>
tags/v2.3.0-RC1
falkTX 4 years ago
parent
commit
698d42656f
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
4 changed files with 238 additions and 58 deletions
  1. +0
    -3
      source/frontend/externalui.py
  2. +103
    -45
      source/frontend/xycontroller-ui
  3. +1
    -1
      source/native-plugins/_data.cpp
  4. +134
    -9
      source/native-plugins/xycontroller.cpp

+ 0
- 3
source/frontend/externalui.py View File

@@ -83,9 +83,6 @@ class ExternalUI(object):
def sendConfigure(self, key, value):
self.send(["configure", key, value])

def sendNote(self, onOff, channel, note, velocity):
self.send(["note", onOff, channel, note, velocity])

# -------------------------------------------------------------------
# DSP Callbacks



+ 103
- 45
source/frontend/xycontroller-ui View File

@@ -54,6 +54,7 @@ class XYGraphicsScene(QGraphicsScene):

self.cc_x = 1
self.cc_y = 2
self.rparent = parent

self.m_channels = []
self.m_mouseLock = False
@@ -189,17 +190,18 @@ class XYGraphicsScene(QGraphicsScene):
self.cursorMoved.emit(xp, yp)

def sendMIDI(self, xp, yp):
rate = float(0xff) / 4;
rate = float(0xff) / 4
msgd = ["cc2" if xp is not None and yp is not None else "cc"]

if xp is not None:
value = xp * rate + rate
#foreach (const int& channel, m_channels)
#qMidiOutData.put(0xB0 + channel - 1, cc_x, value);
msgd.append(self.cc_x)
msgd.append(int(xp * rate + rate))

if yp is not None:
value = yp * rate + rate
#foreach (const int& channel, m_channels)
#qMidiOutData.put(0xB0 + channel - 1, cc_y, value);
elif yp is not None:
msgd.append(self.cc_y)
msgd.append(int(yp * rate + rate))

self.rparent.send(msgd)

# -------------------------------------------------------------------

@@ -212,7 +214,7 @@ class XYGraphicsScene(QGraphicsScene):
def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
self.m_mouseLock = True
self.handleMousePos(event.scenePos())
self.parent().setCursor(Qt.CrossCursor)
self.rparent.setCursor(Qt.CrossCursor)
QGraphicsScene.mousePressEvent(self, event);

def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
@@ -221,7 +223,7 @@ class XYGraphicsScene(QGraphicsScene):

def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
self.m_mouseLock = False
self.parent().setCursor(Qt.ArrowCursor)
self.rparent.setCursor(Qt.ArrowCursor)
QGraphicsScene.mouseReleaseEvent(self, event)

# -----------------------------------------------------------------------
@@ -236,13 +238,6 @@ class XYControllerUI(ExternalUI, QMainWindow):

self.fSaveSizeNowChecker = -1

# ---------------------------------------------------------------
# Internal stuff

self.cc_x = 1
self.cc_y = 2
self.m_channels = []

# ---------------------------------------------------------------
# Set-up GUI stuff

@@ -261,7 +256,14 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.ui.cb_control_x.addItem(MIDI_CC)
self.ui.cb_control_y.addItem(MIDI_CC)

self.ui.graphicsView.centerOn(0, 0)
# ---------------------------------------------------------------
# Initial state

self.m_channels = [1]

self.ui.act_ch_01.setChecked(True)
self.ui.act_show_keyboard.setChecked(True)
self.ui.cb_control_y.setCurrentIndex(1)

# ---------------------------------------------------------------
# Connect actions to functions
@@ -307,12 +309,11 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.setWindowTitle(self.fUiName)
self.ready()

QTimer.singleShot(0, self.slot_updateScreen)

# -------------------------------------------------------------------

@pyqtSlot()
def slot_updateScreen(self):
self.ui.graphicsView.centerOn(0, 0)
self.scene.updateSize(self.ui.graphicsView.size())

dial_x = self.ui.dial_x.rvalue()
@@ -323,15 +324,11 @@ class XYControllerUI(ExternalUI, QMainWindow):

@pyqtSlot(int)
def slot_noteOn(self, note):
pass
#foreach (const int& channel, m_channels)
#qMidiOutData.put(0x90 + channel - 1, note, 100);
self.send(["note", True, note])

@pyqtSlot(int)
def slot_noteOff(self, note):
pass
#foreach (const int& channel, m_channels)
#qMidiOutData.put(0x80 + channel - 1, note, 0);
self.send(["note", False, note])

@pyqtSlot(float)
def slot_knobValueChangedX(self, x: float):
@@ -346,24 +343,24 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)

@pyqtSlot(str)
def slot_checkCC_X(self, text):
def slot_checkCC_X(self, text: str):
if not text:
return

tmp_cc_x = int(text.split(" ",1)[0], 16)
cc_x = int(text.split(" ",1)[0])

self.cc_x = tmp_cc_x;
self.scene.setControlX(self.cc_x);
self.scene.setControlX(cc_x)
self.sendConfigure("cc_x", str(cc_x))

@pyqtSlot(str)
def slot_checkCC_Y(self, text):
def slot_checkCC_Y(self, text: str):
if not text:
return

tmp_cc_y = int(text.split(" ",1)[0], 16)
cc_y = int(text.split(" ",1)[0])

self.cc_y = tmp_cc_y;
self.scene.setControlY(self.cc_y);
self.scene.setControlY(cc_y)
self.sendConfigure("cc_y", str(cc_y))

@pyqtSlot(bool)
def slot_checkChannel(self, clicked):
@@ -375,9 +372,10 @@ class XYControllerUI(ExternalUI, QMainWindow):
if clicked and channel not in self.m_channels:
self.m_channels.append(channel)
elif not clicked and channel in self.m_channels:
self.m_channels.remove(channel);
self.m_channels.remove(channel)

self.scene.setChannels(self.m_channels);
self.scene.setChannels(self.m_channels)
self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))

@pyqtSlot()
def slot_checkChannel_all(self):
@@ -400,6 +398,7 @@ class XYControllerUI(ExternalUI, QMainWindow):

self.m_channels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
self.scene.setChannels(self.m_channels)
self.sendConfigure("channels", ",".join(str(c) for c in self.m_channels))

@pyqtSlot()
def slot_checkChannel_none(self):
@@ -422,11 +421,12 @@ class XYControllerUI(ExternalUI, QMainWindow):

self.m_channels = []
self.scene.setChannels(self.m_channels)
self.sendConfigure("channels", "")

@pyqtSlot(bool)
def slot_setSmooth(self, smooth):
self.sendConfigure("smooth", "yes" if smooth else "no")
self.scene.setSmooth(smooth)
self.sendConfigure("smooth", "yes" if smooth else "no")

if smooth:
dial_x = self.ui.dial_x.rvalue()
@@ -434,22 +434,22 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.scene.setSmoothValues(dial_x / 100, dial_y / 100)

@pyqtSlot(float, float)
def slot_sceneCursorMoved(self, xp, yp):
self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100)
self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100)

def slot_sceneCursorMoved(self, xp: float, yp: float):
self.ui.dial_x.setValue(xp * 100, False)
self.ui.dial_y.setValue(yp * 100, False)
self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100)
self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100)

@pyqtSlot(bool)
def slot_showKeyboard(self, yesno):
self.ui.scrollArea.setVisible(yesno)
self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no")
QTimer.singleShot(0, self.slot_updateScreen)

# -------------------------------------------------------------------
# DSP Callbacks

def dspParameterChanged(self, index, value):
def dspParameterChanged(self, index: int, value: float):
if index == XYCONTROLLER_PARAMETER_X:
self.ui.dial_x.setValue(value, False)
self.scene.setPosX(value / 100, False)
@@ -462,9 +462,7 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100,
self.ui.dial_y.rvalue() / 100)

def dspStateChanged(self, key, value):
print("dspStateChanged", key, value)

def dspStateChanged(self, key: str, value: str):
if key == "guiWidth":
try:
width = int(value)
@@ -495,6 +493,66 @@ class XYControllerUI(ExternalUI, QMainWindow):
dial_y = self.ui.dial_y.rvalue()
self.scene.setSmoothValues(dial_x / 100, dial_y / 100)

elif key == "show-midi-keyboard":
show = (value == "yes")
self.ui.act_show_keyboard.blockSignals(True)
self.ui.act_show_keyboard.setChecked(show)
self.ui.act_show_keyboard.blockSignals(False)
self.ui.scrollArea.setVisible(show)

elif key == "channels":
if value:
self.m_channels = [int(c) for c in value.split(",")]
else:
self.m_channels = []
self.scene.setChannels(self.m_channels)
self.ui.act_ch_01.setChecked(bool(1 in self.m_channels))
self.ui.act_ch_02.setChecked(bool(2 in self.m_channels))
self.ui.act_ch_03.setChecked(bool(3 in self.m_channels))
self.ui.act_ch_04.setChecked(bool(4 in self.m_channels))
self.ui.act_ch_05.setChecked(bool(5 in self.m_channels))
self.ui.act_ch_06.setChecked(bool(6 in self.m_channels))
self.ui.act_ch_07.setChecked(bool(7 in self.m_channels))
self.ui.act_ch_08.setChecked(bool(8 in self.m_channels))
self.ui.act_ch_09.setChecked(bool(9 in self.m_channels))
self.ui.act_ch_10.setChecked(bool(10 in self.m_channels))
self.ui.act_ch_11.setChecked(bool(11 in self.m_channels))
self.ui.act_ch_12.setChecked(bool(12 in self.m_channels))
self.ui.act_ch_13.setChecked(bool(13 in self.m_channels))
self.ui.act_ch_14.setChecked(bool(14 in self.m_channels))
self.ui.act_ch_15.setChecked(bool(15 in self.m_channels))
self.ui.act_ch_16.setChecked(bool(16 in self.m_channels))

elif key == "cc_x":
cc_x = int(value)
self.scene.setControlX(cc_x)

for cc_index in range(len(MIDI_CC_LIST)):
if cc_x == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
self.ui.cb_control_x.blockSignals(True)
self.ui.cb_control_x.setCurrentIndex(cc_index)
self.ui.cb_control_x.blockSignals(False)
break

elif key == "cc_y":
cc_y = int(value)
self.scene.setControlY(cc_y)

for cc_index in range(len(MIDI_CC_LIST)):
if cc_y == int(MIDI_CC_LIST[cc_index].split(" ",1)[0]):
self.ui.cb_control_y.blockSignals(True)
self.ui.cb_control_y.setCurrentIndex(cc_index)
self.ui.cb_control_y.blockSignals(False)
break

def dspNoteReceived(self, onOff, channel, note, velocity):
if channel+1 not in self.m_channels:
return
if onOff:
self.ui.keyboard.sendNoteOn(note, False)
else:
self.ui.keyboard.sendNoteOff(note, False)

# -------------------------------------------------------------------
# ExternalUI Callbacks



+ 1
- 1
source/native-plugins/_data.cpp View File

@@ -563,7 +563,7 @@ static const NativePluginDescriptor sNativePluginDescriptors[] = {
/* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
/* audioIns */ 0,
/* audioOuts */ 0,
/* midiIns */ 0,
/* midiIns */ 1,
/* midiOuts */ 1,
/* paramIns */ 2,
/* paramOuts */ 2,


+ 134
- 9
source/native-plugins/xycontroller.cpp View File

@@ -17,8 +17,12 @@

#include "CarlaDefines.h"

#include "CarlaMIDI.h"
#include "CarlaNativeExtUI.hpp"

#include "midi-queue.hpp"
#include "water/text/StringArray.h"

// -----------------------------------------------------------------------

class XYControllerPlugin : public NativePluginAndUiClass
@@ -34,9 +38,14 @@ public:

XYControllerPlugin(const NativeHostDescriptor* const host)
: NativePluginAndUiClass(host, "xycontroller-ui"),
fParams()
params(),
channels(),
mqueue(),
mqueueRT()
{
carla_zeroStruct(fParams);
carla_zeroStruct(params);
carla_zeroStruct(channels);
channels[0] = true;
}

protected:
@@ -94,7 +103,7 @@ protected:
{
CARLA_SAFE_ASSERT_RETURN(index < kParamCount, 0.0f);

return fParams[index];
return params[index];
}

// -------------------------------------------------------------------
@@ -106,23 +115,139 @@ protected:
{
case kParamInX:
case kParamInY:
fParams[index] = value;
params[index] = value;
break;
}
}

void setCustomData(const char* const key, const char* const value) override
{
CARLA_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',);
CARLA_SAFE_ASSERT_RETURN(value != nullptr,);

if (std::strcmp(key, "channels") == 0)
{
const water::StringArray chans(water::StringArray::fromTokens(value, ",", ""));

carla_zeroStruct(channels);

for (const water::String chan : chans)
{
const int ichan = std::atoi(chan.toRawUTF8());
CARLA_SAFE_ASSERT_INT_CONTINUE(ichan >= 1 && ichan <= 16, ichan);

channels[ichan-1] = true;
}
}
}

// -------------------------------------------------------------------
// Plugin process calls

void process(const float* const*, float**, const uint32_t,
const NativeMidiEvent* const, const uint32_t) override
const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override
{
fParams[kParamOutX] = fParams[kParamInX];
fParams[kParamOutY] = fParams[kParamInY];
params[kParamOutX] = params[kParamInX];
params[kParamOutY] = params[kParamInY];

if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue))
{
uint8_t d1, d2, d3;
NativeMidiEvent ev = { 0, 0, 3, { 0, 0, 0, 0 } };

while (mqueueRT.get(d1, d2, d3))
{
ev.data[0] = d1;
ev.data[1] = d2;
ev.data[2] = d3;
writeMidiEvent(&ev);
}
}

for (uint32_t i=0; i < midiEventCount; ++i)
writeMidiEvent(&midiEvents[i]);
}

// -------------------------------------------------------------------
// Pipe Server calls

bool msgReceived(const char* const msg) noexcept override
{
if (NativePluginAndUiClass::msgReceived(msg))
return true;

if (std::strcmp(msg, "cc") == 0)
{
uint8_t cc, value;
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value), true);

const CarlaMutexLocker cml(mqueue.getMutex());

for (int i=0; i<16; ++i)
{
if (channels[i])
if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc, value))
break;
}

return true;
}

if (std::strcmp(msg, "cc2") == 0)
{
uint8_t cc1, value1, cc2, value2;
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc1), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value1), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(cc2), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(value2), true);

const CarlaMutexLocker cml(mqueue.getMutex());

for (int i=0; i<16; ++i)
{
if (channels[i])
{
if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc1, value1))
break;
if (! mqueue.put(uint8_t(MIDI_STATUS_CONTROL_CHANGE | (i & MIDI_CHANNEL_BIT)), cc2, value2))
break;
}
}

return true;
}

if (std::strcmp(msg, "note") == 0)
{
bool onOff;
uint8_t note;
CARLA_SAFE_ASSERT_RETURN(readNextLineAsBool(onOff), true);
CARLA_SAFE_ASSERT_RETURN(readNextLineAsByte(note), true);

const uint8_t status = onOff ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF;
const uint8_t velocity = onOff ? 100 : 0;

const CarlaMutexLocker cml(mqueue.getMutex());

for (int i=0; i<16; ++i)
{
if (channels[i])
if (! mqueue.put(uint8_t(status | (i & MIDI_CHANNEL_BIT)), note, velocity))
break;
}

return true;
}

return false;
}

private:
float fParams[kParamCount];
float params[kParamCount];
bool channels[16];

MIDIEventQueue<128> mqueue, mqueueRT;

PluginClassEND(XYControllerPlugin)
CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(XYControllerPlugin)
@@ -137,7 +262,7 @@ static const NativePluginDescriptor notesDesc = {
/* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
/* audioIns */ 0,
/* audioOuts */ 0,
/* midiIns */ 0,
/* midiIns */ 1,
/* midiOuts */ 1,
/* paramIns */ 2,
/* paramOuts */ 2,


Loading…
Cancel
Save