xybrid/xybrid/data/pattern.h

141 lines
5.0 KiB
C++

#pragma once
#include <stdint.h>
#include <limits>
#include <memory>
#include <list>
#include <vector>
#include <string>
#include <array>
#include <QString>
namespace Xybrid::Data {
class Project;
struct TimeSignature {
int beatsPerMeasure = 4;
int rowsPerBeat = 4;
int ticksPerRow = 6;
TimeSignature() = default;
TimeSignature(int b, int r, int t) : beatsPerMeasure(b), rowsPerBeat(r), ticksPerRow(t) {}
constexpr int rowsPerMeasure() const { return beatsPerMeasure * rowsPerBeat; }
constexpr bool operator==(const TimeSignature& o) const { return beatsPerMeasure == o.beatsPerMeasure && rowsPerBeat == o.rowsPerBeat && ticksPerRow == o.ticksPerRow; }
constexpr bool operator!=(const TimeSignature& o) const { return !(*this == o); }
};
class Pattern : public std::enable_shared_from_this<Pattern> {
public:
class Row { // with std::unique_ptr<std::vector>, each Row is 12 bytes inline on 64-bit (8 bytes on 32)
public:
static std::array<unsigned char, 2> fallbackParam;
int16_t port = -1;
int16_t note = -1;
std::unique_ptr<std::vector<std::array<unsigned char, 2>>> params = nullptr; // empty by default
Row() = default;
Row(const Row&) noexcept;
Row(Row&&) = default;
Row(int16_t p, int16_t n) : Row() {
port = p;
note = n;
}
Row& operator=(const Row&) noexcept;
bool isEmpty() const { return numParams() == 0 && port == -1 && note == -1; }
size_t numParams() const {
if (!this->params) return 0;
return this->params->size();
}
std::array<unsigned char, 2>& param(size_t index) {
if (!this->params || this->params->size() <= index) return fallbackParam;
return this->params->at(index);
}
void addParam(char c = '.', unsigned char v = 0) {
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
else params->push_back(p);
}
void insertParam(size_t index, char c = ' ', unsigned char v = 0) {
std::array<unsigned char, 2> p {static_cast<unsigned char>(c), v};
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>({p}));
else {
if (index > this->params->size()) index = this->params->size();
params->insert(this->params->begin() + static_cast<ptrdiff_t>(index), p);
}
}
/// Sets parameter at given index, adding struts before if necessary
void setParam(size_t index, char c = ' ', unsigned char v = 0) {
if (!this->params) this->params.reset(new std::vector<std::array<unsigned char, 2>>());
if (params->size() <= index) params->resize(index+1, {' ', 0});
(*params)[index] = {static_cast<unsigned char>(c), v};
}
void removeParam(size_t index) {
if (!this->params || this->params->size() <= index) return; // invalid index
if (this->params->size() == 1) { // deallocate vector entirely if empty
this->params.reset(nullptr);
return;
}
this->params->erase(this->params->begin() + static_cast<ptrdiff_t>(index));
}
};
class Channel {
public:
QString name;
std::vector<Row> rows;
Channel() = default;
Channel(const Channel&) = default;
Channel(int numRows, QString name = "");
};
private:
static Row fallbackRow;
static Channel fallbackChannel;
public:
// raw pointer is fine for now, since a project will never be destroyed without explicitly orphaning patterns
// (and probably deleting them since basically the only reason one would be kept alive is if it's open in the pattern editor,
// which would then immediately update with a pattern from opening a new project, or the window would close)
Project* project;
size_t index; // index in project's pattern list
QString name;
int rows = 64;
double tempo = 0; // don't set playback tempo
TimeSignature time;
int fold = 0;
std::vector<Channel> channels;
Pattern();
Pattern(const Pattern&) = default;
Pattern(int rows, int channels = 0);
void setLength(int rows);
void addChannel(int at = -1);
void deleteChannel(int at);
size_t numChannels() const { return channels.size(); }
bool valid() const;
bool validFor(const Project*) const;
bool validFor(const std::shared_ptr<Project>&) const;
Channel& channel(int channel);
Row& rowAt(int channel, int row);
};
}