144 lines
4.5 KiB
C++
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;
|
|
}
|