Browse Source

Start fixing up midi pattern plugin, WIP

tags/v2.1-rc1
falkTX 5 years ago
parent
commit
b1a04d7f76
4 changed files with 134 additions and 118 deletions
  1. +34
    -52
      source/frontend/widgets/pianoroll.py
  2. +1
    -1
      source/native-plugins/midi-base.hpp
  3. +37
    -25
      source/native-plugins/midi-pattern.cpp
  4. +62
    -40
      source/native-plugins/resources/midipattern-ui

+ 34
- 52
source/frontend/widgets/pianoroll.py View File

@@ -332,7 +332,7 @@ class PianoRoll(QGraphicsScene):
self.quantize_val = quantize_val

### dummy vars that will be changed
self.time_sig = 0
self.time_sig = (0,0)
self.measure_width = 0
self.num_measures = 0
self.max_note_length = 0
@@ -343,8 +343,6 @@ class PianoRoll(QGraphicsScene):
self.header = None
self.play_head = None

self.setTimeSig(time_sig)
self.setMeasures(num_measures)
self.setGridDiv()
self.default_length = 1. / self.grid_div

@@ -352,47 +350,38 @@ class PianoRoll(QGraphicsScene):
# -------------------------------------------------------------------------
# Callbacks

def movePlayHead(self, transport_info):
# TODO: need conversion between frames and PPQ
x = 105. # works for 120bpm
total_duration = self.time_sig[0] * self.num_measures * x
pos = transport_info['frame'] / x
frac = (pos % total_duration) / total_duration
def movePlayHead(self, transportInfo):
ticksPerBeat = transportInfo['ticksPerBeat']
max_ticks = ticksPerBeat * self.time_sig[0] * self.num_measures
cur_tick = ticksPerBeat * self.time_sig[0] * transportInfo['bar'] + ticksPerBeat * transportInfo['beat'] + transportInfo['tick']
frac = (cur_tick % max_ticks) / max_ticks
self.play_head.setPos(QPointF(frac * self.grid_width, 0))

def setTimeSig(self, time_sig):
try:
new_time_sig = list(map(float, time_sig.split('/')))
if len(new_time_sig)==2:
self.time_sig = new_time_sig

self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
self.grid_width = self.measure_width * self.num_measures
self.setGridDiv()
except ValueError:
pass
self.time_sig = time_sig
self.measure_width = self.full_note_width * self.time_sig[0]/self.time_sig[1]
self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
self.grid_width = self.measure_width * self.num_measures
self.setGridDiv()

def setMeasures(self, measures):
try:
self.num_measures = float(measures)
self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
self.grid_width = self.measure_width * self.num_measures
self.refreshScene()
except:
pass
#try:
self.num_measures = float(measures)
self.max_note_length = self.num_measures * self.time_sig[0]/self.time_sig[1]
self.grid_width = self.measure_width * self.num_measures
self.refreshScene()
#except:
#pass

def setDefaultLength(self, length):
try:
v = list(map(float, length.split('/')))
if len(v) < 3:
self.default_length = \
v[0] if len(v)==1 else \
v[0] / v[1]
pos = self.enforce_bounds(self.mousePos)
if self.insert_mode: self.makeGhostNote(pos.x(), pos.y())
except ValueError:
pass
v = list(map(float, length.split('/')))
if len(v) < 3:
self.default_length = \
v[0] if len(v)==1 else \
v[0] / v[1]
pos = self.enforce_bounds(self.mousePos)
if self.insert_mode:
self.makeGhostNote(pos.x(), pos.y())

def setGridDiv(self, div=None):
if not div: div = self.quantize_val
@@ -409,16 +398,13 @@ class PianoRoll(QGraphicsScene):
pass

def setQuantize(self, value):
try:
val = list(map(float, value.split('/')))
if len(val) == 1:
self.quantize(val[0])
self.quantize_val = value
elif len(val) == 2:
self.quantize(val[0] / val[1])
self.quantize_val = value
except ValueError:
pass
val = list(map(float, value.split('/')))
if len(val) == 1:
self.quantize(val[0])
self.quantize_val = value
elif len(val) == 2:
self.quantize(val[0] / val[1])
self.quantize_val = value

