xybrid/xybrid/data/sample.cpp

184 lines
5.6 KiB
C++

#include "sample.h"
using namespace Xybrid::Data;
#include "data/project.h"
#include <deque>
#include <QCborValue>
#include <QCborMap>
#include <QCborArray>
#include <QFileInfo>
#include <QAudioDecoder>
#include <QAudioFormat>
#include <QEventLoop>
#define qs QStringLiteral
std::array<float, 2> Sample::plotBetween(size_t ch, size_t start, size_t end) const {
if (end < start) end = start;
if (ch >= 2 || start >= data[ch].size()) return {0, 0};
end = std::min(end, data[ch].size());
float mx = -100;
float mn = 100;
for (size_t i = start; i <= end; i++) {
auto v = data[ch][i];
mx = std::max(mx, v);
mn = std::min(mn, v);
}
return {mn, mx};
}
QCborMap Sample::toCbor() const {
QCborMap m;
m[qs("name")] = name;
m[qs("rate")] = sampleRate;
{
QCborArray ch;
auto n = static_cast<size_t>(numChannels());
for (size_t i = 0; i < n; i++) {
ch[static_cast<qsizetype>(i)] = QByteArray(reinterpret_cast<const char*>(data[i].data()), static_cast<int>(data[i].size() * sizeof(data[i][0])));
}
m[qs("channels")] = ch;
}
return m;
}
std::shared_ptr<Sample> Sample::fromCbor(const QCborMap& m, QUuid uuid) {
auto smp = std::make_shared<Sample>();
smp->uuid = uuid;
smp->name = m.value("name").toString();
smp->sampleRate = static_cast<int>(m.value("rate").toInteger(48000));
auto ch = m.value("channels").toArray();
auto s = static_cast<size_t>(ch.size());
for (size_t i = 0; i < s; i++) {
auto c = ch[static_cast<qint64>(i)].toByteArray();
auto bs = static_cast<size_t>(c.size());
smp->data[i].resize(bs / sizeof(*smp->data[i].begin()));
memcpy(smp->data[i].data(), c.constData(), bs);
}
return smp;
}
std::shared_ptr<Sample> Sample::fromCbor(const QCborValue& m, QUuid uuid) { return fromCbor(m.toMap(), uuid); }
bool Sample::changeUuid(QUuid newUuid) {
if (!project) return false;
//if (!project->samples.contains(uuid)) return false;
if (project->samples.contains(newUuid)) return false;
auto ptr = this->shared_from_this();
project->samples.remove(uuid);
uuid = newUuid;
project->samples.insert(uuid, ptr);
return true;
}
void Sample::newUuid() { changeUuid(QUuid::createUuid()); }
namespace {
bool blah [[maybe_unused]];
template<typename T> void insertbuf(std::shared_ptr<Sample> smp, const T* data, size_t len, size_t channels) {
for (size_t i = 0; i < len; i++) {
auto tch = i % channels;
if (tch < 2) smp->data[tch].push_back(static_cast<float>(data[i]) / std::numeric_limits<T>::max());
}
}
template<> void insertbuf(std::shared_ptr<Sample> smp, const float* data, size_t len, size_t channels) {
for (size_t i = 0; i < len; i++) {
auto tch = i % channels;
if (tch < 2) smp->data[tch].push_back(data[i]);
}
}
}
std::shared_ptr<Sample> Sample::fromFile(QString fileName) {
QAudioFormat ifmt;
ifmt.setSampleType(QAudioFormat::SignedInt);
ifmt.setSampleSize(16);
auto dec = std::make_shared<QAudioDecoder>();
dec->setAudioFormat(ifmt);
dec->setSourceFilename(fileName);
std::deque<QAudioBuffer> bufs;
QEventLoop loop;
QObject::connect(dec.get(), &QAudioDecoder::bufferReady, &loop, [&bufs, dec] { bufs.push_back(dec->read()); });
QObject::connect(dec.get(), &QAudioDecoder::finished, &loop, [&loop] { loop.exit(); });
QObject::connect(dec.get(), static_cast<void(QAudioDecoder::*)(QAudioDecoder::Error)>(&QAudioDecoder::error), &loop, [&loop] { loop.exit(); });
dec->start();
loop.exec();
if (dec->error()) {
qDebug() << "sample decode error:" << dec->errorString();
return nullptr; // errored
}
if (bufs.empty()) return nullptr; // no sample data
auto fmt = bufs.front().format();
size_t channels = static_cast<size_t>(fmt.channelCount());
if (channels == 0) return nullptr; // zero channels means no sample data
size_t len = 0;
for (auto b : bufs) len += static_cast<size_t>(b.frameCount());
qDebug() << "format:" << fmt;
//qDebug() << "total length:" << len;
auto smp = std::make_shared<Sample>();
if (channels >= 1) smp->data[0].reserve(len);
if (channels >= 2) smp->data[1].reserve(len);
auto st = fmt.sampleType();
auto sz = fmt.sampleSize();
if (st == QAudioFormat::SignedInt) {
if (sz == 16) for (auto b : bufs) insertbuf(smp, b.constData<int16_t>(), static_cast<size_t>(b.sampleCount()), channels);
else if (sz == 32) for (auto b : bufs) insertbuf(smp, b.constData<int32_t>(), static_cast<size_t>(b.sampleCount()), channels);
else return nullptr; // unsupported
} else if (st == QAudioFormat::Float) {
if (sz == 32) for (auto b : bufs) insertbuf(smp, b.constData<float>(), static_cast<size_t>(b.sampleCount()), channels);
else return nullptr; // unsupported
} else return nullptr; // unsupported
smp->sampleRate = fmt.sampleRate();
smp->uuid = QUuid::createUuid();
smp->name = QFileInfo(fileName).baseName();
return smp;
}
namespace {
bool exporting = false;
std::unordered_map<Sample*, bool> exportMap;
}
void Sample::startExport() {
exporting = true;
exportMap.reserve(16);
}
std::vector<std::shared_ptr<Sample>> Sample::finishExport() {
std::vector<std::shared_ptr<Sample>> v;
if (exporting) {
exporting = false;
v.reserve(exportMap.size());
for (auto it : exportMap) v.push_back(it.first->shared_from_this());
exportMap.clear();
}
return v;
}
void Sample::markForExport() {
if (exporting) {
exportMap[this] = true;
}
}