From 81cc81fe3a3ebe5a7b307d01ad733a95c7256fce Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Wed, 29 Apr 2020 19:48:41 +0200 Subject: [PATCH] Implemented basic BPM mechanism --- DrumMachine.pro | 2 - audioformat.cpp | 2 +- audioformat.h | 2 +- audioplayer.cpp | 21 ++- audioplayer.h | 5 + djwidget.cpp | 10 +- djwidget.h | 3 +- djwidget.ui | 6 + flatfilesystemmodel.cpp | 1 - flatfilesystemmodel.h | 14 -- mainwindow.cpp | 6 +- scratchwidget.cpp | 17 +-- scratchwidget.h | 17 ++- synthisizer.cpp | 6 +- synthisizer.h | 1 + trackdeck.cpp | 97 +++++++++++- trackdeck.h | 17 ++- trackdeck.ui | 321 ++++++++++++++++++++++++++++++++-------- 18 files changed, 426 insertions(+), 122 deletions(-) delete mode 100644 flatfilesystemmodel.cpp delete mode 100644 flatfilesystemmodel.h diff --git a/DrumMachine.pro b/DrumMachine.pro index ddd8080..6cf1bad 100755 --- a/DrumMachine.pro +++ b/DrumMachine.pro @@ -16,7 +16,6 @@ SOURCES += \ audioplayer.cpp \ djwidget.cpp \ filesmodel.cpp \ - flatfilesystemmodel.cpp \ graphrenderer.cpp \ jsonconverters.cpp \ main.cpp \ @@ -40,7 +39,6 @@ HEADERS += \ audioplayer.h \ djwidget.h \ filesmodel.h \ - flatfilesystemmodel.h \ graphrenderer.h \ jsonconverters.h \ mainwindow.h \ diff --git a/audioformat.cpp b/audioformat.cpp index b653e9f..b6bf86f 100644 --- a/audioformat.cpp +++ b/audioformat.cpp @@ -4,7 +4,7 @@ const QAudioFormat &audioFormat() { static const QAudioFormat audioFormat = [](){ QAudioFormat format; - format.setSampleRate(sampleRate); + format.setSampleRate(frameRate); format.setChannelCount(channelCount); format.setSampleSize(sampleSize); format.setCodec(codec); diff --git a/audioformat.h b/audioformat.h index e6b504e..5d57bf6 100644 --- a/audioformat.h +++ b/audioformat.h @@ -2,7 +2,7 @@ #include -static constexpr int sampleRate = 44100; +static constexpr int frameRate = 44100; static constexpr int channelCount = 2; static constexpr int sampleSize = 32; static constexpr auto codec = "audio/pcm"; diff --git a/audioplayer.cpp b/audioplayer.cpp index 7232365..5437eb1 100644 --- a/audioplayer.cpp +++ b/audioplayer.cpp @@ -24,7 +24,9 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end) const auto speed = m_speed; const auto buffer = m_buffer; const auto volume = m_volume; + const auto stopOnEnd = m_stopOnEnd; const auto &data = buffer.constData(); + const auto loop = m_loop; const auto frames = std::min(std::distance(begin, end), buffer.frameCount()-position); @@ -38,18 +40,27 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end) bool ended{}; std::transform(static_cast(begin), static_cast(begin+frames), begin, [&](frame_t frame)->frame_t{ - if (ended) + if (ended && stopOnEnd) return frame; - const auto index = std::size_t(position); - if (index >= buffer.frameCount()) + const auto index = std::ptrdiff_t(position); + position += speed; + + if (loop) + { + if (speed < 0 && position < loop->first) + position = loop->second; + else if (speed > 0 && position > loop->second) + position = loop->first; + } + + if ((speed < 0.f && index < 0) || (speed > 0.f && index >= buffer.frameCount())) { ended = true; return frame; } const frame_t &frame2 = data[index]; - position += speed; std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame2), std::begin(frame), [&volume](const sample_t &left, const sample_t &right) { return left + (right*volume); }); @@ -65,7 +76,7 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end) m_lastPositionUpdate = now; } - if (ended && m_stopOnEnd) + if (ended && stopOnEnd) { m_playing = false; emit playingChanged(m_playing); diff --git a/audioplayer.h b/audioplayer.h index ca50d46..2ba4b14 100644 --- a/audioplayer.h +++ b/audioplayer.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -37,6 +38,9 @@ public: const QAudioBuffer &buffer() const { return m_buffer; } void setBuffer(const QAudioBuffer &buffer); + const std::optional> &loop() const { return m_loop; } + void setLoop(const std::optional> &loop) { m_loop = loop; } + void togglePlaying(); void restart(); void stop(); @@ -56,6 +60,7 @@ private: float m_volume{1.f}; bool m_stopOnEnd{true}; QAudioBuffer m_buffer; + std::optional> m_loop; QDateTime m_lastPositionUpdate; }; diff --git a/djwidget.cpp b/djwidget.cpp index 46d94ab..97870b9 100644 --- a/djwidget.cpp +++ b/djwidget.cpp @@ -39,9 +39,7 @@ DjWidget::DjWidget(QWidget *parent) : const auto index = m_directoryModel.index(locations.first()); m_ui->treeViewDirectories->selectionModel()->select(index, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows); - const auto rootIndex = m_filesModel.setRootPath(m_directoryModel.filePath(index)); - m_filesModel.m_root_path = rootIndex; - m_ui->treeViewFiles->setRootIndex(rootIndex); + m_ui->treeViewFiles->setRootIndex(m_filesModel.setRootPath(m_directoryModel.filePath(index))); } } @@ -75,9 +73,5 @@ void DjWidget::directorySelected() { const auto selected = m_ui->treeViewDirectories->currentIndex(); if (selected.isValid()) - { - const auto rootIndex = m_filesModel.setRootPath(m_directoryModel.filePath(selected)); - m_filesModel.m_root_path = rootIndex; - m_ui->treeViewFiles->setRootIndex(rootIndex); - } + m_ui->treeViewFiles->setRootIndex(m_filesModel.setRootPath(m_directoryModel.filePath(selected))); } diff --git a/djwidget.h b/djwidget.h index 6a519f1..405818e 100644 --- a/djwidget.h +++ b/djwidget.h @@ -6,7 +6,6 @@ #include #include "audioformat.h" -#include "flatfilesystemmodel.h" namespace Ui { class DjWidget; } @@ -29,5 +28,5 @@ private: const std::unique_ptr m_ui; QFileSystemModel m_directoryModel; - FlatFileSystemModel m_filesModel; + QFileSystemModel m_filesModel; }; diff --git a/djwidget.ui b/djwidget.ui index 0d48128..f73e2f9 100644 --- a/djwidget.ui +++ b/djwidget.ui @@ -124,6 +124,12 @@ false + + false + + + true + diff --git a/flatfilesystemmodel.cpp b/flatfilesystemmodel.cpp deleted file mode 100644 index 835f076..0000000 --- a/flatfilesystemmodel.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "flatfilesystemmodel.h" diff --git a/flatfilesystemmodel.h b/flatfilesystemmodel.h deleted file mode 100644 index 99cc3b7..0000000 --- a/flatfilesystemmodel.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -class FlatFileSystemModel : public QFileSystemModel -{ -public: - bool hasChildren(const QModelIndex &parent = QModelIndex()) const override { return (parent == m_root_path) ? QFileSystemModel::hasChildren(parent) : false; }; - bool canFetchMore(const QModelIndex &parent) const override { return (parent == m_root_path) ? QFileSystemModel::canFetchMore(parent) : false; }; - void fetchMore(const QModelIndex &parent) override { if ((parent == m_root_path)) QFileSystemModel::fetchMore(parent); }; - -public: - QModelIndex m_root_path; -}; diff --git a/mainwindow.cpp b/mainwindow.cpp index 8bd00e9..c66928b 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -76,6 +76,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par updateMidiDevices(); + connect(m_ui->pushButtonRefreshMidiControllers, &QAbstractButton::pressed, this, &MainWindow::updateMidiDevices); + connect(m_ui->pushButtonMidiController, &QAbstractButton::pressed, this, [this](){ if (m_midiIn.isPortOpen()) m_midiIn.closePort(); @@ -92,6 +94,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par updateAudioDevices(); + connect(m_ui->pushButtonRefreshAudioDevices, &QAbstractButton::pressed, this, &MainWindow::updateAudioDevices); + m_ui->comboBoxAudioDevice->setCurrentIndex(Pa_GetDefaultOutputDevice()); connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice); @@ -171,7 +175,7 @@ void MainWindow::openAudioDevice() PaStream *stream{}; - if (PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, sampleRate, m_ui->spinBoxBufferSize->value(), paNoFlag, &paCallback, this); err != paNoError) + if (PaError err = Pa_OpenStream(&stream, NULL, &outputParameters, frameRate, m_ui->spinBoxBufferSize->value(), paNoFlag, &paCallback, this); err != paNoError) { QMessageBox::warning(this, tr("Error opening stream!"), tr("Error opening stream!") + "\n\n" + Pa_GetErrorText(err)); return; diff --git a/scratchwidget.cpp b/scratchwidget.cpp index 5344d02..f00f109 100644 --- a/scratchwidget.cpp +++ b/scratchwidget.cpp @@ -7,8 +7,6 @@ #include "graphrenderer.h" -constexpr auto theWidth = 100; - ScratchWidget::ScratchWidget(QWidget *parent) : QWidget{parent} { @@ -26,7 +24,7 @@ void ScratchWidget::paintEvent(QPaintEvent *event) painter.setBrush(palette().window()); painter.drawRect(rect()); - if (m_buffer.isValid() && m_position < m_buffer.frameCount() - sampleRate) + if (m_buffer.isValid() && m_position < m_buffer.frameCount() - m_framesPerBeat) { { QPen pen{Qt::blue}; @@ -36,8 +34,8 @@ void ScratchWidget::paintEvent(QPaintEvent *event) const auto doit = [&](int offset) { - int x = ((width()/2)-(float(m_position % sampleRate) / sampleRate * theWidth)) + (theWidth*offset); - const auto pixmap = getPixmap((m_position/sampleRate)+offset); + int x = ((width()/2)-(float(m_position % m_framesPerBeat) / m_framesPerBeat * m_beatWidth)) + (m_beatWidth*offset); + const auto pixmap = getPixmap((m_position/m_framesPerBeat)+offset); if (!pixmap.isNull()) painter.drawPixmap(x, 0, pixmap); }; @@ -69,6 +67,7 @@ void ScratchWidget::mousePressEvent(QMouseEvent *event) m_timestamp = QDateTime::currentDateTime(); setMouseTracking(true); emit scratchBegin(); + emit scratchSpeed(0.f); } } @@ -93,7 +92,7 @@ void ScratchWidget::mouseMoveEvent(QMouseEvent *event) int dx = m_mouseX - event->x(); int dt = m_timestamp.msecsTo(now); - emit scratchSpeed(float(dx) / dt * 5.f); + emit scratchSpeed(float(dx) / dt * m_framesPerBeat / m_beatWidth / 50); m_mouseX = event->x(); m_timestamp = now; @@ -114,14 +113,14 @@ QPixmap ScratchWidget::getPixmap(int index) return *pixmap; } - if (!m_buffer.isValid() || index < 0 || index >= m_buffer.frameCount()/sampleRate) + if (!m_buffer.isValid() || index < 0 || index >= m_buffer.frameCount()/m_framesPerBeat) { qWarning() << index; return {}; } - const auto *begin = m_buffer.constData() + (index*sampleRate); - const auto pixmap = GraphRenderer::render(QSize{theWidth, height()}, begin, begin+sampleRate, palette()); + const auto *begin = m_buffer.constData() + (index*m_framesPerBeat); + const auto pixmap = GraphRenderer::render(QSize{m_beatWidth, height()}, begin, begin+m_framesPerBeat, palette()); m_graphCache.insert(index, new QPixmap{pixmap}); diff --git a/scratchwidget.h b/scratchwidget.h index 06a3ca8..a217361 100644 --- a/scratchwidget.h +++ b/scratchwidget.h @@ -6,6 +6,8 @@ #include #include +#include "audioformat.h" + class ScratchWidget : public QWidget { Q_OBJECT @@ -16,8 +18,14 @@ public: const QAudioBuffer &buffer() const { return m_buffer; } void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache.clear(); repaint(); } - std::size_t position() const { return m_position; } - void setPosition(std::size_t position) { m_position = position; repaint(); } + std::ptrdiff_t position() const { return m_position; } + void setPosition(std::ptrdiff_t position) { m_position = position; repaint(); } + + int beatWidth() const { return m_beatWidth; }; + void setBeatWidth(int beatWidth) { m_beatWidth = beatWidth; m_graphCache.clear(); repaint(); }; + + int framesPerBeat() const { return m_framesPerBeat; } + void setFramesPerBeat(int framesPerBeat) { m_framesPerBeat = framesPerBeat; m_graphCache.clear(); repaint(); } signals: void scratchBegin(); @@ -37,9 +45,12 @@ private: QPixmap getPixmap(int index); QAudioBuffer m_buffer; - std::size_t m_position{}; + std::ptrdiff_t m_position{}; QCache m_graphCache; + int m_beatWidth{100}; + int m_framesPerBeat{frameRate/4}; + bool m_scratching{}; bool m_dragging{}; int m_mouseX; diff --git a/synthisizer.cpp b/synthisizer.cpp index d6cdcc9..7d126f5 100644 --- a/synthisizer.cpp +++ b/synthisizer.cpp @@ -8,17 +8,19 @@ constexpr double pi = std::acos(-1); void Synthisizer::writeSamples(frame_t *begin, frame_t *end) { - if (m_frequency) + const auto frequency = m_frequency; + if (frequency) std::transform(begin, end, begin, [&](frame_t frame){ std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame), [value=std::sin(m_phase)](const sample_t &sample) { return sample + value; }); - m_phase += pi*2./sampleRate*m_frequency; + m_phase += pi*2./frameRate*m_actualFrequency; if (m_phase >= pi*2.) m_phase -= pi*2.; return frame; }); + m_actualFrequency = float(m_actualFrequency+m_frequency)/2.f; } void Synthisizer::messageReceived(const midi::MidiMessage &message) diff --git a/synthisizer.h b/synthisizer.h index 9d867e0..948bc15 100644 --- a/synthisizer.h +++ b/synthisizer.h @@ -15,5 +15,6 @@ public: private: int16_t m_frequency{}; + int16_t m_actualFrequency{}; double m_phase{}; }; diff --git a/trackdeck.cpp b/trackdeck.cpp index 047b4c9..020ca7a 100644 --- a/trackdeck.cpp +++ b/trackdeck.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "audiodecoder.h" @@ -20,12 +21,36 @@ TrackDeck::TrackDeck(QWidget *parent) : m_ui->setupUi(this); m_ui->progressBar->hide(); + m_loopGroup.addButton(m_ui->pushButtonLoopOff, -1); + m_loopGroup.addButton(m_ui->pushButtonLoop1, 1); + m_loopGroup.addButton(m_ui->pushButtonLoop2, 2); + m_loopGroup.addButton(m_ui->pushButtonLoop4, 4); + m_loopGroup.addButton(m_ui->pushButtonLoop8, 8); + connect(&m_loopGroup, qOverload(&QButtonGroup::buttonClicked), [&player=m_player,&bpmInput=*m_ui->doubleSpinBoxBpm](int id){ + const auto position = player.position(); + + const auto bpm = bpmInput.value(); + const auto beatsPerSecond = bpm / 60.; + const auto framesPerBeat = frameRate / beatsPerSecond; + + switch (id) + { + case 1: player.setLoop(std::make_pair(position, position + (framesPerBeat/4))); break; + case 2: player.setLoop(std::make_pair(position, position + (framesPerBeat/2))); break; + case 4: player.setLoop(std::make_pair(position, position + framesPerBeat)); break; + case 8: player.setLoop(std::make_pair(position, position + (framesPerBeat*2))); break; + default: player.setLoop({}); + } + }); + + connect(m_ui->pushButtonBpm, &QAbstractButton::pressed, this, &TrackDeck::bpmTap); + connect(m_ui->pushButtonPlay, &QAbstractButton::pressed, &m_player, &AudioPlayer::togglePlaying); connect(m_ui->pushButtonStop, &QAbstractButton::pressed, &m_player, &AudioPlayer::stop); - connect(m_ui->verticalSliderVolume, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setVolume(float(value)/100.f); }); - - connect(m_ui->horizontalSliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); + connect(m_ui->sliderZoom, &QAbstractSlider::valueChanged, m_ui->scratchWidget, &ScratchWidget::setBeatWidth); + connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); + connect(m_ui->sliderVolume, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setVolume(float(value)/100.f); }); connect(m_ui->previewWidget, &PreviewWidget::positionSelected, &m_player, &AudioPlayer::setPosition); connect(&m_player, &AudioPlayer::positionChanged, m_ui->previewWidget, &PreviewWidget::setPosition); @@ -33,6 +58,15 @@ TrackDeck::TrackDeck(QWidget *parent) : connect(m_ui->scratchWidget, &ScratchWidget::scratchEnd, this, &TrackDeck::scratchEnd); connect(&m_player, &AudioPlayer::positionChanged, m_ui->scratchWidget, &ScratchWidget::setPosition); connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); + + connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::updatePlaybackBpm); + connect(m_ui->doubleSpinBoxBpm, &QDoubleSpinBox::valueChanged, this, &TrackDeck::updatePlaybackBpm); + + connect(&m_timer, &QTimer::timeout, this, &TrackDeck::timeout); + m_timer.setSingleShot(true); + m_timer.setInterval(1000); + + updatePlaybackBpm(); } TrackDeck::~TrackDeck() = default; @@ -141,6 +175,12 @@ void TrackDeck::decodingProgress(int progress, int total) void TrackDeck::decodingFinished(const QAudioBuffer &buffer) { m_player.setBuffer(buffer); + + m_ui->labelTitle->setText(QFileInfo{m_filename}.fileName()); + + for (auto *btn : m_loopGroup.buttons()) + btn->setEnabled(false); + m_ui->previewWidget->setBuffer(buffer); m_ui->scratchWidget->setBuffer(buffer); m_ui->progressBar->hide(); @@ -148,8 +188,8 @@ void TrackDeck::decodingFinished(const QAudioBuffer &buffer) void TrackDeck::scratchBegin() { - disconnect(m_ui->horizontalSliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); - m_ui->horizontalSliderSpeed->setEnabled(false); + disconnect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); + m_ui->sliderSpeed->setEnabled(false); connect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed); disconnect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); @@ -169,8 +209,8 @@ void TrackDeck::scratchEnd() m_player.setStopOnEnd(m_stopOnEndBeforeScratch); disconnect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed); - m_ui->horizontalSliderSpeed->setEnabled(true); - connect(m_ui->horizontalSliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); + m_ui->sliderSpeed->setEnabled(true); + connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); } @@ -181,5 +221,46 @@ void TrackDeck::speedChanged(int value) void TrackDeck::updatePlayButtonText(bool playing) { - m_ui->pushButtonPlay->setText(playing ? tr("▮▮") : tr("▶")); + m_ui->pushButtonBpm->setEnabled(playing); + m_ui->pushButtonPlay->setText(playing ? tr("▮▮") : tr("▶")); +} + +void TrackDeck::bpmTap() +{ + const auto position = m_player.position(); + if (m_bpmTap) + { + std::get<1>(*m_bpmTap) = position; + std::get<2>(*m_bpmTap)++; + const auto framesPerBeat = (std::get<1>(*m_bpmTap)-std::get<0>(*m_bpmTap))/std::get<2>(*m_bpmTap); + const auto beatsPerSecond = frameRate/framesPerBeat; + const auto bpm = 60.*beatsPerSecond; + qDebug() << "framesPerBeat =" << framesPerBeat << "beatsPerSecond =" << beatsPerSecond << "bpm =" << bpm; + m_ui->doubleSpinBoxBpm->setValue(bpm); + } + else + { + m_bpmTap = std::make_tuple(position, position, 0); + } + + m_ui->pushButtonBpm->setText(QString::number(std::get<2>(*m_bpmTap))); + m_timer.start(); +} + +void TrackDeck::timeout() +{ + const auto framesPerBeat = (std::get<1>(*m_bpmTap)-std::get<0>(*m_bpmTap))/std::get<2>(*m_bpmTap); + const auto beatsPerSecond = frameRate/framesPerBeat; + const auto bpm = 60.*beatsPerSecond; + qDebug() << "framesPerBeat =" << framesPerBeat << "beatsPerSecond =" << beatsPerSecond << "bpm =" << bpm; + m_ui->pushButtonBpm->setText(tr("BPM tap")); + m_ui->doubleSpinBoxBpm->setValue(bpm); + for (auto *btn : m_loopGroup.buttons()) + btn->setEnabled(true); + m_bpmTap = {}; +} + +void TrackDeck::updatePlaybackBpm() +{ + m_ui->labelPlaybackBpm->setText(QString::number(m_ui->doubleSpinBoxBpm->value() * m_ui->sliderSpeed->value() / 100., 'f', 2)); } diff --git a/trackdeck.h b/trackdeck.h index 5547067..3c0f337 100644 --- a/trackdeck.h +++ b/trackdeck.h @@ -1,8 +1,12 @@ #pragma once -#include - #include +#include +#include + +#include +#include +#include #include "audioformat.h" #include "audioplayer.h" @@ -40,10 +44,15 @@ private slots: void scratchEnd(); void speedChanged(int value); void updatePlayButtonText(bool playing); + void bpmTap(); + void timeout(); + void updatePlaybackBpm(); private: const std::unique_ptr m_ui; + QButtonGroup m_loopGroup; + std::unique_ptr m_decoder; AudioPlayer m_player; @@ -53,4 +62,8 @@ private: bool m_playingBeforeScratch; float m_speedBeforeScratch; bool m_stopOnEndBeforeScratch; + + QTimer m_timer; + + std::optional> m_bpmTap; }; diff --git a/trackdeck.ui b/trackdeck.ui index b98a0aa..018a757 100644 --- a/trackdeck.ui +++ b/trackdeck.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 752 + 500 @@ -24,21 +24,12 @@ - + - - - - 20 - - - - Track - - - - - + + + + @@ -46,6 +37,42 @@ + + + + + + + + false + + + + 60 + 16777215 + + + + BPM tap + + + + + + + BPM + + + 80.000000000000000 + + + 250.000000000000000 + + + 130.000000000000000 + + + @@ -60,12 +87,101 @@ - + + + + 16 + 16777215 + + + + - + + + true + + + true + + + + + + + + 16 + 16777215 + + + + 1 + + + true + + + + + + + + 16 + 16777215 + + + + 2 + + + true + + + + + + + + 16 + 16777215 + + + + 4 + + + true + + + + + + + + 16 + 16777215 + + + + 8 + + + true + + + + + + + + 100 + 16777215 + + - 80 + 25 - 120 + 200 100 @@ -77,7 +193,7 @@ QSlider::TicksBothSides - 10 + 50 @@ -110,53 +226,132 @@ - - - - 0 - 0 - - - - - 0 - 50 - - - - - 16777215 - 50 - - - - - - - - - 0 - 0 - - - - - 0 - 100 - - - - - 16777215 - 100 - - - + + + + + + + + 0 + 0 + + + + + 0 + 50 + + + + + 16777215 + 50 + + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + + 16777215 + 100 + + + + + + + + + + + + + 8 + + + + 100.00 + + + + + + + 0 + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + 80 + + + 120 + + + 100 + + + Qt::Vertical + + + QSlider::TicksBothSides + + + 10 + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + - + 100