# -------------------------------------------------------------------------
# Event Callbacks
@@ -851,10 +837,9 @@ class ModeIndicator(QWidget):
self.mode = None

def paintEvent(self, event):
painter = QPainter(self)
event.accept()

painter.begin(self)
painter = QPainter(self)

painter.setPen(QPen(QColor(0, 0, 0, 0)))
if self.mode == 'velocity_mode':
@@ -865,9 +850,6 @@ class ModeIndicator(QWidget):
painter.setBrush(QColor(0, 0, 0, 0))
painter.drawRect(0, 0, 30, 20)

# FIXME
painter.end()

def changeMode(self, new_mode):
self.mode = new_mode
self.update()


+ 1
- 1
source/native-plugins/midi-base.hpp View File

@@ -291,7 +291,7 @@ public:
if (fData.count() == 0)
return nullptr;

char* const data((char*)std::calloc(1, fData.count()*maxMsgSize));
char* const data((char*)std::calloc(1, fData.count()*maxMsgSize+1));
CARLA_SAFE_ASSERT_RETURN(data != nullptr, nullptr);

char* dataWrtn = data;


+ 37
- 25
source/native-plugins/midi-pattern.cpp View File

@@ -20,6 +20,9 @@

#include "midi-base.hpp"

// matches UI side
#define TICKS_PER_BEAT 48

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

class MidiPatternPlugin : public NativePluginAndUiClass,
@@ -52,7 +55,7 @@ public:
fParameters[kParameterDefLength] = 4.0f;
fParameters[kParameterQuantize] = 4.0f;

fMaxTicks = 48.0 * fTimeSigNum * 4 /* kParameterMeasures */ / 2; // FIXME: why / 2 ?
fMaxTicks = TICKS_PER_BEAT * fTimeSigNum * 4 /* kParameterMeasures */;
}

protected:
@@ -202,7 +205,7 @@ protected:
fTimeSigNum = 1;
// fall through
case kParameterMeasures:
fMaxTicks = 48.0 * fTimeSigNum * static_cast<double>(fParameters[kParameterMeasures]) /2; // FIXME: why /2 ?
fMaxTicks = TICKS_PER_BEAT * fTimeSigNum * static_cast<double>(fParameters[kParameterMeasures]);
break;
}
}
@@ -210,7 +213,8 @@ protected:
// -------------------------------------------------------------------
// Plugin process calls

