Implemented basic BPM mechanism

This commit is contained in:
2020-04-29 19:48:41 +02:00
parent 2919ca2db6
commit 81cc81fe3a
18 changed files with 426 additions and 122 deletions

View File

@ -16,7 +16,6 @@ SOURCES += \
audioplayer.cpp \ audioplayer.cpp \
djwidget.cpp \ djwidget.cpp \
filesmodel.cpp \ filesmodel.cpp \
flatfilesystemmodel.cpp \
graphrenderer.cpp \ graphrenderer.cpp \
jsonconverters.cpp \ jsonconverters.cpp \
main.cpp \ main.cpp \
@ -40,7 +39,6 @@ HEADERS += \
audioplayer.h \ audioplayer.h \
djwidget.h \ djwidget.h \
filesmodel.h \ filesmodel.h \
flatfilesystemmodel.h \
graphrenderer.h \ graphrenderer.h \
jsonconverters.h \ jsonconverters.h \
mainwindow.h \ mainwindow.h \

View File

@ -4,7 +4,7 @@ const QAudioFormat &audioFormat()
{ {
static const QAudioFormat audioFormat = [](){ static const QAudioFormat audioFormat = [](){
QAudioFormat format; QAudioFormat format;
format.setSampleRate(sampleRate); format.setSampleRate(frameRate);
format.setChannelCount(channelCount); format.setChannelCount(channelCount);
format.setSampleSize(sampleSize); format.setSampleSize(sampleSize);
format.setCodec(codec); format.setCodec(codec);

View File

@ -2,7 +2,7 @@
#include <QAudioFormat> #include <QAudioFormat>
static constexpr int sampleRate = 44100; static constexpr int frameRate = 44100;
static constexpr int channelCount = 2; static constexpr int channelCount = 2;
static constexpr int sampleSize = 32; static constexpr int sampleSize = 32;
static constexpr auto codec = "audio/pcm"; static constexpr auto codec = "audio/pcm";

View File

@ -24,7 +24,9 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end)
const auto speed = m_speed; const auto speed = m_speed;
const auto buffer = m_buffer; const auto buffer = m_buffer;
const auto volume = m_volume; const auto volume = m_volume;
const auto stopOnEnd = m_stopOnEnd;
const auto &data = buffer.constData<frame_t>(); const auto &data = buffer.constData<frame_t>();
const auto loop = m_loop;
const auto frames = std::min<size_t>(std::distance(begin, end), buffer.frameCount()-position); const auto frames = std::min<size_t>(std::distance(begin, end), buffer.frameCount()-position);
@ -38,18 +40,27 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end)
bool ended{}; bool ended{};
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(begin+frames), begin, std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(begin+frames), begin,
[&](frame_t frame)->frame_t{ [&](frame_t frame)->frame_t{
if (ended) if (ended && stopOnEnd)
return frame; return frame;
const auto index = std::size_t(position); const auto index = std::ptrdiff_t(position);
if (index >= buffer.frameCount()) 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; ended = true;
return frame; return frame;
} }
const frame_t &frame2 = data[index]; const frame_t &frame2 = data[index];
position += speed;
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame2), std::begin(frame), 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); }); [&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; m_lastPositionUpdate = now;
} }
if (ended && m_stopOnEnd) if (ended && stopOnEnd)
{ {
m_playing = false; m_playing = false;
emit playingChanged(m_playing); emit playingChanged(m_playing);

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <optional>
#include <QObject> #include <QObject>
#include <QAudioBuffer> #include <QAudioBuffer>
@ -37,6 +38,9 @@ public:
const QAudioBuffer &buffer() const { return m_buffer; } const QAudioBuffer &buffer() const { return m_buffer; }
void setBuffer(const QAudioBuffer &buffer); void setBuffer(const QAudioBuffer &buffer);
const std::optional<std::pair<double, double>> &loop() const { return m_loop; }
void setLoop(const std::optional<std::pair<double, double>> &loop) { m_loop = loop; }
void togglePlaying(); void togglePlaying();
void restart(); void restart();
void stop(); void stop();
@ -56,6 +60,7 @@ private:
float m_volume{1.f}; float m_volume{1.f};
bool m_stopOnEnd{true}; bool m_stopOnEnd{true};
QAudioBuffer m_buffer; QAudioBuffer m_buffer;
std::optional<std::pair<double, double>> m_loop;
QDateTime m_lastPositionUpdate; QDateTime m_lastPositionUpdate;
}; };

