Implemented basic BPM mechanism
This commit is contained in:
@ -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 \
|
||||
|
@ -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);
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
#include <QAudioFormat>
|
||||
|
||||
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";
|
||||
|
@ -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<frame_t>();
|
||||
const auto loop = m_loop;
|
||||
|
||||
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{};
|
||||
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(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);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QAudioBuffer>
|
||||
@ -37,6 +38,9 @@ public:
|
||||
const QAudioBuffer &buffer() const { return m_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 restart();
|
||||
void stop();
|
||||
@ -56,6 +60,7 @@ private:
|
||||
float m_volume{1.f};
|
||||
bool m_stopOnEnd{true};
|
||||
QAudioBuffer m_buffer;
|
||||
std::optional<std::pair<double, double>> m_loop;
|
||||
|
||||
QDateTime m_lastPositionUpdate;
|
||||
};
|
||||
|
10
djwidget.cpp
10
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)));
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "flatfilesystemmodel.h"
|
||||
|
||||
namespace Ui { class DjWidget; }
|
||||
|
||||
@ -29,5 +28,5 @@ private:
|
||||
const std::unique_ptr<Ui::DjWidget> m_ui;
|
||||
|
||||
QFileSystemModel m_directoryModel;
|
||||
FlatFileSystemModel m_filesModel;
|
||||
QFileSystemModel m_filesModel;
|
||||
};
|
||||
|
@ -124,6 +124,12 @@
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="itemsExpandable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -1 +0,0 @@
|
||||
#include "flatfilesystemmodel.h"
|
@ -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;
|
||||
};
|
@ -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;
|
||||
|
@ -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<frame_t>() + (index*sampleRate);
|
||||
const auto pixmap = GraphRenderer::render(QSize{theWidth, height()}, begin, begin+sampleRate, palette());
|
||||
const auto *begin = m_buffer.constData<frame_t>() + (index*m_framesPerBeat);
|
||||
const auto pixmap = GraphRenderer::render(QSize{m_beatWidth, height()}, begin, begin+m_framesPerBeat, palette());
|
||||
|
||||
m_graphCache.insert(index, new QPixmap{pixmap});
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#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<int, QPixmap> m_graphCache;
|
||||
|
||||
int m_beatWidth{100};
|
||||
int m_framesPerBeat{frameRate/4};
|
||||
|
||||
bool m_scratching{};
|
||||
bool m_dragging{};
|
||||
int m_mouseX;
|
||||
|
@ -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)
|
||||
|
@ -15,5 +15,6 @@ public:
|
||||
|
||||
private:
|
||||
int16_t m_frequency{};
|
||||
int16_t m_actualFrequency{};
|
||||
double m_phase{};
|
||||
};
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QButtonGroup>
|
||||
#include <QDebug>
|
||||
|
||||
#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<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->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));
|
||||
}
|
||||
|
17
trackdeck.h
17
trackdeck.h
@ -1,8 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QButtonGroup>
|
||||
#include <QTimer>
|
||||
|
||||
#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<Ui::TrackDeck> m_ui;
|
||||
|
||||
QButtonGroup m_loopGroup;
|
||||
|
||||
std::unique_ptr<AudioDecoder> m_decoder;
|
||||
|
||||
AudioPlayer m_player;
|
||||
@ -53,4 +62,8 @@ private:
|
||||
bool m_playingBeforeScratch;
|
||||
float m_speedBeforeScratch;
|
||||
bool m_stopOnEndBeforeScratch;
|
||||
|
||||
QTimer m_timer;
|
||||
|
||||
std::optional<std::tuple<double, double, int>> m_bpmTap;
|
||||
};
|
||||
|
321
trackdeck.ui
321
trackdeck.ui
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<width>752</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -24,21 +24,12 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelTitle">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Track</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelTitle"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
@ -46,6 +37,42 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
@ -60,12 +87,101 @@
|
||||
</spacer>
|
||||
</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">
|
||||
<number>80</number>
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>120</number>
|
||||
<number>200</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
@ -77,7 +193,7 @@
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -110,53 +226,132 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PreviewWidget" name="previewWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScratchWidget" name="scratchWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="PreviewWidget" name="previewWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>50</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ScratchWidget" name="scratchWidget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>100</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="verticalSliderVolume">
|
||||
<widget class="QSlider" name="sliderVolume">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
|
Reference in New Issue
Block a user