void process(const float**, float**, const uint32_t frames, const NativeMidiEvent*, uint32_t) override
void process(const float**, float**, const uint32_t frames,
const NativeMidiEvent* /*midiEvents*/, uint32_t /*midiEventCount*/) override
{
if (const NativeTimeInfo* const timeInfo = getTimeInfo())
fTimeInfo = *timeInfo;
@@ -247,12 +251,20 @@ protected:
if (! fTimeInfo.bbt.valid)
fTimeInfo.bbt.beatsPerMinute = 120.0;

fTicksPerFrame = 48.0 / (60.0 / fTimeInfo.bbt.beatsPerMinute * getSampleRate());
fTicksPerFrame = TICKS_PER_BEAT / (60.0 / fTimeInfo.bbt.beatsPerMinute * getSampleRate());

/* */ double playPos = fTicksPerFrame*static_cast<double>(fTimeInfo.frame);
const double endPos = playPos + fTicksPerFrame*static_cast<double>(frames);

const double loopedEndPos = std::fmod(endPos, fMaxTicks);
const double loopedEndPos = std::fmod(endPos, fMaxTicks);

/*
for (uint32_t i=0; i<midiEventCount; ++i)
{
uint32_t pos = static_cast<uint32_t>(std::fmod(fTicksPerFrame * static_cast<double>(fTimeInfo.frame + midiEvents[i].time), fMaxTicks));
fMidiOut.addRaw(pos, midiEvents[i].data, midiEvents[i].size);
}
*/

for (; playPos < endPos; playPos += fMaxTicks)
{
@@ -290,13 +302,12 @@ protected:
if (isPipeRunning())
{
char strBuf[0xff+1];
strBuf[0xff] = '\0';
carla_zeroChars(strBuf, 0xff+1);

const double beatsPerBar = fTimeInfo.bbt.valid ? static_cast<double>(fTimeInfo.bbt.beatsPerBar) : 4.0;
const double beatsPerBar = fTimeSigNum;
const double beatsPerMinute = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatsPerMinute : 120.0;
const float beatType = fTimeInfo.bbt.valid ? fTimeInfo.bbt.beatType : 4.0f;

const double ticksPerBeat = 48.0;
const double ticksPerBeat = TICKS_PER_BEAT;
const double ticksPerFrame = ticksPerBeat / (60.0 / beatsPerMinute * getSampleRate());
const double fullTicks = static_cast<double>(ticksPerFrame * static_cast<long double>(fTimeInfo.frame));
const double fullBeats = fullTicks / ticksPerBeat;
@@ -307,24 +318,17 @@ protected:

const CarlaMutexLocker cml(getPipeLock());

if (! writeAndFixMessage("transport"))
return;
if (! writeMessage(fTimeInfo.playing ? "true\n" : "false\n"))
return;
CARLA_SAFE_ASSERT_RETURN(writeMessage("transport\n"),);

std::sprintf(strBuf, P_UINT64 ":%i:%i:%i\n", fTimeInfo.frame, bar, beat, tick);
if (! writeMessage(strBuf))
return;
std::snprintf(strBuf, 0xff, "%i:" P_UINT64 ":%i:%i:%i\n", int(fTimeInfo.playing), fTimeInfo.frame, bar, beat, tick);
CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);

{
const CarlaScopedLocale csl;
std::sprintf(strBuf, "%f:%f:%f\n",
static_cast<double>(beatsPerMinute),
static_cast<double>(beatsPerBar),
static_cast<double>(beatType));
std::snprintf(strBuf, 0xff, "%f\n", beatsPerMinute);
}
if (! writeMessage(strBuf))
return;

CARLA_SAFE_ASSERT_RETURN(writeMessage(strBuf),);

flushMessages();
}
@@ -402,7 +406,7 @@ protected:
data[i] = dvalue;
}

fMidiOut.addRaw(time, data, size);
fMidiOut.addRaw(time /* * TICKS_PER_BEAT */, data, size);

return true;
}
@@ -424,7 +428,7 @@ protected:
data[i] = dvalue;
}

fMidiOut.removeRaw(time, data, size);
fMidiOut.removeRaw(time /* * TICKS_PER_BEAT */, data, size);

return true;
}
@@ -450,13 +454,21 @@ private:
void _sendEventsToUI() const noexcept
{
char strBuf[0xff+1];
strBuf[0xff] = '\0';
carla_zeroChars(strBuf, 0xff);

const CarlaMutexLocker cml1(getPipeLock());
const CarlaMutexLocker cml2(fMidiOut.getLock());

writeMessage("midi-clear-all\n", 15);

writeMessage("parameters\n", 11);
std::snprintf(strBuf, 0xff, "%i:%i:%i:%i\n",
static_cast<int>(fParameters[kParameterTimeSig]),
static_cast<int>(fParameters[kParameterMeasures]),
static_cast<int>(fParameters[kParameterDefLength]),
static_cast<int>(fParameters[kParameterQuantize]));
writeMessage(strBuf);

for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next())
{
const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr));


+ 62
- 40
source/native-plugins/resources/midipattern-ui View File

@@ -42,7 +42,7 @@ from externalui import ExternalUI
# ------------------------------------------------------------------------------------------------------------

class MidiPatternW(ExternalUI, QMainWindow):
PPQ = 48.0
TICKS_PER_BEAT = 48

def __init__(self):
ExternalUI.__init__(self)
@@ -54,6 +54,7 @@ class MidiPatternW(ExternalUI, QMainWindow):
# to be filled with note-on events, while waiting for their matching note-off
self.fPendingNoteOns = [] # (channel, note, velocity, time)

self.fTimeSignature = (4,4)
self.fTransportInfo = {
"playing": False,
"frame": 0,
@@ -61,8 +62,6 @@ class MidiPatternW(ExternalUI, QMainWindow):
"beat": 0,
"tick": 0,
"bpm": 120.0,
"sigNum": 4.0,
"sigDenom": 4.0
}