View File

@ -39,9 +39,7 @@ DjWidget::DjWidget(QWidget *parent) :
const auto index = m_directoryModel.index(locations.first()); const auto index = m_directoryModel.index(locations.first());
m_ui->treeViewDirectories->selectionModel()->select(index, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current|QItemSelectionModel::Rows); 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_ui->treeViewFiles->setRootIndex(m_filesModel.setRootPath(m_directoryModel.filePath(index)));
m_filesModel.m_root_path = rootIndex;
m_ui->treeViewFiles->setRootIndex(rootIndex);
} }
} }
@ -75,9 +73,5 @@ void DjWidget::directorySelected()
{ {
const auto selected = m_ui->treeViewDirectories->currentIndex(); const auto selected = m_ui->treeViewDirectories->currentIndex();
if (selected.isValid()) if (selected.isValid())
{ m_ui->treeViewFiles->setRootIndex(m_filesModel.setRootPath(m_directoryModel.filePath(selected)));
const auto rootIndex = m_filesModel.setRootPath(m_directoryModel.filePath(selected));
m_filesModel.m_root_path = rootIndex;
m_ui->treeViewFiles->setRootIndex(rootIndex);
}
} }

View File

@ -6,7 +6,6 @@
#include <memory> #include <memory>
#include "audioformat.h" #include "audioformat.h"
#include "flatfilesystemmodel.h"
namespace Ui { class DjWidget; } namespace Ui { class DjWidget; }
@ -29,5 +28,5 @@ private:
const std::unique_ptr<Ui::DjWidget> m_ui; const std::unique_ptr<Ui::DjWidget> m_ui;
QFileSystemModel m_directoryModel; QFileSystemModel m_directoryModel;
FlatFileSystemModel m_filesModel; QFileSystemModel m_filesModel;
}; };

View File

@ -124,6 +124,12 @@
<property name="rootIsDecorated"> <property name="rootIsDecorated">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -1 +0,0 @@
#include "flatfilesystemmodel.h"

View File

@ -1,14 +0,0 @@
#pragma once
#include <QFileSystemModel>
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;
};

View File

@ -76,6 +76,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
updateMidiDevices(); updateMidiDevices();
connect(m_ui->pushButtonRefreshMidiControllers, &QAbstractButton::pressed, this, &MainWindow::updateMidiDevices);
connect(m_ui->pushButtonMidiController, &QAbstractButton::pressed, this, [this](){ connect(m_ui->pushButtonMidiController, &QAbstractButton::pressed, this, [this](){
if (m_midiIn.isPortOpen()) if (m_midiIn.isPortOpen())
m_midiIn.closePort(); m_midiIn.closePort();
@ -92,6 +94,8 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
updateAudioDevices(); updateAudioDevices();
connect(m_ui->pushButtonRefreshAudioDevices, &QAbstractButton::pressed, this, &MainWindow::updateAudioDevices);
m_ui->comboBoxAudioDevice->setCurrentIndex(Pa_GetDefaultOutputDevice()); m_ui->comboBoxAudioDevice->setCurrentIndex(Pa_GetDefaultOutputDevice());
connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice); connect(m_ui->pushButtonAudioDevice, &QAbstractButton::pressed, this, &MainWindow::openAudioDevice);
@ -171,7 +175,7 @@ void MainWindow::openAudioDevice()
PaStream *stream{}; 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)); QMessageBox::warning(this, tr("Error opening stream!"), tr("Error opening stream!") + "\n\n" + Pa_GetErrorText(err));
return; return;

View File

@ -7,8 +7,6 @@
#include "graphrenderer.h" #include "graphrenderer.h"
constexpr auto theWidth = 100;
ScratchWidget::ScratchWidget(QWidget *parent) : ScratchWidget::ScratchWidget(QWidget *parent) :
QWidget{parent} QWidget{parent}
{ {
@ -26,7 +24,7 @@ void ScratchWidget::paintEvent(QPaintEvent *event)
painter.setBrush(palette().window()); painter.setBrush(palette().window());
painter.drawRect(rect()); 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}; QPen pen{Qt::blue};
@ -36,8 +34,8 @@ void ScratchWidget::paintEvent(QPaintEvent *event)
const auto doit = [&](int offset) const auto doit = [&](int offset)
{ {
int x = ((width()/2)-(float(m_position % sampleRate) / sampleRate * theWidth)) + (theWidth*offset); int x = ((width()/2)-(float(m_position % m_framesPerBeat) / m_framesPerBeat * m_beatWidth)) + (m_beatWidth*offset);
const auto pixmap = getPixmap((m_position/sampleRate)+offset); const auto pixmap = getPixmap((m_position/m_framesPerBeat)+offset);
if (!pixmap.isNull()) if (!pixmap.isNull())
painter.drawPixmap(x, 0, pixmap); painter.drawPixmap(x, 0, pixmap);
}; };
@ -69,6 +67,7 @@ void ScratchWidget::mousePressEvent(QMouseEvent *event)
m_timestamp = QDateTime::currentDateTime(); m_timestamp = QDateTime::currentDateTime();
setMouseTracking(true); setMouseTracking(true);
emit scratchBegin(); emit scratchBegin();
emit scratchSpeed(0.f);
} }
} }
@ -93,7 +92,7 @@ void ScratchWidget::mouseMoveEvent(QMouseEvent *event)
int dx = m_mouseX - event->x(); int dx = m_mouseX - event->x();
int dt = m_timestamp.msecsTo(now); 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_mouseX = event->x();
m_timestamp = now; m_timestamp = now;
@ -114,14 +113,14 @@ QPixmap ScratchWidget::getPixmap(int index)
return *pixmap; 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; qWarning() << index;
return {}; return {};
} }
const auto *begin = m_buffer.constData<frame_t>() + (index*sampleRate); const auto *begin = m_buffer.constData<frame_t>() + (index*m_framesPerBeat);
const auto pixmap = GraphRenderer::render(QSize{theWidth, height()}, begin, begin+sampleRate, palette()); const auto pixmap = GraphRenderer::render(QSize{m_beatWidth, height()}, begin, begin+m_framesPerBeat, palette());
m_graphCache.insert(index, new QPixmap{pixmap}); m_graphCache.insert(index, new QPixmap{pixmap});

