@@ -627,9 +627,6 @@ | |||||
<layout class="QVBoxLayout" name="verticalLayout_5"> | <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||
<item> | <item> | ||||
<widget class="QDoubleSpinBox" name="dsb_transport_bpm"> | <widget class="QDoubleSpinBox" name="dsb_transport_bpm"> | ||||
<property name="enabled"> | |||||
<bool>false</bool> | |||||
</property> | |||||
<property name="suffix"> | <property name="suffix"> | ||||
<string> BPM</string> | <string> BPM</string> | ||||
</property> | </property> | ||||
@@ -988,6 +988,11 @@ public: | |||||
*/ | */ | ||||
virtual void transportPause() noexcept; | virtual void transportPause() noexcept; | ||||
/*! | |||||
* Set the engine transport bpm to @a bpm. | |||||
*/ | |||||
virtual void transportBPM(const double bpm) noexcept; | |||||
/*! | /*! | ||||
* Relocate the engine transport to @a frames. | * Relocate the engine transport to @a frames. | ||||
*/ | */ | ||||
@@ -433,6 +433,11 @@ CARLA_EXPORT void carla_transport_play(); | |||||
*/ | */ | ||||
CARLA_EXPORT void carla_transport_pause(); | CARLA_EXPORT void carla_transport_pause(); | ||||
/*! | |||||
* Set the engine transport bpm. | |||||
*/ | |||||
CARLA_EXPORT void carla_transport_bpm(double bpm); | |||||
/*! | /*! | ||||
* Relocate the engine transport to a specific frame. | * Relocate the engine transport to a specific frame. | ||||
*/ | */ | ||||
@@ -743,6 +743,14 @@ void carla_transport_pause() | |||||
gStandalone.engine->transportPause(); | gStandalone.engine->transportPause(); | ||||
} | } | ||||
void carla_transport_bpm(double bpm) | |||||
{ | |||||
CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr && gStandalone.engine->isRunning(),); | |||||
carla_debug("carla_transport_bpm(%f)", bpm); | |||||
gStandalone.engine->transportBPM(bpm); | |||||
} | |||||
void carla_transport_relocate(uint64_t frame) | void carla_transport_relocate(uint64_t frame) | ||||
{ | { | ||||
CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr && gStandalone.engine->isRunning(),); | CARLA_SAFE_ASSERT_RETURN(gStandalone.engine != nullptr && gStandalone.engine->isRunning(),); | ||||
@@ -1351,6 +1351,13 @@ void CarlaEngine::transportPause() noexcept | |||||
pData->time.setNeedsReset(); | pData->time.setNeedsReset(); | ||||
} | } | ||||
void CarlaEngine::transportBPM(const double bpm) noexcept | |||||
{ | |||||
try { | |||||
pData->time.setBPM(bpm); | |||||
} CARLA_SAFE_EXCEPTION("CarlaEngine::transportBPM"); | |||||
} | |||||
void CarlaEngine::transportRelocate(const uint64_t frame) noexcept | void CarlaEngine::transportRelocate(const uint64_t frame) noexcept | ||||
{ | { | ||||
pData->time.relocate(frame); | pData->time.relocate(frame); | ||||
@@ -136,6 +136,16 @@ void EngineInternalTime::enableLink(const bool enable) | |||||
needsReset = true; | needsReset = true; | ||||
} | } | ||||
void EngineInternalTime::setBPM(const double bpm) | |||||
{ | |||||
beatsPerMinute = bpm; | |||||
#if defined(HAVE_HYLIA) && !defined(BUILD_BRIDGE) | |||||
if (hylia.instance != nullptr) | |||||
hylia_set_beats_per_minute(hylia.instance, bpm); | |||||
#endif | |||||
} | |||||
void EngineInternalTime::setNeedsReset() noexcept | void EngineInternalTime::setNeedsReset() noexcept | ||||
{ | { | ||||
needsReset = true; | needsReset = true; | ||||
@@ -163,10 +173,8 @@ void EngineInternalTime::fillEngineTimeInfo(const uint32_t newFrames) noexcept | |||||
if (needsReset) | if (needsReset) | ||||
{ | { | ||||
timeInfo.valid = EngineTimeInfo::kValidBBT; | timeInfo.valid = EngineTimeInfo::kValidBBT; | ||||
timeInfo.bbt.beatsPerBar = beatsPerBar; | |||||
timeInfo.bbt.beatType = 4.0f; | timeInfo.bbt.beatType = 4.0f; | ||||
timeInfo.bbt.ticksPerBeat = kTicksPerBeat; | timeInfo.bbt.ticksPerBeat = kTicksPerBeat; | ||||
timeInfo.bbt.beatsPerMinute = beatsPerMinute; | |||||
double abs_beat, abs_tick; | double abs_beat, abs_tick; | ||||
@@ -195,8 +203,8 @@ void EngineInternalTime::fillEngineTimeInfo(const uint32_t newFrames) noexcept | |||||
needsReset = false; | needsReset = false; | ||||
} | } | ||||
timeInfo.bbt.bar = (int32_t)(std::floor(abs_beat / timeInfo.bbt.beatsPerBar) + 0.5); | |||||
timeInfo.bbt.beat = (int32_t)(abs_beat - (timeInfo.bbt.bar * timeInfo.bbt.beatsPerBar) + 1.5); | |||||
timeInfo.bbt.bar = (int32_t)(std::floor(abs_beat / beatsPerBar) + 0.5); | |||||
timeInfo.bbt.beat = (int32_t)(abs_beat - (timeInfo.bbt.bar * beatsPerBar) + 1.5); | |||||
timeInfo.bbt.barStartTick = timeInfo.bbt.bar * beatsPerBar * kTicksPerBeat; | timeInfo.bbt.barStartTick = timeInfo.bbt.bar * beatsPerBar * kTicksPerBeat; | ||||
++timeInfo.bbt.bar; | ++timeInfo.bbt.bar; | ||||
@@ -220,6 +228,8 @@ void EngineInternalTime::fillEngineTimeInfo(const uint32_t newFrames) noexcept | |||||
} | } | ||||
} | } | ||||
timeInfo.bbt.beatsPerBar = beatsPerBar; | |||||
timeInfo.bbt.beatsPerMinute = beatsPerMinute; | |||||
timeInfo.bbt.tick = (int32_t)(ticktmp + 0.5); | timeInfo.bbt.tick = (int32_t)(ticktmp + 0.5); | ||||
tick = ticktmp; | tick = ticktmp; | ||||
@@ -237,10 +247,8 @@ void EngineInternalTime::fillJackTimeInfo(jack_position_t* const pos, const uint | |||||
if (needsReset) | if (needsReset) | ||||
{ | { | ||||
pos->valid = JackPositionBBT; | pos->valid = JackPositionBBT; | ||||
pos->beats_per_bar = beatsPerBar; | |||||
pos->beat_type = 4.0f; | pos->beat_type = 4.0f; | ||||
pos->ticks_per_beat = kTicksPerBeat; | pos->ticks_per_beat = kTicksPerBeat; | ||||
pos->beats_per_minute = beatsPerMinute; | |||||
double abs_beat, abs_tick; | double abs_beat, abs_tick; | ||||
@@ -269,9 +277,9 @@ void EngineInternalTime::fillJackTimeInfo(jack_position_t* const pos, const uint | |||||
needsReset = false; | needsReset = false; | ||||
} | } | ||||
pos->bar = (int32_t)(std::floor(abs_beat / pos->beats_per_bar) + 0.5); | |||||
pos->beat = (int32_t)(abs_beat - (pos->bar * pos->beats_per_bar) + 1.5); | |||||
pos->bar_start_tick = pos->bar * pos->beats_per_bar * kTicksPerBeat; | |||||
pos->bar = (int32_t)(std::floor(abs_beat / beatsPerBar) + 0.5); | |||||
pos->beat = (int32_t)(abs_beat - (pos->bar * beatsPerBar) + 1.5); | |||||
pos->bar_start_tick = pos->bar * beatsPerBar * kTicksPerBeat; | |||||
++pos->bar; | ++pos->bar; | ||||
//ticktmp = abs_tick - pos->bar_start_tick; | //ticktmp = abs_tick - pos->bar_start_tick; | ||||
@@ -294,6 +302,8 @@ void EngineInternalTime::fillJackTimeInfo(jack_position_t* const pos, const uint | |||||
} | } | ||||
} | } | ||||
pos->beats_per_bar = beatsPerBar; | |||||
pos->beats_per_minute = beatsPerMinute; | |||||
pos->tick = (int32_t)(ticktmp + 0.5); | pos->tick = (int32_t)(ticktmp + 0.5); | ||||
tick = ticktmp; | tick = ticktmp; | ||||
} | } | ||||
@@ -121,6 +121,7 @@ public: | |||||
void updateAudioValues(const uint32_t bufferSize, const double sampleRate); | void updateAudioValues(const uint32_t bufferSize, const double sampleRate); | ||||
void enableLink(const bool enable); | void enableLink(const bool enable); | ||||
void setBPM(const double bpm); | |||||
void setNeedsReset() noexcept; | void setNeedsReset() noexcept; | ||||
void relocate(const uint64_t frame) noexcept; | void relocate(const uint64_t frame) noexcept; | ||||
@@ -816,6 +816,7 @@ public: | |||||
#ifdef BUILD_BRIDGE | #ifdef BUILD_BRIDGE | ||||
fIsRunning(false) | fIsRunning(false) | ||||
#else | #else | ||||
fTimebaseMaster(false), | |||||
fUsedGroups(), | fUsedGroups(), | ||||
fUsedPorts(), | fUsedPorts(), | ||||
fUsedConnections(), | fUsedConnections(), | ||||
@@ -951,9 +952,10 @@ public: | |||||
jackbridge_set_freewheel_callback(fClient, carla_jack_freewheel_callback, this); | jackbridge_set_freewheel_callback(fClient, carla_jack_freewheel_callback, this); | ||||
jackbridge_set_latency_callback(fClient, carla_jack_latency_callback, this); | jackbridge_set_latency_callback(fClient, carla_jack_latency_callback, this); | ||||
jackbridge_set_process_callback(fClient, carla_jack_process_callback, this); | jackbridge_set_process_callback(fClient, carla_jack_process_callback, this); | ||||
jackbridge_set_timebase_callback(fClient, true, carla_jack_timebase_callback, this); | |||||
jackbridge_on_shutdown(fClient, carla_jack_shutdown_callback, this); | jackbridge_on_shutdown(fClient, carla_jack_shutdown_callback, this); | ||||
fTimebaseMaster = jackbridge_set_timebase_callback(fClient, true, carla_jack_timebase_callback, this); | |||||
if (pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY) | if (pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY) | ||||
initJackPatchbay(jackClientName); | initJackPatchbay(jackClientName); | ||||
@@ -1373,7 +1375,7 @@ public: | |||||
{ | { | ||||
// old timebase master no longer active, make ourselves master again | // old timebase master no longer active, make ourselves master again | ||||
pData->time.setNeedsReset(); | pData->time.setNeedsReset(); | ||||
jackbridge_set_timebase_callback(fClient, true, carla_jack_timebase_callback, this); | |||||
fTimebaseMaster = jackbridge_set_timebase_callback(fClient, true, carla_jack_timebase_callback, this); | |||||
} | } | ||||
try { | try { | ||||
@@ -1395,6 +1397,31 @@ public: | |||||
} | } | ||||
} | } | ||||
void transportBPM(const double bpm) noexcept override | |||||
{ | |||||
CarlaEngine::transportBPM(bpm); | |||||
if (fClient == nullptr || fTimebaseMaster) | |||||
return; | |||||
jack_position_t jpos; | |||||
// invalidate | |||||
jpos.unique_1 = 1; | |||||
jpos.unique_2 = 2; | |||||
jackbridge_transport_query(fClient, &jpos); | |||||
if (jpos.unique_1 == jpos.unique_2 && (jpos.valid & JackPositionBBT) != 0) | |||||
{ | |||||
carla_stdout("NOTE: Changing BPM without being JACK timebase master"); | |||||
jpos.beats_per_minute = bpm; | |||||
try { | |||||
jackbridge_transport_reposition(fClient, &jpos); | |||||
} catch(...) {} | |||||
} | |||||
} | |||||
void transportRelocate(const uint64_t frame) noexcept override | void transportRelocate(const uint64_t frame) noexcept override | ||||
{ | { | ||||
if (pData->options.transportMode == ENGINE_TRANSPORT_MODE_INTERNAL) | if (pData->options.transportMode == ENGINE_TRANSPORT_MODE_INTERNAL) | ||||
@@ -1984,6 +2011,7 @@ private: | |||||
jack_port_t* fRackPorts[kRackPortCount]; | jack_port_t* fRackPorts[kRackPortCount]; | ||||
bool fTimebaseMaster; | |||||
PatchbayGroupList fUsedGroups; | PatchbayGroupList fUsedGroups; | ||||
PatchbayPortList fUsedPorts; | PatchbayPortList fUsedPorts; | ||||
PatchbayConnectionList fUsedConnections; | PatchbayConnectionList fUsedConnections; | ||||
@@ -236,6 +236,14 @@ protected: | |||||
{ | { | ||||
fEngine->transportPause(); | fEngine->transportPause(); | ||||
} | } | ||||
else if (std::strcmp(msg, "transport_bpm") == 0) | |||||
{ | |||||
double bpm; | |||||
CARLA_SAFE_ASSERT_RETURN(readNextLineAsDouble(bpm), true); | |||||
fEngine->transportBPM(bpm); | |||||
} | |||||
else if (std::strcmp(msg, "transport_relocate") == 0) | else if (std::strcmp(msg, "transport_relocate") == 0) | ||||
{ | { | ||||
uint64_t frame; | uint64_t frame; | ||||
@@ -1382,6 +1382,11 @@ class CarlaHostMeta(object): | |||||
def transport_pause(self): | def transport_pause(self): | ||||
raise NotImplementedError | raise NotImplementedError | ||||
# Pause the engine transport. | |||||
@abstractmethod | |||||
def transport_bpm(self, bpm): | |||||
raise NotImplementedError | |||||
# Relocate the engine transport to a specific frame. | # Relocate the engine transport to a specific frame. | ||||
@abstractmethod | @abstractmethod | ||||
def transport_relocate(self, frame): | def transport_relocate(self, frame): | ||||
@@ -1927,6 +1932,9 @@ class CarlaHostNull(CarlaHostMeta): | |||||
def transport_pause(self): | def transport_pause(self): | ||||
return | return | ||||
def transport_bpm(self, bpm): | |||||
return | |||||
def transport_relocate(self, frame): | def transport_relocate(self, frame): | ||||
return | return | ||||
@@ -2206,6 +2214,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||||
self.lib.carla_transport_pause.argtypes = None | self.lib.carla_transport_pause.argtypes = None | ||||
self.lib.carla_transport_pause.restype = None | self.lib.carla_transport_pause.restype = None | ||||
self.lib.carla_transport_bpm.argtypes = [c_double] | |||||
self.lib.carla_transport_bpm.restype = None | |||||
self.lib.carla_transport_relocate.argtypes = [c_uint64] | self.lib.carla_transport_relocate.argtypes = [c_uint64] | ||||
self.lib.carla_transport_relocate.restype = None | self.lib.carla_transport_relocate.restype = None | ||||
@@ -2477,6 +2488,9 @@ class CarlaHostDLL(CarlaHostMeta): | |||||
def transport_pause(self): | def transport_pause(self): | ||||
self.lib.carla_transport_pause() | self.lib.carla_transport_pause() | ||||
def transport_bpm(self, bpm): | |||||
self.lib.carla_transport_bpm(bpm) | |||||
def transport_relocate(self, frame): | def transport_relocate(self, frame): | ||||
self.lib.carla_transport_relocate(frame) | self.lib.carla_transport_relocate(frame) | ||||
@@ -2812,6 +2826,9 @@ class CarlaHostPlugin(CarlaHostMeta): | |||||
def transport_pause(self): | def transport_pause(self): | ||||
self.sendMsg(["transport_pause"]) | self.sendMsg(["transport_pause"]) | ||||
def transport_bpm(self, bpm): | |||||
self.sendMsg(["transport_bpm", bpm]) | |||||
def transport_relocate(self, frame): | def transport_relocate(self, frame): | ||||
self.sendMsg(["transport_relocate"]) | self.sendMsg(["transport_relocate"]) | ||||
@@ -292,7 +292,7 @@ class HostWindow(QMainWindow): | |||||
self.ui.l_transport_frame.setMinimumWidth(minValueWidth + 3) | self.ui.l_transport_frame.setMinimumWidth(minValueWidth + 3) | ||||
self.ui.l_transport_time.setMinimumWidth(minValueWidth + 3) | self.ui.l_transport_time.setMinimumWidth(minValueWidth + 3) | ||||
if host.isPlugin: | |||||
if host.isControl or host.isPlugin: | |||||
self.ui.b_transport_play.setEnabled(False) | self.ui.b_transport_play.setEnabled(False) | ||||
self.ui.b_transport_stop.setEnabled(False) | self.ui.b_transport_stop.setEnabled(False) | ||||
self.ui.b_transport_backwards.setEnabled(False) | self.ui.b_transport_backwards.setEnabled(False) | ||||
@@ -441,6 +441,7 @@ class HostWindow(QMainWindow): | |||||
self.ui.b_transport_stop.clicked.connect(self.slot_transportStop) | self.ui.b_transport_stop.clicked.connect(self.slot_transportStop) | ||||
self.ui.b_transport_backwards.clicked.connect(self.slot_transportBackwards) | self.ui.b_transport_backwards.clicked.connect(self.slot_transportBackwards) | ||||
self.ui.b_transport_forwards.clicked.connect(self.slot_transportForwards) | self.ui.b_transport_forwards.clicked.connect(self.slot_transportForwards) | ||||
self.ui.dsb_transport_bpm.valueChanged.connect(self.slot_transportBpmChanged) | |||||
self.ui.cb_transport_jack.clicked.connect(self.slot_transportJackEnabled) | self.ui.cb_transport_jack.clicked.connect(self.slot_transportJackEnabled) | ||||
self.ui.cb_transport_link.clicked.connect(self.slot_transportLinkEnabled) | self.ui.cb_transport_link.clicked.connect(self.slot_transportLinkEnabled) | ||||
@@ -1683,7 +1684,9 @@ class HostWindow(QMainWindow): | |||||
self.fLastTransportBPM = bpm | self.fLastTransportBPM = bpm | ||||
if bpm > 0.0: | if bpm > 0.0: | ||||
self.ui.dsb_transport_bpm.blockSignals(True) | |||||
self.ui.dsb_transport_bpm.setValue(bpm) | self.ui.dsb_transport_bpm.setValue(bpm) | ||||
self.ui.dsb_transport_bpm.blockSignals(False) | |||||
self.ui.dsb_transport_bpm.setStyleSheet("") | self.ui.dsb_transport_bpm.setStyleSheet("") | ||||
else: | else: | ||||
self.ui.dsb_transport_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }") | self.ui.dsb_transport_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }") | ||||
@@ -1725,6 +1728,10 @@ class HostWindow(QMainWindow): | |||||
self.host.transport_relocate(newFrame) | self.host.transport_relocate(newFrame) | ||||
@pyqtSlot(float) | |||||
def slot_transportBpmChanged(self, newValue): | |||||
self.host.transport_bpm(newValue) | |||||
@pyqtSlot() | @pyqtSlot() | ||||
def slot_transportForwards(self): | def slot_transportForwards(self): | ||||
if self.fSampleRate == 0.0 or self.host.isPlugin or not self.host.is_engine_running(): | if self.fSampleRate == 0.0 or self.host.isPlugin or not self.host.is_engine_running(): | ||||