(
+ kMenuBarHintConfigKey, true)) {
+ return;
+ }
+ QString title = tr("Allow Mixxx to hide the menu bar?");
+ //: Always show the menu bar?
+ QString hideBtnLabel = tr("Hide");
+ QString showBtnLabel = tr("Always show");
+ //: Keep formatting tags (bold text) and
(linebreak).
+ //: %1 is the placeholder for the 'Always show' button label
+ QString desc = tr(
+ "The Mixxx menu bar is hidden and can be toggled with a single press "
+ "of the Alt key.
"
+ "Click %1 to agree.
"
+ "Click %2 to disable that, for example if you don't use Mixxx "
+ "with a keyboard.
"
+ "You can change this setting any time in Preferences -> Interface."
+ "
") // line break for some extra margin to the checkbox
+ .arg(hideBtnLabel, showBtnLabel);
+
+ QMessageBox msg;
+ msg.setIcon(QMessageBox::Question);
+ msg.setWindowTitle(title);
+ msg.setText(desc);
+ QCheckBox askAgainCheckBox;
+ askAgainCheckBox.setText(tr("Ask me again"));
+ askAgainCheckBox.setCheckState(Qt::Checked);
+ msg.setCheckBox(&askAgainCheckBox);
+ QPushButton* pHideBtn = msg.addButton(hideBtnLabel, QMessageBox::AcceptRole);
+ QPushButton* pShowBtn = msg.addButton(showBtnLabel, QMessageBox::RejectRole);
+ msg.setDefaultButton(pShowBtn);
+ msg.exec();
+
+ m_pCoreServices->getSettings()->setValue(
+ kMenuBarHintConfigKey,
+ askAgainCheckBox.checkState() == Qt::Checked ? 1 : 0);
+
+ m_pCoreServices->getSettings()->setValue(
+ kHideMenuBarConfigKey,
+ msg.clickedButton() == pHideBtn ? 1 : 0);
+}
+#endif
+
+QDialog::DialogCode MixxxMainWindow::soundDeviceErrorDlg(
+ const QString& title, const QString& text, bool* retryClicked) {
+ QMessageBox msgBox;
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setWindowTitle(title);
+ msgBox.setText(text);
+
+ QPushButton* retryButton =
+ msgBox.addButton(tr("Retry"), QMessageBox::ActionRole);
+ QPushButton* reconfigureButton =
+ msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole);
+ QPushButton* wikiButton =
+ msgBox.addButton(tr("Help"), QMessageBox::ActionRole);
+ QPushButton* exitButton =
+ msgBox.addButton(tr("Exit"), QMessageBox::ActionRole);
+
+ while (true) {
+ msgBox.exec();
+
+ if (msgBox.clickedButton() == retryButton) {
+ m_pCoreServices->getSoundManager()->clearAndQueryDevices();
+ *retryClicked = true;
+ return QDialog::Accepted;
+ } else if (msgBox.clickedButton() == wikiButton) {
+ mixxx::DesktopHelper::openUrl(QUrl(MIXXX_WIKI_TROUBLESHOOTING_SOUND_URL));
+ wikiButton->setEnabled(false);
+ } else if (msgBox.clickedButton() == reconfigureButton) {
+ msgBox.hide();
+
+ m_pCoreServices->getSoundManager()->clearAndQueryDevices();
+ // This way of opening the dialog allows us to use it synchronously
+ m_pPrefDlg->setWindowModality(Qt::ApplicationModal);
+ // Open preferences, sound hardware page is selected (default on first call)
+ m_pPrefDlg->exec();
+ if (m_pPrefDlg->result() == QDialog::Accepted) {
+ return QDialog::Accepted;
+ }
+
+ msgBox.show();
+ } else if (msgBox.clickedButton() == exitButton) {
+ // Will finally quit Mixxx
+ return QDialog::Rejected;
+ }
+ }
+}
+
+QDialog::DialogCode MixxxMainWindow::soundDeviceBusyDlg(bool* retryClicked) {
+ QString title(tr("Sound Device Busy"));
+ QString text(
+ " " %
+ tr("Mixxx was unable to open all the configured sound devices.") +
+ "
" %
+ m_pCoreServices->getSoundManager()->getErrorDeviceName() %
+ " is used by another application or not plugged in."
+ "
"
+ "- " %
+ tr("Retry after closing the other application "
+ "or reconnecting a sound device") %
+ "
"
+ "- " %
+ tr("Reconfigure Mixxx's sound device settings.") %
+ "
"
+ "- " %
+ tr("Get Help from the Mixxx Wiki.") %
+ "
"
+ "- " %
+ tr("Exit Mixxx.") %
+ "
"
+ "
");
+ return soundDeviceErrorDlg(title, text, retryClicked);
+}
+
+QDialog::DialogCode MixxxMainWindow::soundDeviceErrorMsgDlg(
+ SoundDeviceStatus status, bool* retryClicked) {
+ QString title(tr("Sound Device Error"));
+ QString text(" " %
+ tr("Mixxx was unable to open all the configured sound "
+ "devices.") +
+ "
" %
+ m_pCoreServices->getSoundManager()
+ ->getLastErrorMessage(status)
+ .replace("\n", "
") %
+ "
"
+ "- " %
+ tr("Retry after fixing an issue") %
+ "
"
+ "- " %
+ tr("Reconfigure Mixxx's sound device settings.") %
+ "
"
+ "- " %
+ tr("Get Help from the Mixxx Wiki.") %
+ "
"
+ "- " %
+ tr("Exit Mixxx.") %
+ "
"
+ "
");
+ return soundDeviceErrorDlg(title, text, retryClicked);
+}
+
+QDialog::DialogCode MixxxMainWindow::noOutputDlg(bool* continueClicked) {
+ QMessageBox msgBox;
+ msgBox.setIcon(QMessageBox::Warning);
+ msgBox.setWindowTitle(tr("No Output Devices"));
+ msgBox.setText(
+ "" + tr("Mixxx was configured without any output sound devices. "
+ "Audio processing will be disabled without a configured output device.") +
+ ""
+ "- " +
+ tr("Continue without any outputs.") +
+ "
"
+ "- " +
+ tr("Reconfigure Mixxx's sound device settings.") +
+ "
"
+ "- " +
+ tr("Exit Mixxx.") +
+ "
"
+ "
");
+
+ QPushButton* continueButton =
+ msgBox.addButton(tr("Continue"), QMessageBox::ActionRole);
+ QPushButton* reconfigureButton =
+ msgBox.addButton(tr("Reconfigure"), QMessageBox::ActionRole);
+ QPushButton* exitButton =
+ msgBox.addButton(tr("Exit"), QMessageBox::ActionRole);
+
+ while (true) {
+ msgBox.exec();
+
+ if (msgBox.clickedButton() == continueButton) {
+ *continueClicked = true;
+ return QDialog::Accepted;
+ } else if (msgBox.clickedButton() == reconfigureButton) {
+ msgBox.hide();
+
+ // This way of opening the dialog allows us to use it synchronously
+ m_pPrefDlg->setWindowModality(Qt::ApplicationModal);
+ m_pPrefDlg->exec();
+ if (m_pPrefDlg->result() == QDialog::Accepted) {
+ return QDialog::Accepted;
+ }
+
+ msgBox.show();
+
+ } else if (msgBox.clickedButton() == exitButton) {
+ // Will finally quit Mixxx
+ return QDialog::Rejected;
+ }
+ }
+}
+
+void MixxxMainWindow::slotUpdateWindowTitle(TrackPointer pTrack) {
+ QString appTitle = VersionStore::applicationName();
+ QString filePath;
+
+ // If we have a track, use getInfo() to format a summary string and prepend
+ // it to the title.
+ // TODO(rryan): Does this violate Mac App Store policies?
+ if (pTrack) {
+ QString trackInfo = pTrack->getInfo();
+ if (!trackInfo.isEmpty()) {
+ appTitle = QString("%1 | %2").arg(trackInfo, appTitle);
+ }
+ filePath = pTrack->getLocation();
+ }
+ setWindowTitle(appTitle);
+
+ // Display a draggable proxy icon for the track in the title bar on
+ // platforms that support it, e.g. macOS
+ setWindowFilePath(filePath);
+}
+
+void MixxxMainWindow::createMenuBar() {
+ qWarning() << " $ createMenuBar";
+ ScopedTimer t(QStringLiteral("MixxxMainWindow::createMenuBar"));
+ DEBUG_ASSERT(m_pCoreServices->getKeyboardConfig());
+ m_pMenuBar = make_parented(
+ this, m_pCoreServices->getSettings(), m_pCoreServices->getKeyboardConfig().get());
+ if (m_pCentralWidget) {
+ m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet());
+ }
+ setMenuBar(m_pMenuBar);
+}
+
+void MixxxMainWindow::connectMenuBar() {
+ // This function might be invoked multiple times on startup
+ // so all connections must be unique!
+ qWarning() << " $ connectMenuBar";
+
+ ScopedTimer t(QStringLiteral("MixxxMainWindow::connectMenuBar"));
+ connect(this,
+ &MixxxMainWindow::skinLoaded,
+ m_pMenuBar,
+ &WMainMenuBar::onNewSkinLoaded,
+ Qt::UniqueConnection);
+
+ // Misc
+ connect(m_pMenuBar,
+ &WMainMenuBar::quit,
+ this,
+ &MixxxMainWindow::close,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::showPreferences,
+ this,
+ &MixxxMainWindow::slotOptionsPreferences,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::loadTrackToDeck,
+ this,
+ &MixxxMainWindow::slotFileLoadSongPlayer,
+ Qt::UniqueConnection);
+
+ connect(m_pMenuBar,
+ &WMainMenuBar::showKeywheel,
+ this,
+ &MixxxMainWindow::slotShowKeywheel,
+ Qt::UniqueConnection);
+
+ // Fullscreen
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleFullScreen,
+ this,
+ &MixxxMainWindow::slotViewFullScreen,
+ Qt::UniqueConnection);
+ connect(this,
+ &MixxxMainWindow::fullScreenChanged,
+ m_pMenuBar,
+ &WMainMenuBar::onFullScreenStateChange,
+ Qt::UniqueConnection);
+ // Refresh the Fullscreen checkbox for the case we went fullscreen earlier
+ m_pMenuBar->onFullScreenStateChange(isFullScreen());
+
+ // Keyboard shortcuts
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleKeyboardShortcuts,
+ m_pCoreServices.get(),
+ &mixxx::CoreServices::slotOptionsKeyboard,
+ Qt::UniqueConnection);
+
+ // Help
+ connect(m_pMenuBar,
+ &WMainMenuBar::showAbout,
+ this,
+ &MixxxMainWindow::slotHelpAbout,
+ Qt::UniqueConnection);
+
+ // Developer
+ connect(m_pMenuBar,
+ &WMainMenuBar::reloadSkin,
+ this,
+ &MixxxMainWindow::rebootMixxxView,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleDeveloperTools,
+ this,
+ &MixxxMainWindow::slotDeveloperTools,
+ Qt::UniqueConnection);
+
+ if (m_pCoreServices->getRecordingManager()) {
+ connect(m_pCoreServices->getRecordingManager().get(),
+ &RecordingManager::isRecording,
+ m_pMenuBar,
+ &WMainMenuBar::onRecordingStateChange,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleRecording,
+ m_pCoreServices->getRecordingManager().get(),
+ &RecordingManager::slotSetRecording,
+ Qt::UniqueConnection);
+ m_pMenuBar->onRecordingStateChange(
+ m_pCoreServices->getRecordingManager()->isRecordingActive());
+ }
+
+#ifdef __BROADCAST__
+ if (m_pCoreServices->getBroadcastManager()) {
+ connect(m_pCoreServices->getBroadcastManager().get(),
+ &BroadcastManager::broadcastEnabled,
+ m_pMenuBar,
+ &WMainMenuBar::onBroadcastingStateChange,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleBroadcasting,
+ m_pCoreServices->getBroadcastManager().get(),
+ &BroadcastManager::setEnabled,
+ Qt::UniqueConnection);
+ m_pMenuBar->onBroadcastingStateChange(m_pCoreServices->getBroadcastManager()->isEnabled());
+ }
+#endif
+
+#ifdef __VINYLCONTROL__
+ if (m_pCoreServices->getVinylControlManager()) {
+ connect(m_pMenuBar,
+ &WMainMenuBar::toggleVinylControl,
+ m_pCoreServices->getVinylControlManager().get(),
+ &VinylControlManager::toggleVinylControl,
+ Qt::UniqueConnection);
+ connect(m_pCoreServices->getVinylControlManager().get(),
+ &VinylControlManager::vinylControlDeckEnabled,
+ m_pMenuBar,
+ &WMainMenuBar::onVinylControlDeckEnabledStateChange,
+ Qt::UniqueConnection);
+ }
+#endif
+
+ auto pPlayerManager = m_pCoreServices->getPlayerManager();
+ if (pPlayerManager) {
+ connect(pPlayerManager.get(),
+ &PlayerManager::numberOfDecksChanged,
+ m_pMenuBar,
+ &WMainMenuBar::onNumberOfDecksChanged,
+ Qt::UniqueConnection);
+ m_pMenuBar->onNumberOfDecksChanged(pPlayerManager->numberOfDecks());
+ }
+
+ if (m_pCoreServices->getTrackCollectionManager()) {
+ connect(m_pMenuBar,
+ &WMainMenuBar::rescanLibrary,
+ m_pCoreServices->getTrackCollectionManager().get(),
+ &TrackCollectionManager::startLibraryScan,
+ Qt::UniqueConnection);
+ connect(m_pCoreServices->getTrackCollectionManager().get(),
+ &TrackCollectionManager::libraryScanStarted,
+ m_pMenuBar,
+ &WMainMenuBar::onLibraryScanStarted,
+ Qt::UniqueConnection);
+ connect(m_pCoreServices->getTrackCollectionManager().get(),
+ &TrackCollectionManager::libraryScanFinished,
+ m_pMenuBar,
+ &WMainMenuBar::onLibraryScanFinished,
+ Qt::UniqueConnection);
+ }
+
+ if (m_pCoreServices->getLibrary()) {
+ connect(m_pMenuBar,
+ &WMainMenuBar::createCrate,
+ m_pCoreServices->getLibrary().get(),
+ &Library::slotCreateCrate,
+ Qt::UniqueConnection);
+ connect(m_pMenuBar,
+ &WMainMenuBar::createPlaylist,
+ m_pCoreServices->getLibrary().get(),
+ &Library::slotCreatePlaylist,
+ Qt::UniqueConnection);
+ }
+
+#ifdef __ENGINEPRIME__
+ DEBUG_ASSERT(m_pLibraryExporter);
+ connect(m_pMenuBar,
+ &WMainMenuBar::exportLibrary,
+ m_pLibraryExporter.get(),
+ &mixxx::LibraryExporter::slotRequestExport,
+ Qt::UniqueConnection);
+#endif
+}
+
+/// Enable/disable listening to Alt key press for toggling the menubar.
+#ifndef __APPLE__
+void MixxxMainWindow::slotUpdateMenuBarAltKeyConnection() {
+ if (!m_pCoreServices->getKeyboardEventFilter() || !m_pMenuBar) {
+ return;
+ }
+
+ if (m_pCoreServices->getSettings()->getValue(kHideMenuBarConfigKey, false)) {
+ // with Qt::UniqueConnection we don't need to check whether we're already connected
+ connect(m_pCoreServices->getKeyboardEventFilter().get(),
+ &KeyboardEventFilter::altPressedWithoutKeys,
+ m_pMenuBar,
+ &WMainMenuBar::slotToggleMenuBar,
+ Qt::UniqueConnection);
+ m_pMenuBar->hideMenuBar();
+ } else {
+ disconnect(m_pCoreServices->getKeyboardEventFilter().get(),
+ &KeyboardEventFilter::altPressedWithoutKeys,
+ m_pMenuBar,
+ &WMainMenuBar::slotToggleMenuBar);
+ m_pMenuBar->showMenuBar();
+ }
+}
+#endif
+
+void MixxxMainWindow::slotFileLoadSongPlayer(int deck) {
+ QString group = PlayerManager::groupForDeck(deck - 1);
+
+ QString loadTrackText = tr("Load track to Deck %1").arg(QString::number(deck));
+ QString deckWarningMessage = tr("Deck %1 is currently playing a track.")
+ .arg(QString::number(deck));
+ QString areYouSure = tr("Are you sure you want to load a new track?");
+
+ if (ControlObject::get(ConfigKey(group, "play")) > 0.0) {
+ int ret = QMessageBox::warning(this,
+ VersionStore::applicationName(),
+ deckWarningMessage + "\n" + areYouSure,
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+
+ if (ret != QMessageBox::Yes) {
+ return;
+ }
+ }
+
+ UserSettingsPointer pConfig = m_pCoreServices->getSettings();
+ QString trackPath =
+ QFileDialog::getOpenFileName(
+ this,
+ loadTrackText,
+ pConfig->getValueString(mixxx::library::prefs::kLegacyDirectoryConfigKey),
+ QString("Audio (%1)")
+ .arg(SoundSourceProxy::getSupportedFileNamePatterns().join(" ")));
+
+ if (!trackPath.isNull()) {
+ // The user has picked a file via a file dialog. This means the system
+ // sandboxer (if we are sandboxed) has granted us permission to this
+ // folder. Create a security bookmark while we have permission so that
+ // we can access the folder on future runs. We need to canonicalize the
+ // path so we first wrap the directory string with a QDir.
+ mixxx::FileInfo fileInfo(trackPath);
+ Sandbox::createSecurityToken(&fileInfo);
+
+ m_pCoreServices->getPlayerManager()->slotLoadToDeck(trackPath, deck);
+ }
+}
+
+void MixxxMainWindow::slotDeveloperTools(bool visible) {
+ if (visible) {
+ if (m_pDeveloperToolsDlg == nullptr) {
+ UserSettingsPointer pConfig = m_pCoreServices->getSettings();
+ m_pDeveloperToolsDlg = new DlgDeveloperTools(this, pConfig);
+ connect(m_pDeveloperToolsDlg,
+ &DlgDeveloperTools::destroyed,
+ this,
+ &MixxxMainWindow::slotDeveloperToolsClosed);
+ connect(this,
+ &MixxxMainWindow::closeDeveloperToolsDlgChecked,
+ m_pDeveloperToolsDlg,
+ &DlgDeveloperTools::done);
+ connect(m_pDeveloperToolsDlg,
+ &DlgDeveloperTools::destroyed,
+ m_pMenuBar,
+ &WMainMenuBar::onDeveloperToolsHidden);
+ }
+ m_pMenuBar->onDeveloperToolsShown();
+ m_pDeveloperToolsDlg->show();
+ m_pDeveloperToolsDlg->activateWindow();
+ } else {
+ emit closeDeveloperToolsDlgChecked(0);
+ }
+}
+
+void MixxxMainWindow::slotDeveloperToolsClosed() {
+ m_pDeveloperToolsDlg = nullptr;
+}
+
+void MixxxMainWindow::slotViewFullScreen(bool toggle) {
+ qWarning() << " $ slotViewFullScreen" << toggle;
+ if (isFullScreen() == toggle) {
+ qWarning() << " (no-op)";
+ return;
+ }
+
+ // Just switch the window state here. eventFilter() will catch the
+ // QWindowStateChangeEvent and inform the menu bar that fullscreen changed.
+ if (toggle) {
+ qWarning() << " > showFullScreen()";
+ showFullScreen();
+ } else {
+ qWarning() << " > showNormal()";
+ showNormal();
+ }
+}
+
+void MixxxMainWindow::slotOptionsPreferences() {
+ m_pPrefDlg->show();
+ m_pPrefDlg->raise();
+ m_pPrefDlg->activateWindow();
+}
+
+void MixxxMainWindow::slotNoVinylControlInputConfigured() {
+ QMessageBox::StandardButton btn = QMessageBox::warning(
+ this,
+ VersionStore::applicationName(),
+ tr("There is no input device selected for this vinyl control.\n"
+ "Please select an input device in the sound hardware preferences first."),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Cancel);
+ if (btn == QMessageBox::Ok) {
+ m_pPrefDlg->show();
+ m_pPrefDlg->showSoundHardwarePage();
+ }
+}
+
+void MixxxMainWindow::slotNoDeckPassthroughInputConfigured() {
+ QMessageBox::StandardButton btn = QMessageBox::warning(
+ this,
+ VersionStore::applicationName(),
+ tr("There is no input device selected for this passthrough control.\n"
+ "Please select an input device in the sound hardware preferences first."),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Cancel);
+ if (btn == QMessageBox::Ok) {
+ m_pPrefDlg->show();
+ m_pPrefDlg->showSoundHardwarePage();
+ }
+}
+
+void MixxxMainWindow::slotNoMicrophoneInputConfigured() {
+ QMessageBox::StandardButton btn = QMessageBox::question(
+ this,
+ VersionStore::applicationName(),
+ tr("There is no input device selected for this microphone.\n"
+ "Do you want to select an input device?"),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Cancel);
+ if (btn == QMessageBox::Ok) {
+ m_pPrefDlg->show();
+ m_pPrefDlg->showSoundHardwarePage();
+ }
+}
+
+void MixxxMainWindow::slotNoAuxiliaryInputConfigured() {
+ QMessageBox::StandardButton btn = QMessageBox::question(
+ this,
+ VersionStore::applicationName(),
+ tr("There is no input device selected for this auxiliary.\n"
+ "Do you want to select an input device?"),
+ QMessageBox::Ok | QMessageBox::Cancel,
+ QMessageBox::Cancel);
+ if (btn == QMessageBox::Ok) {
+ m_pPrefDlg->show();
+ m_pPrefDlg->showSoundHardwarePage();
+ }
+}
+
+void MixxxMainWindow::slotHelpAbout() {
+ DlgAbout* about = new DlgAbout;
+ about->show();
+}
+
+void MixxxMainWindow::slotShowKeywheel(bool toggle) {
+ if (!m_pKeywheel) {
+ m_pKeywheel = make_parented(this, m_pCoreServices->getSettings());
+ // uncheck the menu item on window close
+ connect(m_pKeywheel.get(),
+ &DlgKeywheel::finished,
+ m_pMenuBar,
+ &WMainMenuBar::onKeywheelChange);
+ }
+ if (toggle) {
+ m_pKeywheel->show();
+ m_pKeywheel->raise();
+ } else {
+ m_pKeywheel->hide();
+ }
+}
+
+void MixxxMainWindow::slotTooltipModeChanged(mixxx::preferences::Tooltips tt) {
+ m_toolTipsCfg = tt;
+#ifdef MIXXX_USE_QOPENGL
+ ToolTipQOpenGL::singleton().setActive(
+ m_toolTipsCfg == mixxx::preferences::Tooltips::On);
+#endif
+}
+
+void MixxxMainWindow::rebootMixxxView() {
+ qDebug() << "Now in rebootMixxxView...";
+
+ // safe geometry for later restoration
+ const QRect initGeometry = geometry();
+
+ // We need to tell the menu bar that we are about to delete the old skin and
+ // create a new one. It holds "visibility" controls (e.g. "Show Samplers")
+ // that need to be deleted -- otherwise we can't tell what features the skin
+ // supports since the controls from the previous skin will be left over.
+ m_pMenuBar->onNewSkinAboutToLoad();
+
+ if (m_pCentralWidget) {
+ m_pCentralWidget->hide();
+ WaveformWidgetFactory::instance()->destroyWidgets();
+ delete m_pCentralWidget;
+ m_pCentralWidget = nullptr;
+ }
+
+ // Workaround for changing skins while fullscreen, just go out of fullscreen
+ // mode. If you change skins while in fullscreen (on Linux, at least) the
+ // window returns to 0,0 but and the backdrop disappears so it looks as if
+ // it is not fullscreen, but acts as if it is.
+ bool wasFullScreen = isFullScreen();
+ if (wasFullScreen) {
+ showMaximized();
+ }
+
+ tryParseAndSetDefaultStyleSheet();
+
+ if (!loadConfiguredSkin()) {
+ QMessageBox::critical(this,
+ tr("Error in skin file"),
+ tr("The selected skin cannot be loaded."));
+ // m_pWidgetParent is NULL, we can't continue.
+ return;
+ }
+ m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet());
+
+ setCentralWidget(m_pCentralWidget);
+#ifdef __LINUX__
+ // don't adjustSize() on Linux as this wouldn't use the entire available area
+ // to paint the new skin with X11
+ // https://github.com/mixxxdj/mixxx/issues/9309
+#else
+ adjustSize();
+#endif
+
+ if (wasFullScreen) {
+ showFullScreen();
+ } else {
+ // Programmatic placement at this point is very problematic.
+ // The screen() method returns stale data (primary screen)
+ // until the user interacts with mixxx again. Keyboard shortcuts
+ // do not count, moving window, opening menu etc does
+ // Therefore the placement logic was removed by a simple geometry restore.
+ // If the minimum size of the new skin is larger then the restored
+ // geometry, the window will be enlarged right & bottom which is
+ // safe as the menu is still reachable.
+ setGeometry(initGeometry);
+ }
+
+ qDebug() << "rebootMixxxView DONE";
+}
+
+bool MixxxMainWindow::loadConfiguredSkin() {
+ // TODO: use std::shared_ptr throughout skin widgets instead of these hacky get() calls
+ m_pCentralWidget = m_pSkinLoader->loadConfiguredSkin(this,
+ &m_skinCreatedControls,
+ m_pCoreServices.get());
+ if (centralWidget() == m_pLaunchImage) {
+ initializationProgressUpdate(100, "");
+ }
+ emit skinLoaded();
+ return m_pCentralWidget != nullptr;
+}
+
+/// Try to load default styles that can be overridden by skins
+void MixxxMainWindow::tryParseAndSetDefaultStyleSheet() {
+ const QString resPath = m_pCoreServices->getSettings()->getResourcePath();
+ QFile file(resPath + "/skins/default.qss");
+ if (file.open(QIODevice::ReadOnly)) {
+ QByteArray fileBytes = file.readAll();
+ QString style = QString::fromUtf8(fileBytes);
+ setStyleSheet(style);
+ } else {
+ qWarning() << "Failed to load default skin styles /skins/default.qss!";
+ }
+}
+
+/// Catch ToolTip and WindowStateChange events
+bool MixxxMainWindow::eventFilter(QObject* obj, QEvent* event) {
+ if (event->type() == QEvent::ToolTip) {
+ // always show tooltips in the preferences window
+ QWidget* activeWindow = QApplication::activeWindow();
+ if (activeWindow &&
+ QLatin1String(activeWindow->metaObject()->className()) !=
+ "DlgPreferences") {
+ // return true for no tool tips
+ switch (m_toolTipsCfg) {
+ case mixxx::preferences::Tooltips::OnlyInLibrary:
+ if (dynamic_cast(obj) != nullptr) {
+ return true;
+ }
+ break;
+ case mixxx::preferences::Tooltips::On:
+ break;
+ case mixxx::preferences::Tooltips::Off:
+ return true;
+ default:
+ DEBUG_ASSERT(!"m_toolTipsCfg value unknown");
+ return true;
+ }
+ }
+ } else if (event->type() == QEvent::WindowStateChange) {
+#ifndef __APPLE__
+ qWarning() << "$ WindowStateChange:" << windowState();
+ if (windowState() == m_prevState) {
+ // Ignore no-op. This happens if another window is raised above
+ // MixxxMianWindow, e.g. DlgPeferences. In such a case event->oldState()
+ // will be Qt::WindowNoState which is wrong anyway, so there is nothing
+ // to do internally.
+ qWarning() << "$ WindowStateChange IGNORE";
+ return QMainWindow::eventFilter(obj, event);
+ }
+ m_prevState = windowState();
+#endif
+ // Detect if we entered or quit fullscreen mode.
+ QWindowStateChangeEvent* changeEvent =
+ static_cast(event);
+ const bool wasFullScreen = changeEvent->oldState() & Qt::WindowFullScreen;
+ const bool isFullScreenNow = windowState() & Qt::WindowFullScreen;
+ if ((isFullScreenNow && !wasFullScreen) ||
+ (!isFullScreenNow && wasFullScreen)) {
+ qWarning() << "$ fullscreen changed, now"
+ << (isFullScreenNow ? "fullscreen" : "window");
+#ifdef __LINUX__
+ // Fix for "No menu bar with ubuntu unity in full screen mode"
+ // (issues #6072 and #6689). Before touching anything here, please
+ // read those bugs.
+ // Set this attribute instead of calling setNativeMenuBar(false),
+ // see https://github.com/mixxxdj/mixxx/issues/11320
+ if (m_supportsGlobalMenuBar) {
+ qWarning() << "$ global menu > rebuild";
+ QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, isFullScreenNow);
+ createMenuBar();
+ connectMenuBar();
+ }
+#endif
+
+#ifndef __APPLE__
+#ifdef __LINUX__
+ // Only show the dialog if we are able to have the menubar in the
+ // main window, only then we're able to hide it.
+ if (!m_supportsGlobalMenuBar || isFullScreenNow)
+#endif
+ {
+ alwaysHideMenuBarDlg();
+ slotUpdateMenuBarAltKeyConnection();
+ }
+#endif
+
+ // This will toggle the Fullscreen checkbox and hide the menubar if
+ // we go fullscreen.
+ // Skip this during startup or the launchimage will be shifted
+ // up & down when the menu is shown menu and 'hidden'. The menu
+ // will be updated when the skin finished loading.
+ if (centralWidget() != m_pLaunchImage) {
+ emit fullScreenChanged(isFullScreen());
+ }
+ }
+ }
+ // standard event processing
+ return QMainWindow::eventFilter(obj, event);
+}
+
+void MixxxMainWindow::closeEvent(QCloseEvent* event) {
+ // WARNING: We can receive a CloseEvent while only partially
+ // initialized. This is because we call QApplication::processEvents to
+ // render LaunchImage progress in the constructor.
+ if (!confirmExit()) {
+ event->ignore();
+ return;
+ }
+ QMainWindow::closeEvent(event);
+}
+
+void MixxxMainWindow::checkDirectRendering() {
+ // IF
+ // * A waveform viewer exists
+ // AND
+ // * The waveform viewer is an OpenGL waveform viewer
+ // AND
+ // * The waveform viewer does not have direct rendering enabled.
+ // THEN
+ // * Warn user
+
+ WaveformWidgetFactory* factory = WaveformWidgetFactory::instance();
+ if (!factory) {
+ return;
+ }
+
+ UserSettingsPointer pConfig = m_pCoreServices->getSettings();
+
+ if (!factory->isOpenGlAvailable() && !factory->isOpenGlesAvailable() &&
+ pConfig->getValueString(ConfigKey("[Direct Rendering]", "Warned")) != QString("yes")) {
+ QMessageBox::warning(nullptr,
+ tr("OpenGL Direct Rendering"),
+ tr("Direct rendering is not enabled on your machine.
"
+ "This means that the waveform displays will be very
"
+ "slow and may tax your CPU heavily. Either update "
+ "your
"
+ "configuration to enable direct rendering, or disable
"
+ "the waveform displays in the Mixxx preferences by "
+ "selecting
"
+ "\"Empty\" as the waveform display in the 'Interface' "
+ "section."));
+ pConfig->set(ConfigKey("[Direct Rendering]", "Warned"), QString("yes"));
+ }
+}
+
+bool MixxxMainWindow::confirmExit() {
+ bool playing(false);
+ bool playingSampler(false);
+ auto pPlayerManager = m_pCoreServices->getPlayerManager();
+ unsigned int deckCount = pPlayerManager->numDecks();
+ unsigned int samplerCount = pPlayerManager->numSamplers();
+ for (unsigned int i = 0; i < deckCount; ++i) {
+ if (ControlObject::toBool(
+ ConfigKey(PlayerManager::groupForDeck(i), "play"))) {
+ playing = true;
+ break;
+ }
+ }
+ for (unsigned int i = 0; i < samplerCount; ++i) {
+ if (ControlObject::toBool(
+ ConfigKey(PlayerManager::groupForSampler(i), "play"))) {
+ playingSampler = true;
+ break;
+ }
+ }
+ if (playing) {
+ QMessageBox::StandardButton btn = QMessageBox::question(this,
+ tr("Confirm Exit"),
+ tr("A deck is currently playing. Exit Mixxx?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (btn == QMessageBox::No) {
+ return false;
+ }
+ } else if (playingSampler) {
+ QMessageBox::StandardButton btn = QMessageBox::question(this,
+ tr("Confirm Exit"),
+ tr("A sampler is currently playing. Exit Mixxx?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No);
+ if (btn == QMessageBox::No) {
+ return false;
+ }
+ }
+ if (m_pPrefDlg && m_pPrefDlg->isVisible()) {
+ QMessageBox::StandardButton btn = QMessageBox::question(
+ this, tr("Confirm Exit"), tr("The preferences window is still open.") + "
" + tr("Discard any changes and exit Mixxx?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
+ if (btn == QMessageBox::No) {
+ return false;
+ } else {
+ m_pPrefDlg->close();
+ }
+ }
+
+ return true;
+}
+
+void MixxxMainWindow::initializationProgressUpdate(int progress, const QString& serviceName) {
+ if (m_pLaunchImage) {
+ m_pLaunchImage->progress(progress, serviceName);
+ }
+ qApp->processEvents();
+}
+
+void MixxxMainWindow::oscEnable() {
+ UserSettingsPointer pConfig;
+ QString MixxxOSCStatusFilePath = m_pCoreServices->getSettings()->getSettingsPath();
+ QString MixxxOSCStatusFileLocation = MixxxOSCStatusFilePath + "/MixxxOSCStatus.txt";
+ QFile MixxxOSCStatusFile(MixxxOSCStatusFileLocation);
+ MixxxOSCStatusFile.remove();
+ MixxxOSCStatusFile.open(QIODevice::ReadWrite | QIODevice::Append);
+ QTextStream MixxxOSCStatusTxt(&MixxxOSCStatusFile);
+ if (m_pCoreServices->getSettings()->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+ MixxxOSCStatusTxt << QString("OSC enabled") << "\n";
+// OscFunctionsSendPtrChar(m_pCoreServices->getSettings(), "[Master]", "StartReset", "1");
+// enum DefOscBodyType OscBodyType = INTBODY;
+ OscFunctionsSendPtrType(m_pCoreServices->getSettings(), "[Master]", "StartReset", INTBODY, "", 1, 0, 0);
+
+ //void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig,
+// QString OscGroup,
+// QString OscKey,
+// enum DefOscBodyType OscBodyType,
+// QString OscMessageBodyQString,
+// int OscMessageBodyInt,
+// double OscMessageBodyDouble,
+// float OscMessageBodyFloat) {
+
+
+ OscReceiverMain(m_pCoreServices->getSettings());
+
+ } else {
+ MixxxOSCStatusTxt << QString("OSC NOT enabled") << "\n";
+ }
+ MixxxOSCStatusFile.close();
+}
diff --git a/src/analyzer/constants.h b/src/analyzer/constants.h
index 42d84db7217..5e670a37dc0 100644
--- a/src/analyzer/constants.h
+++ b/src/analyzer/constants.h
@@ -10,7 +10,6 @@ namespace mixxx {
// fixed number of channels like the engine does, usually 2 = stereo.
constexpr audio::ChannelCount kAnalysisChannels = mixxx::kEngineChannelOutputCount;
constexpr audio::ChannelCount kAnalysisMaxChannels = mixxx::kMaxEngineChannelInputCount;
-constexpr int kMaxSupportedStems = 4;
constexpr SINT kAnalysisFramesPerChunk = 4096;
constexpr SINT kAnalysisSamplesPerChunk =
kAnalysisFramesPerChunk * kAnalysisMaxChannels;
diff --git a/src/engine/20240918 BU enginebuffer.cpp b/src/engine/20240918 BU enginebuffer.cpp
new file mode 100644
index 00000000000..5244db43087
--- /dev/null
+++ b/src/engine/20240918 BU enginebuffer.cpp
@@ -0,0 +1,1886 @@
+#include "engine/enginebuffer.h"
+
+#include
+
+#include "control/controllinpotmeter.h"
+#include "control/controlpotmeter.h"
+#include "control/controlproxy.h"
+#include "control/controlpushbutton.h"
+#include "engine/bufferscalers/enginebufferscalelinear.h"
+#include "engine/bufferscalers/enginebufferscalest.h"
+#include "engine/cachingreader/cachingreader.h"
+#include "engine/channels/enginechannel.h"
+#include "engine/controls/bpmcontrol.h"
+#include "engine/controls/clockcontrol.h"
+#include "engine/controls/cuecontrol.h"
+#include "engine/controls/enginecontrol.h"
+#include "engine/controls/keycontrol.h"
+#include "engine/controls/loopingcontrol.h"
+#include "engine/controls/quantizecontrol.h"
+#include "engine/controls/ratecontrol.h"
+#include "engine/enginemixer.h"
+#include "engine/readaheadmanager.h"
+#include "engine/sync/enginesync.h"
+#include "engine/sync/synccontrol.h"
+#include "moc_enginebuffer.cpp"
+#include "preferences/usersettings.h"
+#include "track/track.h"
+#include "util/assert.h"
+#include "util/compatibility/qatomic.h"
+#include "util/defs.h"
+#include "util/logger.h"
+#include "util/sample.h"
+#include "util/timer.h"
+#include "waveform/visualplayposition.h"
+
+#ifdef __RUBBERBAND__
+#include "engine/bufferscalers/enginebufferscalerubberband.h"
+#endif
+
+#ifdef __VINYLCONTROL__
+#include "engine/controls/vinylcontrolcontrol.h"
+#endif
+
+// EVE OSC
+//#include
+//#include
+
+//#include
+//#include
+
+//#include "osc/ip/UdpSocket.h"
+//#include "osc/osc/OscOutboundPacketStream.h"
+
+//#define oscClientAddress "192.168.0.125"
+//#define oscPortOut 9000
+//#define OUTPUT_BUFFER_SIZE 1024
+//#define IP_MTU_SIZE 1536
+//#include "osc/oscfunctions.h"
+
+namespace {
+const mixxx::Logger kLogger("EngineBuffer");
+
+constexpr double kLinearScalerElipsis =
+ 1.00058; // 2^(0.01/12): changes < 1 cent allows a linear scaler
+
+// Rate at which the playpos slider is updated
+constexpr int kPlaypositionUpdateRate = 15; // updates per second
+
+const QString kAppGroup = QStringLiteral("[App]");
+
+} // anonymous namespace
+
+//EveOSC
+enum DefOscBodyType {
+ STRINGBODY = 1,
+ INTBODY = 2,
+ DOUBLEBODY = 3,
+ FLOATBODY = 4
+};
+
+void OscFunctionsSendChar(QString Oscgroup, QString OscKey, QString OscMessageBody);
+void OscFunctionsSendFloat(QString OscGroup, QString OscKey, float OscMessageBody);
+void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig,
+ QString OscGroup,
+ QString OscKey,
+ enum DefOscBodyType OscBodyType,
+ QString OscMessageBodyQString,
+ int OscMessageBodyInt,
+ double OscMessageBodyDouble,
+ float OscMessageBodyFloat);
+// EveOSC
+
+EngineBuffer::EngineBuffer(const QString& group,
+ UserSettingsPointer pConfig,
+ EngineChannel* pChannel,
+ EngineMixer* pMixingEngine,
+ mixxx::audio::ChannelCount maxSupportedChannel)
+ : m_group(group),
+ m_pConfig(pConfig),
+ m_pLoopingControl(nullptr),
+ m_pSyncControl(nullptr),
+ m_pVinylControlControl(nullptr),
+ m_pRateControl(nullptr),
+ m_pBpmControl(nullptr),
+ m_pKeyControl(nullptr),
+ m_pReadAheadManager(nullptr),
+ m_pReader(nullptr),
+ m_playPos(kInitialPlayPosition),
+ m_speed_old(0),
+ m_actual_speed(0),
+ m_tempo_ratio_old(1.),
+ m_scratching_old(false),
+ m_reverse_old(false),
+ m_pitch_old(0),
+ m_baserate_old(0),
+ m_rate_old(0.),
+ m_trackEndPositionOld(mixxx::audio::kInvalidFramePos),
+ m_slipPos(mixxx::audio::kStartFramePos),
+ m_dSlipRate(1.0),
+ m_bSlipEnabledProcessing(false),
+ m_slipModeState(SlipModeState::Disabled),
+ m_pRepeat(nullptr),
+ m_startButton(nullptr),
+ m_endButton(nullptr),
+ m_bScalerOverride(false),
+ m_iSeekPhaseQueued(0),
+ m_iEnableSyncQueued(SYNC_REQUEST_NONE),
+ m_iSyncModeQueued(static_cast(SyncMode::Invalid)),
+ m_bPlayAfterLoading(false),
+ m_channelCount(mixxx::kEngineChannelOutputCount),
+ m_pCrossfadeBuffer(SampleUtil::alloc(
+ kMaxEngineFrames * mixxx::kMaxEngineChannelInputCount)),
+ m_bCrossfadeReady(false),
+ m_iLastBufferSize(0) {
+ // This should be a static assertion, but isValid() is not constexpr.
+ DEBUG_ASSERT(kInitialPlayPosition.isValid());
+
+ m_queuedSeek.setValue(kNoQueuedSeek);
+
+ // zero out crossfade buffer
+ SampleUtil::clear(m_pCrossfadeBuffer, kMaxEngineFrames * mixxx::kMaxEngineChannelInputCount);
+
+ m_pReader = new CachingReader(group, pConfig, maxSupportedChannel);
+ connect(m_pReader, &CachingReader::trackLoading,
+ this, &EngineBuffer::slotTrackLoading,
+ Qt::DirectConnection);
+ connect(m_pReader, &CachingReader::trackLoaded,
+ this, &EngineBuffer::slotTrackLoaded,
+ Qt::DirectConnection);
+ connect(m_pReader, &CachingReader::trackLoadFailed,
+ this, &EngineBuffer::slotTrackLoadFailed,
+ Qt::DirectConnection);
+
+ // Play button
+ m_playButton = new ControlPushButton(ConfigKey(m_group, "play"));
+ m_playButton->setButtonMode(mixxx::control::ButtonMode::Toggle);
+ m_playButton->connectValueChangeRequest(
+ this, &EngineBuffer::slotControlPlayRequest,
+ Qt::DirectConnection);
+
+ //Play from Start Button (for sampler)
+ m_playStartButton = new ControlPushButton(ConfigKey(m_group, "start_play"));
+ connect(m_playStartButton, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlPlayFromStart,
+ Qt::DirectConnection);
+
+ // Jump to start and stop button
+ m_stopStartButton = new ControlPushButton(ConfigKey(m_group, "start_stop"));
+ connect(m_stopStartButton, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlJumpToStartAndStop,
+ Qt::DirectConnection);
+
+ //Stop playback (for sampler)
+ m_stopButton = new ControlPushButton(ConfigKey(m_group, "stop"));
+ connect(m_stopButton, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlStop,
+ Qt::DirectConnection);
+
+ // Start button
+ m_startButton = new ControlPushButton(ConfigKey(m_group, "start"));
+ m_startButton->setButtonMode(mixxx::control::ButtonMode::Trigger);
+ connect(m_startButton, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlStart,
+ Qt::DirectConnection);
+
+ // End button
+ m_endButton = new ControlPushButton(ConfigKey(m_group, "end"));
+ connect(m_endButton, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlEnd,
+ Qt::DirectConnection);
+
+ m_pSlipButton = new ControlPushButton(ConfigKey(m_group, "slip_enabled"));
+ m_pSlipButton->setButtonMode(mixxx::control::ButtonMode::Toggle);
+
+ m_playposSlider = new ControlLinPotmeter(
+ ConfigKey(m_group, "playposition"), 0.0, 1.0, 0, 0, true);
+ connect(m_playposSlider, &ControlObject::valueChanged,
+ this, &EngineBuffer::slotControlSeek,
+ Qt::DirectConnection);
+
+ // Control used to communicate ratio playpos to GUI thread
+ m_visualPlayPos = VisualPlayPosition::getVisualPlayPosition(m_group);
+
+ m_pRepeat = new ControlPushButton(ConfigKey(m_group, "repeat"));
+ m_pRepeat->setButtonMode(mixxx::control::ButtonMode::Toggle);
+
+ m_pSampleRate = new ControlProxy(kAppGroup, QStringLiteral("samplerate"), this);
+
+ m_pTrackSamples = new ControlObject(ConfigKey(m_group, "track_samples"));
+ m_pTrackSampleRate = new ControlObject(ConfigKey(m_group, "track_samplerate"));
+
+ m_pTrackType = new ControlObject(ConfigKey(m_group, "track_type"));
+ m_pTrackTypeLength = new ControlObject(ConfigKey(m_group, "track_type_length"));
+ m_pTrackArtistLength = new ControlObject(ConfigKey(m_group, "track_artist_length"));
+ m_pTrackArtist_1 = new ControlObject(ConfigKey(m_group, "track_artist_1"));
+ m_pTrackArtist_2 = new ControlObject(ConfigKey(m_group, "track_artist_2"));
+ m_pTrackArtist_3 = new ControlObject(ConfigKey(m_group, "track_artist_3"));
+ m_pTrackArtist_4 = new ControlObject(ConfigKey(m_group, "track_artist_4"));
+ m_pTrackArtist_5 = new ControlObject(ConfigKey(m_group, "track_artist_5"));
+
+ m_pTrackTitleLength = new ControlObject(ConfigKey(m_group, "track_title_length"));
+ m_pTrackTitle_1 = new ControlObject(ConfigKey(m_group, "track_title_1"));
+ m_pTrackTitle_2 = new ControlObject(ConfigKey(m_group, "track_title_2"));
+ m_pTrackTitle_3 = new ControlObject(ConfigKey(m_group, "track_title_3"));
+ m_pTrackTitle_4 = new ControlObject(ConfigKey(m_group, "track_title_4"));
+ m_pTrackTitle_5 = new ControlObject(ConfigKey(m_group, "track_title_5"));
+
+ m_pKeylock = new ControlPushButton(ConfigKey(m_group, "keylock"), true);
+ m_pKeylock->setButtonMode(mixxx::control::ButtonMode::Toggle);
+
+ m_pReplayGain = new ControlProxy(m_group, QStringLiteral("replaygain"), this);
+
+ m_pTrackLoaded = new ControlObject(ConfigKey(m_group, "track_loaded"), false);
+ m_pTrackLoaded->setReadOnly();
+
+ // Quantization Controller for enabling and disabling the
+ // quantization (alignment) of loop in/out positions and (hot)cues with
+ // beats.
+ QuantizeControl* quantize_control = new QuantizeControl(group, pConfig);
+ addControl(quantize_control);
+ m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize"));
+
+ // Create the Loop Controller
+ m_pLoopingControl = new LoopingControl(group, pConfig);
+ addControl(m_pLoopingControl);
+
+ m_pEngineSync = pMixingEngine->getEngineSync();
+
+ m_pSyncControl = new SyncControl(group, pConfig, pChannel, m_pEngineSync);
+
+#ifdef __VINYLCONTROL__
+ m_pVinylControlControl = new VinylControlControl(group, pConfig);
+ addControl(m_pVinylControlControl);
+#endif
+
+ // Create the Rate Controller
+ m_pRateControl = new RateControl(group, pConfig);
+ // Add the Rate Controller
+ addControl(m_pRateControl);
+ // Looping Control needs Rate Control for Reverse Button
+ m_pLoopingControl->setRateControl(m_pRateControl);
+
+ // Create the BPM Controller
+ m_pBpmControl = new BpmControl(group, pConfig);
+ addControl(m_pBpmControl);
+
+ // TODO(rryan) remove this dependence?
+ m_pRateControl->setBpmControl(m_pBpmControl);
+ m_pSyncControl->setEngineControls(m_pRateControl, m_pBpmControl);
+ pMixingEngine->getEngineSync()->addSyncableDeck(m_pSyncControl);
+ addControl(m_pSyncControl);
+
+ m_pKeyControl = new KeyControl(group, pConfig);
+ addControl(m_pKeyControl);
+
+ // Create the clock controller
+ m_pClockControl = new ClockControl(group, pConfig);
+ addControl(m_pClockControl);
+
+ // Create the cue controller
+ m_pCueControl = new CueControl(group, pConfig);
+ addControl(m_pCueControl);
+
+ connect(m_pLoopingControl,
+ &LoopingControl::loopReset,
+ m_pCueControl,
+ &CueControl::slotLoopReset,
+ Qt::DirectConnection);
+ connect(m_pLoopingControl,
+ &LoopingControl::loopUpdated,
+ m_pCueControl,
+ &CueControl::slotLoopUpdated,
+ Qt::DirectConnection);
+ connect(m_pLoopingControl,
+ &LoopingControl::loopEnabledChanged,
+ m_pCueControl,
+ &CueControl::slotLoopEnabledChanged,
+ Qt::DirectConnection);
+ connect(m_pCueControl,
+ &CueControl::loopRemove,
+ m_pLoopingControl,
+ &LoopingControl::slotLoopRemove,
+ Qt::DirectConnection);
+
+ m_pReadAheadManager = new ReadAheadManager(m_pReader,
+ m_pLoopingControl);
+ m_pReadAheadManager->addRateControl(m_pRateControl);
+
+ m_pKeylockEngine = new ControlProxy(kAppGroup, QStringLiteral("keylock_engine"), this);
+ m_pKeylockEngine->connectValueChanged(this,
+ &EngineBuffer::slotKeylockEngineChanged,
+ Qt::DirectConnection);
+ // Construct scaling objects
+ m_pScaleLinear = new EngineBufferScaleLinear(m_pReadAheadManager);
+ m_pScaleST = new EngineBufferScaleST(m_pReadAheadManager);
+#ifdef __RUBBERBAND__
+ m_pScaleRB = new EngineBufferScaleRubberBand(m_pReadAheadManager);
+#endif
+ slotKeylockEngineChanged(m_pKeylockEngine->get());
+ m_pScaleVinyl = m_pScaleLinear;
+ m_pScale = m_pScaleVinyl;
+ m_pScale->clear();
+ m_bScalerChanged = true;
+
+ m_pPassthroughEnabled = new ControlProxy(group, "passthrough", this);
+ m_pPassthroughEnabled->connectValueChanged(this, &EngineBuffer::slotPassthroughChanged,
+ Qt::DirectConnection);
+
+#ifdef __SCALER_DEBUG__
+ df.setFileName("mixxx-debug.csv");
+ df.open(QIODevice::WriteOnly | QIODevice::Text);
+ writer.setDevice(&df);
+#endif
+
+ // Now that all EngineControls have been created call setEngineMixer.
+ // TODO(XXX): Get rid of EngineControl::setEngineMixer and
+ // EngineControl::setEngineBuffer entirely and pass them through the
+ // constructor.
+ setEngineMixer(pMixingEngine);
+}
+
+EngineBuffer::~EngineBuffer() {
+#ifdef __SCALER_DEBUG__
+ //close the writer
+ df.close();
+#endif
+ delete m_pReadAheadManager;
+ delete m_pReader;
+
+ delete m_playButton;
+ delete m_playStartButton;
+ delete m_stopStartButton;
+
+ delete m_startButton;
+ delete m_endButton;
+ delete m_stopButton;
+ delete m_playposSlider;
+
+ delete m_pSlipButton;
+ delete m_pRepeat;
+ delete m_pSampleRate;
+
+ delete m_pTrackLoaded;
+ delete m_pTrackSamples;
+ delete m_pTrackSampleRate;
+
+ delete m_pTrackType;
+ delete m_pTrackTypeLength;
+ delete m_pTrackArtistLength;
+ delete m_pTrackArtist_1;
+ delete m_pTrackArtist_2;
+ delete m_pTrackArtist_3;
+ delete m_pTrackArtist_4;
+ delete m_pTrackArtist_5;
+
+ delete m_pTrackTitleLength;
+ delete m_pTrackTitle_1;
+ delete m_pTrackTitle_2;
+ delete m_pTrackTitle_3;
+ delete m_pTrackTitle_4;
+ delete m_pTrackTitle_5;
+
+ delete m_pScaleLinear;
+ delete m_pScaleST;
+#ifdef __RUBBERBAND__
+ delete m_pScaleRB;
+#endif
+
+ delete m_pKeylock;
+ delete m_pReplayGain;
+
+ SampleUtil::free(m_pCrossfadeBuffer);
+
+ qDeleteAll(m_engineControls);
+}
+
+void EngineBuffer::bindWorkers(EngineWorkerScheduler* pWorkerScheduler) {
+ m_pReader->setScheduler(pWorkerScheduler);
+}
+
+void EngineBuffer::enableIndependentPitchTempoScaling(bool bEnable,
+ const int iBufferSize) {
+ // MUST ACQUIRE THE PAUSE MUTEX BEFORE CALLING THIS METHOD
+
+ // When no time-stretching or pitch-shifting is needed we use our own linear
+ // interpolation code (EngineBufferScaleLinear). It is faster and sounds
+ // much better for scratching.
+
+ // m_pScaleKeylock and m_pScaleVinyl could change out from under us,
+ // so cache it.
+ EngineBufferScale* keylock_scale = m_pScaleKeylock;
+ EngineBufferScale* vinyl_scale = m_pScaleVinyl;
+
+ if (bEnable && m_pScale != keylock_scale) {
+ if (m_speed_old != 0.0) {
+ // Crossfade if we are not paused.
+ // If we start from zero a ramping gain is
+ // applied later
+ readToCrossfadeBuffer(iBufferSize);
+ }
+ m_pScale = keylock_scale;
+ m_pScale->clear();
+ m_bScalerChanged = true;
+ } else if (!bEnable && m_pScale != vinyl_scale) {
+ if (m_speed_old != 0.0) {
+ // Crossfade if we are not paused
+ // (for slow speeds below 0.1 the vinyl_scale is used)
+ readToCrossfadeBuffer(iBufferSize);
+ }
+ m_pScale = vinyl_scale;
+ m_pScale->clear();
+ m_bScalerChanged = true;
+ }
+}
+
+mixxx::Bpm EngineBuffer::getBpm() const {
+ return m_pBpmControl->getBpm();
+}
+
+mixxx::Bpm EngineBuffer::getLocalBpm() const {
+ return m_pBpmControl->getLocalBpm();
+}
+
+void EngineBuffer::setBeatLoop(mixxx::audio::FramePos startPosition, bool enabled) {
+ m_pLoopingControl->setBeatLoop(startPosition, enabled);
+}
+
+void EngineBuffer::setLoop(mixxx::audio::FramePos startPosition,
+ mixxx::audio::FramePos endPositon,
+ bool enabled) {
+ m_pLoopingControl->setLoop(startPosition, endPositon, enabled);
+}
+
+void EngineBuffer::setEngineMixer(EngineMixer* pEngineMixer) {
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ pControl->setEngineMixer(pEngineMixer);
+ }
+}
+
+void EngineBuffer::queueNewPlaypos(mixxx::audio::FramePos position, enum SeekRequest seekType) {
+ // All seeks need to be done in the Engine thread so queue it up.
+ // Write the position before the seek type, to reduce a possible race
+ // condition effect
+ VERIFY_OR_DEBUG_ASSERT(seekType != SEEK_PHASE) {
+ // SEEK_PHASE with a position is not supported
+ // use SEEK_STANDARD for that
+ seekType = SEEK_STANDARD;
+ }
+ m_queuedSeek.setValue({position, seekType});
+}
+
+void EngineBuffer::requestSyncPhase() {
+ // Don't overwrite m_iSeekQueued
+ m_iSeekPhaseQueued = 1;
+}
+
+void EngineBuffer::requestEnableSync(bool enabled) {
+ // If we're not playing, the queued event won't get processed so do it now.
+ if (m_playButton->get() == 0.0) {
+ if (enabled) {
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::Follower);
+ } else {
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::None);
+ }
+ return;
+ }
+ SyncRequestQueued enable_request =
+ static_cast(atomicLoadRelaxed(m_iEnableSyncQueued));
+ if (enabled) {
+ m_iEnableSyncQueued = SYNC_REQUEST_ENABLE;
+ } else {
+ // If sync is enabled and disabled very quickly, it's is a one-shot
+ // sync event and needs to be handled specially. Otherwise the sync
+ // state will get stuck on or won't go on at all.
+ if (enable_request == SYNC_REQUEST_ENABLE) {
+ m_iEnableSyncQueued = SYNC_REQUEST_ENABLEDISABLE;
+ } else {
+ // Note that there is no DISABLEENABLE, because that's an irrelevant
+ // queuing. Moreover, ENABLEDISABLEENABLE is also redundant, so
+ // we don't have to handle any special cases.
+ m_iEnableSyncQueued = SYNC_REQUEST_DISABLE;
+ }
+ }
+}
+
+void EngineBuffer::requestSyncMode(SyncMode mode) {
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << getGroup() << "EngineBuffer::requestSyncMode";
+ }
+ if (m_playButton->get() == 0.0) {
+ // If we're not playing, the queued event won't get processed so do it now.
+ m_pEngineSync->requestSyncMode(m_pSyncControl, mode);
+ } else {
+ m_iSyncModeQueued = static_cast(mode);
+ }
+}
+
+void EngineBuffer::readToCrossfadeBuffer(const int iBufferSize) {
+ if (!m_bCrossfadeReady) {
+ // Read buffer, as if there where no parameter change
+ // (Must be called only once per callback)
+ m_pScale->scaleBuffer(m_pCrossfadeBuffer, iBufferSize);
+ // Restore the original position that was lost due to scaleBuffer() above
+ m_pReadAheadManager->notifySeek(m_playPos.toSamplePos(m_channelCount));
+ m_bCrossfadeReady = true;
+ }
+}
+
+// WARNING: This method is not thread safe and must not be called from outside
+// the engine callback!
+void EngineBuffer::setNewPlaypos(mixxx::audio::FramePos position) {
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << m_group << "EngineBuffer::setNewPlaypos" << position;
+ }
+
+ m_playPos = position;
+
+ if (m_rate_old != 0.0) {
+ // Before seeking, read extra buffer for crossfading
+ // this also sets m_pReadAheadManager to newpos
+ readToCrossfadeBuffer(m_iLastBufferSize);
+ } else {
+ m_pReadAheadManager->notifySeek(m_playPos.toSamplePos(m_channelCount));
+ }
+ m_pScale->clear();
+
+ // Ensures that the playpos slider gets updated in next process call
+ m_iSamplesSinceLastIndicatorUpdate = 1000000;
+
+ // Must hold the engineLock while using m_engineControls
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ pControl->notifySeek(m_playPos);
+ }
+
+ verifyPlay(); // verify or update play button and indicator
+}
+
+QString EngineBuffer::getGroup() const {
+ return m_group;
+}
+
+double EngineBuffer::getSpeed() const {
+ return m_speed_old;
+}
+
+bool EngineBuffer::getScratching() const {
+ return m_scratching_old;
+}
+
+bool EngineBuffer::isReverse() const {
+ return m_reverse_old;
+}
+
+// WARNING: Always called from the EngineWorker thread pool
+void EngineBuffer::slotTrackLoading() {
+ // Pause EngineBuffer from processing frames
+ m_pause.lock();
+ // Setting m_iTrackLoading inside a m_pause.lock ensures that
+ // track buffer is not processed when starting to load a new one
+ m_iTrackLoading = 1;
+ m_pause.unlock();
+
+ // Set play here, to signal the user that the play command is adopted
+ m_playButton->set((double)m_bPlayAfterLoading);
+ setTrackEndPosition(mixxx::audio::kInvalidFramePos); // Stop renderer
+}
+
+void EngineBuffer::loadFakeTrack(TrackPointer pTrack, bool bPlay) {
+ if (bPlay) {
+ m_playButton->set((double)bPlay);
+ }
+ slotTrackLoaded(pTrack,
+ pTrack->getSampleRate(),
+ pTrack->getChannels(),
+ mixxx::audio::FramePos::fromEngineSamplePos(
+ pTrack->getSampleRate() * pTrack->getDuration()));
+}
+
+// WARNING: Always called from the EngineWorker thread pool
+void EngineBuffer::slotTrackLoaded(TrackPointer pTrack,
+ mixxx::audio::SampleRate trackSampleRate,
+ mixxx::audio::ChannelCount trackChannelCount,
+ mixxx::audio::FramePos trackNumFrame) {
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << getGroup() << "EngineBuffer::slotTrackLoaded";
+ }
+ TrackPointer pOldTrack = m_pCurrentTrack;
+ m_pause.lock();
+
+ m_visualPlayPos->setInvalid();
+ m_playPos = kInitialPlayPosition; // for execute seeks to 0.0
+ m_pCurrentTrack = pTrack;
+
+ m_channelCount = trackChannelCount;
+ if (m_channelCount > mixxx::audio::ChannelCount::stereo()) {
+ // The sample count is indicated downmix. This means that for stem
+ // track, we only consider the track in stereo, as it is perceived by
+ // the user on deck output
+ VERIFY_OR_DEBUG_ASSERT(m_channelCount % mixxx::audio::ChannelCount::stereo() == 0) {
+ // Make it stereo for the frame calculation
+ kLogger.warning() << "Odd number of channel in the track is not supported";
+ };
+ } else {
+ // The EngineBuffer only works with stereo channels. If the track is
+ // mono, it will be passed through the AudioSourceStereoProxy. See
+ // CachingReaderChunk::bufferSampleFrames
+ m_channelCount = mixxx::audio::ChannelCount::stereo();
+ }
+
+ m_pTrackSamples->set(trackNumFrame.toEngineSamplePos());
+ m_pTrackSampleRate->set(trackSampleRate.toDouble());
+ m_pTrackLoaded->forceSet(1);
+
+ // Eve start
+ // Type
+ QString TrackInfoType = pTrack->getType();
+ QString TrackInfoTypeTest = TrackInfoType;
+ int TrackInfoTypeTestLength = TrackInfoTypeTest.length();
+ if (TrackInfoTypeTestLength > 5) {
+ TrackInfoType = TrackInfoTypeTest.mid(0, 5);
+ };
+ m_pTrackTypeLength->set(TrackInfoTypeTestLength);
+
+ int CharType[5];
+ for (int i = 1; i <= 5; i++) {
+ CharType[i - 1] = 0;
+ }
+
+ for (int i = 1; i <= TrackInfoType.length(); i++) {
+ if ((TrackInfoType.at(i - 1).toLatin1()) < 0) {
+ CharType[i - 1] = ((TrackInfoType.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharType[i - 1] = (TrackInfoType.at(i - 1).toLatin1());
+ };
+ }
+
+ double TrackTypePart = 0.0;
+ TrackTypePart = (1.0 * CharType[0] * 1000000000000) + (1.0 * CharType[1] * 1000000000) + (1.0 * CharType[2] * 1000000) + (1.0 * CharType[3] * 1000) + (1.0 * CharType[4] * 1);
+ m_pTrackType->set(TrackTypePart);
+
+ // Title
+ QString TrackInfoTitle = pTrack->getTitle();
+ QString TrackInfoTitleTest = TrackInfoTitle;
+ int TrackInfoTitleTestLength = TrackInfoTitleTest.length();
+ if (TrackInfoTitleTestLength > 200) {
+ TrackInfoTitle = TrackInfoTitleTest.mid(0, 200);
+ };
+ m_pTrackTitleLength->set(TrackInfoTitleTestLength);
+
+ int CharTitle[200];
+ for (int i = 1; i <= 200; i++) {
+ CharTitle[i - 1] = 0;
+ }
+
+ // for (int i = 1; i <= TrackInfoTitle.length(); i++) {
+ for (int i = 1; i <= 25; i++) {
+ if ((TrackInfoTitle.at(i - 1).toLatin1()) < 0) {
+ CharTitle[i - 1] = ((TrackInfoTitle.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharTitle[i - 1] = (TrackInfoTitle.at(i - 1).toLatin1());
+ };
+ }
+
+ // Artist
+ QString TrackInfoArtist = pTrack->getArtist();
+ QString TrackInfoArtistTest = TrackInfoArtist;
+ int TrackInfoArtistTestLength = TrackInfoArtistTest.length();
+ if (TrackInfoArtistTestLength > 200) {
+ TrackInfoArtist = TrackInfoArtist.mid(0, 200);
+ };
+ m_pTrackArtistLength->set(TrackInfoArtistTestLength);
+
+ int CharArtist[200];
+ for (int i = 1; i <= 200; i++) {
+ CharArtist[i - 1] = 0;
+ }
+
+ // for (int i = 1; i <= TrackInfoArtist.length(); i++) {
+ for (int i = 1; i <= 25; i++) {
+ if ((TrackInfoArtist.at(i - 1).toLatin1()) < 0) {
+ CharArtist[i - 1] = ((TrackInfoArtist.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharArtist[i - 1] = (TrackInfoArtist.at(i - 1).toLatin1());
+ };
+ }
+
+ double TrackTitlePart_1 = 0.0;
+ double TrackTitlePart_2 = 0.0;
+ double TrackTitlePart_3 = 0.0;
+ double TrackTitlePart_4 = 0.0;
+ double TrackTitlePart_5 = 0.0;
+
+ TrackTitlePart_1 = (1.0 * CharTitle[0] * 1000000000000) + (1.0 * CharTitle[1] * 1000000000) + (1.0 * CharTitle[2] * 1000000) + (1.0 * CharTitle[3] * 1000) + (1.0 * CharTitle[4] * 1);
+ TrackTitlePart_2 = (1.0 * CharTitle[5] * 1000000000000) + (1.0 * CharTitle[6] * 1000000000) + (1.0 * CharTitle[7] * 1000000) + (1.0 * CharTitle[8] * 1000) + (1.0 * CharTitle[9] * 1);
+ TrackTitlePart_3 = (1.0 * CharTitle[10] * 1000000000000) + (1.0 * CharTitle[11] * 1000000000) + (1.0 * CharTitle[12] * 1000000) + (1.0 * CharTitle[13] * 1000) + (1.0 * CharTitle[14] * 1);
+ TrackTitlePart_4 = (1.0 * CharTitle[15] * 1000000000000) + (1.0 * CharTitle[16] * 1000000000) + (1.0 * CharTitle[17] * 1000000) + (1.0 * CharTitle[18] * 1000) + (1.0 * CharTitle[19] * 1);
+ TrackTitlePart_5 = (1.0 * CharTitle[20] * 1000000000000) + (1.0 * CharTitle[21] * 1000000000) + (1.0 * CharTitle[22] * 1000000) + (1.0 * CharTitle[23] * 1000) + (1.0 * CharTitle[24] * 1);
+
+ m_pTrackTitle_1->set(TrackTitlePart_1);
+ m_pTrackTitle_2->set(TrackTitlePart_2);
+ m_pTrackTitle_3->set(TrackTitlePart_3);
+ m_pTrackTitle_4->set(TrackTitlePart_4);
+ m_pTrackTitle_5->set(TrackTitlePart_5);
+
+ double TrackArtistPart_1 = 0.0;
+ double TrackArtistPart_2 = 0.0;
+ double TrackArtistPart_3 = 0.0;
+ double TrackArtistPart_4 = 0.0;
+ double TrackArtistPart_5 = 0.0;
+
+ TrackArtistPart_1 = (1.0 * CharArtist[0] * 1000000000000) + (1.0 * CharArtist[1] * 1000000000) + (1.0 * CharArtist[2] * 1000000) + (1.0 * CharArtist[3] * 1000) + (1.0 * CharArtist[4] * 1);
+ TrackArtistPart_2 = (1.0 * CharArtist[5] * 1000000000000) + (1.0 * CharArtist[6] * 1000000000) + (1.0 * CharArtist[7] * 1000000) + (1.0 * CharArtist[8] * 1000) + (1.0 * CharArtist[9] * 1);
+ TrackArtistPart_3 = (1.0 * CharArtist[10] * 1000000000000) + (1.0 * CharArtist[11] * 1000000000) + (1.0 * CharArtist[12] * 1000000) + (1.0 * CharArtist[13] * 1000) + (1.0 * CharArtist[14] * 1);
+ TrackArtistPart_4 = (1.0 * CharArtist[15] * 1000000000000) + (1.0 * CharArtist[16] * 1000000000) + (1.0 * CharArtist[17] * 1000000) + (1.0 * CharArtist[18] * 1000) + (1.0 * CharArtist[19] * 1);
+ TrackArtistPart_5 = (1.0 * CharArtist[20] * 1000000000000) + (1.0 * CharArtist[21] * 1000000000) + (1.0 * CharArtist[22] * 1000000) + (1.0 * CharArtist[23] * 1000) + (1.0 * CharArtist[24] * 1);
+
+ m_pTrackArtist_1->set(TrackArtistPart_1);
+ m_pTrackArtist_2->set(TrackArtistPart_2);
+ m_pTrackArtist_3->set(TrackArtistPart_3);
+ m_pTrackArtist_4->set(TrackArtistPart_4);
+ m_pTrackArtist_5->set(TrackArtistPart_5);
+
+// EveOSC begin
+ if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+// OscFunctionsSendChar(getGroup(), "TrackArtist", pTrack->getArtist().toLatin1());
+// OscFunctionsSendChar(getGroup(), "TrackTitle", pTrack->getTitle().toLatin1());
+ enum DefOscBodyType OscBodyType = STRINGBODY;
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "TrackArtist", STRINGBODY, pTrack->getArtist().toLatin1(), 0, 0, 0);
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "TrackTitle", STRINGBODY, pTrack->getTitle().toLatin1(), 0, 0, 0);
+
+ float oscTrackInfoDuration = pTrack->getDuration();
+ int oscTrackInfoDurationCalcMin = oscTrackInfoDuration / 60;
+ int oscTrackInfoDurationCalcSec = oscTrackInfoDuration - (oscTrackInfoDurationCalcMin * 60);
+
+ QString oscTrackInfoDurationCalc;
+
+ if (oscTrackInfoDurationCalcSec < 10) {
+ oscTrackInfoDurationCalc = QString("%1:0%2").arg(oscTrackInfoDurationCalcMin).arg(oscTrackInfoDurationCalcSec);
+ } else {
+ oscTrackInfoDurationCalc = QString("%1:%2").arg(oscTrackInfoDurationCalcMin).arg(oscTrackInfoDurationCalcSec);
+ };
+
+ QByteArray oscTrackInfoDurationBa = oscTrackInfoDurationCalc.toLocal8Bit();
+ const char* oscBodyMessageDuration = oscTrackInfoDurationBa.data();
+
+ //OscFunctionsSendChar(getGroup(), "Duration", oscBodyMessageDuration);
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "TDuration", STRINGBODY, oscBodyMessageDuration, 0, 0, 0);
+ }
+// EveOSC end
+
+ // Reset slip mode
+ m_pSlipButton->set(0);
+ m_bSlipEnabledProcessing = false;
+ m_slipPos = mixxx::audio::kStartFramePos;
+ m_dSlipRate = 0;
+ m_slipModeState = SlipModeState::Disabled;
+
+ m_pReplayGain->set(pTrack->getReplayGain().getRatio());
+
+ m_queuedSeek.setValue(kNoQueuedSeek);
+
+ // Reset the pitch value for the new track.
+ m_pause.unlock();
+
+ notifyTrackLoaded(pTrack, pOldTrack);
+
+ // Check if we are cloning another channel before doing any seeking.
+ // This replaces m_queuedSeek populated form CueControl
+ EngineChannel* pChannel = atomicLoadRelaxed(m_pChannelToCloneFrom);
+ if (pChannel) {
+ m_queuedSeek.setValue(kCloneSeek);
+ m_iSeekPhaseQueued = 0;
+ }
+
+ // Start buffer processing after all EngineContols are up to date
+ // with the current track e.g track is seeked to Cue
+ m_iTrackLoading = 0;
+}
+
+// WARNING: Always called from the EngineWorker thread pool
+void EngineBuffer::slotTrackLoadFailed(TrackPointer pTrack,
+ const QString& reason) {
+ m_iTrackLoading = 0;
+ m_pChannelToCloneFrom = nullptr;
+
+ // Loading of a new track failed.
+ // eject the currently loaded track (the old Track) as well
+ ejectTrack();
+ emit trackLoadFailed(pTrack, reason);
+}
+
+void EngineBuffer::ejectTrack() {
+ // clear track values in any case, may fix https://github.com/mixxxdj/mixxx/issues/8000
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << "EngineBuffer::ejectTrack()";
+ }
+ TrackPointer pOldTrack = m_pCurrentTrack;
+ m_pause.lock();
+
+ m_visualPlayPos->set(0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ SlipModeState::Disabled,
+ false,
+ false,
+ false,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0);
+ doSeekPlayPos(mixxx::audio::kStartFramePos, SEEK_EXACT);
+
+ m_pCurrentTrack.reset();
+ setTrackEndPosition(mixxx::audio::kInvalidFramePos);
+ m_pTrackSampleRate->set(0);
+ m_pTrackLoaded->forceSet(0);
+
+ m_pTrackType->set(0);
+ m_pTrackTypeLength->set(0);
+ m_pTrackArtistLength->set(0);
+ m_pTrackArtist_1->set(0);
+ m_pTrackArtist_2->set(0);
+ m_pTrackArtist_3->set(0);
+ m_pTrackArtist_4->set(0);
+ m_pTrackArtist_5->set(0);
+
+ m_pTrackTitleLength->set(0);
+ m_pTrackTitle_1->set(0);
+ m_pTrackTitle_2->set(0);
+ m_pTrackTitle_3->set(0);
+ m_pTrackTitle_4->set(0);
+ m_pTrackTitle_5->set(0);
+
+ // EveOSC begin
+ //std::mutex mmm;
+ //std::lock_guard lock(mmm);
+ if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+// OscFunctionsSendChar(getGroup(), "TrackArtist", "no track loaded");
+// OscFunctionsSendChar(getGroup(), "TrackTitle", "no track loaded");
+// OscFunctionsSendChar(getGroup(), "Duration", "0:00");
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "TrackArtist", STRINGBODY, "no track loaded", 0, 0, 0);
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "TrackTitle", STRINGBODY, "no track loaded", 0, 0, 0);
+ OscFunctionsSendPtrType(m_pConfig, getGroup(), "Duration", STRINGBODY, "0:00", 0, 0, 0);
+
+
+ }
+ //std::lock_guard unlock(mmm);
+ // EveOSC end
+
+ m_playButton->set(0.0);
+ m_playposSlider->set(0);
+ m_pCueControl->resetIndicators();
+
+ m_pReplayGain->set(0.0);
+
+ m_queuedSeek.setValue(kNoQueuedSeek);
+
+ m_pause.unlock();
+
+ // Close open file handles by unloading the current track
+ m_pReader->newTrack(TrackPointer());
+
+ if (pOldTrack) {
+ notifyTrackLoaded(TrackPointer(), pOldTrack);
+ }
+ m_iTrackLoading = 0;
+ m_pChannelToCloneFrom = nullptr;
+}
+
+void EngineBuffer::notifyTrackLoaded(
+ TrackPointer pNewTrack, TrackPointer pOldTrack) {
+ if (pOldTrack) {
+ disconnect(
+ pOldTrack.get(),
+ &Track::beatsUpdated,
+ this,
+ &EngineBuffer::slotUpdatedTrackBeats);
+ }
+
+ // First inform engineControls directly
+ // Note: we are still in a worker thread.
+ const auto trackEndPosition = getTrackEndPosition();
+ const auto sampleRate = mixxx::audio::SampleRate::fromDouble(m_pTrackSampleRate->get());
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ pControl->setFrameInfo(m_playPos, trackEndPosition, sampleRate);
+ pControl->trackLoaded(pNewTrack);
+ }
+
+ if (pNewTrack) {
+ connect(pNewTrack.get(),
+ &Track::beatsUpdated,
+ this,
+ &EngineBuffer::slotUpdatedTrackBeats,
+ Qt::DirectConnection);
+ connect(pNewTrack.get(),
+ &Track::bpmLockChanged,
+ m_pBpmControl,
+ &BpmControl::trackBpmLockChanged,
+ Qt::DirectConnection);
+ bool bpmLocked = pNewTrack.get()->isBpmLocked();
+ m_pBpmControl->trackBpmLockChanged(bpmLocked);
+ }
+
+ // Inform BaseTrackPlayer via a queued connection
+ emit trackLoaded(pNewTrack, pOldTrack);
+}
+
+void EngineBuffer::slotPassthroughChanged(double enabled) {
+ if (enabled != 0) {
+ // If passthrough was enabled, stop playing the current track.
+ slotControlStop(1.0);
+ // Disable CUE and Play indicators
+ m_pCueControl->resetIndicators();
+ } else {
+ // Update CUE and Play indicators. Note: m_pCueControl->updateIndicators()
+ // is not sufficient.
+ updateIndicatorsAndModifyPlay(false, false);
+ }
+}
+
+// WARNING: This method runs in both the GUI thread and the Engine Thread
+void EngineBuffer::slotControlSeek(double fractionalPos) {
+ doSeekFractional(fractionalPos, SEEK_STANDARD);
+}
+
+// WARNING: This method is called by EngineControl and runs in the engine thread
+void EngineBuffer::seekAbs(mixxx::audio::FramePos position) {
+ DEBUG_ASSERT(position.isValid());
+ doSeekPlayPos(position, SEEK_STANDARD);
+}
+
+// WARNING: This method is called by EngineControl and runs in the engine thread
+void EngineBuffer::seekExact(mixxx::audio::FramePos position) {
+ DEBUG_ASSERT(position.isValid());
+ doSeekPlayPos(position, SEEK_EXACT);
+}
+
+double EngineBuffer::fractionalPlayposFromAbsolute(mixxx::audio::FramePos absolutePlaypos) {
+ if (!m_trackEndPositionOld.isValid()) {
+ return 0.0;
+ }
+
+ const auto position = std::min(absolutePlaypos, m_trackEndPositionOld);
+ return position.value() / m_trackEndPositionOld.value();
+}
+
+void EngineBuffer::doSeekFractional(double fractionalPos, enum SeekRequest seekType) {
+ // Prevent NaN's from sneaking into the engine.
+ VERIFY_OR_DEBUG_ASSERT(!util_isnan(fractionalPos)) {
+ return;
+ }
+
+ // FIXME: Use maybe invalid here
+ const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition();
+ VERIFY_OR_DEBUG_ASSERT(trackEndPosition.isValid()) {
+ return;
+ }
+ const auto seekPosition = trackEndPosition * fractionalPos;
+ doSeekPlayPos(seekPosition, seekType);
+}
+
+void EngineBuffer::doSeekPlayPos(mixxx::audio::FramePos position, enum SeekRequest seekType) {
+#ifdef __VINYLCONTROL__
+ // Notify the vinyl control that a seek has taken place in case it is in
+ // absolute mode and needs be switched to relative.
+ if (m_pVinylControlControl) {
+ m_pVinylControlControl->notifySeekQueued();
+ }
+#endif
+
+ queueNewPlaypos(position, seekType);
+}
+
+bool EngineBuffer::updateIndicatorsAndModifyPlay(bool newPlay, bool oldPlay) {
+ // If no track is currently loaded, turn play off. If a track is loading
+ // allow the set since it might apply to a track we are loading due to the
+ // asynchrony.
+ bool playPossible = true;
+ const QueuedSeek queuedSeek = m_queuedSeek.getValue();
+ if ((!m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0) ||
+ (m_pCurrentTrack && atomicLoadRelaxed(m_iTrackLoading) == 0 &&
+ m_playPos >= getTrackEndPosition() &&
+ queuedSeek.seekType == SEEK_NONE) ||
+ m_pPassthroughEnabled->toBool()) {
+ // play not possible
+ playPossible = false;
+ }
+
+ return m_pCueControl->updateIndicatorsAndModifyPlay(newPlay, oldPlay, playPossible);
+}
+
+void EngineBuffer::verifyPlay() {
+ bool play = m_playButton->toBool();
+ bool verifiedPlay = updateIndicatorsAndModifyPlay(play, play);
+ if (play != verifiedPlay) {
+ m_playButton->setAndConfirm(verifiedPlay ? 1.0 : 0.0);
+ }
+}
+
+void EngineBuffer::slotControlPlayRequest(double v) {
+ bool oldPlay = m_playButton->toBool();
+ bool verifiedPlay = updateIndicatorsAndModifyPlay(v > 0.0, oldPlay);
+
+ if (!oldPlay && verifiedPlay) {
+ if (m_pQuantize->toBool()
+#ifdef __VINYLCONTROL__
+ && m_pVinylControlControl && !m_pVinylControlControl->isEnabled()
+#endif
+ ) {
+ requestSyncPhase();
+ }
+ }
+
+ // set and confirm must be called here in any case to update the widget toggle state
+ m_playButton->setAndConfirm(verifiedPlay ? 1.0 : 0.0);
+}
+
+void EngineBuffer::slotControlStart(double v)
+{
+ if (v > 0.0) {
+ doSeekFractional(0., SEEK_EXACT);
+ }
+}
+
+void EngineBuffer::slotControlEnd(double v)
+{
+ if (v > 0.0) {
+ doSeekFractional(1., SEEK_EXACT);
+ }
+}
+
+void EngineBuffer::slotControlPlayFromStart(double v)
+{
+ if (v > 0.0) {
+ doSeekFractional(0., SEEK_EXACT);
+ m_playButton->set(1);
+ }
+}
+
+void EngineBuffer::slotControlJumpToStartAndStop(double v)
+{
+ if (v > 0.0) {
+ doSeekFractional(0., SEEK_EXACT);
+ m_playButton->set(0);
+ }
+}
+
+void EngineBuffer::slotControlStop(double v)
+{
+ if (v > 0.0) {
+ m_playButton->set(0);
+ }
+}
+
+void EngineBuffer::slotKeylockEngineChanged(double dIndex) {
+ if (m_bScalerOverride) {
+ return;
+ }
+ const KeylockEngine engine = static_cast(dIndex);
+ switch (engine) {
+ case KeylockEngine::SoundTouch:
+ m_pScaleKeylock = m_pScaleST;
+ break;
+#ifdef __RUBBERBAND__
+ case KeylockEngine::RubberBandFaster:
+ m_pScaleRB->useEngineFiner(false);
+ m_pScaleKeylock = m_pScaleRB;
+ break;
+ case KeylockEngine::RubberBandFiner:
+ m_pScaleRB->useEngineFiner(
+ true); // in case of Rubberband V2 it falls back to RUBBERBAND_FASTER
+ m_pScaleKeylock = m_pScaleRB;
+ break;
+#endif
+ default:
+ slotKeylockEngineChanged(static_cast(defaultKeylockEngine()));
+ break;
+ }
+}
+
+void EngineBuffer::processTrackLocked(
+ CSAMPLE* pOutput, const int iBufferSize, mixxx::audio::SampleRate sampleRate) {
+ ScopedTimer t(QStringLiteral("EngineBuffer::process_pauselock"));
+
+ m_trackSampleRateOld = mixxx::audio::SampleRate::fromDouble(m_pTrackSampleRate->get());
+ m_trackEndPositionOld = getTrackEndPosition();
+
+ double baseSampleRate = 0.0;
+ if (sampleRate.isValid()) {
+ baseSampleRate = m_trackSampleRateOld / sampleRate;
+ }
+
+ // Sync requests can affect rate, so process those first.
+ processSyncRequests();
+
+ // Note: play is also active during cue preview
+ bool paused = !m_playButton->toBool();
+ KeyControl::PitchTempoRatio pitchTempoRatio = m_pKeyControl->getPitchTempoRatio();
+
+ // The pitch adjustment in Ratio (1.0 being normal
+ // pitch. 2.0 is a full octave shift up).
+ double pitchRatio = pitchTempoRatio.pitchRatio;
+ double tempoRatio = pitchTempoRatio.tempoRatio;
+ const bool keylock_enabled = pitchTempoRatio.keylock;
+
+ bool is_scratching = false;
+ bool is_reverse = false;
+
+ // Update the slipped position and seek to it if slip mode was disabled.
+ processSlip(iBufferSize);
+
+ // Note: This may affect the m_playPos, play, scaler and crossfade buffer
+ processSeek(paused);
+
+ // speed is the ratio between track-time and real-time
+ // (1.0 being normal rate. 2.0 plays at 2x speed -- 2 track seconds
+ // pass for every 1 real second). Depending on whether
+ // keylock is enabled, this is applied to either the rate or the tempo.
+ int outputBufferSize = iBufferSize;
+ int stereoPairCount = m_channelCount / mixxx::audio::ChannelCount::stereo();
+ // The speed is calculated out of the buffer size for the stereo channel
+ // output, after mixing multi channel (stem) together
+ if (stereoPairCount > 1) {
+ outputBufferSize = iBufferSize / stereoPairCount;
+ }
+ double speed = m_pRateControl->calculateSpeed(
+ baseSampleRate,
+ tempoRatio,
+ paused,
+ outputBufferSize,
+ &is_scratching,
+ &is_reverse);
+
+ bool useIndependentPitchAndTempoScaling = false;
+
+ // TODO(owen): Maybe change this so that rubberband doesn't disable
+ // keylock on scratch. (just check m_pScaleKeylock == m_pScaleST)
+ if (is_scratching || fabs(speed) > 1.9) {
+ // Scratching and high speeds with always disables keylock
+ // because Soundtouch sounds terrible in these conditions. Rubberband
+ // sounds better, but still has some problems (it may reallocate in
+ // a party-crashing manner at extremely slow speeds).
+ // High seek speeds also disables keylock. Our pitch slider could go
+ // to 90%, so that's the cutoff point.
+
+ // Force pitchRatio to the linear pitch set by speed
+ pitchRatio = speed;
+ // This is for the natural speed pitch found on turn tables
+ } else if (fabs(speed) < 0.1) {
+ // We have pre-allocated big buffers in Rubberband and Soundtouch for
+ // a minimum speed of 0.1. Slower speeds will re-allocate much bigger
+ // buffers which may cause underruns.
+ // Disable keylock under these conditions.
+
+ // Force pitchRatio to the linear pitch set by speed
+ pitchRatio = speed;
+ } else if (keylock_enabled) {
+ // always use IndependentPitchAndTempoScaling
+ // to avoid clicks when crossing the linear pitch
+ // in this case it is most likely that the user
+ // will have an non linear pitch
+ // Note: We have undesired noise when cossfading between scalers
+ useIndependentPitchAndTempoScaling = true;
+ } else {
+ // We might have have temporary speed change, so adjust pitch if not locked
+ // Note: This will not update key and tempo widgets
+ if (tempoRatio != 0) {
+ pitchRatio *= (speed / tempoRatio);
+ }
+
+ // Check if we are off-linear (musical key has been adjusted
+ // independent from speed) to determine if the keylock scaler
+ // should be used even though keylock is disabled.
+ if (speed != 0.0) {
+ double offlinear = pitchRatio / speed;
+ if (offlinear > kLinearScalerElipsis ||
+ offlinear < 1 / kLinearScalerElipsis) {
+ // only enable keylock scaler if pitch adjustment is at
+ // least 1 cent. Everything below is not hear-able.
+ useIndependentPitchAndTempoScaling = true;
+ }
+ }
+ }
+
+ if (speed != 0.0) {
+ // Do not switch scaler when we have no transport
+ enableIndependentPitchTempoScaling(useIndependentPitchAndTempoScaling,
+ iBufferSize);
+ } else if (m_speed_old != 0 && !is_scratching) {
+ // we are stopping, collect samples for fade out
+ readToCrossfadeBuffer(iBufferSize);
+ // Clear the scaler information
+ m_pScale->clear();
+ }
+
+ // How speed/tempo/pitch are related:
+ // Processing is done in two parts, the first part is calculated inside
+ // the KeyKontrol class and effects the visual key/pitch widgets.
+ // The Speed slider controls the tempoRatio and a speedSliderPitchRatio,
+ // the pitch amount caused by it.
+ // By default the speed slider controls pitch and tempo with the same
+ // value.
+ // If key lock is enabled, the speedSliderPitchRatio is decoupled from
+ // the speed slider (const).
+ //
+ // With preference mode KeylockMode = kLockOriginalKey
+ // the speedSliderPitchRatio is reset to 1 and back to the tempoRatio
+ // (natural vinyl Pitch) when keylock is disabled and enabled.
+ //
+ // With preference mode KeylockMode = kCurrentKey
+ // the speedSliderPitchRatio is not reset when keylock is enabled.
+ // This mode allows to enable keylock
+ // while the track is already played. You can reset to the tracks
+ // original pitch by resetting the pitch knob to center. When disabling
+ // keylock the pitch is reset to the linear vinyl pitch.
+
+ // The Pitch knob turns if the speed slider is moved without keylock.
+ // This is useful to get always an analog impression of current pitch,
+ // and its distance to the original track pitch
+ //
+ // The Pitch_Adjust knob does not reflect the speedSliderPitchRatio.
+ // So it is is useful for controller mappings, because it is not
+ // changed by the speed slider or keylock.
+
+ // In the second part all other speed changing controls are processed.
+ // They may produce an additional pitch if keylock is disabled or
+ // override the pitch in scratching case.
+ // If pitch ratio and tempo ratio are equal, a linear scaler is used,
+ // otherwise tempo and pitch are processed individual
+
+ double rate = 0;
+ // If the base samplerate, speed, or pitch has changed, we need to update the
+ // scaler. Also, if we have changed scalers then we need to update the
+ // scaler.
+ if (baseSampleRate != m_baserate_old || speed != m_speed_old ||
+ pitchRatio != m_pitch_old || tempoRatio != m_tempo_ratio_old ||
+ m_bScalerChanged) {
+ // The rate returned by the scale object can be different from the
+ // wanted rate! Make sure new scaler has proper position. This also
+ // crossfades between the old scaler and new scaler to prevent
+ // clicks.
+
+ // Handle direction change.
+ // The linear scaler supports ramping though zero.
+ // This is used for scratching, but not for reverse
+ // For the other, crossfade forward and backward samples
+ if ((m_speed_old * speed < 0) && // Direction has changed!
+ (m_pScale != m_pScaleVinyl || // only m_pScaleLinear supports going though 0
+ m_reverse_old != is_reverse)) { // no pitch change when reversing
+ //XXX: Trying to force RAMAN to read from correct
+ // playpos when rate changes direction - Albert
+ readToCrossfadeBuffer(iBufferSize);
+ // Clear the scaler information
+ m_pScale->clear();
+ }
+
+ m_baserate_old = baseSampleRate;
+ m_speed_old = speed;
+ m_pitch_old = pitchRatio;
+ m_tempo_ratio_old = tempoRatio;
+ m_reverse_old = is_reverse;
+
+ // Now we need to update the scaler with the main sample rate, the
+ // base rate (ratio between sample rate of the source audio and the
+ // main samplerate), the deck speed, the pitch shift, and whether
+ // the deck speed should affect the pitch.
+
+ m_pScale->setScaleParameters(baseSampleRate,
+ &speed,
+ &pitchRatio);
+
+ // The way we treat rate inside of EngineBuffer is actually a
+ // description of "sample consumption rate" or percentage of samples
+ // consumed relative to playing back the track at its native sample
+ // rate and normal speed. pitch_adjust does not change the playback
+ // rate.
+ rate = baseSampleRate * speed;
+
+ // Scaler is up to date now.
+ m_bScalerChanged = false;
+ } else {
+ // Scaler did not need updating. By definition this means we are at
+ // our old rate.
+ rate = m_rate_old;
+ }
+
+ const mixxx::audio::FramePos playpos_old = m_playPos;
+ bool bCurBufferPaused = false;
+ bool atEnd = false;
+ bool backwards = rate < 0;
+ const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition();
+ if (trackEndPosition.isValid()) {
+ atEnd = m_playPos >= trackEndPosition;
+ if (atEnd && !backwards) {
+ // do not play past end
+ bCurBufferPaused = true;
+ } else if (rate == 0 && !is_scratching) {
+ // do not process samples if have no transport
+ // the linear scaler supports ramping down to 0
+ // this is used for pause by scratching only
+ bCurBufferPaused = true;
+ }
+ } else {
+ // Track has already been ejected.
+ bCurBufferPaused = true;
+ }
+
+ m_rate_old = rate;
+
+ // If the buffer is not paused, then scale the audio.
+ if (!bCurBufferPaused) {
+ // Perform scaling of Reader buffer into buffer.
+ const double framesRead = m_pScale->scaleBuffer(pOutput, iBufferSize);
+
+ // TODO(XXX): The result framesRead might not be an integer value.
+ // Converting to samples here does not make sense. All positional
+ // calculations should be done in frames instead of samples! Otherwise
+ // rounding errors might occur when converting from samples back to
+ // frames later.
+
+ if (m_bScalerOverride) {
+ // If testing, we don't have a real log so we fake the position.
+ m_playPos += framesRead;
+ } else {
+ // Adjust filepos_play by the amount we processed.
+ m_playPos = m_pReadAheadManager->getFilePlaypositionFromLog(
+ m_playPos, framesRead, m_channelCount);
+ }
+ // Note: The last buffer of a track is padded with silence.
+ // This silence is played together with the last samples in the last
+ // callback and the m_playPos is advanced behind the end of the track.
+ // If repeat is enabled, scaler->scaleBuffer() wraps around at end/start
+ // and fills the buffer with samples from the other end of the track.
+
+ if (m_bCrossfadeReady) {
+ // Bring pOutput with the new parameters in and fade out the old one,
+ // stored with the old parameters in m_pCrossfadeBuffer
+ SampleUtil::linearCrossfadeBuffersIn(
+ pOutput, m_pCrossfadeBuffer, iBufferSize, m_channelCount);
+ }
+ // Note: we do not fade here if we pass the end or the start of
+ // the track in reverse direction
+ // because we assume that the track samples itself start and stop
+ // towards zero.
+ // If it turns out that ramping is required be aware that the end
+ // or start may pass in the middle of the buffer.
+ } else {
+ // Pause
+ if (m_bCrossfadeReady) {
+ // We don't ramp here, since EnginePregain handles fades
+ // from and to speed == 0
+ SampleUtil::copy(pOutput, m_pCrossfadeBuffer, iBufferSize);
+ } else {
+ SampleUtil::clear(pOutput, iBufferSize);
+ }
+ }
+
+ m_actual_speed = (m_playPos - playpos_old) / (iBufferSize / 2);
+ // qDebug() << "Ramped Speed" << m_actual_speed / m_speed_old;
+
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ // m_playPos is already updated here and points to the end of the played buffer
+ pControl->setFrameInfo(m_playPos, trackEndPosition, m_trackSampleRateOld);
+ pControl->process(rate, m_playPos, iBufferSize);
+ }
+
+ m_scratching_old = is_scratching;
+
+ // If we're repeating and crossed the track boundary, ReadAheadManager already
+ // wrapped around the playposition.
+ // To ensure quantize is respected we request a phase sync.
+ // TODO(ronso) This just restores previous repeat+quantize behaviour. I'm not
+ // sure whether that was actually desired or just a side effect of seeking.
+ // Ife it's really desired, should this be moved to looping control in order
+ // to set the sync'ed playposition right away and fill the wrap-around buffer
+ // with correct samples from the sync'ed loop in / track start position?
+ if (m_pRepeat->toBool() && m_pQuantize->toBool() &&
+ (m_playPos > playpos_old) == backwards) {
+ // TODO() The resulting seek is processed in the following callback
+ // That is to late
+ requestSyncPhase();
+ }
+
+ bool end_of_track = atEnd && !backwards;
+
+ // If playbutton is pressed and we're at the end of track release play button
+ if (m_playButton->toBool() && end_of_track) {
+ m_playButton->set(0.);
+ }
+
+ // Give the Reader hints as to which chunks of the current song we
+ // really care about. It will try very hard to keep these in memory
+ hintReader(rate);
+}
+
+void EngineBuffer::process(CSAMPLE* pOutput, const int iBufferSize) {
+ // Bail if we receive a buffer size with incomplete sample frames. Assert in debug builds.
+ VERIFY_OR_DEBUG_ASSERT((iBufferSize % m_channelCount) == 0) {
+ return;
+ }
+ m_pReader->process();
+ // Steps:
+ // - Lookup new reader information
+ // - Calculate current rate
+ // - Scale the audio with m_pScale, copy the resulting samples into the
+ // output buffer
+ // - Give EngineControl's a chance to do work / request seeks, etc
+ // - Process repeat mode if we're at the end or beginning of a track
+ // - Set last sample value (m_fLastSampleValue) so that rampOut works? Other
+ // miscellaneous upkeep issues.
+
+ m_sampleRate = mixxx::audio::SampleRate::fromDouble(m_pSampleRate->get());
+
+ // If the sample rate has changed, force Rubberband to reset so that
+ // it doesn't reallocate when the user engages keylock during playback.
+ // We do this even if rubberband is not active.
+ m_pScaleLinear->setSignal(m_sampleRate, m_channelCount);
+ m_pScaleST->setSignal(m_sampleRate, m_channelCount);
+#ifdef __RUBBERBAND__
+ m_pScaleRB->setSignal(m_sampleRate, m_channelCount);
+#endif
+
+ bool hasStableTrack = m_pTrackLoaded->toBool() && m_iTrackLoading.loadAcquire() == 0;
+ if (hasStableTrack && m_pause.tryLock()) {
+ processTrackLocked(pOutput, iBufferSize, m_sampleRate);
+ // release the pauselock
+ m_pause.unlock();
+ } else {
+ // We are loading a new Track
+
+ // Here the old track was playing and loading the new track is in
+ // progress. We can't predict when it happens, so we are not able
+ // to collect old samples. New samples are also not in place and
+ // we can't predict when they will be in place.
+ // If one does this, a click from breaking the last track is somehow
+ // natural and he should know that such sound should not be played to
+ // the main (audience).
+ // Workaround: Simply pause the track before.
+
+ // TODO(XXX):
+ // A click free solution requires more refactoring how loading a track
+ // is handled. For now we apply a rectangular Gain change here which
+ // may click.
+
+ SampleUtil::clear(pOutput, iBufferSize);
+
+ m_rate_old = 0;
+ m_speed_old = 0;
+ m_actual_speed = 0;
+ m_scratching_old = false;
+ }
+
+#ifdef __SCALER_DEBUG__
+ for (int i=0; iupdateAudible();
+
+ m_iLastBufferSize = iBufferSize;
+ m_bCrossfadeReady = false;
+}
+
+void EngineBuffer::processSlip(int iBufferSize) {
+ // Do a single read from m_bSlipEnabled so we don't run in to race conditions.
+ bool enabled = m_pSlipButton->toBool();
+ if (enabled != m_bSlipEnabledProcessing) {
+ m_bSlipEnabledProcessing = enabled;
+ if (enabled) {
+ m_slipPos = m_playPos;
+ m_dSlipRate = m_rate_old;
+ } else {
+ // TODO(owen) assuming that looping will get canceled properly
+ seekExact(m_slipPos.toNearestFrameBoundary());
+ m_slipPos = mixxx::audio::kStartFramePos;
+ }
+ }
+
+ // Increment slip position even if it was just toggled -- this ensures the position is correct.
+ if (enabled) {
+ // `iBufferSize` originates from `SoundManager::onDeviceOutputCallback`
+ // and is always a multiple of channel count, so we can safely use integer division
+ // to find the number of frames per buffer here.
+ //
+ // TODO: Check if we can replace `iBufferSize` with the number of
+ // frames per buffer in most engine method signatures to avoid this
+ // back and forth calculations.
+ const int bufferFrameCount = iBufferSize / m_channelCount;
+ DEBUG_ASSERT(bufferFrameCount * m_channelCount == iBufferSize);
+ const mixxx::audio::FrameDiff_t slipDelta =
+ static_cast(bufferFrameCount) * m_dSlipRate;
+ // Simulate looping if a regular loop is active
+ if (m_pLoopingControl->isLoopingEnabled() &&
+ m_pLoopingControl->loopWasEnabledBeforeSlipEnable() &&
+ !m_pLoopingControl->isLoopRollActive()) {
+ const mixxx::audio::FramePos newPos = m_slipPos + slipDelta;
+ m_slipPos = m_pLoopingControl->adjustedPositionForCurrentLoop(
+ newPos,
+ m_dSlipRate < 0);
+ m_slipModeState = SlipModeState::Armed;
+ } else {
+ m_slipPos += slipDelta;
+ m_slipModeState = SlipModeState::Running;
+ }
+ } else {
+ m_slipModeState = SlipModeState::Disabled;
+ }
+}
+
+void EngineBuffer::processSyncRequests() {
+ SyncRequestQueued enable_request =
+ static_cast(
+ m_iEnableSyncQueued.fetchAndStoreRelease(SYNC_REQUEST_NONE));
+ SyncMode mode_request =
+ static_cast(m_iSyncModeQueued.fetchAndStoreRelease(
+ static_cast(SyncMode::Invalid)));
+ switch (enable_request) {
+ case SYNC_REQUEST_ENABLE:
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::Follower);
+ break;
+ case SYNC_REQUEST_DISABLE:
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::None);
+ break;
+ case SYNC_REQUEST_ENABLEDISABLE:
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::Follower);
+ m_pEngineSync->requestSyncMode(m_pSyncControl, SyncMode::None);
+ break;
+ case SYNC_REQUEST_NONE:
+ break;
+ }
+ if (mode_request != SyncMode::Invalid) {
+ m_pEngineSync->requestSyncMode(m_pSyncControl,
+ static_cast(mode_request));
+ }
+}
+
+void EngineBuffer::processSeek(bool paused) {
+ m_previousBufferSeek = false;
+
+ const QueuedSeek queuedSeek = m_queuedSeek.getValue();
+
+ SeekRequests seekType = queuedSeek.seekType;
+ mixxx::audio::FramePos position = queuedSeek.position;
+
+ // Add SEEK_PHASE bit, if any
+ if (m_iSeekPhaseQueued.fetchAndStoreRelease(0)) {
+ seekType |= SEEK_PHASE;
+ }
+
+ switch (seekType) {
+ case SEEK_NONE:
+ return;
+ case SEEK_PHASE:
+ // only adjust phase
+ position = m_playPos;
+ break;
+ case SEEK_STANDARD:
+ if (m_pQuantize->toBool()) {
+ seekType |= SEEK_PHASE;
+ }
+ // new position was already set above
+ break;
+ case SEEK_EXACT:
+ case SEEK_EXACT_PHASE: // artificial state = SEEK_EXACT | SEEK_PHASE
+ case SEEK_STANDARD_PHASE: // artificial state = SEEK_STANDARD | SEEK_PHASE
+ // new position was already set above
+ break;
+ case SEEK_CLONE: {
+ // Cloning another channels position.
+ EngineChannel* pOtherChannel = m_pChannelToCloneFrom.fetchAndStoreRelaxed(nullptr);
+ VERIFY_OR_DEBUG_ASSERT(pOtherChannel) {
+ return;
+ }
+ position = pOtherChannel->getEngineBuffer()->getExactPlayPos();
+ } break;
+ default:
+ DEBUG_ASSERT(!"Unhandled seek request type");
+ m_queuedSeek.setValue(kNoQueuedSeek);
+ return;
+ }
+
+ VERIFY_OR_DEBUG_ASSERT(position.isValid()) {
+ return;
+ }
+
+ // Don't allow the playposition to go past the end.
+ position = std::min(position, m_trackEndPositionOld);
+
+ if (!paused && (seekType & SEEK_PHASE)) {
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << "EngineBuffer::processSeek" << getGroup() << "Seeking phase";
+ }
+ const mixxx::audio::FramePos syncPosition =
+ m_pBpmControl->getBeatMatchPosition(position, true, true);
+ position = m_pLoopingControl->getSyncPositionInsideLoop(position, syncPosition);
+ if (kLogger.traceEnabled()) {
+ kLogger.trace()
+ << "EngineBuffer::processSeek" << getGroup() << "seek info:" << m_playPos
+ << "->" << position;
+ }
+ }
+ if (position != m_playPos) {
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << "EngineBuffer::processSeek" << getGroup() << "Seek to" << position;
+ }
+ setNewPlaypos(position);
+ m_previousBufferSeek = true;
+ }
+ // Reset the m_queuedSeek value after it has been processed in
+ // setNewPlaypos() so that the Engine Controls have always access to the
+ // position of the upcoming buffer cycle (used for loop cues)
+ m_queuedSeek.setValue(kNoQueuedSeek);
+}
+
+void EngineBuffer::postProcessLocalBpm() {
+ m_pBpmControl->updateLocalBpm();
+}
+
+void EngineBuffer::postProcess(const int iBufferSize) {
+ // The order of events here is very delicate. It's necessary to update
+ // some values before others, because the later updates may require
+ // values from the first update. Do not make calls here that could affect
+ // which Syncable is leader or could cause Syncables to try to match
+ // beat distances. During these calls those values are inconsistent.
+ if (kLogger.traceEnabled()) {
+ kLogger.trace() << getGroup() << "EngineBuffer::postProcess";
+ }
+ const mixxx::Bpm localBpm = m_pBpmControl->getLocalBpm();
+ double beatDistance = m_pBpmControl->updateBeatDistance();
+ const SyncMode mode = m_pSyncControl->getSyncMode();
+ if (localBpm.isValid()) {
+ m_pSyncControl->setLocalBpm(localBpm);
+ m_pSyncControl->reportPlayerSpeed(m_speed_old, m_scratching_old);
+ if (isLeader(mode)) {
+ m_pEngineSync->notifyBeatDistanceChanged(m_pSyncControl, beatDistance);
+ } else if (isFollower(mode)) {
+ m_pSyncControl->updateTargetBeatDistance();
+ }
+ } else if (mode == SyncMode::LeaderSoft) {
+ // If this channel has been automatically chosen to be the leader but
+ // no BPM is available, another channel may take over leadership and
+ // this channel becomes a follower. This may happen if the track is
+ // analyzed upon load and avoids sudden tempo jumps on the other deck
+ // while the analysis is still running.
+ requestSyncMode(SyncMode::Follower);
+ }
+
+ // Update all the indicators that EngineBuffer publishes to allow
+ // external parts of Mixxx to observe its status.
+ updateIndicators(m_speed_old, iBufferSize);
+}
+
+mixxx::audio::FramePos EngineBuffer::queuedSeekPosition() const {
+ const QueuedSeek queuedSeek = m_queuedSeek.getValue();
+ if (queuedSeek.seekType == SEEK_NONE) {
+ return {};
+ }
+
+ return queuedSeek.position;
+}
+
+void EngineBuffer::updateIndicators(double speed, int iBufferSize) {
+ if (!m_playPos.isValid() ||
+ !m_trackSampleRateOld.isValid() ||
+ m_pPassthroughEnabled->toBool()) {
+ // Skip indicator updates with invalid values to prevent undefined behavior,
+ // e.g. in WaveformRenderBeat::draw().
+ //
+ // This is known to happen if Deck Passthrough is active, when either no
+ // track is loaded or a track was loaded but processSeek() has not been
+ // called yet.
+ return;
+ }
+
+ // Increase samplesCalculated by the buffer size
+ m_iSamplesSinceLastIndicatorUpdate += iBufferSize;
+
+ const double fFractionalPlaypos = fractionalPlayposFromAbsolute(m_playPos);
+ const double fFractionalSlipPos = fractionalPlayposFromAbsolute(m_slipPos);
+
+ auto loopInfo = m_pLoopingControl->getLoopInfo();
+
+ double fFractionalLoopStartPos = 0.0;
+ if (loopInfo.startPosition.isValid()) {
+ fFractionalLoopStartPos = fractionalPlayposFromAbsolute(loopInfo.startPosition);
+ }
+ double fFractionalLoopEndPos = 0.0;
+ if (loopInfo.endPosition.isValid()) {
+ fFractionalLoopEndPos = fractionalPlayposFromAbsolute(loopInfo.endPosition);
+ }
+
+ const double tempoTrackSeconds = m_trackEndPositionOld.value() /
+ m_trackSampleRateOld / getRateRatio();
+ if (speed > 0 && fFractionalPlaypos == 1.0) {
+ // Play pos at Track end
+ speed = 0;
+ }
+
+ double effectiveSlipRate = m_dSlipRate;
+ if (effectiveSlipRate > 0.0 && fFractionalSlipPos == 1.0) {
+ // Slip pos at Track end
+ effectiveSlipRate = 0.0;
+ }
+
+ // Update indicators that are only updated after every
+ // sampleRate/kiUpdateRate samples processed. (e.g. playposSlider)
+ if (m_iSamplesSinceLastIndicatorUpdate >
+ (mixxx::kEngineChannelOutputCount * m_pSampleRate->get() /
+ kPlaypositionUpdateRate)) {
+ m_playposSlider->set(fFractionalPlaypos);
+ m_pCueControl->updateIndicators();
+ }
+
+ // Update visual control object, this needs to be done more often than the
+ // playpos slider
+ m_visualPlayPos->set(
+ fFractionalPlaypos,
+ speed * m_baserate_old,
+ static_cast(iBufferSize) /
+ m_trackEndPositionOld.toEngineSamplePos(),
+ fFractionalSlipPos,
+ effectiveSlipRate,
+ m_slipModeState,
+ m_pLoopingControl->isLoopingEnabled(),
+ m_pLoopingControl->isAdjustLoopInActive(),
+ m_pLoopingControl->isAdjustLoopOutActive(),
+ fFractionalLoopStartPos,
+ fFractionalLoopEndPos,
+ tempoTrackSeconds,
+ iBufferSize / mixxx::kEngineChannelOutputCount / m_sampleRate.toDouble() * 1000000.0);
+
+ // TODO: Especially with long audio buffers, jitter is visible. This can be fixed by moving the
+ // ClockControl::updateIndicators into the waveform update loop which is synced with the display refresh rate.
+ // Via the visual play position it's possible to access to the sample that is currently played,
+ // and not the one that have been processed as in the current solution.
+ m_pClockControl->updateIndicators(speed * m_baserate_old, m_playPos, m_sampleRate);
+}
+
+void EngineBuffer::hintReader(const double dRate) {
+ m_hintList.clear();
+ m_pReadAheadManager->hintReader(dRate, &m_hintList, m_channelCount);
+
+ //if slipping, hint about virtual position so we're ready for it
+ if (m_bSlipEnabledProcessing) {
+ Hint hint;
+ hint.frame = static_cast(m_slipPos.toLowerFrameBoundary().value());
+ hint.type = Hint::Type::SlipPosition;
+ if (m_dSlipRate >= 0) {
+ hint.frameCount = Hint::kFrameCountForward;
+ } else {
+ hint.frameCount = Hint::kFrameCountBackward;
+ }
+ m_hintList.append(hint);
+ }
+
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ pControl->hintReader(&m_hintList);
+ }
+ m_pReader->hintAndMaybeWake(m_hintList);
+}
+
+// WARNING: This method runs in the GUI thread
+#ifdef __STEM__
+void EngineBuffer::loadTrack(TrackPointer pTrack,
+ uint stemMask,
+ bool play,
+ EngineChannel* pChannelToCloneFrom) {
+#else
+void EngineBuffer::loadTrack(TrackPointer pTrack,
+ bool play,
+ EngineChannel* pChannelToCloneFrom) {
+#endif
+ if (pTrack) {
+ // Signal to the reader to load the track. The reader will respond with
+ // trackLoading and then either with trackLoaded or trackLoadFailed signals.
+ m_bPlayAfterLoading = play;
+#ifdef __STEM__
+ m_pReader->newTrack(pTrack, stemMask);
+#else
+ m_pReader->newTrack(pTrack);
+#endif
+ atomicStoreRelaxed(m_pChannelToCloneFrom, pChannelToCloneFrom);
+ } else {
+ // Loading a null track means "eject"
+ ejectTrack();
+ }
+}
+
+void EngineBuffer::addControl(EngineControl* pControl) {
+ // Connect to signals from EngineControl here...
+ m_engineControls.push_back(pControl);
+ pControl->setEngineBuffer(this);
+}
+
+bool EngineBuffer::isTrackLoaded() const {
+ if (m_pCurrentTrack) {
+ return true;
+ }
+ return false;
+}
+
+TrackPointer EngineBuffer::getLoadedTrack() const {
+ return m_pCurrentTrack;
+}
+
+mixxx::audio::FramePos EngineBuffer::getExactPlayPos() const {
+ // Is updated during postProcess(), after all decks already have been processed
+ if (!m_visualPlayPos->isValid()) {
+ return mixxx::audio::kStartFramePos;
+ }
+ return getTrackEndPosition() * m_visualPlayPos->getEnginePlayPos();
+}
+
+double EngineBuffer::getVisualPlayPos() const {
+ return m_visualPlayPos->getEnginePlayPos();
+}
+
+mixxx::audio::FramePos EngineBuffer::getTrackEndPosition() const {
+ return mixxx::audio::FramePos::fromEngineSamplePosMaybeInvalid(
+ m_pTrackSamples->get());
+}
+
+void EngineBuffer::setTrackEndPosition(mixxx::audio::FramePos position) {
+ m_pTrackSamples->set(position.toEngineSamplePosMaybeInvalid());
+}
+
+double EngineBuffer::getUserOffset() const {
+ return m_pBpmControl->getUserOffset();
+}
+
+double EngineBuffer::getRateRatio() const {
+ if (m_pBpmControl != nullptr) {
+ return m_pBpmControl->getRateRatio();
+ }
+ return 1.0;
+}
+
+void EngineBuffer::collectFeatures(GroupFeatureState* pGroupFeatures) const {
+ if (m_pBpmControl != nullptr) {
+ m_pBpmControl->collectFeatures(pGroupFeatures, m_actual_speed);
+ }
+}
+
+void EngineBuffer::slotUpdatedTrackBeats() {
+ TrackPointer pTrack = m_pCurrentTrack;
+ if (pTrack) {
+ for (const auto& pControl : std::as_const(m_engineControls)) {
+ pControl->trackBeatsUpdated(pTrack->getBeats());
+ }
+ }
+}
+
+void EngineBuffer::setScalerForTest(
+ EngineBufferScale* pScaleVinyl,
+ EngineBufferScale* pScaleKeylock) {
+ m_pScaleVinyl = pScaleVinyl;
+ m_pScaleKeylock = pScaleKeylock;
+ m_pScale = m_pScaleVinyl;
+ m_pScale->clear();
+ m_bScalerChanged = true;
+ // This bool is permanently set and can't be undone.
+ m_bScalerOverride = true;
+}
diff --git a/src/engine/cachingreader/cachingreader.cpp b/src/engine/cachingreader/cachingreader.cpp
index d2b868fa353..eb8953611db 100644
--- a/src/engine/cachingreader/cachingreader.cpp
+++ b/src/engine/cachingreader/cachingreader.cpp
@@ -205,7 +205,11 @@ CachingReaderChunkForOwner* CachingReader::lookupChunkAndFreshen(SINT chunkIndex
}
// Invoked from the UI thread!!
+#ifdef __STEM__
+void CachingReader::newTrack(TrackPointer pTrack, uint stemMask) {
+#else
void CachingReader::newTrack(TrackPointer pTrack) {
+#endif
auto newState = pTrack ? STATE_TRACK_LOADING : STATE_TRACK_UNLOADING;
auto oldState = m_state.fetchAndStoreAcquire(newState);
@@ -220,7 +224,11 @@ void CachingReader::newTrack(TrackPointer pTrack) {
kLogger.warning()
<< "Loading a new track while loading a track may lead to inconsistent states";
}
+#ifdef __STEM__
+ m_worker.newTrack(std::move(pTrack), stemMask);
+#else
m_worker.newTrack(std::move(pTrack));
+#endif
}
// Called from the engine thread
diff --git a/src/engine/cachingreader/cachingreader.h b/src/engine/cachingreader/cachingreader.h
index 384f12a8047..05fc71e5c9a 100644
--- a/src/engine/cachingreader/cachingreader.h
+++ b/src/engine/cachingreader/cachingreader.h
@@ -116,7 +116,11 @@ class CachingReader : public QObject {
// Request that the CachingReader load a new track. These requests are
// processed in the work thread, so the reader must be woken up via wake()
// for this to take effect.
+#ifdef __STEM__
+ void newTrack(TrackPointer pTrack, uint stemMask = 0);
+#else
void newTrack(TrackPointer pTrack);
+#endif
void setScheduler(EngineWorkerScheduler* pScheduler) {
m_worker.setScheduler(pScheduler);
diff --git a/src/engine/cachingreader/cachingreaderworker.cpp b/src/engine/cachingreader/cachingreaderworker.cpp
index cb4d12488e4..bed83f053dd 100644
--- a/src/engine/cachingreader/cachingreaderworker.cpp
+++ b/src/engine/cachingreader/cachingreaderworker.cpp
@@ -92,10 +92,20 @@ ReaderStatusUpdate CachingReaderWorker::processReadRequest(
}
// WARNING: Always called from a different thread (GUI)
+#ifdef __STEM__
+void CachingReaderWorker::newTrack(TrackPointer pTrack, uint stemMask) {
+#else
void CachingReaderWorker::newTrack(TrackPointer pTrack) {
+#endif
{
const auto locker = lockMutex(&m_newTrackMutex);
+#ifdef __STEM__
+ m_pNewTrack = NewTrackRequest{
+ pTrack,
+ stemMask};
+#else
m_pNewTrack = pTrack;
+#endif
m_newTrackAvailable.storeRelease(1);
}
workReady();
@@ -113,16 +123,25 @@ void CachingReaderWorker::run() {
// Request is initialized by reading from FIFO
CachingReaderChunkReadRequest request;
if (m_newTrackAvailable.loadAcquire()) {
+#ifdef __STEM__
+ NewTrackRequest pLoadTrack;
+#else
TrackPointer pLoadTrack;
+#endif
{ // locking scope
const auto locker = lockMutex(&m_newTrackMutex);
pLoadTrack = m_pNewTrack;
- m_pNewTrack.reset();
m_newTrackAvailable.storeRelease(0);
} // implicitly unlocks the mutex
+#ifdef __STEM__
+ if (pLoadTrack.track) {
+ // in this case the engine is still running with the old track
+ loadTrack(pLoadTrack.track, pLoadTrack.stemMask);
+#else
if (pLoadTrack) {
// in this case the engine is still running with the old track
loadTrack(pLoadTrack);
+#endif
} else {
// here, the engine is already stopped
unloadTrack();
@@ -168,7 +187,11 @@ void CachingReaderWorker::unloadTrack() {
m_pReaderStatusFIFO->writeBlocking(&update, 1);
}
+#ifdef __STEM__
+void CachingReaderWorker::loadTrack(const TrackPointer& pTrack, uint stemMask) {
+#else
void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) {
+#endif
// This emit is directly connected and returns synchronized
// after the engine has been stopped.
emit trackLoading();
@@ -190,6 +213,9 @@ void CachingReaderWorker::loadTrack(const TrackPointer& pTrack) {
mixxx::AudioSource::OpenParams config;
config.setChannelCount(m_maxSupportedChannel);
+#ifdef __STEM__
+ config.setStemMask(stemMask);
+#endif
m_pAudioSource = SoundSourceProxy(pTrack).openAudioSource(config);
if (!m_pAudioSource) {
kLogger.warning()
diff --git a/src/engine/cachingreader/cachingreaderworker.h b/src/engine/cachingreader/cachingreaderworker.h
index a99bbb150a6..d4c8320fe04 100644
--- a/src/engine/cachingreader/cachingreaderworker.h
+++ b/src/engine/cachingreader/cachingreaderworker.h
@@ -104,7 +104,11 @@ class CachingReaderWorker : public EngineWorker {
~CachingReaderWorker() override = default;
// Request to load a new track. wake() must be called afterwards.
+#ifdef __STEM__
+ void newTrack(TrackPointer pTrack, uint stemMask);
+#else
void newTrack(TrackPointer pTrack);
+#endif
// Run upkeep operations like loading tracks and reading from file. Run by a
// thread pool via the EngineWorkerScheduler.
@@ -122,6 +126,12 @@ class CachingReaderWorker : public EngineWorker {
void trackLoadFailed(TrackPointer pTrack, const QString& reason);
private:
+#ifdef __STEM__
+ struct NewTrackRequest {
+ TrackPointer track;
+ uint stemMask;
+ };
+#endif
const QString m_group;
QString m_tag;
@@ -134,7 +144,11 @@ class CachingReaderWorker : public EngineWorker {
// lock to touch.
QMutex m_newTrackMutex;
QAtomicInt m_newTrackAvailable;
+#ifdef __STEM__
+ NewTrackRequest m_pNewTrack;
+#else
TrackPointer m_pNewTrack;
+#endif
void discardAllPendingRequests();
@@ -147,7 +161,11 @@ class CachingReaderWorker : public EngineWorker {
void unloadTrack();
/// Internal method to load a track. Emits trackLoaded when finished.
+#ifdef __STEM__
+ void loadTrack(const TrackPointer& pTrack, uint stemMask);
+#else
void loadTrack(const TrackPointer& pTrack);
+#endif
ReaderStatusUpdate processReadRequest(
const CachingReaderChunkReadRequest& request);
diff --git a/src/engine/channels/enginedeck.cpp b/src/engine/channels/enginedeck.cpp
index 25e02a674c7..02ddf9a05b4 100644
--- a/src/engine/channels/enginedeck.cpp
+++ b/src/engine/channels/enginedeck.cpp
@@ -14,8 +14,6 @@
#ifdef __STEM__
namespace {
-constexpr int kMaxSupportedStems = 4;
-
QString getGroupForStem(const QString& deckGroup, int stemIdx) {
DEBUG_ASSERT(deckGroup.endsWith("]"));
return QStringLiteral("%1Stem%2]")
@@ -74,9 +72,9 @@ EngineDeck::EngineDeck(
m_pStemCount = std::make_unique(ConfigKey(getGroup(), "stem_count"));
m_pStemCount->setReadOnly();
- m_stemGain.reserve(kMaxSupportedStems);
- m_stemMute.reserve(kMaxSupportedStems);
- for (int stemIdx = 1; stemIdx <= kMaxSupportedStems; stemIdx++) {
+ m_stemGain.reserve(mixxx::kMaxSupportedStems);
+ m_stemMute.reserve(mixxx::kMaxSupportedStems);
+ for (int stemIdx = 1; stemIdx <= mixxx::kMaxSupportedStems; stemIdx++) {
m_stemGain.emplace_back(std::make_unique(
ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("volume"))));
// The default value is ignored and override with the medium value by
@@ -98,7 +96,7 @@ void EngineDeck::slotTrackLoaded(TrackPointer pNewTrack,
}
if (m_pConfig->getValue(
ConfigKey("[Mixer Profile]", "stem_auto_reset"), true)) {
- for (int stemIdx = 0; stemIdx < kMaxSupportedStems; stemIdx++) {
+ for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) {
m_stemGain[stemIdx]->set(1.0);
m_stemMute[stemIdx]->set(0.0);
;
@@ -206,7 +204,7 @@ void EngineDeck::cloneStemState(const EngineDeck* deckToClone) {
VERIFY_OR_DEBUG_ASSERT(deckToClone) {
return;
}
- for (int stemIdx = 0; stemIdx < kMaxSupportedStems; stemIdx++) {
+ for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) {
m_stemGain[stemIdx]->set(deckToClone->m_stemGain[stemIdx]->get());
m_stemMute[stemIdx]->set(deckToClone->m_stemMute[stemIdx]->get());
}
diff --git a/src/engine/engine.h b/src/engine/engine.h
index 4df4ab6c270..02bf959b4fa 100644
--- a/src/engine/engine.h
+++ b/src/engine/engine.h
@@ -8,6 +8,13 @@ static constexpr audio::ChannelCount kEngineChannelOutputCount =
audio::ChannelCount::stereo();
static constexpr audio::ChannelCount kMaxEngineChannelInputCount =
audio::ChannelCount::stem();
+// The following constant is always defined as it used for the waveform data
+// struct, which must stay consistent, whether the STEM feature is enabled or
+// not.
+constexpr int kMaxSupportedStems = 4;
+#ifdef __STEM__
+constexpr uint kNoStemSelected = 0;
+#endif
// Contains the information needed to process a buffer of audio
class EngineParameters final {
diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp
index 68ba74dc41f..a5b1e2aea19 100644
--- a/src/engine/enginebuffer.cpp
+++ b/src/engine/enginebuffer.cpp
@@ -41,6 +41,9 @@
#include "engine/controls/vinylcontrolcontrol.h"
#endif
+// EVE OSC
+// #include "osc/oscfunctions.h"
+
namespace {
const mixxx::Logger kLogger("EngineBuffer");
@@ -54,6 +57,24 @@ const QString kAppGroup = QStringLiteral("[App]");
} // anonymous namespace
+// EveOSC
+enum DefOscBodyType {
+ STRINGBODY = 1,
+ INTBODY = 2,
+ DOUBLEBODY = 3,
+ FLOATBODY = 4
+};
+
+void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig,
+ QString OscGroup,
+ QString OscKey,
+ enum DefOscBodyType OscBodyType,
+ QString OscMessageBodyQString,
+ int OscMessageBodyInt,
+ double OscMessageBodyDouble,
+ float OscMessageBodyFloat);
+// EveOSC
+
EngineBuffer::EngineBuffer(const QString& group,
UserSettingsPointer pConfig,
EngineChannel* pChannel,
@@ -173,6 +194,22 @@ EngineBuffer::EngineBuffer(const QString& group,
m_pTrackSamples = new ControlObject(ConfigKey(m_group, "track_samples"));
m_pTrackSampleRate = new ControlObject(ConfigKey(m_group, "track_samplerate"));
+ m_pTrackType = new ControlObject(ConfigKey(m_group, "track_type"));
+ m_pTrackTypeLength = new ControlObject(ConfigKey(m_group, "track_type_length"));
+ m_pTrackArtistLength = new ControlObject(ConfigKey(m_group, "track_artist_length"));
+ m_pTrackArtist_1 = new ControlObject(ConfigKey(m_group, "track_artist_1"));
+ m_pTrackArtist_2 = new ControlObject(ConfigKey(m_group, "track_artist_2"));
+ m_pTrackArtist_3 = new ControlObject(ConfigKey(m_group, "track_artist_3"));
+ m_pTrackArtist_4 = new ControlObject(ConfigKey(m_group, "track_artist_4"));
+ m_pTrackArtist_5 = new ControlObject(ConfigKey(m_group, "track_artist_5"));
+
+ m_pTrackTitleLength = new ControlObject(ConfigKey(m_group, "track_title_length"));
+ m_pTrackTitle_1 = new ControlObject(ConfigKey(m_group, "track_title_1"));
+ m_pTrackTitle_2 = new ControlObject(ConfigKey(m_group, "track_title_2"));
+ m_pTrackTitle_3 = new ControlObject(ConfigKey(m_group, "track_title_3"));
+ m_pTrackTitle_4 = new ControlObject(ConfigKey(m_group, "track_title_4"));
+ m_pTrackTitle_5 = new ControlObject(ConfigKey(m_group, "track_title_5"));
+
m_pKeylock = new ControlPushButton(ConfigKey(m_group, "keylock"), true);
m_pKeylock->setButtonMode(mixxx::control::ButtonMode::Toggle);
@@ -312,6 +349,22 @@ EngineBuffer::~EngineBuffer() {
delete m_pTrackSamples;
delete m_pTrackSampleRate;
+ delete m_pTrackType;
+ delete m_pTrackTypeLength;
+ delete m_pTrackArtistLength;
+ delete m_pTrackArtist_1;
+ delete m_pTrackArtist_2;
+ delete m_pTrackArtist_3;
+ delete m_pTrackArtist_4;
+ delete m_pTrackArtist_5;
+
+ delete m_pTrackTitleLength;
+ delete m_pTrackTitle_1;
+ delete m_pTrackTitle_2;
+ delete m_pTrackTitle_3;
+ delete m_pTrackTitle_4;
+ delete m_pTrackTitle_5;
+
delete m_pScaleLinear;
delete m_pScaleST;
#ifdef __RUBBERBAND__
@@ -563,6 +616,193 @@ void EngineBuffer::slotTrackLoaded(TrackPointer pTrack,
m_pTrackSampleRate->set(trackSampleRate.toDouble());
m_pTrackLoaded->forceSet(1);
+ // Eve start
+ // Type
+ QString TrackInfoType = pTrack->getType();
+ QString TrackInfoTypeTest = TrackInfoType;
+ int TrackInfoTypeTestLength = TrackInfoTypeTest.length();
+ if (TrackInfoTypeTestLength > 5) {
+ TrackInfoType = TrackInfoTypeTest.mid(0, 5);
+ };
+ m_pTrackTypeLength->set(TrackInfoTypeTestLength);
+
+ int CharType[5];
+ for (int i = 1; i <= 5; i++) {
+ CharType[i - 1] = 0;
+ }
+
+ for (int i = 1; i <= TrackInfoType.length(); i++) {
+ if ((TrackInfoType.at(i - 1).toLatin1()) < 0) {
+ CharType[i - 1] = ((TrackInfoType.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharType[i - 1] = (TrackInfoType.at(i - 1).toLatin1());
+ };
+ }
+
+ double TrackTypePart = 0.0;
+ TrackTypePart = (1.0 * CharType[0] * 1000000000000) +
+ (1.0 * CharType[1] * 1000000000) + (1.0 * CharType[2] * 1000000) +
+ (1.0 * CharType[3] * 1000) + (1.0 * CharType[4] * 1);
+ m_pTrackType->set(TrackTypePart);
+
+ // Title
+ QString TrackInfoTitle = pTrack->getTitle();
+ QString TrackInfoTitleTest = TrackInfoTitle;
+ int TrackInfoTitleTestLength = TrackInfoTitleTest.length();
+ if (TrackInfoTitleTestLength > 200) {
+ TrackInfoTitle = TrackInfoTitleTest.mid(0, 200);
+ };
+ m_pTrackTitleLength->set(TrackInfoTitleTestLength);
+
+ int CharTitle[200];
+ for (int i = 1; i <= 200; i++) {
+ CharTitle[i - 1] = 0;
+ }
+
+ // for (int i = 1; i <= TrackInfoTitle.length(); i++) {
+ for (int i = 1; i <= 25; i++) {
+ if ((TrackInfoTitle.at(i - 1).toLatin1()) < 0) {
+ CharTitle[i - 1] = ((TrackInfoTitle.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharTitle[i - 1] = (TrackInfoTitle.at(i - 1).toLatin1());
+ };
+ }
+
+ // Artist
+ QString TrackInfoArtist = pTrack->getArtist();
+ QString TrackInfoArtistTest = TrackInfoArtist;
+ int TrackInfoArtistTestLength = TrackInfoArtistTest.length();
+ if (TrackInfoArtistTestLength > 200) {
+ TrackInfoArtist = TrackInfoArtist.mid(0, 200);
+ };
+ m_pTrackArtistLength->set(TrackInfoArtistTestLength);
+
+ int CharArtist[200];
+ for (int i = 1; i <= 200; i++) {
+ CharArtist[i - 1] = 0;
+ }
+
+ // for (int i = 1; i <= TrackInfoArtist.length(); i++) {
+ for (int i = 1; i <= 25; i++) {
+ if ((TrackInfoArtist.at(i - 1).toLatin1()) < 0) {
+ CharArtist[i - 1] = ((TrackInfoArtist.at(i - 1).toLatin1()) + 300);
+ } else {
+ CharArtist[i - 1] = (TrackInfoArtist.at(i - 1).toLatin1());
+ };
+ }
+
+ double TrackTitlePart_1 = 0.0;
+ double TrackTitlePart_2 = 0.0;
+ double TrackTitlePart_3 = 0.0;
+ double TrackTitlePart_4 = 0.0;
+ double TrackTitlePart_5 = 0.0;
+
+ TrackTitlePart_1 = (1.0 * CharTitle[0] * 1000000000000) +
+ (1.0 * CharTitle[1] * 1000000000) + (1.0 * CharTitle[2] * 1000000) +
+ (1.0 * CharTitle[3] * 1000) + (1.0 * CharTitle[4] * 1);
+ TrackTitlePart_2 = (1.0 * CharTitle[5] * 1000000000000) +
+ (1.0 * CharTitle[6] * 1000000000) + (1.0 * CharTitle[7] * 1000000) +
+ (1.0 * CharTitle[8] * 1000) + (1.0 * CharTitle[9] * 1);
+ TrackTitlePart_3 = (1.0 * CharTitle[10] * 1000000000000) +
+ (1.0 * CharTitle[11] * 1000000000) + (1.0 * CharTitle[12] * 1000000) +
+ (1.0 * CharTitle[13] * 1000) + (1.0 * CharTitle[14] * 1);
+ TrackTitlePart_4 = (1.0 * CharTitle[15] * 1000000000000) +
+ (1.0 * CharTitle[16] * 1000000000) + (1.0 * CharTitle[17] * 1000000) +
+ (1.0 * CharTitle[18] * 1000) + (1.0 * CharTitle[19] * 1);
+ TrackTitlePart_5 = (1.0 * CharTitle[20] * 1000000000000) +
+ (1.0 * CharTitle[21] * 1000000000) + (1.0 * CharTitle[22] * 1000000) +
+ (1.0 * CharTitle[23] * 1000) + (1.0 * CharTitle[24] * 1);
+
+ m_pTrackTitle_1->set(TrackTitlePart_1);
+ m_pTrackTitle_2->set(TrackTitlePart_2);
+ m_pTrackTitle_3->set(TrackTitlePart_3);
+ m_pTrackTitle_4->set(TrackTitlePart_4);
+ m_pTrackTitle_5->set(TrackTitlePart_5);
+
+ double TrackArtistPart_1 = 0.0;
+ double TrackArtistPart_2 = 0.0;
+ double TrackArtistPart_3 = 0.0;
+ double TrackArtistPart_4 = 0.0;
+ double TrackArtistPart_5 = 0.0;
+
+ TrackArtistPart_1 = (1.0 * CharArtist[0] * 1000000000000) +
+ (1.0 * CharArtist[1] * 1000000000) +
+ (1.0 * CharArtist[2] * 1000000) + (1.0 * CharArtist[3] * 1000) +
+ (1.0 * CharArtist[4] * 1);
+ TrackArtistPart_2 = (1.0 * CharArtist[5] * 1000000000000) +
+ (1.0 * CharArtist[6] * 1000000000) +
+ (1.0 * CharArtist[7] * 1000000) + (1.0 * CharArtist[8] * 1000) +
+ (1.0 * CharArtist[9] * 1);
+ TrackArtistPart_3 = (1.0 * CharArtist[10] * 1000000000000) +
+ (1.0 * CharArtist[11] * 1000000000) +
+ (1.0 * CharArtist[12] * 1000000) + (1.0 * CharArtist[13] * 1000) +
+ (1.0 * CharArtist[14] * 1);
+ TrackArtistPart_4 = (1.0 * CharArtist[15] * 1000000000000) +
+ (1.0 * CharArtist[16] * 1000000000) +
+ (1.0 * CharArtist[17] * 1000000) + (1.0 * CharArtist[18] * 1000) +
+ (1.0 * CharArtist[19] * 1);
+ TrackArtistPart_5 = (1.0 * CharArtist[20] * 1000000000000) +
+ (1.0 * CharArtist[21] * 1000000000) +
+ (1.0 * CharArtist[22] * 1000000) + (1.0 * CharArtist[23] * 1000) +
+ (1.0 * CharArtist[24] * 1);
+
+ m_pTrackArtist_1->set(TrackArtistPart_1);
+ m_pTrackArtist_2->set(TrackArtistPart_2);
+ m_pTrackArtist_3->set(TrackArtistPart_3);
+ m_pTrackArtist_4->set(TrackArtistPart_4);
+ m_pTrackArtist_5->set(TrackArtistPart_5);
+
+ // EveOSC begin
+ if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "TrackArtist",
+ STRINGBODY,
+ pTrack->getArtist().toLatin1(),
+ 0,
+ 0,
+ 0);
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "TrackTitle",
+ STRINGBODY,
+ pTrack->getTitle().toLatin1(),
+ 0,
+ 0,
+ 0);
+
+ float oscTrackInfoDuration = pTrack->getDuration();
+ int oscTrackInfoDurationCalcMin = oscTrackInfoDuration / 60;
+ int oscTrackInfoDurationCalcSec = oscTrackInfoDuration - (oscTrackInfoDurationCalcMin * 60);
+
+ QString oscTrackInfoDurationCalc;
+
+ if (oscTrackInfoDurationCalcSec < 10) {
+ oscTrackInfoDurationCalc =
+ QString("%1:0%2")
+ .arg(oscTrackInfoDurationCalcMin)
+ .arg(oscTrackInfoDurationCalcSec);
+ } else {
+ oscTrackInfoDurationCalc =
+ QString("%1:%2")
+ .arg(oscTrackInfoDurationCalcMin)
+ .arg(oscTrackInfoDurationCalcSec);
+ };
+
+ QByteArray oscTrackInfoDurationBa = oscTrackInfoDurationCalc.toLocal8Bit();
+ const char* oscBodyMessageDuration = oscTrackInfoDurationBa.data();
+
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "TDuration",
+ STRINGBODY,
+ oscBodyMessageDuration,
+ 0,
+ 0,
+ 0);
+ }
+ // EveOSC end
+
// Reset slip mode
m_pSlipButton->set(0);
m_bSlipEnabledProcessing = false;
@@ -632,6 +872,50 @@ void EngineBuffer::ejectTrack() {
m_pTrackSampleRate->set(0);
m_pTrackLoaded->forceSet(0);
+ m_pTrackType->set(0);
+ m_pTrackTypeLength->set(0);
+ m_pTrackArtistLength->set(0);
+ m_pTrackArtist_1->set(0);
+ m_pTrackArtist_2->set(0);
+ m_pTrackArtist_3->set(0);
+ m_pTrackArtist_4->set(0);
+ m_pTrackArtist_5->set(0);
+
+ m_pTrackTitleLength->set(0);
+ m_pTrackTitle_1->set(0);
+ m_pTrackTitle_2->set(0);
+ m_pTrackTitle_3->set(0);
+ m_pTrackTitle_4->set(0);
+ m_pTrackTitle_5->set(0);
+
+ // EveOSC begin
+ if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "TrackArtist",
+ STRINGBODY,
+ "no track loaded",
+ 0,
+ 0,
+ 0);
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "TrackTitle",
+ STRINGBODY,
+ "no track loaded",
+ 0,
+ 0,
+ 0);
+ OscFunctionsSendPtrType(m_pConfig,
+ getGroup(),
+ "Duration",
+ STRINGBODY,
+ "0:00",
+ 0,
+ 0,
+ 0);
+ }
+
m_playButton->set(0.0);
m_playposSlider->set(0);
m_pCueControl->resetIndicators();
@@ -1546,12 +1830,25 @@ void EngineBuffer::hintReader(const double dRate) {
}
// WARNING: This method runs in the GUI thread
-void EngineBuffer::loadTrack(TrackPointer pTrack, bool play, EngineChannel* pChannelToCloneFrom) {
+#ifdef __STEM__
+void EngineBuffer::loadTrack(TrackPointer pTrack,
+ uint stemMask,
+ bool play,
+ EngineChannel* pChannelToCloneFrom) {
+#else
+void EngineBuffer::loadTrack(TrackPointer pTrack,
+ bool play,
+ EngineChannel* pChannelToCloneFrom) {
+#endif
if (pTrack) {
// Signal to the reader to load the track. The reader will respond with
// trackLoading and then either with trackLoaded or trackLoadFailed signals.
m_bPlayAfterLoading = play;
+#ifdef __STEM__
+ m_pReader->newTrack(pTrack, stemMask);
+#else
m_pReader->newTrack(pTrack);
+#endif
atomicStoreRelaxed(m_pChannelToCloneFrom, pChannelToCloneFrom);
} else {
// Loading a null track means "eject"
diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h
index b0ef812686f..6495b6c958b 100644
--- a/src/engine/enginebuffer.h
+++ b/src/engine/enginebuffer.h
@@ -18,6 +18,8 @@
#include "track/track_decl.h"
#include "util/types.h"
+// #include "osc/oscfunctions.h"
+
#ifdef __RUBBERBAND__
#include "engine/bufferscalers/enginebufferscalerubberband.h"
#endif
@@ -215,7 +217,16 @@ class EngineBuffer : public EngineObject {
// Request that the EngineBuffer load a track. Since the process is
// asynchronous, EngineBuffer will emit a trackLoaded signal when the load
// has completed.
- void loadTrack(TrackPointer pTrack, bool play, EngineChannel* pChannelToCloneFrom);
+#ifdef __STEM__
+ void loadTrack(TrackPointer pTrack,
+ uint stemMask,
+ bool play,
+ EngineChannel* pChannelToCloneFrom);
+#else
+ void loadTrack(TrackPointer pTrack,
+ bool play,
+ EngineChannel* pChannelToCloneFrom);
+#endif
void setChannelIndex(int channelIndex) {
m_channelIndex = channelIndex;
@@ -394,6 +405,26 @@ class EngineBuffer : public EngineObject {
ControlObject* m_pTrackSamples;
ControlObject* m_pTrackSampleRate;
+ // TrackType
+ ControlObject* m_pTrackType;
+ ControlObject* m_pTrackTypeLength;
+
+ // TrackArtist
+ ControlObject* m_pTrackArtistLength;
+ ControlObject* m_pTrackArtist_1;
+ ControlObject* m_pTrackArtist_2;
+ ControlObject* m_pTrackArtist_3;
+ ControlObject* m_pTrackArtist_4;
+ ControlObject* m_pTrackArtist_5;
+
+ // TrackTitle
+ ControlObject* m_pTrackTitleLength;
+ ControlObject* m_pTrackTitle_1;
+ ControlObject* m_pTrackTitle_2;
+ ControlObject* m_pTrackTitle_3;
+ ControlObject* m_pTrackTitle_4;
+ ControlObject* m_pTrackTitle_5;
+
ControlPushButton* m_playButton;
ControlPushButton* m_playStartButton;
ControlPushButton* m_stopStartButton;
diff --git a/src/library/analysis/analysisfeature.cpp b/src/library/analysis/analysisfeature.cpp
index a713f57ddfc..82b64ca3e49 100644
--- a/src/library/analysis/analysisfeature.cpp
+++ b/src/library/analysis/analysisfeature.cpp
@@ -81,7 +81,11 @@ void AnalysisFeature::bindLibraryWidget(WLibrary* libraryWidget,
&DlgAnalysis::loadTrackToPlayer,
this,
[=, this](TrackPointer track, const QString& group) {
- emit loadTrackToPlayer(track, group, false);
+ emit loadTrackToPlayer(track, group,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
});
connect(m_pAnalysisView,
&DlgAnalysis::analyzeTracks,
diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h
index a23f1ae9fe7..df8cb786acc 100644
--- a/src/library/autodj/autodjprocessor.h
+++ b/src/library/autodj/autodjprocessor.h
@@ -201,7 +201,11 @@ class AutoDJProcessor : public QObject {
AutoDJError toggleAutoDJ(bool enable);
signals:
+#ifdef __STEM__
+ void loadTrackToPlayer(TrackPointer pTrack, const QString& group, uint stemMask, bool play);
+#else
void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play);
+#endif
void autoDJStateChanged(AutoDJProcessor::AutoDJState state);
void autoDJError(AutoDJProcessor::AutoDJError error);
void transitionTimeChanged(int time);
@@ -230,7 +234,11 @@ class AutoDJProcessor : public QObject {
protected:
// The following virtual signal wrappers are used for testing
virtual void emitLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play) {
- emit loadTrackToPlayer(pTrack, group, play);
+ emit loadTrackToPlayer(pTrack, group,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ play);
}
virtual void emitAutoDJStateChanged(AutoDJProcessor::AutoDJState state) {
emit autoDJStateChanged(state);
diff --git a/src/library/autodj/dlgautodj.h b/src/library/autodj/dlgautodj.h
index 9ac38a1e722..f4138ec86f5 100644
--- a/src/library/autodj/dlgautodj.h
+++ b/src/library/autodj/dlgautodj.h
@@ -49,7 +49,11 @@ class DlgAutoDJ : public QWidget, public Ui::DlgAutoDJ, public LibraryView {
signals:
void addRandomTrackButton(bool buttonChecked);
void loadTrack(TrackPointer tio);
+#ifdef __STEM__
+ void loadTrackToPlayer(TrackPointer tio, const QString& group, uint stemMask, bool);
+#else
void loadTrackToPlayer(TrackPointer tio, const QString& group, bool);
+#endif
void trackSelected(TrackPointer pTrack);
private:
diff --git a/src/library/library.cpp b/src/library/library.cpp
index 21f82be564e..99fb2d429f3 100644
--- a/src/library/library.cpp
+++ b/src/library/library.cpp
@@ -552,14 +552,25 @@ void Library::slotLoadLocationToPlayer(const QString& location, const QString& g
auto trackRef = TrackRef::fromFilePath(location);
TrackPointer pTrack = m_pTrackCollectionManager->getOrAddTrack(trackRef);
if (pTrack) {
+#ifdef __STEM__
+ emit loadTrackToPlayer(pTrack, group, mixxx::kNoStemSelected, play);
+#else
emit loadTrackToPlayer(pTrack, group, play);
+#endif
}
}
+#ifdef __STEM__
+void Library::slotLoadTrackToPlayer(
+ TrackPointer pTrack, const QString& group, uint stemMask, bool play) {
+ emit loadTrackToPlayer(pTrack, group, stemMask, play);
+}
+#else
void Library::slotLoadTrackToPlayer(
TrackPointer pTrack, const QString& group, bool play) {
emit loadTrackToPlayer(pTrack, group, play);
}
+#endif
void Library::slotRefreshLibraryModels() {
m_pMixxxLibraryFeature->refreshLibraryModels();
diff --git a/src/library/library.h b/src/library/library.h
index e0190084eeb..75b2e4e014a 100644
--- a/src/library/library.h
+++ b/src/library/library.h
@@ -114,7 +114,11 @@ class Library: public QObject {
void slotShowTrackModel(QAbstractItemModel* model);
void slotSwitchToView(const QString& view);
void slotLoadTrack(TrackPointer pTrack);
+#ifdef __STEM__
+ void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, uint stemMask, bool play);
+#else
void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play);
+#endif
void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play);
void slotRefreshLibraryModels();
void slotCreatePlaylist();
@@ -127,7 +131,16 @@ class Library: public QObject {
void showTrackModel(QAbstractItemModel* model, bool restoreState = true);
void switchToView(const QString& view);
void loadTrack(TrackPointer pTrack);
- void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false);
+#ifdef __STEM__
+ void loadTrackToPlayer(TrackPointer pTrack,
+ const QString& group,
+ uint stemMask,
+ bool play = false);
+#else
+ void loadTrackToPlayer(TrackPointer pTrack,
+ const QString& group,
+ bool play = false);
+#endif
void restoreSearch(const QString&);
void search(const QString& text);
void disableSearch();
diff --git a/src/library/librarycontrol.cpp b/src/library/librarycontrol.cpp
index 0b69650eb51..74874e3e040 100644
--- a/src/library/librarycontrol.cpp
+++ b/src/library/librarycontrol.cpp
@@ -38,6 +38,19 @@ LoadToGroupController::LoadToGroupController(LibraryControl* pParent, const QStr
this,
&LoadToGroupController::slotLoadToGroupAndPlay);
+#ifdef __STEM__
+ m_loadSelectedTrackStems =
+ std::make_unique(ConfigKey(group, "load_selected_track_stems"));
+ connect(m_loadSelectedTrackStems.get(),
+ &ControlObject::valueChanged,
+ this,
+ [this](double value) {
+ if (value >= 0 && value <= 2 << mixxx::kMaxSupportedStems) {
+ emit loadToGroup(m_group, static_cast(value), false);
+ }
+ });
+#endif
+
connect(this,
&LoadToGroupController::loadToGroup,
pParent,
@@ -48,13 +61,24 @@ LoadToGroupController::~LoadToGroupController() = default;
void LoadToGroupController::slotLoadToGroup(double v) {
if (v > 0) {
- emit loadToGroup(m_group, false);
+ emit loadToGroup(m_group,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
}
}
void LoadToGroupController::slotLoadToGroupAndPlay(double v) {
if (v > 0) {
- emit loadToGroup(m_group, true);
+#ifdef __STEM__
+ emit loadToGroup(m_group,
+ mixxx::kNoStemSelected,
+ true);
+#else
+ emit loadToGroup(m_group,
+ true);
+#endif
}
}
@@ -600,14 +624,22 @@ void LibraryControl::slotUpdateTrackMenuControl(bool visible) {
m_pShowTrackMenu->setAndConfirm(visible ? 1.0 : 0.0);
}
+#ifdef __STEM__
+void LibraryControl::slotLoadSelectedTrackToGroup(const QString& group, uint stemMask, bool play) {
+#else
void LibraryControl::slotLoadSelectedTrackToGroup(const QString& group, bool play) {
+#endif
if (!m_pLibraryWidget) {
return;
}
WTrackTableView* pTrackTableView = m_pLibraryWidget->getCurrentTrackTableView();
if (pTrackTableView) {
+#ifdef __STEM__
+ pTrackTableView->loadSelectedTrackToGroup(group, stemMask, play);
+#else
pTrackTableView->loadSelectedTrackToGroup(group, play);
+#endif
}
}
diff --git a/src/library/librarycontrol.h b/src/library/librarycontrol.h
index 65e21013ea4..dbc3ea66c8b 100644
--- a/src/library/librarycontrol.h
+++ b/src/library/librarycontrol.h
@@ -23,7 +23,14 @@ class LoadToGroupController : public QObject {
virtual ~LoadToGroupController();
signals:
- void loadToGroup(const QString& group, bool);
+#ifdef __STEM__
+ void loadToGroup(const QString& group,
+ uint stemMask,
+ bool);
+#else
+ void loadToGroup(const QString& group,
+ bool);
+#endif
public slots:
void slotLoadToGroup(double v);
@@ -33,6 +40,10 @@ class LoadToGroupController : public QObject {
const QString m_group;
std::unique_ptr m_pLoadControl;
std::unique_ptr m_pLoadAndPlayControl;
+
+#ifdef __STEM__
+ std::unique_ptr m_loadSelectedTrackStems;
+#endif
};
class LibraryControl : public QObject {
@@ -54,7 +65,11 @@ class LibraryControl : public QObject {
public slots:
// Deprecated navigation slots
+#ifdef __STEM__
+ void slotLoadSelectedTrackToGroup(const QString& group, uint stemMask, bool play);
+#else
void slotLoadSelectedTrackToGroup(const QString& group, bool play);
+#endif
void slotUpdateTrackMenuControl(bool visible);
private slots:
diff --git a/src/library/libraryfeature.h b/src/library/libraryfeature.h
index 61972192dcd..9840d4424b2 100644
--- a/src/library/libraryfeature.h
+++ b/src/library/libraryfeature.h
@@ -135,7 +135,16 @@ class LibraryFeature : public QObject {
void showTrackModel(QAbstractItemModel* model, bool restoreState = true);
void switchToView(const QString& view);
void loadTrack(TrackPointer pTrack);
- void loadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play = false);
+#ifdef __STEM__
+ void loadTrackToPlayer(TrackPointer pTrack,
+ const QString& group,
+ uint stemMask,
+ bool play = false);
+#else
+ void loadTrackToPlayer(TrackPointer pTrack,
+ const QString& group,
+ bool play = false);
+#endif
/// saves the scroll, selection and current state of the library model
void saveModelState();
/// restores the scroll, selection and current state of the library model
diff --git a/src/library/recording/dlgrecording.h b/src/library/recording/dlgrecording.h
index 8d715056319..3ea574632cb 100644
--- a/src/library/recording/dlgrecording.h
+++ b/src/library/recording/dlgrecording.h
@@ -37,7 +37,11 @@ class DlgRecording : public QWidget, public Ui::DlgRecording, public virtual Lib
signals:
void loadTrack(TrackPointer tio);
- void loadTrackToPlayer(TrackPointer tio, const QString& group, bool play);
+#ifdef __STEM__
+ void loadTrackToPlayer(TrackPointer tio, const QString& group, uint stemMask, bool);
+#else
+ void loadTrackToPlayer(TrackPointer tio, const QString& group, bool);
+#endif
void restoreSearch(const QString& search);
void restoreModelState();
diff --git a/src/library/tabledelegates/previewbuttondelegate.cpp b/src/library/tabledelegates/previewbuttondelegate.cpp
index 87c897ec95f..d4b695a3636 100644
--- a/src/library/tabledelegates/previewbuttondelegate.cpp
+++ b/src/library/tabledelegates/previewbuttondelegate.cpp
@@ -220,7 +220,11 @@ void PreviewButtonDelegate::buttonClicked() {
TrackPointer pTrack = pTrackModel->getTrack(m_currentEditedCellIndex);
if (pTrack && pTrack != pOldTrack) {
// Load to preview deck and start playing
- emit loadTrackToPlayer(pTrack, kPreviewDeckGroup, true);
+ emit loadTrackToPlayer(pTrack, kPreviewDeckGroup,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ true);
startedPlaying = true;
} else if (pTrack == pOldTrack && !isPreviewDeckPlaying()) {
// Since the Preview deck might be hidden, starting at the main cue
diff --git a/src/library/tabledelegates/previewbuttondelegate.h b/src/library/tabledelegates/previewbuttondelegate.h
index a42b206bc12..a7a9be95dbe 100644
--- a/src/library/tabledelegates/previewbuttondelegate.h
+++ b/src/library/tabledelegates/previewbuttondelegate.h
@@ -54,7 +54,11 @@ class PreviewButtonDelegate : public TableItemDelegate {
const QModelIndex& index) const override;
signals:
- void loadTrackToPlayer(const TrackPointer& pTrack, const QString& group, bool play);
+#ifdef __STEM__
+ void loadTrackToPlayer(const TrackPointer& pTrack, const QString& group, uint stemMask, bool);
+#else
+ void loadTrackToPlayer(const TrackPointer& pTrack, const QString& group, bool);
+#endif
void buttonSetChecked(bool);
public slots:
diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp
index 1b8162c7fe9..8a44f5a0a50 100644
--- a/src/mixer/basetrackplayer.cpp
+++ b/src/mixer/basetrackplayer.cpp
@@ -20,14 +20,15 @@
#include "vinylcontrol/defs_vinylcontrol.h"
#include "waveform/renderers/waveformwidgetrenderer.h"
+// EVE OSC
+// #include "osc/oscfunctions.h"
+// EVE OSC
+
namespace {
constexpr double kNoTrackColor = -1;
constexpr double kShiftCuesOffsetMillis = 10;
constexpr double kShiftCuesOffsetSmallMillis = 1;
-#ifdef __STEM__
-constexpr int kMaxSupportedStems = 4;
-#endif
const QString kEffectGroupFormat = QStringLiteral("[EqualizerRack1_%1_Effect1]");
inline double trackColorToDouble(mixxx::RgbColor::optional_t color) {
@@ -35,6 +36,24 @@ inline double trackColorToDouble(mixxx::RgbColor::optional_t color) {
}
} // namespace
+// EveOSC
+enum DefOscBodyType {
+ STRINGBODY = 1,
+ INTBODY = 2,
+ DOUBLEBODY = 3,
+ FLOATBODY = 4
+};
+
+void OscFunctionsSendPtrType(UserSettingsPointer m_pConfig,
+ QString OscGroup,
+ QString OscKey,
+ enum DefOscBodyType OscBodyType,
+ QString OscMessageBodyQString,
+ int OscMessageBodyInt,
+ double OscMessageBodyDouble,
+ float OscMessageBodyFloat);
+// EveOSC
+
BaseTrackPlayer::BaseTrackPlayer(PlayerManager* pParent, const QString& group)
: BasePlayer(pParent, group) {
}
@@ -101,7 +120,7 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(
// Duration of the current song, we create this one because nothing else does.
m_pDuration = std::make_unique(
- ConfigKey(getGroup(), "duration"));
+ ConfigKey(getGroup(), "duration"));
// Track color of the current track
m_pTrackColor = std::make_unique(
@@ -289,9 +308,9 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(
}
#ifdef __STEM__
- m_pStemColors.reserve(kMaxSupportedStems);
+ m_pStemColors.reserve(mixxx::kMaxSupportedStems);
QString group = getGroup();
- for (int stemIdx = 1; stemIdx <= kMaxSupportedStems; stemIdx++) {
+ for (int stemIdx = 1; stemIdx <= mixxx::kMaxSupportedStems; stemIdx++) {
QString stemGroup = QStringLiteral("%1Stem%2]")
.arg(group.left(group.size() - 1),
QString::number(stemIdx));
@@ -420,7 +439,11 @@ void BaseTrackPlayerImpl::slotEjectTrack(double v) {
if (elapsed < mixxx::Duration::fromMillis(kUnreplaceDelay)) {
TrackPointer lastEjected = m_pPlayerManager->getSecondLastEjectedTrack();
if (lastEjected) {
- slotLoadTrack(lastEjected, false);
+ slotLoadTrack(lastEjected,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
}
return;
}
@@ -429,11 +452,14 @@ void BaseTrackPlayerImpl::slotEjectTrack(double v) {
if (!m_pLoadedTrack) {
TrackPointer lastEjected = m_pPlayerManager->getLastEjectedTrack();
if (lastEjected) {
- slotLoadTrack(lastEjected, false);
+ slotLoadTrack(lastEjected,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
}
return;
}
-
m_pChannel->getEngineBuffer()->ejectTrack();
}
@@ -543,10 +569,17 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() {
disconnect(m_pLoadedTrack.get(), nullptr, m_pKey.get(), nullptr);
}
-void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) {
- //qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup() << pNewTrack.get();
- // Before loading the track, ensure we have access. This uses lazy
- // evaluation to make sure track isn't NULL before we dereference it.
+#ifdef __STEM__
+void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack,
+ uint stemMask,
+ bool bPlay) {
+#else
+void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack,
+ bool bPlay) {
+#endif
+ // qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup() << pNewTrack.get();
+ // Before loading the track, ensure we have access. This uses lazy
+ // evaluation to make sure track isn't NULL before we dereference it.
if (pNewTrack) {
auto fileInfo = pNewTrack->getFileInfo();
if (!Sandbox::askForAccess(&fileInfo)) {
@@ -566,7 +599,17 @@ void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) {
// Request a new track from EngineBuffer
EngineBuffer* pEngineBuffer = m_pChannel->getEngineBuffer();
+#ifdef __STEM__
+ pEngineBuffer->loadTrack(pNewTrack,
+ stemMask,
+ bPlay,
+ m_pChannelToCloneFrom);
+
+ // Select a specific stem if requested
+ emit selectedStems(stemMask);
+#else
pEngineBuffer->loadTrack(pNewTrack, bPlay, m_pChannelToCloneFrom);
+#endif
}
void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, const QString& reason) {
@@ -600,8 +643,8 @@ void BaseTrackPlayerImpl::slotLoadFailed(TrackPointer pTrack, const QString& rea
}
void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
- TrackPointer pOldTrack) {
- //qDebug() << "BaseTrackPlayerImpl::slotTrackLoaded" << pNewTrack.get() << pOldTrack.get();
+ TrackPointer pOldTrack) {
+ // qDebug() << "BaseTrackPlayerImpl::slotTrackLoaded" << pNewTrack.get() << pOldTrack.get();
if (!pNewTrack &&
pOldTrack &&
pOldTrack == m_pLoadedTrack) {
@@ -635,8 +678,8 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
m_pKey->set(m_pLoadedTrack->getKey());
slotSetTrackColor(m_pLoadedTrack->getColor());
- if(m_pConfig->getValue(
- ConfigKey("[Mixer Profile]", "EqAutoReset"), false)) {
+ if (m_pConfig->getValue(
+ ConfigKey("[Mixer Profile]", "EqAutoReset"), false)) {
if (m_pLowFilter) {
m_pLowFilter->set(1.0);
}
@@ -657,7 +700,7 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
}
}
if (m_pConfig->getValue(
- ConfigKey("[Mixer Profile]", "GainAutoReset"), false)) {
+ ConfigKey("[Mixer Profile]", "GainAutoReset"), false)) {
m_pPreGain->set(1.0);
}
@@ -698,7 +741,7 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
#ifdef __STEM__
if (m_pStemColors.size()) {
const auto& stemInfo = m_pLoadedTrack->getStemInfo();
- DEBUG_ASSERT(stemInfo.size() <= kMaxSupportedStems);
+ DEBUG_ASSERT(stemInfo.size() <= mixxx::kMaxSupportedStems);
int stemIdx = 0;
for (const auto& stemColorCo : m_pStemColors) {
auto color = kNoTrackColor;
@@ -726,6 +769,51 @@ void BaseTrackPlayerImpl::slotTrackLoaded(TrackPointer pNewTrack,
// Update the PlayerInfo class that is used in EngineBroadcast to replace
// the metadata of a stream
PlayerInfo::instance().setTrackInfo(getGroup(), m_pLoadedTrack);
+ QString trackInfoArtist = " ";
+ QString trackInfoTitle = " ";
+ QString DeckStatusTxtLine2 = " ";
+ QString DeckStatusTxtLine3 = " ";
+ QString DeckStatusTxtLine4 = " ";
+ QTime tempStatusTime = QTime::currentTime();
+ QString DeckStatusTime = tempStatusTime.toString("hh:mm:ss");
+
+ if (pNewTrack) {
+ // QString trackInfo = pNewTrack->getInfo();
+ trackInfoArtist = pNewTrack->getArtist();
+ trackInfoTitle = pNewTrack->getTitle();
+ trackInfoArtist.replace("\"", "''");
+ trackInfoTitle.replace("\"", "''");
+ DeckStatusTxtLine2 = "Artist : \"" + trackInfoArtist + "\",";
+ DeckStatusTxtLine3 = "Title : \"" + trackInfoTitle + "\",";
+ DeckStatusTxtLine4 = "Time : \"" + DeckStatusTime + "\",";
+
+ } else {
+ DeckStatusTxtLine2 = "Artist : \" \",";
+ DeckStatusTxtLine3 = "Title : \" \",";
+ DeckStatusTxtLine4 = "Time : \"" + DeckStatusTime + "\",";
+ }
+ QString trackInfoDeck = getGroup();
+ trackInfoDeck.replace("[Channel", "");
+ trackInfoDeck.replace("]", "");
+ QString DeckStatusFilePath = m_pConfig->getSettingsPath();
+ DeckStatusFilePath.replace("Roaming", "Local");
+ DeckStatusFilePath.replace("\\", "/");
+ QString DeckStatusFileLocation =
+ DeckStatusFilePath + "/controllers/Status" + getGroup() + ".js";
+ // Different file for each Deck / Sampler
+ QString DeckStatusTxtLine1 = "var TrackDeck" + trackInfoDeck + " = { ";
+ QString DeckStatusTxtLine5 = "};";
+ QFile DeckStatusFile(DeckStatusFileLocation);
+ DeckStatusFile.remove();
+ DeckStatusFile.open(QIODevice::ReadWrite | QIODevice::Append);
+ // DeckStatusFile.open(QIODevice::ReadWrite | QIODevice::Append);
+ QTextStream DeckStatusTxt(&DeckStatusFile);
+ DeckStatusTxt << DeckStatusTxtLine1 << "\n";
+ DeckStatusTxt << DeckStatusTxtLine2 << "\n";
+ DeckStatusTxt << DeckStatusTxtLine3 << "\n";
+ DeckStatusTxt << DeckStatusTxtLine4 << "\n";
+ DeckStatusTxt << DeckStatusTxtLine5 << "\n";
+ DeckStatusFile.close();
}
TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const {
@@ -777,7 +865,11 @@ void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) {
m_pChannelToCloneFrom = pChannel;
bool play = ControlObject::toBool(ConfigKey(m_pChannelToCloneFrom->getGroup(), "play"));
- slotLoadTrack(pTrack, play);
+ slotLoadTrack(pTrack,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ play);
}
void BaseTrackPlayerImpl::slotLoadTrackFromDeck(double d) {
@@ -801,7 +893,11 @@ void BaseTrackPlayerImpl::loadTrackFromGroup(const QString& group) {
return;
}
- slotLoadTrack(pTrack, false);
+ slotLoadTrack(pTrack,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
}
bool BaseTrackPlayerImpl::isTrackMenuControlAvailable() {
@@ -923,6 +1019,12 @@ void BaseTrackPlayerImpl::slotTrackRatingChangeRequestRelative(int change) {
void BaseTrackPlayerImpl::slotPlayToggled(double value) {
if (value == 0 && m_replaygainPending) {
setReplayGain(m_pLoadedTrack->getReplayGain().getRatio());
+
+ // EveOSC begin
+ // if (m_pConfig->getValue(ConfigKey("[OSC]", "OscEnabled"))) {
+ // OscFunctionsSendFloat(getGroup(), "play", value);
+ // }
+ // EveOSC end
}
}
@@ -961,8 +1063,8 @@ void BaseTrackPlayerImpl::slotVinylControlEnabled(double v) {
}
void BaseTrackPlayerImpl::slotWaveformZoomValueChangeRequest(double v) {
- if (v <= WaveformWidgetRenderer::s_waveformMaxZoom
- && v >= WaveformWidgetRenderer::s_waveformMinZoom) {
+ if (v <= WaveformWidgetRenderer::s_waveformMaxZoom &&
+ v >= WaveformWidgetRenderer::s_waveformMinZoom) {
m_pWaveformZoom->setAndConfirm(v);
}
}
diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h
index 537fc092079..28bcd88b17e 100644
--- a/src/mixer/basetrackplayer.h
+++ b/src/mixer/basetrackplayer.h
@@ -46,7 +46,14 @@ class BaseTrackPlayer : public BasePlayer {
};
public slots:
- virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0;
+#ifdef __STEM__
+ virtual void slotLoadTrack(TrackPointer pTrack,
+ uint stemMask,
+ bool bPlay = false) = 0;
+#else
+ virtual void slotLoadTrack(TrackPointer pTrack,
+ bool bPlay = false) = 0;
+#endif
virtual void slotCloneFromGroup(const QString& group) = 0;
virtual void slotCloneDeck() = 0;
virtual void slotEjectTrack(double) = 0;
@@ -57,6 +64,9 @@ class BaseTrackPlayer : public BasePlayer {
void newTrackLoaded(TrackPointer pLoadedTrack);
void trackUnloaded(TrackPointer pUnloadedTrack);
void loadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack);
+#ifdef __STEM__
+ void selectedStems(uint stemMask);
+#endif
void playerEmpty();
void noVinylControlInputConfigured();
void trackRatingChanged(int rating);
@@ -94,7 +104,14 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {
TrackPointer loadFakeTrack(bool bPlay, double filebpm);
public slots:
- void slotLoadTrack(TrackPointer track, bool bPlay) final;
+#ifdef __STEM__
+ void slotLoadTrack(TrackPointer track,
+ uint stemMask,
+ bool bPlay) final;
+#else
+ void slotLoadTrack(TrackPointer track,
+ bool bPlay) final;
+#endif
void slotEjectTrack(double) final;
void slotCloneFromGroup(const QString& group) final;
void slotCloneDeck() final;
diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp
index d54d474f73f..2a38360aacf 100644
--- a/src/mixer/playermanager.cpp
+++ b/src/mixer/playermanager.cpp
@@ -670,7 +670,13 @@ void PlayerManager::slotCloneDeck(const QString& source_group, const QString& ta
pPlayer->slotCloneFromGroup(source_group);
}
-void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play) {
+#ifdef __STEM__
+void PlayerManager::slotLoadTrackToPlayer(
+ TrackPointer pTrack, const QString& group, uint stemMask, bool play) {
+#else
+void PlayerManager::slotLoadTrackToPlayer(
+ TrackPointer pTrack, const QString& group, bool play) {
+#endif
// Do not lock mutex in this method unless it is changed to access
// PlayerManager state.
BaseTrackPlayer* pPlayer = getPlayer(group);
@@ -721,7 +727,11 @@ void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, const QString& gr
if (clone) {
pPlayer->slotCloneDeck();
} else {
+#ifdef __STEM__
+ pPlayer->slotLoadTrack(pTrack, stemMask, play);
+#else
pPlayer->slotLoadTrack(pTrack, play);
+#endif
}
m_lastLoadedPlayer = group;
@@ -773,7 +783,11 @@ void PlayerManager::slotLoadTrackIntoNextAvailableDeck(TrackPointer pTrack) {
return;
}
- pDeck->slotLoadTrack(pTrack, false);
+ pDeck->slotLoadTrack(pTrack,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
}
void PlayerManager::slotLoadLocationIntoNextAvailableDeck(const QString& location, bool play) {
diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h
index 96300baba16..b5298ae2493 100644
--- a/src/mixer/playermanager.h
+++ b/src/mixer/playermanager.h
@@ -194,7 +194,11 @@ class PlayerManager : public QObject, public PlayerManagerInterface {
public slots:
// Slots for loading tracks into a Player, which is either a Sampler or a Deck
+#ifdef __STEM__
+ void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, uint stemMask, bool play);
+#else
void slotLoadTrackToPlayer(TrackPointer pTrack, const QString& group, bool play);
+#endif
void slotLoadLocationToPlayer(const QString& location, const QString& group, bool play);
void slotLoadLocationToPlayerMaybePlay(const QString& location, const QString& group);
diff --git a/src/mixer/samplerbank.cpp b/src/mixer/samplerbank.cpp
index 4e03f6cdea7..dca2e1b4112 100644
--- a/src/mixer/samplerbank.cpp
+++ b/src/mixer/samplerbank.cpp
@@ -212,7 +212,12 @@ bool SamplerBank::loadSamplerBankFromPath(const QString& samplerBankPath) {
}
if (location.isEmpty()) {
- m_pPlayerManager->slotLoadTrackToPlayer(TrackPointer(), group, false);
+ m_pPlayerManager->slotLoadTrackToPlayer(
+ TrackPointer(), group,
+#ifdef __STEM__
+ mixxx::kNoStemSelected,
+#endif
+ false);
} else {
m_pPlayerManager->slotLoadLocationToPlayer(location, group, false);
}
diff --git a/src/mixxxmainwindow.cpp b/src/mixxxmainwindow.cpp
index c5b44237bf9..3fc44a892bc 100644
--- a/src/mixxxmainwindow.cpp
+++ b/src/mixxxmainwindow.cpp
@@ -62,6 +62,11 @@
#include "vinylcontrol/vinylcontrolmanager.h"
#endif
+// EveOSC
+#include "osc/oscfunctions.h"
+#include "osc/oscreceiver.cpp"
+// EveOSC
+
namespace {
#ifdef __LINUX__
// Detect if the desktop supports a global menu to decide whether we need to rebuild
@@ -132,6 +137,9 @@ MixxxMainWindow::MixxxMainWindow(std::shared_ptr pCoreServi
m_pGuiTick = new GuiTick();
m_pVisualsManager = new VisualsManager();
+ // EveOSC
+ oscEnable();
+ // EveOSC
}
#ifdef MIXXX_USE_QOPENGL
@@ -325,7 +333,7 @@ void MixxxMainWindow::initialize() {
reportCriticalErrorAndQuit(
"default skin cannot be loaded - see mixxx trace for more information");
m_pCentralWidget = oldWidget;
- //TODO (XXX) add dialog to warn user and launch skin choice page
+ // TODO (XXX) add dialog to warn user and launch skin choice page
} else {
m_pMenuBar->setStyleSheet(m_pCentralWidget->styleSheet());
}
@@ -600,7 +608,7 @@ void MixxxMainWindow::alwaysHideMenuBarDlg() {
#endif
QDialog::DialogCode MixxxMainWindow::soundDeviceErrorDlg(
- const QString &title, const QString &text, bool* retryClicked) {
+ const QString& title, const QString& text, bool* retryClicked) {
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setWindowTitle(title);
@@ -615,8 +623,7 @@ QDialog::DialogCode MixxxMainWindow::soundDeviceErrorDlg(
QPushButton* exitButton =
msgBox.addButton(tr("Exit"), QMessageBox::ActionRole);
- while (true)
- {
+ while (true) {
msgBox.exec();
if (msgBox.clickedButton() == retryButton) {
@@ -705,19 +712,18 @@ QDialog::DialogCode MixxxMainWindow::noOutputDlg(bool* continueClicked) {
msgBox.setWindowTitle(tr("No Output Devices"));
msgBox.setText(
"" + tr("Mixxx was configured without any output sound devices. "
- "Audio processing will be disabled without a configured output device.") +
+ "Audio processing will be disabled without a configured output device.") +
""
- "- " +
- tr("Continue without any outputs.") +
- "
"
- "- " +
- tr("Reconfigure Mixxx's sound device settings.") +
- "
"
- "- " +
- tr("Exit Mixxx.") +
- "
"
- "
"
- );
+ "" +
+ tr("Continue without any outputs.") +
+ ""
+ "" +
+ tr("Reconfigure Mixxx's sound device settings.") +
+ ""
+ "" +
+ tr("Exit Mixxx.") +
+ ""
+ "