diff --git a/README.md b/README.md index 49137e0..628672d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Features:
- Drag'n'Drop. Drag'n'Drop tracks right to the Bloody Player window to add tracks to the tracklist. You can also drag'n'drop folders to add tracks from it.
- Tracklists. Save the current tracklist and open it later.
- SFX. Add sound effects or load your VST plugin.
-- Tracklist management. Move tracks in the tracklist or delete some of them by right-clicking on the track or using hotkeys.
-- Search. Use Ctrl + F to open the search window to search for the desired track in the tracklist. - Oscillogram. Click on the horizontal oscillogram displaying amplitude from time to change the current track's position.
+- Repeat section. Click the right mouse button on the oscillogram to set the left bound for the repetition, click the right mouse button again to set the right bound and make a repetition section in which music will repeat.
+- Tracklist management. Move tracks in the tracklist or delete some of them by right-clicking on the track or using hotkeys.
+- Search. Use Ctrl + F to open the search window to search for the desired track in the tracklist.
- "Repeat Track" / "Random Track". Use buttons under the volume slider to set "Repeat Track" / "Random Track" functions.
diff --git a/TODO.txt b/TODO.txt index 44ccdfc..8a3c505 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,8 +1,8 @@ -- Speed up the process of adding the tracks. +- Add a little (non-modal window) "Tutorial" (with pictures) on first startup: tell user to drag'n'drop, Ctrl + F, RMB to create repeat section, RMB on tracks, FX. +And if user not finished th tutorial then show it again on next start. Or, if user checked "don't show again" don't show it again. +- Speed up the process of adding the tracks. - When adding music, we need to add it in the same order in which it was in the folder. -- Do something with color banding. - Seems like with 'speed (by pitch)' effect we also need to increase mid-high frequencies on like 3-4 dB and maybe do the same thing with the low and the mid-low. -- Add bounds in which track will repeat by clicking RMB on the oscillogram. Track's volume in the transition from end bound to start bound will change? - After the tracklist was cleared volume returns to the default value of 75%, we don't want that, don't change master volume when tracklist is clear and allow to change it if tracklist is clear. - Add winsock2 and make 'Check for Updates' button in menu (see https://stackoverflow.com/questions/39931347/simple-http-get-with-winsock and https://github.com/VioletGiraffe/github-releases-autoupdater). - With winsock2 create a server and if another audio file is being opened a new instance of Player will check if one is running and if true then this new track will be sent to currently running instance. diff --git a/ide/BloodyPlayer.pro.user b/ide/BloodyPlayer.pro.user index 5d584e0..4a95323 100644 --- a/ide/BloodyPlayer.pro.user +++ b/ide/BloodyPlayer.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/src/Controller/controller.cpp b/src/Controller/controller.cpp index abe3c16..69f1ea1 100644 --- a/src/Controller/controller.cpp +++ b/src/Controller/controller.cpp @@ -42,6 +42,11 @@ void Controller::setTrackPos(unsigned int graphPos) pAudioService->setTrackPos(graphPos); } +void Controller::setRepeatPoint(unsigned int graphPos) +{ + pAudioService->setRepeatPoint(graphPos); +} + std::string Controller::getBloodyVersion() { return pAudioService->getBloodyVersion(); diff --git a/src/Controller/controller.h b/src/Controller/controller.h index ce74095..2ffd1ff 100644 --- a/src/Controller/controller.h +++ b/src/Controller/controller.h @@ -82,6 +82,7 @@ class Controller void addTracks (std::vector paths); void setVolume (float fNewVolume); void setTrackPos (unsigned int graphPos); + void setRepeatPoint (unsigned int graphPos); // Get diff --git a/src/Model/AudioService/audioservice.cpp b/src/Model/AudioService/audioservice.cpp index dba0153..b7673c8 100644 --- a/src/Model/AudioService/audioservice.cpp +++ b/src/Model/AudioService/audioservice.cpp @@ -21,11 +21,13 @@ AudioService::AudioService(MainWindow* pMainWindow) { sBloodyVersion = "1.16.1"; + this->pMainWindow = pMainWindow; pSystem = nullptr; pRndGen = new std::mt19937_64( std::random_device{}() ); iCurrentlyDrawingTrackIndex = new size_t(0); + bMonitorTracks = false; bIsSomeTrackPlaying = false; bRepeatTrack = false; @@ -33,6 +35,10 @@ AudioService::AudioService(MainWindow* pMainWindow) bDrawing = false; bCurrentTrackPaused = false; + + cRepeatSectionState = 0; + + // FX pPitch = nullptr; pPitchForTime = nullptr; @@ -46,6 +52,7 @@ AudioService::AudioService(MainWindow* pMainWindow) fCurrentSpeedByPitch = 1.0f; fCurrentSpeedByTime = 1.0f; + FMODinit(); } @@ -469,6 +476,13 @@ void AudioService::playTrack(size_t iTrackIndex, bool bDontLockMutex) vTracksHistory.erase( vTracksHistory.begin() ); } } + + if (cRepeatSectionState != 0) + { + pMainWindow->eraseRepeatSection(); + + cRepeatSectionState = 0; + } } else { @@ -503,12 +517,26 @@ void AudioService::setTrackPos(unsigned int graphPos) // track->getMaxValueOnGraph() - 100% // graphPos - x% - // cast to unsigned long long to avoid overflow double fPosMult = graphPos / static_cast(tracks[iCurrentlyPlayingTrackIndex]->getMaxValueOnGraph()); + // cast to avoid overflow unsigned int iPosInMS = static_cast(tracks[iCurrentlyPlayingTrackIndex]->getLengthInMS() * fPosMult); - if ( tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS(iPosInMS) ) + + if (cRepeatSectionState == 2) { - pMainWindow->setCurrentPos(fPosMult, tracks[iCurrentlyPlayingTrackIndex]->getCurrentTime()); + if ( (iPosInMS > iFirstRepeatTimePos) && (iPosInMS < iSecondRepeatTimePos) ) + { + if ( tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS(iPosInMS) ) + { + pMainWindow->setCurrentPos(fPosMult, tracks[iCurrentlyPlayingTrackIndex]->getCurrentTime()); + } + } + } + else + { + if ( tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS(iPosInMS) ) + { + pMainWindow->setCurrentPos(fPosMult, tracks[iCurrentlyPlayingTrackIndex]->getCurrentTime()); + } } } @@ -522,6 +550,70 @@ void AudioService::setTrackPos(unsigned int graphPos) mtxTracksVec.unlock(); } +void AudioService::setRepeatPoint(unsigned int graphPos) +{ + mtxTracksVec.lock(); + + if ( (tracks.size() > 0) && (bIsSomeTrackPlaying || bCurrentTrackPaused) ) + { + // track->getMaxValueOnGraph() - 100% + // graphPos - x% + + double fPosMult = graphPos / static_cast(tracks[iCurrentlyPlayingTrackIndex]->getMaxValueOnGraph()); + + if (cRepeatSectionState == 0) + { + // Set the first point + pMainWindow->setRepeatPoint(true, fPosMult); + + cRepeatSectionState = 1; + + unsigned int iPosInMS = static_cast(tracks[iCurrentlyPlayingTrackIndex]->getLengthInMS() * fPosMult); + iFirstRepeatTimePos = iPosInMS; + + + // Set track pos + if ( tracks[iCurrentlyPlayingTrackIndex]->getPositionInMS() < iFirstRepeatTimePos ) + { + if ( tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS(iFirstRepeatTimePos) ) + { + pMainWindow->setCurrentPos(fPosMult, tracks[iCurrentlyPlayingTrackIndex]->getCurrentTime()); + } + } + } + else if (cRepeatSectionState == 1) + { + // One point already on graph, set the second one + pMainWindow->setRepeatPoint(false, fPosMult); + + // Done + cRepeatSectionState = 2; + + unsigned int iPosInMS = static_cast(tracks[iCurrentlyPlayingTrackIndex]->getLengthInMS() * fPosMult); + iSecondRepeatTimePos = iPosInMS; + + + // Set track pos + if ( tracks[iCurrentlyPlayingTrackIndex]->getPositionInMS() > iSecondRepeatTimePos ) + { + if ( tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS(iFirstRepeatTimePos) ) + { + pMainWindow->setCurrentPos(fPosMult, tracks[iCurrentlyPlayingTrackIndex]->getCurrentTime()); + } + } + } + else + { + // User pressed RMB again, erase section + pMainWindow->eraseRepeatSection(); + + cRepeatSectionState = 0; + } + } + + mtxTracksVec.unlock(); +} + std::string AudioService::getBloodyVersion() { return sBloodyVersion; @@ -1345,6 +1437,29 @@ void AudioService::monitorTrack() } else { + if (cRepeatSectionState == 2) + { + if ( tracks[iCurrentlyPlayingTrackIndex]->getPositionInMS() > iSecondRepeatTimePos - MAX_TIME_ERROR_MS ) + { + for (float i = fCurrentVolume; i >= 0.0f; i-= 0.01f) + { + tracks[iCurrentlyPlayingTrackIndex]->setVolume(i); + + std::this_thread::sleep_for (std::chrono::milliseconds(2)); + } + + tracks[iCurrentlyPlayingTrackIndex]->setPositionInMS (iFirstRepeatTimePos); + + for (float i = 0; i <= fCurrentVolume; i+= 0.01f) + { + tracks[iCurrentlyPlayingTrackIndex]->setVolume(i); + + std::this_thread::sleep_for (std::chrono::milliseconds(2)); + } + } + } + + // track->getLengthInMS() - 1.0 // track->getPositionInMS() - x diff --git a/src/Model/AudioService/audioservice.h b/src/Model/AudioService/audioservice.h index 3ce0b05..71c0557 100644 --- a/src/Model/AudioService/audioservice.h +++ b/src/Model/AudioService/audioservice.h @@ -89,6 +89,7 @@ class AudioService void addTracks (std::vector paths); void setVolume (float fNewVolume); void setTrackPos (unsigned int graphPos); + void setRepeatPoint (unsigned int graphPos); // Get @@ -155,6 +156,24 @@ class AudioService bool bDrawing; + // Search + std::vector vSearchResult; + size_t iCurrentPosInSearchVec; + bool bFirstSearchAfterKeyChange; + + + // Tracks + std::vector tracks; + std::vector vTracksHistory; + + + // Repeat section + char cRepeatSectionState; + unsigned int iFirstRepeatTimePos; + unsigned int iSecondRepeatTimePos; + + + std::mutex mtxTracksVec; std::mutex mtxThreadLoadAddTrack; std::mutex mtxLoadThreadDone; @@ -176,15 +195,4 @@ class AudioService bool bRepeatTrack; bool bRandomNextTrack; bool bMonitorTracks; - - - // Search - std::vector vSearchResult; - size_t iCurrentPosInSearchVec; - bool bFirstSearchAfterKeyChange; - - - // Tracks - std::vector tracks; - std::vector vTracksHistory; }; diff --git a/src/View/MainWindow/mainwindow.cpp b/src/View/MainWindow/mainwindow.cpp index 4be12b1..5de71ef 100644 --- a/src/View/MainWindow/mainwindow.cpp +++ b/src/View/MainWindow/mainwindow.cpp @@ -61,6 +61,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(this, &MainWindow::signalAddDataToGraph, this, &MainWindow::slotAddDataToGraph); connect(this, &MainWindow::signalSetCurrentPos, this, &MainWindow::slotSetCurrentPos); connect(this, &MainWindow::signalHideVSTWindow, this, &MainWindow::slotHideVSTWindow); + connect(this, &MainWindow::signalSetRepeatPoint, this, &MainWindow::slotSetRepeatPoint); + connect(this, &MainWindow::signalEraseRepeatSection, this, &MainWindow::slotEraseRepeatSection); // Tracklist connect connect(ui->scrollArea, &TrackList::signalDrop, this, &MainWindow::slotDrop); @@ -70,7 +72,7 @@ MainWindow::MainWindow(QWidget *parent) : // Graph ui->widget_graph->addGraph(); ui->widget_graph->xAxis->setRange(0, MAX_X_AXIS_VALUE); - ui->widget_graph->yAxis->setRange(0.0, 1.0); + ui->widget_graph->yAxis->setRange(0.0, MAX_Y_AXIS_VALUE); QPen pen; pen.setWidth(1); @@ -89,15 +91,47 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->widget_graph, &QCustomPlot::mousePress, this, &MainWindow::slotClickOnGraph); + // fill rect backgnd = new QCPItemRect(ui->widget_graph); backgnd->topLeft->setType(QCPItemPosition::ptAxisRectRatio); backgnd->topLeft->setCoords(0, 0); backgnd->bottomRight->setType(QCPItemPosition::ptAxisRectRatio); - backgnd->bottomRight->setCoords(0, 1); - backgnd->setBrush(QBrush(QColor(0, 0, 0, 130))); + backgnd->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + backgnd->setBrush(QBrush(QColor(0, 0, 0, PLAYED_SECTION_ALPHA))); backgnd->setPen(Qt::NoPen); + + backgndRight = new QCPItemRect(ui->widget_graph); + backgndRight->topLeft->setType(QCPItemPosition::ptAxisRectRatio); + backgndRight->topLeft->setCoords(0, 0); + backgndRight->bottomRight->setType(QCPItemPosition::ptAxisRectRatio); + backgndRight->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + backgndRight->setBrush(QBrush(QColor(0, 0, 0, PLAYED_SECTION_ALPHA))); + backgndRight->setPen(Qt::NoPen); + + + // repeat left + repeatLeft = new QCPItemRect(ui->widget_graph); + repeatLeft->topLeft->setType(QCPItemPosition::ptAxisRectRatio); + repeatLeft->topLeft->setCoords(0, 0); + repeatLeft->bottomRight->setType(QCPItemPosition::ptAxisRectRatio); + repeatLeft->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + repeatLeft->setBrush(QBrush(QColor(0, 0, 0, REPEAT_GRAYED_ALPHA))); + repeatLeft->setPen(Qt::NoPen); + + + // repeat right + repeatRight = new QCPItemRect(ui->widget_graph); + repeatRight->topLeft->setType(QCPItemPosition::ptAxisRectRatio); + repeatRight->topLeft->setCoords(0, 0); + repeatRight->bottomRight->setType(QCPItemPosition::ptAxisRectRatio); + repeatRight->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + + repeatRight->setBrush(QBrush(QColor(0, 0, 0, REPEAT_GRAYED_ALPHA))); + repeatRight->setPen(Qt::NoPen); + + // text pGraphTextTrackTime = new QCPItemText(ui->widget_graph); pGraphTextTrackTime->position->setType(QCPItemPosition::ptAxisRectRatio); @@ -257,6 +291,16 @@ void MainWindow::setCurrentPos(double x, std::string time) emit signalSetCurrentPos(x, time); } +void MainWindow::setRepeatPoint(bool bFirstPoint, double x) +{ + emit signalSetRepeatPoint(bFirstPoint, x); +} + +void MainWindow::eraseRepeatSection() +{ + emit signalEraseRepeatSection(); +} + void MainWindow::setFocusOnTrack(size_t index) { // Wait a little for all track widgets to show @@ -590,9 +634,45 @@ void MainWindow::slotSetCurrentPos(double x, std::string time) ui->widget_graph->replot(); } +void MainWindow::slotSetRepeatPoint(bool bFirstPoint, double x) +{ + if (bFirstPoint) + { + repeatLeft->topLeft->setCoords(0, 0); + repeatLeft->bottomRight->setCoords(x, MAX_Y_AXIS_VALUE); + } + else + { + backgndRight->topLeft->setCoords(x, 0); + backgndRight->bottomRight->setCoords(1.0, MAX_Y_AXIS_VALUE); + + repeatRight->topLeft->setCoords(x, 0); + repeatRight->bottomRight->setCoords(1.0, MAX_Y_AXIS_VALUE); + } +} + +void MainWindow::slotEraseRepeatSection() +{ + repeatLeft->topLeft->setCoords(0, 0); + repeatLeft->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + + repeatRight->topLeft->setCoords(0, 0); + repeatRight->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); + + backgndRight->topLeft->setCoords(0, 0); + backgndRight->bottomRight->setCoords(0, MAX_Y_AXIS_VALUE); +} + void MainWindow::slotClickOnGraph(QMouseEvent* ev) { - pController->setTrackPos( static_cast(ui->widget_graph->xAxis->pixelToCoord(ev->pos().x())) ); + if (ev->button() == Qt::MouseButton::LeftButton) + { + pController->setTrackPos( static_cast(ui->widget_graph->xAxis->pixelToCoord(ev->pos().x())) ); + } + else if (ev->button() == Qt::MouseButton::RightButton) + { + pController->setRepeatPoint( static_cast(ui->widget_graph->xAxis->pixelToCoord(ev->pos().x())) ); + } } void MainWindow::slotSetPan(float fPan) diff --git a/src/View/MainWindow/mainwindow.h b/src/View/MainWindow/mainwindow.h index 6e68e5a..f86ef5c 100644 --- a/src/View/MainWindow/mainwindow.h +++ b/src/View/MainWindow/mainwindow.h @@ -53,6 +53,8 @@ class MainWindow : public QMainWindow void signalAddDataToGraph (float* pData, unsigned int iSizeInSamples, unsigned int iSamplesInOne); void signalSetCurrentPos (double x, std::string time); + void signalSetRepeatPoint (bool bFirstPoint, double x); + void signalEraseRepeatSection (); void signalClearGraph (bool stopTrack = false); void signalSetXMaxToGraph (unsigned int iMaxX); @@ -105,6 +107,8 @@ class MainWindow : public QMainWindow void addDataToGraph (float* pData, unsigned int iSizeInSamples, unsigned int iSamplesInOne); void setCurrentPos (double x, std::string time); + void setRepeatPoint (bool bFirstPoint, double x); + void eraseRepeatSection (); void clearGraph (bool stopTrack = false); void setXMaxToGraph (unsigned int iMaxX); @@ -198,6 +202,8 @@ private slots: void slotAddDataToGraph (float* pData, unsigned int iSizeInSamples, unsigned int iSamplesInOne); void slotSetCurrentPos (double x, std::string time); + void slotSetRepeatPoint (bool bFirstPoint, double x); + void slotEraseRepeatSection (); void slotClearGraph (bool stopTrack = false); void slotSetXMaxToGraph (unsigned int iMaxX); void slotClickOnGraph (QMouseEvent* ev); @@ -273,6 +279,9 @@ private slots: QSystemTrayIcon* pTrayIcon; QCPItemText* pGraphTextTrackTime; QCPItemRect* backgnd; + QCPItemRect* repeatLeft; + QCPItemRect* repeatRight; + QCPItemRect* backgndRight; std::mutex mtxAddTrackWidget; diff --git a/src/View/MainWindow/mainwindow.ui b/src/View/MainWindow/mainwindow.ui index 61914b0..d756016 100644 --- a/src/View/MainWindow/mainwindow.ui +++ b/src/View/MainWindow/mainwindow.ui @@ -65,6 +65,18 @@ color: white; + + + 0 + 0 + + + + + 64 + 16777215 + + @@ -361,6 +373,18 @@ QPushButton:pressed + + + 0 + 0 + + + + + 64 + 16777215 + + @@ -600,6 +624,18 @@ QPushButton:pressed + + + 0 + 0 + + + + + 64 + 16777215 + + @@ -856,6 +892,18 @@ QPushButton:pressed + + + 0 + 0 + + + + + 64 + 16777215 + + @@ -1095,6 +1143,18 @@ QPushButton:pressed + + + 0 + 0 + + + + + 64 + 16777215 + + @@ -1418,6 +1478,18 @@ QSlider::sub-page:disabled true + + + 0 + 0 + + + + + 64 + 16777215 + + <html> <head> @@ -1466,6 +1538,18 @@ QPushButton:checked + + + 0 + 0 + + + + + 64 + 16777215 + + <html> <head> @@ -1514,6 +1598,18 @@ QPushButton:checked + + + 0 + 0 + + + + + 64 + 16777215 + + <html> <head> @@ -1554,6 +1650,18 @@ QPushButton:pressed + + + 0 + 0 + + + + + 64 + 16777215 + + <html> <head> diff --git a/src/globalparams.h b/src/globalparams.h index ca5b55e..f1258d3 100644 --- a/src/globalparams.h +++ b/src/globalparams.h @@ -9,3 +9,6 @@ // graph #define MAX_X_AXIS_VALUE 1000 +#define MAX_Y_AXIS_VALUE 1.02 +#define PLAYED_SECTION_ALPHA 130 +#define REPEAT_GRAYED_ALPHA 120 diff --git a/src/main.cpp b/src/main.cpp index 8b12655..11b46c8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "../src/View/MainWindow/mainwindow.h" +#include "../src/View/MainWindow/mainwindow.h" #include int main(int argc, char *argv[]) @@ -8,11 +8,6 @@ int main(int argc, char *argv[]) if (w.isSystemReady()) { - // Hide maximize button - Qt::WindowFlags flags = w.windowFlags(); - flags &= ~Qt::WindowMaximizeButtonHint; - w.setWindowFlags(flags); - w.show(); if (argc > 1)