View File

@ -6,6 +6,8 @@
#include <QDateTime> #include <QDateTime>
#include <QTimer> #include <QTimer>
#include "audioformat.h"
class ScratchWidget : public QWidget class ScratchWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -16,8 +18,14 @@ public:
const QAudioBuffer &buffer() const { return m_buffer; } const QAudioBuffer &buffer() const { return m_buffer; }
void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache.clear(); repaint(); } void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache.clear(); repaint(); }
std::size_t position() const { return m_position; } std::ptrdiff_t position() const { return m_position; }
void setPosition(std::size_t position) { m_position = position; repaint(); } 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: signals:
void scratchBegin(); void scratchBegin();
@ -37,9 +45,12 @@ private:
QPixmap getPixmap(int index); QPixmap getPixmap(int index);
QAudioBuffer m_buffer; QAudioBuffer m_buffer;
std::size_t m_position{}; std::ptrdiff_t m_position{};
QCache<int, QPixmap> m_graphCache; QCache<int, QPixmap> m_graphCache;
int m_beatWidth{100};
int m_framesPerBeat{frameRate/4};
bool m_scratching{}; bool m_scratching{};
bool m_dragging{}; bool m_dragging{};
int m_mouseX; int m_mouseX;

View File

@ -8,17 +8,19 @@ constexpr double pi = std::acos(-1);
void Synthisizer::writeSamples(frame_t *begin, frame_t *end) 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(begin, end, begin, [&](frame_t frame){
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame), std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame),
[value=std::sin(m_phase)](const sample_t &sample) { return sample + value; }); [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.) if (m_phase >= pi*2.)
m_phase -= pi*2.; m_phase -= pi*2.;
return frame; return frame;
}); });
m_actualFrequency = float(m_actualFrequency+m_frequency)/2.f;
} }
void Synthisizer::messageReceived(const midi::MidiMessage &message) void Synthisizer::messageReceived(const midi::MidiMessage &message)

View File

@ -15,5 +15,6 @@ public:
private: private:
int16_t m_frequency{}; int16_t m_frequency{};
int16_t m_actualFrequency{};
double m_phase{}; double m_phase{};
}; };

View File