self.ui.act_edit_insert.triggered.connect(self.slot_editInsertMode)
@@ -79,7 +78,7 @@ class MidiPatternW(ExternalUI, QMainWindow):
self.ui.defaultLengthBox.currentIndexChanged[int].connect(self.slot_paramChanged)
self.ui.quantizeBox.currentIndexChanged[int].connect(self.slot_paramChanged)

self.ui.timeSigBox.currentIndexChanged[str].connect(self.ui.piano.setTimeSig)
self.ui.timeSigBox.currentIndexChanged[str].connect(self.slot_setTimeSignature)
self.ui.measureBox.currentIndexChanged[str].connect(self.ui.piano.setMeasures)
self.ui.defaultLengthBox.currentIndexChanged[str].connect(self.ui.piano.setDefaultLength)
self.ui.quantizeBox.currentIndexChanged[str].connect(self.ui.piano.setGridDiv)
@@ -132,6 +131,18 @@ class MidiPatternW(ExternalUI, QMainWindow):

self.sendControl(param, index)

def slot_setTimeSignature(self, sigtext):
try:
timesig = tuple(map(float, sigtext.split('/')))
except ValueError:
pass

if len(timesig) != 2:
return

self.fTimeSignature = timesig
self.ui.piano.setTimeSig(timesig)

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

@@ -139,13 +150,28 @@ class MidiPatternW(ExternalUI, QMainWindow):
value = int(value)

if index == 0: # TimeSig
self.ui.timeSigBox.blockSignals(True)
self.ui.timeSigBox.setCurrentIndex(value)
self.slot_setTimeSignature(self.ui.timeSigBox.currentText())
self.ui.timeSigBox.blockSignals(False)

elif index == 1: # Measures
self.ui.measureBox.blockSignals(True)
self.ui.measureBox.setCurrentIndex(value-1)
self.ui.piano.setMeasures(self.ui.measureBox.currentText())
self.ui.measureBox.blockSignals(False)

elif index == 2: # DefLength
self.ui.defaultLengthBox.blockSignals(True)
self.ui.defaultLengthBox.setCurrentIndex(value)
self.ui.piano.setDefaultLength(self.ui.defaultLengthBox.currentText())
self.ui.defaultLengthBox.blockSignals(False)

elif index == 3: # Quantize
self.ui.quantizeBox.blockSignals(True)
self.ui.quantizeBox.setCurrentIndex(value)
self.ui.piano.setQuantize(self.ui.quantizeBox.currentText())
self.ui.quantizeBox.blockSignals(False)

def dspStateChanged(self, key, value):
pass
@@ -193,21 +219,21 @@ class MidiPatternW(ExternalUI, QMainWindow):
# Custom callback

def updateMeasureBox(self, index):
self.measureBox.setCurrentIndex(index-1)
self.ui.measureBox.setCurrentIndex(index-1)

def sendMsg(self, data):
msg = data[0]
if msg == "midievent-remove":
if msg == "midievent-add":
note, start, length, vel = data[1:5]
note_start = start * 60. / self.fTransportInfo["bpm"] * 4. / self.fTransportInfo["sigDenom"] * self.PPQ
note_stop = note_start + length * 60. / self.fTransportInfo["bpm"] * 4. * self.fTransportInfo["sigNum"] / self.fTransportInfo["sigDenom"] * self.PPQ
note_start = start * 60. / self.fTransportInfo["bpm"] * self.TICKS_PER_BEAT
note_stop = note_start + length * 60. / self.fTransportInfo["bpm"] * 4. * self.fTimeSignature[0] / self.fTimeSignature[1] * self.TICKS_PER_BEAT
self.send([msg, note_start, 3, MIDI_STATUS_NOTE_ON, note, vel])
self.send([msg, note_stop, 3, MIDI_STATUS_NOTE_OFF, note, vel])

