xybrid/xybrid/audio/audioengine.h

144 lines
4.5 KiB
C++

#pragma once
#include <memory>
#include <list>
#include <vector>
#include <deque>
#include <unordered_map>
#include <atomic>
#include <array>
#include <QIODevice>
#include <QAudioOutput>
#include <QSemaphore>
#include <QWaitCondition>
class QThread;
namespace Xybrid::Data {
class Project;
class Pattern;
class Node;
}
namespace Xybrid::Audio {
class AudioWorkerCore {
protected:
__attribute__((always_inline)) inline void processQueue();
};
class AudioWorker;
template <typename T> struct PointerCompare {
bool operator()(T* a, T* b) const { return *a == *b; }
//size_t operator()(T* a) const { return std::hash<T>()(*a); }
size_t operator()(T* a) const { return qHash(*a); }
};
struct NoteInfo {
bool valid = false;
uint8_t port = 0;
uint16_t noteId = 0;
NoteInfo() = default;
NoteInfo(uint8_t p, uint16_t nId) { valid = true; port = p; noteId = nId; }
};
class AudioEngine : public QIODevice, private AudioWorkerCore {
friend class AudioWorker;
friend class AudioWorkerCore;
Q_OBJECT
explicit AudioEngine(QObject *parent = nullptr);
public:
enum PlaybackMode : uint8_t {
Stopped, // stopped
Playing, // playing track
Paused, // paused during playback
Previewing, // instrument live preview
Rendering, // rendering to file
};
private:
QThread* thread;
std::unique_ptr<QAudioOutput> output;
int sampleRate = 48000;
std::vector<float> buffer[2];
size_t bufPos = 0;
static const constexpr size_t tickBufSize = (1024*1024*5); // 5mb should be enough
std::unique_ptr<size_t[]> tickBuf;
std::atomic<size_t*> tickBufPtr;
size_t* tickBufEnd;
PlaybackMode mode = Stopped;
size_t tickId = 0;
std::shared_ptr<Data::Project> project;
std::vector<AudioWorker*> workers;
QSemaphore wsem;
std::vector<std::shared_ptr<Data::Node>> queue;
std::atomic_size_t queueIndex;
bool queueValid;
void buildQueue();
std::array<uint16_t, 256> portLastNoteId;
std::vector<NoteInfo> chTrack;
std::vector<NoteInfo> noteEndQueue;
std::unordered_map<QString*, NoteInfo, PointerCompare<QString>, PointerCompare<QString>> nameTrack;
std::vector<uint8_t> buf; /// preallocated buffer for building commands
uint8_t previewPort_ = 0;
uint16_t previewNote_ = 0;
Data::Node* previewNode = nullptr;
// playback timing and position
double tempo = 140.0;
int seqPos;
int curRow;
int curTick;
double tickAcc; // accumulator for tick remainder
void postInit();
void initAudio(bool startNow = false);
void deinitAudio();
Data::Pattern* findPattern(int = 0);
void nextTick();
void processNodes();
public:
static void init();
inline constexpr PlaybackMode playbackMode() const { return mode; }
inline constexpr const std::shared_ptr<Data::Project>& playingProject() const { return project; }
void play(std::shared_ptr<Data::Project>, int fromPos = -1);
void stop();
uint16_t preview(std::shared_ptr<Data::Project>, int16_t port, int16_t note, uint16_t nId = 0, Data::Node* node = nullptr);
void render(std::shared_ptr<Data::Project>, QString);
inline uint8_t previewPort() const { return previewPort_; }
inline void invalidateQueue(Data::Project* p) { if (p == project.get()) queueValid = false; }
void* tickAlloc(size_t size);
inline size_t curTickId() const { return tickId; }
inline size_t curTickSize() const { return buffer[0].size(); }
inline int curSampleRate() const { return sampleRate; }
inline double curTempo() const { return tempo; }
// QIODevice functions
qint64 readData(char* data, qint64 maxlen) override;
qint64 writeData(const char*, qint64) override { return 0; }
qint64 bytesAvailable() const override { return 0; } // not actually used by QAudioOutput
signals:
void playbackModeChanged();
public slots:
};
class AudioWorker : public QObject, private AudioWorkerCore {
friend class AudioEngine;
Q_OBJECT
explicit AudioWorker(QObject* parent = nullptr);
QThread* thread;
QSemaphore signal;
void invoke();
[[noreturn]] void processLoop();
};
extern AudioEngine* audioEngine;
}