@ -9,6 +9,7 @@
#include <QTextStream> #include <QTextStream>
#include <QUrl> #include <QUrl>
#include <QFileInfo> #include <QFileInfo>
#include <QButtonGroup>
#include <QDebug> #include <QDebug>
#include "audiodecoder.h" #include "audiodecoder.h"
@ -20,12 +21,36 @@ TrackDeck::TrackDeck(QWidget *parent) :
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->progressBar->hide(); 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<int>(&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->pushButtonPlay, &QAbstractButton::pressed, &m_player, &AudioPlayer::togglePlaying);
connect(m_ui->pushButtonStop, &QAbstractButton::pressed, &m_player, &AudioPlayer::stop); 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->sliderZoom, &QAbstractSlider::valueChanged, m_ui->scratchWidget, &ScratchWidget::setBeatWidth);
connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
connect(m_ui->horizontalSliderSpeed, &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_ui->previewWidget, &PreviewWidget::positionSelected, &m_player, &AudioPlayer::setPosition);
connect(&m_player, &AudioPlayer::positionChanged, m_ui->previewWidget, &PreviewWidget::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_ui->scratchWidget, &ScratchWidget::scratchEnd, this, &TrackDeck::scratchEnd);
connect(&m_player, &AudioPlayer::positionChanged, m_ui->scratchWidget, &ScratchWidget::setPosition); connect(&m_player, &AudioPlayer::positionChanged, m_ui->scratchWidget, &ScratchWidget::setPosition);
connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); 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; TrackDeck::~TrackDeck() = default;
@ -141,6 +175,12 @@ void TrackDeck::decodingProgress(int progress, int total)
void TrackDeck::decodingFinished(const QAudioBuffer &buffer) void TrackDeck::decodingFinished(const QAudioBuffer &buffer)
{ {
m_player.setBuffer(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->previewWidget->setBuffer(buffer);
m_ui->scratchWidget->setBuffer(buffer); m_ui->scratchWidget->setBuffer(buffer);
m_ui->progressBar->hide(); m_ui->progressBar->hide();
@ -148,8 +188,8 @@ void TrackDeck::decodingFinished(const QAudioBuffer &buffer)
void TrackDeck::scratchBegin() void TrackDeck::scratchBegin()
{ {
disconnect(m_ui->horizontalSliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); disconnect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
m_ui->horizontalSliderSpeed->setEnabled(false); m_ui->sliderSpeed->setEnabled(false);
connect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed); connect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed);
disconnect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); disconnect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText);
@ -169,8 +209,8 @@ void TrackDeck::scratchEnd()
m_player.setStopOnEnd(m_stopOnEndBeforeScratch); m_player.setStopOnEnd(m_stopOnEndBeforeScratch);
disconnect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed); disconnect(m_ui->scratchWidget, &ScratchWidget::scratchSpeed, &m_player, &AudioPlayer::setSpeed);
m_ui->horizontalSliderSpeed->setEnabled(true); m_ui->sliderSpeed->setEnabled(true);
connect(m_ui->horizontalSliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged); connect(m_ui->sliderSpeed, &QAbstractSlider::valueChanged, this, &TrackDeck::speedChanged);
connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText); connect(&m_player, &AudioPlayer::playingChanged, this, &TrackDeck::updatePlayButtonText);
} }
@ -181,5 +221,46 @@ void TrackDeck::speedChanged(int value)
void TrackDeck::updatePlayButtonText(bool playing) 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));
} }

View File