elif msg == "midievent-add":
elif msg == "midievent-remove":
note, start, length, vel = data[1:5]
note_start = start * 60. / self.fTransportInfo["bpm"] * self.PPQ
note_stop = note_start + length * 60. / self.fTransportInfo["bpm"] * 4. * self.fTransportInfo["sigNum"] / self.fTransportInfo["sigDenom"] * self.PPQ
note_start = start * 60. / self.fTransportInfo["bpm"] * self.TICKS_PER_BEAT # 4. / self.fTransportInfo["sigDenom"] * self.TICKS_PER_BEAT
note_stop = note_start + length * 60. / self.fTransportInfo["bpm"] * 4. * self.fTimeSignature[0] / self.fTimeSignature[1] * self.TICKS_PER_BEAT
self.send([msg, note_start, 3, MIDI_STATUS_NOTE_ON, note, vel])
self.send([msg, note_stop, 3, MIDI_STATUS_NOTE_OFF, note, vel])

@@ -222,20 +248,14 @@ class MidiPatternW(ExternalUI, QMainWindow):
# adds single midi event
time = int(self.readlineblock())
size = int(self.readlineblock())
data = []

for x in range(size):
data.append(int(self.readlineblock()))
data = tuple(int(self.readlineblock()) for x in range(size))

self.handleMidiEvent(time, size, data)

elif msg == "transport":
playing = bool(self.readlineblock() == "true")
frame, bar, beat, tick = [int(i) for i in self.readlineblock().split(":")]
bpm, sigNum, sigDenom = [float(i) for i in self.readlineblock().split(":")]

if beat != self.fTransportInfo["beat"]:
print(beat)
playing, frame, bar, beat, tick = tuple(int(i) for i in self.readlineblock().split(":"))
bpm = float(self.readlineblock())
playing = bool(int(playing))

old_frame = self.fTransportInfo['frame']

@@ -246,13 +266,19 @@ class MidiPatternW(ExternalUI, QMainWindow):
"beat": beat,
"tick": tick,
"bpm": bpm,
"sigNum": sigNum,
"sigDenom": sigDenom
"ticksPerBeat": self.TICKS_PER_BEAT,
}

if old_frame != frame:
self.ui.piano.movePlayHead(self.fTransportInfo)

elif msg == "parameters":
timesig, measures, deflength, quantize = tuple(int(i) for i in self.readlineblock().split(":"))
self.dspParameterChanged(0, timesig)
self.dspParameterChanged(1, measures)
self.dspParameterChanged(2, deflength)
self.dspParameterChanged(3, quantize)

else:
ExternalUI.msgCallback(self, msg)

@@ -260,16 +286,11 @@ class MidiPatternW(ExternalUI, QMainWindow):
# Internal stuff

def handleMidiEvent(self, time, size, data):
#print("Got MIDI Event on UI", time, size, data)

# NOTE: for now time comes in frames, which might not be desirable
# we'll convert it to a smaller value for now (seconds)
# later on we can have time as PPQ or similar

time /= self.PPQ
#print("handleMidiEvent", time, size, data)

status = MIDI_GET_STATUS_FROM_DATA(data)
channel = MIDI_GET_CHANNEL_FROM_DATA(data)

if status == MIDI_STATUS_NOTE_ON:
note = data[1]
velo = data[2]
@@ -279,27 +300,28 @@ class MidiPatternW(ExternalUI, QMainWindow):

elif status == MIDI_STATUS_NOTE_OFF:
note = data[1]
velo = data[2]

# find previous note-on that matches this note and channel
for noteOnMsg in self.fPendingNoteOns:
channel_, note_, velo_, time_ = noteOnMsg
on_channel, on_note, on_velo, on_time = noteOnMsg

if channel_ != channel:
if on_channel != channel:
continue
if note_ != note:
if on_note != note:
continue

# found it
#print("{} {} {} {}\n".format(note, time_, time-time_, velo_))
start = time_ / 60. * self.fTransportInfo["bpm"] / 4. * self.fTransportInfo["sigDenom"]
length = (time - time_) / 60. * self.fTransportInfo["bpm"] / 4. / self.fTransportInfo["sigNum"] * self.fTransportInfo["sigDenom"]
self.ui.piano.drawNote(note, start, length, velo_)

# remove from list
self.fPendingNoteOns.remove(noteOnMsg)
break

else:
return

self.ui.piano.drawNote(note,
on_time/self.TICKS_PER_BEAT,
(time-on_time)/self.TICKS_PER_BEAT/self.fTimeSignature[0], on_velo)


#--------------- main ------------------
if __name__ == '__main__':
import resources_rc


Loading…
Cancel
Save