Added DJ audio track player
This commit is contained in:
@ -14,7 +14,10 @@ SOURCES += \
|
||||
audiodecoder.cpp \
|
||||
audioformat.cpp \
|
||||
audioplayer.cpp \
|
||||
djwidget.cpp \
|
||||
filesmodel.cpp \
|
||||
flatfilesystemmodel.cpp \
|
||||
graphrenderer.cpp \
|
||||
jsonconverters.cpp \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
@ -23,15 +26,22 @@ SOURCES += \
|
||||
presetdetailwidget.cpp \
|
||||
presets.cpp \
|
||||
presetsmodel.cpp \
|
||||
previewwidget.cpp \
|
||||
sampleswidget.cpp \
|
||||
samplewidget.cpp \
|
||||
sequencerwidget.cpp
|
||||
scratchwidget.cpp \
|
||||
sequencerwidget.cpp \
|
||||
synthisizer.cpp \
|
||||
trackdeck.cpp
|
||||
|
||||
HEADERS += \
|
||||
audiodecoder.h \
|
||||
audioformat.h \
|
||||
audioplayer.h \
|
||||
djwidget.h \
|
||||
filesmodel.h \
|
||||
flatfilesystemmodel.h \
|
||||
graphrenderer.h \
|
||||
jsonconverters.h \
|
||||
mainwindow.h \
|
||||
midicontainers.h \
|
||||
@ -39,16 +49,22 @@ HEADERS += \
|
||||
presetdetailwidget.h \
|
||||
presets.h \
|
||||
presetsmodel.h \
|
||||
previewwidget.h \
|
||||
sampleswidget.h \
|
||||
samplewidget.h \
|
||||
sequencerwidget.h
|
||||
scratchwidget.h \
|
||||
sequencerwidget.h \
|
||||
synthisizer.h \
|
||||
trackdeck.h
|
||||
|
||||
FORMS += \
|
||||
djwidget.ui \
|
||||
mainwindow.ui \
|
||||
presetdetailwidget.ui \
|
||||
sampleswidget.ui \
|
||||
samplewidget.ui \
|
||||
sequencerwidget.ui
|
||||
sequencerwidget.ui \
|
||||
trackdeck.ui
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc
|
||||
|
@ -22,10 +22,19 @@ AudioDecoder::AudioDecoder(QObject *parent) :
|
||||
m_decoder.setAudioFormat(audioFormat());
|
||||
}
|
||||
|
||||
void AudioDecoder::startDecoding(std::shared_ptr<QIODevice> device)
|
||||
void AudioDecoder::startDecodingFilename(const QString &filename)
|
||||
{
|
||||
qDebug() << "called" << device.get();
|
||||
if (m_decoder.state() == QAudioDecoder::DecodingState)
|
||||
m_decoder.stop();
|
||||
|
||||
m_decoder.setSourceFilename(filename);
|
||||
m_device = nullptr;
|
||||
m_bytearray.clear();
|
||||
m_decoder.start();
|
||||
}
|
||||
|
||||
void AudioDecoder::startDecodingDevice(std::shared_ptr<QIODevice> device)
|
||||
{
|
||||
if (m_decoder.state() == QAudioDecoder::DecodingState)
|
||||
m_decoder.stop();
|
||||
|
||||
@ -42,7 +51,6 @@ void AudioDecoder::error(const QAudioDecoder::Error error)
|
||||
|
||||
void AudioDecoder::finished()
|
||||
{
|
||||
qDebug() << "called";
|
||||
emit decodingFinished(QAudioBuffer{std::move(m_bytearray), audioFormat()});
|
||||
}
|
||||
|
||||
@ -71,7 +79,6 @@ void AudioDecoder::durationChanged(const qint64 duration)
|
||||
const auto &format = m_decoder.audioFormat();
|
||||
const auto reserve = (format.sampleSize()/8) * format.sampleRate() * format.channelCount() * (duration + 1000) / 1000;
|
||||
m_bytearray.reserve(reserve);
|
||||
qDebug() << "duration:" << duration << reserve;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -22,7 +22,8 @@ signals:
|
||||
void decodingFinished(const QAudioBuffer &buffer);
|
||||
|
||||
public slots:
|
||||
void startDecoding(std::shared_ptr<QIODevice> device);
|
||||
void startDecodingFilename(const QString &filename);
|
||||
void startDecodingDevice(std::shared_ptr<QIODevice> device);
|
||||
|
||||
private slots:
|
||||
void error(const QAudioDecoder::Error error);
|
||||
|
@ -19,7 +19,14 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end)
|
||||
if (!m_playing)
|
||||
return;
|
||||
|
||||
const auto frames = std::min<size_t>(std::distance(begin, end), m_buffer.frameCount()-m_position);
|
||||
// thread safe copies
|
||||
auto position = m_position;
|
||||
const auto speed = m_speed;
|
||||
const auto buffer = m_buffer;
|
||||
const auto volume = m_volume;
|
||||
const auto &data = buffer.constData<frame_t>();
|
||||
|
||||
const auto frames = std::min<size_t>(std::distance(begin, end), buffer.frameCount()-position);
|
||||
|
||||
if (!frames)
|
||||
{
|
||||
@ -28,25 +35,80 @@ void AudioPlayer::writeSamples(frame_t *begin, frame_t *end)
|
||||
return;
|
||||
}
|
||||
|
||||
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(begin+frames), m_buffer.constData<frame_t>() + m_position, begin,
|
||||
[](const frame_t &frame, const frame_t &frame2)->frame_t{
|
||||
frame_t newFrame;
|
||||
std::transform(std::begin(frame), std::end(frame), std::begin(frame2), std::begin(newFrame),
|
||||
[](const sample_t &left, const sample_t &right) { return left + right; });
|
||||
return newFrame;
|
||||
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)
|
||||
return frame;
|
||||
|
||||
const auto index = std::size_t(position);
|
||||
if (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); });
|
||||
|
||||
return frame;
|
||||
});
|
||||
|
||||
m_position += frames;
|
||||
m_position = position;
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
if (m_lastPositionUpdate.isNull() || m_lastPositionUpdate.msecsTo(now) > 100)
|
||||
emit positionChanged(m_position);
|
||||
|
||||
if (ended)
|
||||
emit playingChanged(m_playing = false);
|
||||
}
|
||||
|
||||
void AudioPlayer::setPlaying(bool playing)
|
||||
{
|
||||
m_playing = playing;
|
||||
emit playingChanged(playing);
|
||||
}
|
||||
|
||||
void AudioPlayer::setPosition(double position)
|
||||
{
|
||||
m_position = position;
|
||||
emit positionChanged(position);
|
||||
}
|
||||
|
||||
void AudioPlayer::setSpeed(float speed)
|
||||
{
|
||||
m_speed = speed;
|
||||
emit speedChanged(speed);
|
||||
}
|
||||
|
||||
void AudioPlayer::setVolume(float volume)
|
||||
{
|
||||
m_volume = volume;
|
||||
emit volumeChanged(volume);
|
||||
}
|
||||
|
||||
void AudioPlayer::setBuffer(const QAudioBuffer &buffer)
|
||||
{
|
||||
stop();
|
||||
emit bufferChanged(m_buffer = buffer);
|
||||
}
|
||||
|
||||
void AudioPlayer::togglePlaying()
|
||||
{
|
||||
emit playingChanged(m_playing = !m_playing);
|
||||
}
|
||||
|
||||
void AudioPlayer::restart()
|
||||
{
|
||||
setPosition(0);
|
||||
setPosition(0.);
|
||||
setPlaying(true);
|
||||
}
|
||||
|
||||
void AudioPlayer::stop()
|
||||
{
|
||||
setPosition(0);
|
||||
setPosition(0.);
|
||||
setPlaying(false);
|
||||
}
|
||||
|
@ -20,25 +20,36 @@ public:
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
bool playing() const { return m_playing; }
|
||||
void setPlaying(bool playing) { m_playing = playing; emit playingChanged(playing); }
|
||||
void setPlaying(bool playing);
|
||||
|
||||
std::size_t position() const { return m_position; }
|
||||
void setPosition(std::size_t position) { m_position = position; emit positionChanged(position); }
|
||||
double position() const { return m_position; }
|
||||
void setPosition(double position);
|
||||
|
||||
float speed() const { return m_speed; }
|
||||
void setSpeed(float speed);
|
||||
|
||||
float volume() const { return m_volume; }
|
||||
void setVolume(float volume);
|
||||
|
||||
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||
void setBuffer(const QAudioBuffer &buffer) { emit bufferChanged(m_buffer = buffer); }
|
||||
void setBuffer(const QAudioBuffer &buffer);
|
||||
|
||||
void togglePlaying();
|
||||
void restart();
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void playingChanged(bool playing);
|
||||
void positionChanged(std::size_t position);
|
||||
void positionChanged(double position);
|
||||
void speedChanged(float speed);
|
||||
void volumeChanged(float volume);
|
||||
void bufferChanged(const QAudioBuffer &buffer);
|
||||
|
||||
private:
|
||||
bool m_playing{false};
|
||||
std::size_t m_position{};
|
||||
double m_position{};
|
||||
float m_speed{1.f};
|
||||
float m_volume{1.f};
|
||||
QAudioBuffer m_buffer;
|
||||
|
||||
QDateTime m_lastPositionUpdate;
|
||||
|
83
djwidget.cpp
Normal file
83
djwidget.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "djwidget.h"
|
||||
#include "ui_djwidget.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStandardPaths>
|
||||
|
||||
DjWidget::DjWidget(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::DjWidget>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
const auto createSlot = [this](TrackDeck &trackDeck){
|
||||
return [this,&trackDeck](){
|
||||
const auto index = m_ui->treeViewFiles->currentIndex();
|
||||
if (!index.isValid())
|
||||
return;
|
||||
trackDeck.loadTrack(m_filesModel.filePath(index));
|
||||
};
|
||||
};
|
||||
|
||||
connect(m_ui->pushButtonLoadDeckA, &QAbstractButton::pressed, m_ui->trackDeckA, createSlot(*m_ui->trackDeckA));
|
||||
connect(m_ui->pushButtonLoadDeckB, &QAbstractButton::pressed, m_ui->trackDeckB, createSlot(*m_ui->trackDeckB));
|
||||
|
||||
m_directoryModel.setFilter(QDir::Dirs|QDir::Drives|QDir::NoDotAndDotDot);
|
||||
const auto rootIndex = m_directoryModel.setRootPath(QDir::homePath());
|
||||
m_ui->treeViewDirectories->setModel(&m_directoryModel);
|
||||
m_ui->treeViewDirectories->setRootIndex(rootIndex);
|
||||
for (auto i = 0, count = m_directoryModel.columnCount(); i < count; i++)
|
||||
m_ui->treeViewDirectories->setColumnHidden(i, i!=0);
|
||||
|
||||
connect(m_ui->treeViewDirectories->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DjWidget::directorySelected);
|
||||
|
||||
m_filesModel.setFilter(QDir::AllEntries|QDir::NoDotAndDotDot);
|
||||
m_ui->treeViewFiles->setModel(&m_filesModel);
|
||||
|
||||
if (const auto locations = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); !locations.isEmpty())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
DjWidget::~DjWidget() = default;
|
||||
|
||||
void DjWidget::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
m_ui->trackDeckA->injectDecodingThread(thread);
|
||||
m_ui->trackDeckB->injectDecodingThread(thread);
|
||||
}
|
||||
|
||||
void DjWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
const auto count = std::distance(begin, end);
|
||||
|
||||
for (TrackDeck *trackDeck : {m_ui->trackDeckA, m_ui->trackDeckB})
|
||||
{
|
||||
frame_t buffer[count];
|
||||
std::fill(buffer, buffer+count, frame_t{0.f,0.f});
|
||||
trackDeck->writeSamples(buffer, buffer+count);
|
||||
std::transform(static_cast<const frame_t *>(begin), static_cast<const frame_t *>(end), buffer, begin, [volume=1.f](const frame_t &frame, const frame_t &frame2){
|
||||
frame_t newFrame;
|
||||
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame2), std::begin(newFrame),
|
||||
[&volume](const sample_t &left, const sample_t &right) { return left + (right*volume); });
|
||||
return newFrame;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
33
djwidget.h
Normal file
33
djwidget.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFileSystemModel>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "flatfilesystemmodel.h"
|
||||
|
||||
namespace Ui { class DjWidget; }
|
||||
|
||||
class DjWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DjWidget(QWidget *parent = nullptr);
|
||||
~DjWidget() override;
|
||||
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
private slots:
|
||||
void directorySelected();
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::DjWidget> m_ui;
|
||||
|
||||
QFileSystemModel m_directoryModel;
|
||||
FlatFileSystemModel m_filesModel;
|
||||
};
|
145
djwidget.ui
Normal file
145
djwidget.ui
Normal file
@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DjWidget</class>
|
||||
<widget class="QWidget" name="DjWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>765</width>
|
||||
<height>519</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,1">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,1">
|
||||
<item>
|
||||
<widget class="TrackDeck" name="trackDeckA" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::VLine</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="TrackDeck" name="trackDeckB" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="horizontalSlider">
|
||||
<property name="minimum">
|
||||
<number>-100</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="invertedAppearance">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTreeView" name="treeViewDirectories">
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoadDeckA">
|
||||
<property name="text">
|
||||
<string>Load in Deck A</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonLoadDeckB">
|
||||
<property name="text">
|
||||
<string>Load in Deck B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="treeViewFiles">
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>TrackDeck</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>trackdeck.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
1
flatfilesystemmodel.cpp
Normal file
1
flatfilesystemmodel.cpp
Normal file
@ -0,0 +1 @@
|
||||
#include "flatfilesystemmodel.h"
|
14
flatfilesystemmodel.h
Normal file
14
flatfilesystemmodel.h
Normal file
@ -0,0 +1,14 @@
|
||||
#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;
|
||||
};
|
59
graphrenderer.cpp
Normal file
59
graphrenderer.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#include "graphrenderer.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <QPixmap>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
|
||||
QPixmap GraphRenderer::render(const QSize &size, const frame_t *frameBegin, const frame_t *frameEnd, const QPalette &palette)
|
||||
{
|
||||
QPixmap pixmap{size};
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&pixmap);
|
||||
|
||||
painter.fillRect(pixmap.rect(), palette.base());
|
||||
|
||||
painter.setPen(Qt::white);
|
||||
painter.setBrush(Qt::black);
|
||||
|
||||
painter.drawRect(QRect({}, size));
|
||||
|
||||
render(size, frameBegin, frameEnd, painter, palette);
|
||||
painter.end();
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
void GraphRenderer::render(const QSize &size, const frame_t *frameBegin, const frame_t *frameEnd, QPainter &painter, const QPalette &palette)
|
||||
{
|
||||
if (frameEnd == frameBegin)
|
||||
return;
|
||||
|
||||
painter.setPen(QPen{palette.color(QPalette::Text)});
|
||||
painter.setBrush(palette.text());
|
||||
|
||||
const auto framesPerPixel = std::distance(frameBegin, frameEnd) / size.width();
|
||||
|
||||
for (int x = 0; x < size.width(); x++)
|
||||
{
|
||||
const frame_t *begin = frameBegin + (x * framesPerPixel);
|
||||
const frame_t *end = begin + framesPerPixel;
|
||||
|
||||
frame_t min{1.f, 1.f}, max{-1.f, -1.f};
|
||||
for (auto iter = begin; iter != end; iter++)
|
||||
{
|
||||
if ((*iter)[0] < min[0])
|
||||
min[0] = (*iter)[0];
|
||||
if ((*iter)[1] < min[1])
|
||||
min[1] = (*iter)[1];
|
||||
if ((*iter)[0] > max[0])
|
||||
max[0] = (*iter)[0];
|
||||
if ((*iter)[1] > max[1])
|
||||
max[1] = (*iter)[1];
|
||||
}
|
||||
|
||||
painter.drawLine(x, (size.height() / 2) - (min[0] * (size.height() / 2)),
|
||||
x, (size.height() / 2) + (max[0] * (size.height() / 2)));
|
||||
}
|
||||
}
|
14
graphrenderer.h
Normal file
14
graphrenderer.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
class QPixmap;
|
||||
class QSize;
|
||||
class QPainter;
|
||||
class QPalette;
|
||||
|
||||
#include "audioformat.h"
|
||||
|
||||
namespace GraphRenderer
|
||||
{
|
||||
QPixmap render(const QSize &size, const frame_t *frameBegin, const frame_t *frameEnd, const QPalette &palette);
|
||||
void render(const QSize &size, const frame_t *frameBegin, const frame_t *frameEnd, QPainter &painter, const QPalette &palette);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QMetaEnum>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
@ -53,7 +54,7 @@ int paCallback(const void *inputBuffer, void *outputBuffer,
|
||||
MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *parent) :
|
||||
QMainWindow{parent},
|
||||
m_ui{std::make_unique<Ui::MainWindow>()},
|
||||
m_paStream(nullptr, DummyDeleter),
|
||||
m_paStream{nullptr, DummyDeleter},
|
||||
m_presetsModel{*presetsConfig.presets}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
@ -61,6 +62,16 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
|
||||
|
||||
connect(&m_midiIn, &MidiInWrapper::messageReceived, this, &MainWindow::messageReceived);
|
||||
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_decoderThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
||||
m_decoderThread.start(QThread::HighestPriority);
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
m_ui->samplesWidget->injectDecodingThread(m_decoderThread);
|
||||
m_ui->djWidget->injectDecodingThread(m_decoderThread);
|
||||
|
||||
connect(m_ui->sequencerWidget, &SequencerWidget::triggerSample, m_ui->samplesWidget, &SamplesWidget::sequencerTriggerSample);
|
||||
|
||||
updateMidiDevices();
|
||||
@ -75,6 +86,7 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
|
||||
m_midiIn.openPort(index);
|
||||
}
|
||||
|
||||
m_ui->comboBoxMidiController->setDisabled(m_midiIn.isPortOpen());
|
||||
m_ui->pushButtonMidiController->setText(m_midiIn.isPortOpen() ? tr("Close") : tr("Open"));
|
||||
});
|
||||
|
||||
@ -99,7 +111,11 @@ MainWindow::MainWindow(const presets::PresetsConfig &presetsConfig, QWidget *par
|
||||
connect(m_ui->presetsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &MainWindow::currentRowChanged);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
m_decoderThread.exit();
|
||||
m_decoderThread.wait();
|
||||
}
|
||||
|
||||
void MainWindow::selectFirstPreset()
|
||||
{
|
||||
@ -119,6 +135,15 @@ void MainWindow::writeSamples(frame_t *begin, frame_t *end)
|
||||
std::fill(begin, end, frame_t{0.,0.});
|
||||
|
||||
m_ui->samplesWidget->writeSamples(begin, end);
|
||||
m_ui->djWidget->writeSamples(begin, end);
|
||||
m_synthisizer.writeSamples(begin, end);
|
||||
|
||||
std::transform(begin, end, begin, [factor=float(m_ui->horizontalSliderMaster->value())/100.f](frame_t frame){
|
||||
std::transform(std::cbegin(frame), std::cend(frame), std::begin(frame), [&factor](const sample_t &sample){
|
||||
return sample*factor;
|
||||
});
|
||||
return frame;
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::openAudioDevice()
|
||||
@ -126,6 +151,8 @@ void MainWindow::openAudioDevice()
|
||||
if (m_paStream)
|
||||
{
|
||||
m_paStream = nullptr;
|
||||
m_ui->comboBoxAudioDevice->setEnabled(true);
|
||||
m_ui->spinBoxBufferSize->setEnabled(true);
|
||||
m_ui->pushButtonAudioDevice->setText(tr("Open"));
|
||||
}
|
||||
else
|
||||
@ -169,6 +196,8 @@ void MainWindow::openAudioDevice()
|
||||
smartPtr.get_deleter() = PaStreamStopperAndCloser;
|
||||
|
||||
m_paStream = std::move(smartPtr);
|
||||
m_ui->comboBoxAudioDevice->setEnabled(false);
|
||||
m_ui->spinBoxBufferSize->setEnabled(false);
|
||||
m_ui->pushButtonAudioDevice->setText(tr("Close"));
|
||||
}
|
||||
}
|
||||
@ -179,7 +208,10 @@ void MainWindow::messageReceived(const midi::MidiMessage &message)
|
||||
.arg(message.flag?"true":"false", QMetaEnum::fromType<midi::Command>().valueToKey(int(message.cmd)))
|
||||
.arg(message.channel).arg(message.note).arg(message.velocity), 1000);
|
||||
|
||||
m_ui->samplesWidget->messageReceived(message);
|
||||
if (m_ui->comboBoxMidiType->currentIndex() == 0)
|
||||
m_ui->samplesWidget->messageReceived(message);
|
||||
else if (m_ui->comboBoxMidiType->currentIndex() == 1)
|
||||
m_synthisizer.messageReceived(message);
|
||||
}
|
||||
|
||||
void MainWindow::currentRowChanged(const QModelIndex ¤t)
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QThread>
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
@ -11,6 +12,7 @@
|
||||
#include "presetsmodel.h"
|
||||
#include "filesmodel.h"
|
||||
#include "midiinwrapper.h"
|
||||
#include "synthisizer.h"
|
||||
|
||||
namespace Ui { class MainWindow; }
|
||||
namespace presets { struct PresetsConfig; }
|
||||
@ -43,6 +45,10 @@ private:
|
||||
|
||||
MidiInWrapper m_midiIn;
|
||||
|
||||
QThread m_decoderThread;
|
||||
|
||||
Synthisizer m_synthisizer;
|
||||
|
||||
PresetsModel m_presetsModel;
|
||||
QSortFilterProxyModel m_presetsProxyModel;
|
||||
|
||||
|
@ -101,6 +101,19 @@
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxMidiController"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonRefreshMidiControllers">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>↻</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonMidiController">
|
||||
<property name="text">
|
||||
@ -108,6 +121,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxMidiType">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Samples</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Synthisizer</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
@ -124,6 +151,19 @@
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxAudioDevice"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonRefreshAudioDevices">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>↻</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxBufferSize">
|
||||
<property name="maximum">
|
||||
@ -186,28 +226,38 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="presetsView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QTreeView" name="presetsView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="PresetDetailWidget" name="presetDetailWidget">
|
||||
<attribute name="title">
|
||||
<string/>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QTreeView" name="filesView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="title">
|
||||
<string/>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QSplitter" name="splitter_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="PresetDetailWidget" name="presetDetailWidget"/>
|
||||
<widget class="QTreeView" name="filesView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="DjWidget" name="djWidget" native="true"/>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" stretch="1,0">
|
||||
@ -254,6 +304,12 @@
|
||||
<header>sequencerwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DjWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>djwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -12,5 +12,5 @@ PresetDetailWidget::~PresetDetailWidget() = default;
|
||||
|
||||
void PresetDetailWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
88
previewwidget.cpp
Normal file
88
previewwidget.cpp
Normal file
@ -0,0 +1,88 @@
|
||||
#include "previewwidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include "graphrenderer.h"
|
||||
|
||||
PreviewWidget::PreviewWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void PreviewWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
if (m_graphCache.isNull() || m_graphCache.size() != size())
|
||||
{
|
||||
m_graphCache = QPixmap{size()};
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&m_graphCache);
|
||||
|
||||
painter.setBrush(palette().base());
|
||||
|
||||
painter.drawRect(m_graphCache.rect());
|
||||
|
||||
if (m_buffer.isValid())
|
||||
GraphRenderer::render(size(), m_buffer.constData<frame_t>(), m_buffer.constData<frame_t>() + m_buffer.frameCount(), painter, palette());
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(this);
|
||||
|
||||
painter.drawPixmap(0, 0, m_graphCache);
|
||||
|
||||
if (m_buffer.frameCount())
|
||||
{
|
||||
std::size_t currentSample = double(m_position) / m_buffer.frameCount() * width();
|
||||
|
||||
QPen pen(Qt::red);
|
||||
pen.setWidth(3);
|
||||
painter.setPen(pen);
|
||||
painter.drawLine(currentSample, 0, currentSample, height());
|
||||
}
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void PreviewWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
setMouseTracking(true);
|
||||
clicked(event->x(), event->y());
|
||||
}
|
||||
|
||||
QWidget::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
setMouseTracking(false);
|
||||
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::LeftButton))
|
||||
clicked(event->x(), event->y());
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void PreviewWidget::clicked(int x, int y)
|
||||
{
|
||||
if (!m_buffer.isValid())
|
||||
return;
|
||||
|
||||
const auto samplesPerPixel = m_buffer.frameCount() / width();
|
||||
|
||||
emit positionSelected(x * samplesPerPixel);
|
||||
}
|
35
previewwidget.h
Normal file
35
previewwidget.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPixmap>
|
||||
#include <QAudioBuffer>
|
||||
|
||||
class PreviewWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PreviewWidget(QWidget *parent = nullptr);
|
||||
|
||||
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||
void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache = {}; repaint(); }
|
||||
|
||||
double position() const { return m_position; }
|
||||
void setPosition(double position) { m_position = position; repaint(); }
|
||||
|
||||
signals:
|
||||
void positionSelected(double sample);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void clicked(int x, int y);
|
||||
|
||||
QAudioBuffer m_buffer;
|
||||
double m_position{};
|
||||
QPixmap m_graphCache;
|
||||
};
|
@ -16,13 +16,6 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
||||
m_cache.setCacheDirectory("cache");
|
||||
m_networkAccessManager.setCache(&m_cache);
|
||||
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
connect(&m_decoderThread, &QThread::started, &eventLoop, &QEventLoop::quit);
|
||||
m_decoderThread.start(QThread::HighestPriority);
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
connect(m_ui->checkBox, &QCheckBox::toggled, this, &SamplesWidget::updateWidgets);
|
||||
|
||||
connect(m_ui->pushButtonStopAll, &QAbstractButton::pressed, this, &SamplesWidget::stopAll);
|
||||
@ -30,7 +23,6 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
||||
for (const auto &ref : getWidgets())
|
||||
{
|
||||
ref.get().injectNetworkAccessManager(m_networkAccessManager);
|
||||
ref.get().injectDecodingThread(m_decoderThread);
|
||||
connect(&ref.get(), &SampleWidget::chokeTriggered, this, &SamplesWidget::chokeTriggered);
|
||||
}
|
||||
|
||||
@ -52,11 +44,7 @@ SamplesWidget::SamplesWidget(QWidget *parent) :
|
||||
m_ui->sampleWidget_24->setNote(72);
|
||||
}
|
||||
|
||||
SamplesWidget::~SamplesWidget()
|
||||
{
|
||||
m_decoderThread.exit();
|
||||
m_decoderThread.wait();
|
||||
}
|
||||
SamplesWidget::~SamplesWidget() = default;
|
||||
|
||||
void SamplesWidget::setPreset(const presets::Preset &preset)
|
||||
{
|
||||
@ -94,6 +82,12 @@ void SamplesWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
ref.get().writeSamples(begin, end);
|
||||
}
|
||||
|
||||
void SamplesWidget::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
for (const auto &ref : getWidgets())
|
||||
ref.get().injectDecodingThread(thread);
|
||||
}
|
||||
|
||||
void SamplesWidget::sequencerTriggerSample(int index)
|
||||
{
|
||||
const auto widgets = getWidgets();
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <QWidget>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QThread>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "presets.h"
|
||||
@ -30,6 +29,8 @@ public:
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
public slots:
|
||||
void sequencerTriggerSample(int index);
|
||||
|
||||
@ -46,7 +47,5 @@ private:
|
||||
QNetworkDiskCache m_cache;
|
||||
QNetworkAccessManager m_networkAccessManager;
|
||||
|
||||
QThread m_decoderThread;
|
||||
|
||||
presets::Preset m_preset;
|
||||
};
|
||||
|
@ -23,6 +23,9 @@ SampleWidget::SampleWidget(QWidget *parent) :
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->dialSpeed, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setSpeed(float(value)/100.f); });
|
||||
connect(m_ui->dialVolume, &QAbstractSlider::valueChanged, &m_player, [&player=m_player](int value){ player.setVolume(float(value)/100.f); });
|
||||
|
||||
connect(&m_player, &AudioPlayer::playingChanged, this, &SampleWidget::updateStatus);
|
||||
|
||||
connect(m_ui->pushButton, &QAbstractButton::pressed, this, [this](){ pressed(127); });
|
||||
@ -38,6 +41,8 @@ void SampleWidget::setFile(const QString &presetId, const presets::File &file)
|
||||
m_presetId = presetId;
|
||||
m_file = file;
|
||||
|
||||
m_player.setBuffer({});
|
||||
|
||||
startRequest();
|
||||
|
||||
const auto setupLabel = [&](const auto &value, QLabel *label){
|
||||
@ -84,6 +89,26 @@ void SampleWidget::setNote(quint8 note)
|
||||
m_ui->noteSpinBox->setValue(note);
|
||||
}
|
||||
|
||||
int SampleWidget::speed() const
|
||||
{
|
||||
return m_ui->dialSpeed->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setSpeed(int speed)
|
||||
{
|
||||
m_ui->dialSpeed->setValue(speed);
|
||||
}
|
||||
|
||||
int SampleWidget::volume() const
|
||||
{
|
||||
return m_ui->dialVolume->value();
|
||||
}
|
||||
|
||||
void SampleWidget::setVolume(int volume)
|
||||
{
|
||||
m_ui->dialVolume->setValue(volume);
|
||||
}
|
||||
|
||||
std::optional<int> SampleWidget::choke() const
|
||||
{
|
||||
if (!m_file)
|
||||
@ -121,10 +146,10 @@ void SampleWidget::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){
|
||||
m_decoder = std::make_unique<AudioDecoder>();
|
||||
connect(this, &SampleWidget::startDecoding, m_decoder.get(), &AudioDecoder::startDecoding);
|
||||
connect(this, &SampleWidget::startDecoding, m_decoder.get(), &AudioDecoder::startDecodingDevice);
|
||||
connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &SampleWidget::decodingFinished);
|
||||
if (m_reply && m_reply->isFinished() && m_reply->error() == QNetworkReply::NoError)
|
||||
m_decoder->startDecoding(m_reply);
|
||||
m_decoder->startDecodingDevice(m_reply);
|
||||
});
|
||||
}
|
||||
|
||||
@ -136,7 +161,7 @@ void SampleWidget::writeSamples(frame_t *begin, frame_t *end)
|
||||
void SampleWidget::updateStatus()
|
||||
{
|
||||
QPalette pal;
|
||||
if (m_file && m_file->color)
|
||||
if (m_file && m_file->color && m_player.buffer().isValid())
|
||||
{
|
||||
const auto bright = m_player.playing() ? 255 : 155;
|
||||
const auto dark = m_player.playing() ?
|
||||
@ -183,7 +208,6 @@ void SampleWidget::updateStatus()
|
||||
|
||||
void SampleWidget::requestFinished()
|
||||
{
|
||||
qDebug() << "called" << m_reply->error() << m_reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute);
|
||||
if (m_reply->error() == QNetworkReply::NoError)
|
||||
{
|
||||
emit startDecoding(m_reply);
|
||||
@ -193,9 +217,10 @@ void SampleWidget::requestFinished()
|
||||
|
||||
void SampleWidget::decodingFinished(const QAudioBuffer &buffer)
|
||||
{
|
||||
qDebug() << "called";
|
||||
m_reply = nullptr;
|
||||
m_player.setBuffer(buffer);
|
||||
setSpeed(100);
|
||||
setVolume(100);
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,12 @@ public:
|
||||
quint8 note() const;
|
||||
void setNote(quint8 note);
|
||||
|
||||
int speed() const;
|
||||
void setSpeed(int speed);
|
||||
|
||||
int volume() const;
|
||||
void setVolume(int volume);
|
||||
|
||||
std::optional<int> choke() const;
|
||||
|
||||
void pressed(quint8 velocity);
|
||||
|
@ -24,7 +24,7 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<property name="text">
|
||||
@ -32,21 +32,65 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="dialSpeed">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>80</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>120</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="notchTarget">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="notchesVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="dialVolume">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="notchTarget">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
<property name="notchesVisible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
|
45
scratchwidget.cpp
Normal file
45
scratchwidget.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "scratchwidget.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "graphrenderer.h"
|
||||
|
||||
ScratchWidget::ScratchWidget(QWidget *parent) :
|
||||
QWidget{parent}
|
||||
{
|
||||
}
|
||||
|
||||
void ScratchWidget::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QPainter painter;
|
||||
painter.begin(this);
|
||||
|
||||
painter.setPen({});
|
||||
painter.setBrush(palette().window());
|
||||
painter.drawRect(rect());
|
||||
|
||||
{
|
||||
QPen pen{Qt::red};
|
||||
pen.setWidth(3);
|
||||
painter.setPen(pen);
|
||||
}
|
||||
|
||||
painter.drawLine(width()/2, 0, width()/2, height());
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
void ScratchWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ScratchWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ScratchWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
|
||||
}
|
31
scratchwidget.h
Normal file
31
scratchwidget.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QAudioBuffer>
|
||||
|
||||
class ScratchWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ScratchWidget(QWidget *parent = nullptr);
|
||||
|
||||
const QAudioBuffer &buffer() const { return m_buffer; }
|
||||
void setBuffer(const QAudioBuffer &buffer) { m_buffer = buffer; m_graphCache.clear(); repaint(); }
|
||||
|
||||
double position() const { return m_position; }
|
||||
void setPosition(double position) { m_position = position; repaint(); }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
QAudioBuffer m_buffer;
|
||||
double m_position{};
|
||||
std::vector<QPixmap> m_graphCache;
|
||||
};
|
33
synthisizer.cpp
Normal file
33
synthisizer.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "synthisizer.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "midicontainers.h"
|
||||
|
||||
constexpr double pi = std::acos(-1);
|
||||
|
||||
void Synthisizer::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
if (m_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;
|
||||
if (m_phase >= pi*2.)
|
||||
m_phase -= pi*2.;
|
||||
|
||||
return frame;
|
||||
});
|
||||
}
|
||||
|
||||
void Synthisizer::messageReceived(const midi::MidiMessage &message)
|
||||
{
|
||||
if (message.cmd == midi::Command::NoteOn)
|
||||
m_frequency = 440.*std::pow(std::pow(2., 1./12.), message.note-48);
|
||||
else if (message.cmd == midi::Command::NoteOff)
|
||||
{
|
||||
if (m_frequency == int16_t(440.*std::pow(std::pow(2., 1./12.), message.note-48)))
|
||||
m_frequency = 0;
|
||||
}
|
||||
}
|
19
synthisizer.h
Normal file
19
synthisizer.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "audioformat.h"
|
||||
|
||||
namespace midi { class MidiMessage; }
|
||||
|
||||
class Synthisizer
|
||||
{
|
||||
public:
|
||||
void setFrequency(int16_t frequency) { m_frequency = frequency; }
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
void messageReceived(const midi::MidiMessage &message);
|
||||
|
||||
private:
|
||||
int16_t m_frequency{};
|
||||
double m_phase{};
|
||||
};
|
145
trackdeck.cpp
Normal file
145
trackdeck.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
#include "trackdeck.h"
|
||||
#include "ui_trackdeck.h"
|
||||
|
||||
#include <QAbstractEventDispatcher>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDragLeaveEvent>
|
||||
#include <QDropEvent>
|
||||
#include <QMimeData>
|
||||
#include <QTextStream>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
|
||||
#include "audiodecoder.h"
|
||||
|
||||
TrackDeck::TrackDeck(QWidget *parent) :
|
||||
QWidget{parent},
|
||||
m_ui{std::make_unique<Ui::TrackDeck>()}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_ui->progressBar->hide();
|
||||
|
||||
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, &m_player, [&player=m_player](int value){ player.setSpeed(float(value)/100.f); });
|
||||
|
||||
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->scratchWidget, &ScratchWidget::setPosition);
|
||||
connect(&m_player, &AudioPlayer::playingChanged, m_ui->pushButtonPlay, [&button=*m_ui->pushButtonPlay](bool playing){ button.setText(playing ? tr("▮▮") : tr("▶")); });
|
||||
}
|
||||
|
||||
TrackDeck::~TrackDeck() = default;
|
||||
|
||||
void TrackDeck::loadTrack(const QString &filename)
|
||||
{
|
||||
m_filename = filename;
|
||||
emit startDecoding(filename);
|
||||
m_ui->progressBar->show();
|
||||
m_ui->progressBar->setValue(0);
|
||||
}
|
||||
|
||||
void TrackDeck::injectDecodingThread(QThread &thread)
|
||||
{
|
||||
QMetaObject::invokeMethod(QAbstractEventDispatcher::instance(&thread), [this](){
|
||||
m_decoder = std::make_unique<AudioDecoder>();
|
||||
connect(this, &TrackDeck::startDecoding, m_decoder.get(), &AudioDecoder::startDecodingFilename);
|
||||
connect(m_decoder.get(), &AudioDecoder::progress, this, &TrackDeck::decodingProgress);
|
||||
connect(m_decoder.get(), &AudioDecoder::decodingFinished, this, &TrackDeck::decodingFinished);
|
||||
if (!m_filename.isEmpty())
|
||||
m_decoder->startDecodingFilename(m_filename);
|
||||
});
|
||||
}
|
||||
|
||||
void TrackDeck::writeSamples(frame_t *begin, frame_t *end)
|
||||
{
|
||||
m_player.writeSamples(begin, end);
|
||||
}
|
||||
|
||||
void TrackDeck::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (!event->mimeData()->hasFormat("text/uri-list"))
|
||||
return;
|
||||
|
||||
auto data = event->mimeData()->data("text/uri-list");
|
||||
|
||||
QTextStream textStream(data, QIODevice::ReadOnly | QIODevice::Text);
|
||||
if (textStream.atEnd())
|
||||
return;
|
||||
|
||||
const QUrl url(textStream.readLine());
|
||||
if (!url.isLocalFile())
|
||||
return;
|
||||
|
||||
const QFileInfo fileInfo(url.toLocalFile());
|
||||
if (!fileInfo.exists())
|
||||
return;
|
||||
|
||||
if (!fileInfo.isFile())
|
||||
return;
|
||||
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void TrackDeck::dragLeaveEvent(QDragLeaveEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
}
|
||||
|
||||
void TrackDeck::dropEvent(QDropEvent *event)
|
||||
{
|
||||
if (!event->mimeData()->hasFormat("text/uri-list"))
|
||||
{
|
||||
qWarning() << "wrong type";
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = event->mimeData()->data("text/uri-list");
|
||||
|
||||
QTextStream textStream(data, QIODevice::ReadOnly | QIODevice::Text);
|
||||
if (textStream.atEnd())
|
||||
{
|
||||
qWarning() << "no lines";
|
||||
return;
|
||||
}
|
||||
|
||||
const QUrl url(textStream.readLine());
|
||||
if (!url.isLocalFile())
|
||||
{
|
||||
qWarning() << "isnt local file";
|
||||
return;
|
||||
}
|
||||
|
||||
const QFileInfo fileInfo(url.toLocalFile());
|
||||
if (!fileInfo.exists())
|
||||
{
|
||||
qWarning() << "doesnt exist";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileInfo.isFile())
|
||||
{
|
||||
qWarning() << "isnt file";
|
||||
return;
|
||||
}
|
||||
|
||||
loadTrack(url.toLocalFile());
|
||||
}
|
||||
|
||||
void TrackDeck::decodingProgress(int progress, int total)
|
||||
{
|
||||
m_ui->progressBar->setMaximum(total);
|
||||
m_ui->progressBar->setValue(progress);
|
||||
}
|
||||
|
||||
void TrackDeck::decodingFinished(const QAudioBuffer &buffer)
|
||||
{
|
||||
m_player.setBuffer(buffer);
|
||||
m_ui->previewWidget->setBuffer(buffer);
|
||||
m_ui->scratchWidget->setBuffer(buffer);
|
||||
m_ui->progressBar->hide();
|
||||
}
|
48
trackdeck.h
Normal file
48
trackdeck.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "audioformat.h"
|
||||
#include "audioplayer.h"
|
||||
|
||||
namespace Ui { class TrackDeck; }
|
||||
class AudioDecoder;
|
||||
class QAudioBuffer;
|
||||
|
||||
class TrackDeck : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrackDeck(QWidget *parent = nullptr);
|
||||
~TrackDeck() override;
|
||||
|
||||
void loadTrack(const QString &filename);
|
||||
|
||||
void injectDecodingThread(QThread &thread);
|
||||
|
||||
void writeSamples(frame_t *begin, frame_t *end);
|
||||
|
||||
signals:
|
||||
void startDecoding(const QString &filename);
|
||||
|
||||
protected:
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void decodingProgress(int progress, int total);
|
||||
void decodingFinished(const QAudioBuffer &buffer);
|
||||
|
||||
private:
|
||||
const std::unique_ptr<Ui::TrackDeck> m_ui;
|
||||
|
||||
std::unique_ptr<AudioDecoder> m_decoder;
|
||||
|
||||
AudioPlayer m_player;
|
||||
|
||||
QString m_filename;
|
||||
};
|
195
trackdeck.ui
Normal file
195
trackdeck.ui
Normal file
@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TrackDeck</class>
|
||||
<widget class="QWidget" name="TrackDeck">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<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">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>24</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="horizontalSliderSpeed">
|
||||
<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::Horizontal</enum>
|
||||
</property>
|
||||
<property name="tickPosition">
|
||||
<enum>QSlider::TicksBothSides</enum>
|
||||
</property>
|
||||
<property name="tickInterval">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonPlay">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>▶</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonStop">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>◼</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</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>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="verticalSliderVolume">
|
||||
<property name="maximum">
|
||||
<number>100</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>25</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PreviewWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>previewwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ScratchWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>scratchwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Reference in New Issue
Block a user