@ -1,8 +1,12 @@
#pragma once #pragma once
#include <QWidget>
#include <memory> #include <memory>
#include <optional>
#include <tuple>
#include <QWidget>
#include <QButtonGroup>
#include <QTimer>
#include "audioformat.h" #include "audioformat.h"
#include "audioplayer.h" #include "audioplayer.h"
@ -40,10 +44,15 @@ private slots:
void scratchEnd(); void scratchEnd();
void speedChanged(int value); void speedChanged(int value);
void updatePlayButtonText(bool playing); void updatePlayButtonText(bool playing);
void bpmTap();
void timeout();
void updatePlaybackBpm();
private: private:
const std::unique_ptr<Ui::TrackDeck> m_ui; const std::unique_ptr<Ui::TrackDeck> m_ui;
QButtonGroup m_loopGroup;
std::unique_ptr<AudioDecoder> m_decoder; std::unique_ptr<AudioDecoder> m_decoder;
AudioPlayer m_player; AudioPlayer m_player;
@ -53,4 +62,8 @@ private:
bool m_playingBeforeScratch; bool m_playingBeforeScratch;
float m_speedBeforeScratch; float m_speedBeforeScratch;
bool m_stopOnEndBeforeScratch; bool m_stopOnEndBeforeScratch;
QTimer m_timer;
std::optional<std::tuple<double, double, int>> m_bpmTap;
}; };

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>752</width>
<height>300</height> <height>500</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -24,21 +24,12 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0"> <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
<item> <item>
<widget class="QLabel" name="labelTitle"> <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
<property name="font"> <item>
<font> <widget class="QLabel" name="labelTitle"/>
<pointsize>20</pointsize> </item>
</font>
</property>
<property name="text">
<string>Track</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progressBar">
<property name="value"> <property name="value">
@ -46,6 +37,42 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButtonBpm">
<property name="enabled">
<bool>false</bool>
</property>
<property name="maximumSize">
<size>
<width>60</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>BPM tap</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="doubleSpinBoxBpm">
<property name="suffix">
<string>BPM</string>
</property>
<property name="minimum">
<double>80.000000000000000</double>
</property>
<property name="maximum">
<double>250.000000000000000</double>
</property>
<property name="value">
<double>130.000000000000000</double>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
@ -60,12 +87,101 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QSlider" name="horizontalSliderSpeed"> <widget class="QPushButton" name="pushButtonLoopOff">
<property name="maximumSize">
<size>
<width>16</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>-</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonLoop1">
<property name="maximumSize">
<size>
<width>16</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>1</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonLoop2">
<property name="maximumSize">
<size>
<width>16</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>2</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonLoop4">
<property name="maximumSize">
<size>
<width>16</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>4</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonLoop8">
<property name="maximumSize">
<size>
<width>16</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>8</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="sliderZoom">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
<property name="minimum"> <property name="minimum">
<number>80</number> <number>25</number>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>120</number> <number>200</number>
</property> </property>
<property name="value"> <property name="value">
<number>100</number> <number>100</number>
@ -77,7 +193,7 @@
<enum>QSlider::TicksBothSides</enum> <enum>QSlider::TicksBothSides</enum>
</property> </property>
<property name="tickInterval"> <property name="tickInterval">
<number>10</number> <number>50</number>
</property> </property>
</widget> </widget>
</item> </item>
@ -110,53 +226,132 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="PreviewWidget" name="previewWidget" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<property name="sizePolicy"> <item>
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> <layout class="QVBoxLayout" name="verticalLayout">
<horstretch>0</horstretch> <item>
<verstretch>0</verstretch> <widget class="PreviewWidget" name="previewWidget" native="true">
</sizepolicy> <property name="sizePolicy">
</property> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<property name="minimumSize"> <horstretch>0</horstretch>
<size> <verstretch>0</verstretch>
<width>0</width> </sizepolicy>
<height>50</height> </property>
</size> <property name="minimumSize">
</property> <size>
<property name="maximumSize"> <width>0</width>
<size> <height>50</height>
<width>16777215</width> </size>
<height>50</height> </property>
</size> <property name="maximumSize">
</property> <size>
</widget> <width>16777215</width>
</item> <height>50</height>
<item> </size>
<widget class="ScratchWidget" name="scratchWidget" native="true"> </property>
<property name="sizePolicy"> </widget>
<sizepolicy hsizetype="Preferred" vsizetype="Fixed"> </item>
<horstretch>0</horstretch> <item>
<verstretch>0</verstretch> <widget class="ScratchWidget" name="scratchWidget" native="true">
</sizepolicy> <property name="sizePolicy">
</property> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<property name="minimumSize"> <horstretch>0</horstretch>
<size> <verstretch>0</verstretch>
<width>0</width> </sizepolicy>
<height>100</height> </property>
</size> <property name="minimumSize">
</property> <size>
<property name="maximumSize"> <width>0</width>
<size> <height>100</height>
<width>16777215</width> </size>
<height>100</height> </property>
</size> <property name="maximumSize">
</property> <size>
</widget> <width>16777215</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,1">
<item>
<widget class="QLabel" name="labelPlaybackBpm">
<property name="font">
<font>
<pointsize>8</pointsize>
</font>
</property>
<property name="text">
<string>100.00</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,1,0">
<property name="spacing">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSlider" name="sliderSpeed">
<property name="minimum">
<number>80</number>
</property>
<property name="maximum">
<number>120</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBothSides</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QSlider" name="verticalSliderVolume"> <widget class="QSlider" name="sliderVolume">
<property name="maximum"> <property name="maximum">
<number>100</number> <number>100</number>
</property> </property>