sample looping!
parent
b57974e066
commit
39f5966c0f
|
@ -64,6 +64,11 @@ QCborMap Sample::toCbor() const {
|
|||
m[qs("channels")] = ch;
|
||||
}
|
||||
|
||||
if (loopStart >= 0) { // only store if there is a loop point
|
||||
m[qs("loopStart")] = loopStart;
|
||||
m[qs("loopEnd")] = loopEnd;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
@ -84,6 +89,9 @@ std::shared_ptr<Sample> Sample::fromCbor(const QCborMap& m, QUuid uuid) {
|
|||
memcpy(smp->data[i].data(), c.constData(), bs);
|
||||
}
|
||||
|
||||
smp->loopStart = static_cast<int>(m.value("loopStart").toInteger(-1));
|
||||
smp->loopEnd = static_cast<int>(m.value("loopEnd").toInteger(-1));
|
||||
|
||||
return smp;
|
||||
}
|
||||
std::shared_ptr<Sample> Sample::fromCbor(const QCborValue& m, QUuid uuid) { return fromCbor(m.toMap(), uuid); }
|
||||
|
|
|
@ -26,6 +26,9 @@ namespace Xybrid::Data {
|
|||
|
||||
std::array<std::vector<float>, 2> data;
|
||||
|
||||
int loopStart = -1;
|
||||
int loopEnd = -1;
|
||||
|
||||
inline AudioFrame operator[] (size_t at) const {
|
||||
if (data[1].empty()) return {data[0][at]};
|
||||
return {data[0][at], data[1][at]};
|
||||
|
|
|
@ -371,6 +371,29 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
connect(ui->sampleList->selectionModel(), &QItemSelectionModel::currentChanged, this, [this, mdl](const QModelIndex& ind, const QModelIndex& old [[maybe_unused]]) {
|
||||
selectSampleForEditing(mdl->itemAt(ind));
|
||||
});
|
||||
|
||||
// edit pane
|
||||
connect(ui->groupSampleLoop, &QGroupBox::toggled, this, [this](bool on) {
|
||||
if (editingSample) {
|
||||
if (on) {
|
||||
editingSample->loopStart = ui->spinSampleLoopStart->value();
|
||||
editingSample->loopEnd = ui->spinSampleLoopEnd->value();
|
||||
} else {
|
||||
editingSample->loopStart = -1;
|
||||
editingSample->loopEnd = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(ui->spinSampleLoopStart, qOverload<int>(&QSpinBox::valueChanged), this, [this](int v) {
|
||||
if (editingSample && ui->groupSampleLoop->isChecked()) {
|
||||
editingSample->loopStart = v;
|
||||
}
|
||||
});
|
||||
connect(ui->spinSampleLoopEnd, qOverload<int>(&QSpinBox::valueChanged), this, [this](int v) {
|
||||
if (editingSample && ui->groupSampleLoop->isChecked()) {
|
||||
editingSample->loopEnd = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set up signaling from project to UI
|
||||
|
@ -626,20 +649,40 @@ bool MainWindow::selectPatternForEditing(Pattern* pattern) {
|
|||
|
||||
void MainWindow::selectSampleForEditing(std::shared_ptr<Xybrid::Data::Sample> smp) {
|
||||
if (!smp || smp->project != project.get()) { // no valid sample selected
|
||||
editingSample = nullptr;
|
||||
ui->sampleViewPane->setEnabled(false);
|
||||
ui->sampleInfo->setText(qs("(no sample selected)"));
|
||||
|
||||
ui->groupSampleLoop->setChecked(false);
|
||||
ui->spinSampleLoopStart->setValue(0);
|
||||
ui->spinSampleLoopEnd->setValue(0);
|
||||
} else {
|
||||
editingSample = smp;
|
||||
ui->sampleViewPane->setEnabled(true);
|
||||
ui->sampleInfo->setText(
|
||||
qs("%1 // %2\n%3 %4Hz, %5 frames (%6)")
|
||||
.arg(smp->name.section('/', -1, -1))
|
||||
.arg(smp->uuid.toString())
|
||||
.arg(smp->numChannels() == 2 ? qs("Stereo") : qs("Mono"))
|
||||
.arg(smp->name.section('/', -1, -1),
|
||||
smp->uuid.toString(),
|
||||
smp->numChannels() == 2 ? qs("Stereo") : qs("Mono"))
|
||||
.arg(smp->sampleRate)
|
||||
.arg(smp->length())
|
||||
.arg(Util::sampleLength(smp->sampleRate, smp->length()))
|
||||
);
|
||||
|
||||
ui->spinSampleLoopStart->setRange(0, smp->length());
|
||||
ui->spinSampleLoopEnd->setRange(0, smp->length());
|
||||
|
||||
if (smp->loopStart < 0) { // loop disabled
|
||||
ui->groupSampleLoop->setChecked(false);
|
||||
ui->spinSampleLoopStart->setValue(0);
|
||||
ui->spinSampleLoopEnd->setValue(smp->length());
|
||||
} else {
|
||||
ui->groupSampleLoop->setChecked(true);
|
||||
ui->spinSampleLoopStart->setValue(smp->loopStart);
|
||||
ui->spinSampleLoopEnd->setValue(smp->loopEnd);
|
||||
}
|
||||
}
|
||||
|
||||
ui->waveformPreview->setSample(smp);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Xybrid {
|
|||
UISocket* socket;
|
||||
std::shared_ptr<Data::Project> project;
|
||||
std::shared_ptr<Data::Pattern> editingPattern;
|
||||
std::shared_ptr<Data::Sample> editingSample;
|
||||
|
||||
QUndoStack* undoStack;
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>true</bool>
|
||||
|
@ -687,6 +687,161 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="sampleLoopRow" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupSampleLoop">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Loop</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSampleLoopStart">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinSampleLoopStart">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>4</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelSampleLoopEnd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>End</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinSampleLoopEnd">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>4</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -13,15 +13,22 @@ namespace Xybrid::NodeLib {
|
|||
extern const std::array<std::array<double, LUT_TAPS>, LUT_STEPS> resamplerLUT;
|
||||
|
||||
inline Data::AudioFrame resamp(Data::Sample* smp, double pos) {
|
||||
auto loop = smp->loopStart >= 0;
|
||||
auto len = static_cast<ptrdiff_t>(smp->length());
|
||||
auto ls = static_cast<ptrdiff_t>(smp->loopStart);
|
||||
auto le = static_cast<ptrdiff_t>(smp->loopEnd);
|
||||
auto ll = le - ls;
|
||||
|
||||
double ip = std::floor(pos);
|
||||
auto& pt = NodeLib::resamplerLUT[static_cast<size_t>((pos - ip)*NodeLib::LUT_STEPS) % NodeLib::LUT_STEPS];
|
||||
|
||||
Data::AudioFrame out(0.0);
|
||||
|
||||
auto ii = static_cast<ptrdiff_t>(ip) - 3;
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
auto si = ii+static_cast<ptrdiff_t>(i);
|
||||
if (si >= 0 && si < static_cast<ptrdiff_t>(smp->length())) out += (*smp)[static_cast<size_t>(si)] * pt[i];
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (loop && ii > le) ii = (ii - ls) % ll + ls;
|
||||
if (ii >= 0 && ii < len) out += (*smp)[static_cast<size_t>(ii)] * pt[i];
|
||||
ii++;
|
||||
}
|
||||
|
||||
return out;
|
||||
|
|
Loading…
Reference in New Issue