@@ -332,7 +332,7 @@ class PianoRoll(QGraphicsScene): | |||||
self.quantize_val = quantize_val | self.quantize_val = quantize_val | ||||
### dummy vars that will be changed | ### dummy vars that will be changed | ||||
self.time_sig = 0 | |||||
self.time_sig = (0,0) | |||||
self.measure_width = 0 | self.measure_width = 0 | ||||
self.num_measures = 0 | self.num_measures = 0 | ||||
self.max_note_length = 0 | self.max_note_length = 0 | ||||
@@ -343,8 +343,6 @@ class PianoRoll(QGraphicsScene): | |||||
self.header = None | self.header = None | ||||
self.play_head = None | self.play_head = None | ||||
self.setTimeSig(time_sig) | |||||
self.setMeasures(num_measures) | |||||
self.setGridDiv() | self.setGridDiv() | ||||
self.default_length = 1. / self.grid_div | self.default_length = 1. / self.grid_div | ||||
@@ -352,47 +350,38 @@ class PianoRoll(QGraphicsScene): | |||||
# ------------------------------------------------------------------------- | # ------------------------------------------------------------------------- | ||||
# Callbacks | # 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)) | self.play_head.setPos(QPointF(frac * self.grid_width, 0)) | ||||
def setTimeSig(self, time_sig): | 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): | 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): | 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): | def setGridDiv(self, div=None): | ||||
if not div: div = self.quantize_val | if not div: div = self.quantize_val | ||||
@@ -409,16 +398,13 @@ class PianoRoll(QGraphicsScene): | |||||
pass | pass | ||||
def setQuantize(self, value): | 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 | # Event Callbacks | ||||
@@ -851,10 +837,9 @@ class ModeIndicator(QWidget): | |||||
self.mode = None | self.mode = None | ||||
def paintEvent(self, event): | def paintEvent(self, event): | ||||
painter = QPainter(self) | |||||
event.accept() | event.accept() | ||||
painter.begin(self) | |||||
painter = QPainter(self) | |||||
painter.setPen(QPen(QColor(0, 0, 0, 0))) | painter.setPen(QPen(QColor(0, 0, 0, 0))) | ||||
if self.mode == 'velocity_mode': | if self.mode == 'velocity_mode': | ||||
@@ -865,9 +850,6 @@ class ModeIndicator(QWidget): | |||||
painter.setBrush(QColor(0, 0, 0, 0)) | painter.setBrush(QColor(0, 0, 0, 0)) | ||||
painter.drawRect(0, 0, 30, 20) | painter.drawRect(0, 0, 30, 20) | ||||
# FIXME | |||||
painter.end() | |||||
def changeMode(self, new_mode): | def changeMode(self, new_mode): | ||||
self.mode = new_mode | self.mode = new_mode | ||||
self.update() | self.update() | ||||
@@ -291,7 +291,7 @@ public: | |||||
if (fData.count() == 0) | if (fData.count() == 0) | ||||
return nullptr; | 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); | CARLA_SAFE_ASSERT_RETURN(data != nullptr, nullptr); | ||||
char* dataWrtn = data; | char* dataWrtn = data; | ||||
@@ -20,6 +20,9 @@ | |||||
#include "midi-base.hpp" | #include "midi-base.hpp" | ||||
// matches UI side | |||||
#define TICKS_PER_BEAT 48 | |||||
// ----------------------------------------------------------------------- | // ----------------------------------------------------------------------- | ||||
class MidiPatternPlugin : public NativePluginAndUiClass, | class MidiPatternPlugin : public NativePluginAndUiClass, | ||||
@@ -52,7 +55,7 @@ public: | |||||
fParameters[kParameterDefLength] = 4.0f; | fParameters[kParameterDefLength] = 4.0f; | ||||
fParameters[kParameterQuantize] = 4.0f; | fParameters[kParameterQuantize] = 4.0f; | ||||
fMaxTicks = 48.0 * fTimeSigNum * 4 /* kParameterMeasures */ / 2; // FIXME: why / 2 ? | |||||
fMaxTicks = TICKS_PER_BEAT * fTimeSigNum * 4 /* kParameterMeasures */; | |||||
} | } | ||||
protected: | protected: | ||||
@@ -202,7 +205,7 @@ protected: | |||||
fTimeSigNum = 1; | fTimeSigNum = 1; | ||||
// fall through | // fall through | ||||
case kParameterMeasures: | 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; | break; | ||||
} | } | ||||
} | } | ||||
@@ -210,7 +213,8 @@ protected: | |||||
// ------------------------------------------------------------------- | // ------------------------------------------------------------------- | ||||
// Plugin process calls | // 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()) | if (const NativeTimeInfo* const timeInfo = getTimeInfo()) | ||||
fTimeInfo = *timeInfo; | fTimeInfo = *timeInfo; | ||||
@@ -247,12 +251,20 @@ protected: | |||||
if (! fTimeInfo.bbt.valid) | if (! fTimeInfo.bbt.valid) | ||||
fTimeInfo.bbt.beatsPerMinute = 120.0; | 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); | /* */ double playPos = fTicksPerFrame*static_cast<double>(fTimeInfo.frame); | ||||
const double endPos = playPos + fTicksPerFrame*static_cast<double>(frames); | 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) | for (; playPos < endPos; playPos += fMaxTicks) | ||||
{ | { | ||||
@@ -290,13 +302,12 @@ protected: | |||||
if (isPipeRunning()) | if (isPipeRunning()) | ||||
{ | { | ||||
char strBuf[0xff+1]; | 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 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 ticksPerFrame = ticksPerBeat / (60.0 / beatsPerMinute * getSampleRate()); | ||||
const double fullTicks = static_cast<double>(ticksPerFrame * static_cast<long double>(fTimeInfo.frame)); | const double fullTicks = static_cast<double>(ticksPerFrame * static_cast<long double>(fTimeInfo.frame)); | ||||
const double fullBeats = fullTicks / ticksPerBeat; | const double fullBeats = fullTicks / ticksPerBeat; | ||||
@@ -307,24 +318,17 @@ protected: | |||||
const CarlaMutexLocker cml(getPipeLock()); | 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; | 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(); | flushMessages(); | ||||
} | } | ||||
@@ -402,7 +406,7 @@ protected: | |||||
data[i] = dvalue; | data[i] = dvalue; | ||||
} | } | ||||
fMidiOut.addRaw(time, data, size); | |||||
fMidiOut.addRaw(time /* * TICKS_PER_BEAT */, data, size); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -424,7 +428,7 @@ protected: | |||||
data[i] = dvalue; | data[i] = dvalue; | ||||
} | } | ||||
fMidiOut.removeRaw(time, data, size); | |||||
fMidiOut.removeRaw(time /* * TICKS_PER_BEAT */, data, size); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -450,13 +454,21 @@ private: | |||||
void _sendEventsToUI() const noexcept | void _sendEventsToUI() const noexcept | ||||
{ | { | ||||
char strBuf[0xff+1]; | char strBuf[0xff+1]; | ||||
strBuf[0xff] = '\0'; | |||||
carla_zeroChars(strBuf, 0xff); | |||||
const CarlaMutexLocker cml1(getPipeLock()); | const CarlaMutexLocker cml1(getPipeLock()); | ||||
const CarlaMutexLocker cml2(fMidiOut.getLock()); | const CarlaMutexLocker cml2(fMidiOut.getLock()); | ||||
writeMessage("midi-clear-all\n", 15); | 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()) | for (LinkedList<const RawMidiEvent*>::Itenerator it = fMidiOut.iteneratorBegin(); it.valid(); it.next()) | ||||
{ | { | ||||
const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr)); | const RawMidiEvent* const rawMidiEvent(it.getValue(nullptr)); | ||||
@@ -42,7 +42,7 @@ from externalui import ExternalUI | |||||
# ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
class MidiPatternW(ExternalUI, QMainWindow): | class MidiPatternW(ExternalUI, QMainWindow): | ||||
PPQ = 48.0 | |||||
TICKS_PER_BEAT = 48 | |||||
def __init__(self): | def __init__(self): | ||||
ExternalUI.__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 | # to be filled with note-on events, while waiting for their matching note-off | ||||
self.fPendingNoteOns = [] # (channel, note, velocity, time) | self.fPendingNoteOns = [] # (channel, note, velocity, time) | ||||
self.fTimeSignature = (4,4) | |||||
self.fTransportInfo = { | self.fTransportInfo = { | ||||
"playing": False, | "playing": False, | ||||
"frame": 0, | "frame": 0, | ||||
@@ -61,8 +62,6 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
"beat": 0, | "beat": 0, | ||||
"tick": 0, | "tick": 0, | ||||
"bpm": 120.0, | "bpm": 120.0, | ||||
"sigNum": 4.0, | |||||
"sigDenom": 4.0 | |||||
} | } | ||||
self.ui.act_edit_insert.triggered.connect(self.slot_editInsertMode) | 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.defaultLengthBox.currentIndexChanged[int].connect(self.slot_paramChanged) | ||||
self.ui.quantizeBox.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.measureBox.currentIndexChanged[str].connect(self.ui.piano.setMeasures) | ||||
self.ui.defaultLengthBox.currentIndexChanged[str].connect(self.ui.piano.setDefaultLength) | self.ui.defaultLengthBox.currentIndexChanged[str].connect(self.ui.piano.setDefaultLength) | ||||
self.ui.quantizeBox.currentIndexChanged[str].connect(self.ui.piano.setGridDiv) | self.ui.quantizeBox.currentIndexChanged[str].connect(self.ui.piano.setGridDiv) | ||||
@@ -132,6 +131,18 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
self.sendControl(param, index) | 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 | # DSP Callbacks | ||||
@@ -139,13 +150,28 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
value = int(value) | value = int(value) | ||||
if index == 0: # TimeSig | if index == 0: # TimeSig | ||||
self.ui.timeSigBox.blockSignals(True) | |||||
self.ui.timeSigBox.setCurrentIndex(value) | self.ui.timeSigBox.setCurrentIndex(value) | ||||
self.slot_setTimeSignature(self.ui.timeSigBox.currentText()) | |||||
self.ui.timeSigBox.blockSignals(False) | |||||
elif index == 1: # Measures | elif index == 1: # Measures | ||||
self.ui.measureBox.blockSignals(True) | |||||
self.ui.measureBox.setCurrentIndex(value-1) | self.ui.measureBox.setCurrentIndex(value-1) | ||||
self.ui.piano.setMeasures(self.ui.measureBox.currentText()) | |||||
self.ui.measureBox.blockSignals(False) | |||||
elif index == 2: # DefLength | elif index == 2: # DefLength | ||||
self.ui.defaultLengthBox.blockSignals(True) | |||||
self.ui.defaultLengthBox.setCurrentIndex(value) | self.ui.defaultLengthBox.setCurrentIndex(value) | ||||
self.ui.piano.setDefaultLength(self.ui.defaultLengthBox.currentText()) | |||||
self.ui.defaultLengthBox.blockSignals(False) | |||||
elif index == 3: # Quantize | elif index == 3: # Quantize | ||||
self.ui.quantizeBox.blockSignals(True) | |||||
self.ui.quantizeBox.setCurrentIndex(value) | self.ui.quantizeBox.setCurrentIndex(value) | ||||
self.ui.piano.setQuantize(self.ui.quantizeBox.currentText()) | |||||
self.ui.quantizeBox.blockSignals(False) | |||||
def dspStateChanged(self, key, value): | def dspStateChanged(self, key, value): | ||||
pass | pass | ||||
@@ -193,21 +219,21 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
# Custom callback | # Custom callback | ||||
def updateMeasureBox(self, index): | def updateMeasureBox(self, index): | ||||
self.measureBox.setCurrentIndex(index-1) | |||||
self.ui.measureBox.setCurrentIndex(index-1) | |||||
def sendMsg(self, data): | def sendMsg(self, data): | ||||
msg = data[0] | msg = data[0] | ||||
if msg == "midievent-remove": | |||||
if msg == "midievent-add": | |||||
note, start, length, vel = data[1:5] | 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_start, 3, MIDI_STATUS_NOTE_ON, note, vel]) | ||||
self.send([msg, note_stop, 3, MIDI_STATUS_NOTE_OFF, 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, 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_start, 3, MIDI_STATUS_NOTE_ON, note, vel]) | ||||
self.send([msg, note_stop, 3, MIDI_STATUS_NOTE_OFF, 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 | # adds single midi event | ||||
time = int(self.readlineblock()) | time = int(self.readlineblock()) | ||||
size = 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) | self.handleMidiEvent(time, size, data) | ||||
elif msg == "transport": | 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'] | old_frame = self.fTransportInfo['frame'] | ||||
@@ -246,13 +266,19 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
"beat": beat, | "beat": beat, | ||||
"tick": tick, | "tick": tick, | ||||
"bpm": bpm, | "bpm": bpm, | ||||
"sigNum": sigNum, | |||||
"sigDenom": sigDenom | |||||
"ticksPerBeat": self.TICKS_PER_BEAT, | |||||
} | } | ||||
if old_frame != frame: | if old_frame != frame: | ||||
self.ui.piano.movePlayHead(self.fTransportInfo) | 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: | else: | ||||
ExternalUI.msgCallback(self, msg) | ExternalUI.msgCallback(self, msg) | ||||
@@ -260,16 +286,11 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
# Internal stuff | # Internal stuff | ||||
def handleMidiEvent(self, time, size, data): | 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) | status = MIDI_GET_STATUS_FROM_DATA(data) | ||||
channel = MIDI_GET_CHANNEL_FROM_DATA(data) | channel = MIDI_GET_CHANNEL_FROM_DATA(data) | ||||
if status == MIDI_STATUS_NOTE_ON: | if status == MIDI_STATUS_NOTE_ON: | ||||
note = data[1] | note = data[1] | ||||
velo = data[2] | velo = data[2] | ||||
@@ -279,27 +300,28 @@ class MidiPatternW(ExternalUI, QMainWindow): | |||||
elif status == MIDI_STATUS_NOTE_OFF: | elif status == MIDI_STATUS_NOTE_OFF: | ||||
note = data[1] | note = data[1] | ||||
velo = data[2] | |||||
# find previous note-on that matches this note and channel | # find previous note-on that matches this note and channel | ||||
for noteOnMsg in self.fPendingNoteOns: | 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 | continue | ||||
if note_ != note: | |||||
if on_note != note: | |||||
continue | continue | ||||
# found it | # 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) | self.fPendingNoteOns.remove(noteOnMsg) | ||||
break | 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 ------------------ | #--------------- main ------------------ | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
import resources_rc | import resources_rc | ||||