Skip to content

Commit 904b3c6

Browse files
committed
Sampler: Implement an offset setting for samples
1 parent 920ba51 commit 904b3c6

10 files changed

Lines changed: 210 additions & 20 deletions

File tree

src/application/service/sampler_controller.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ void SamplerController::setSelectedPad(int selectedPad)
4141
emit selectedPadVolumeChanged();
4242
emit selectedPadCutoffChanged();
4343
emit selectedPadHpfCutoffChanged();
44+
emit selectedPadStartOffsetChanged();
45+
emit selectedPadDurationChanged();
4446
}
4547
}
4648

@@ -124,6 +126,51 @@ void SamplerController::setSelectedPadHpfCutoff(double cutoff)
124126
}
125127
}
126128

129+
int SamplerController::selectedPadStartOffsetSeconds() const
130+
{
131+
if (!m_sampler || m_selectedPad < 0) {
132+
return 0;
133+
}
134+
return static_cast<int>(m_sampler->sampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad)));
135+
}
136+
137+
void SamplerController::setSelectedPadStartOffsetSeconds(int seconds)
138+
{
139+
if (m_sampler && m_selectedPad >= 0) {
140+
const double currentOffset = m_sampler->sampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad));
141+
const double milliseconds = (currentOffset - std::floor(currentOffset)) * 1000.0;
142+
m_sampler->setSampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad), static_cast<double>(seconds) + milliseconds / 1000.0);
143+
emit selectedPadStartOffsetChanged();
144+
}
145+
}
146+
147+
int SamplerController::selectedPadStartOffsetMilliseconds() const
148+
{
149+
if (!m_sampler || m_selectedPad < 0) {
150+
return 0;
151+
}
152+
const double offset = m_sampler->sampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad));
153+
return static_cast<int>(std::round((offset - std::floor(offset)) * 1000.0));
154+
}
155+
156+
void SamplerController::setSelectedPadStartOffsetMilliseconds(int milliseconds)
157+
{
158+
if (m_sampler && m_selectedPad >= 0) {
159+
const double currentOffset = m_sampler->sampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad));
160+
const double seconds = std::floor(currentOffset);
161+
m_sampler->setSampleStartOffset(static_cast<uint8_t>(36 + m_selectedPad), seconds + static_cast<double>(milliseconds) / 1000.0);
162+
emit selectedPadStartOffsetChanged();
163+
}
164+
}
165+
166+
double SamplerController::selectedPadDuration() const
167+
{
168+
if (!m_sampler || m_selectedPad < 0) {
169+
return 0.0;
170+
}
171+
return m_sampler->sampleDuration(static_cast<uint8_t>(36 + m_selectedPad));
172+
}
173+
127174
bool SamplerController::channelMode() const
128175
{
129176
if (!m_sampler) {
@@ -165,6 +212,8 @@ void SamplerController::initialize()
165212
emit selectedPadVolumeChanged();
166213
emit selectedPadCutoffChanged();
167214
emit selectedPadHpfCutoffChanged();
215+
emit selectedPadStartOffsetChanged();
216+
emit selectedPadDurationChanged();
168217
}
169218
emit channelModeChanged();
170219
}
@@ -189,6 +238,9 @@ void SamplerController::loadSample(int padIndex, const QString & filePath)
189238
const int note = 36 + padIndex;
190239
m_sampler->loadSample(static_cast<uint8_t>(note), filePath.toStdString());
191240
m_padModel->updatePad(padIndex);
241+
if (padIndex == m_selectedPad) {
242+
emit selectedPadDurationChanged();
243+
}
192244
}
193245

194246
void SamplerController::clearSample(int padIndex)

src/application/service/sampler_controller.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class SamplerController : public QObject
3535
Q_PROPERTY(double selectedPadVolume READ selectedPadVolume WRITE setSelectedPadVolume NOTIFY selectedPadVolumeChanged)
3636
Q_PROPERTY(double selectedPadCutoff READ selectedPadCutoff WRITE setSelectedPadCutoff NOTIFY selectedPadCutoffChanged)
3737
Q_PROPERTY(double selectedPadHpfCutoff READ selectedPadHpfCutoff WRITE setSelectedPadHpfCutoff NOTIFY selectedPadHpfCutoffChanged)
38+
Q_PROPERTY(int selectedPadStartOffsetSeconds READ selectedPadStartOffsetSeconds WRITE setSelectedPadStartOffsetSeconds NOTIFY selectedPadStartOffsetChanged)
39+
Q_PROPERTY(int selectedPadStartOffsetMilliseconds READ selectedPadStartOffsetMilliseconds WRITE setSelectedPadStartOffsetMilliseconds NOTIFY selectedPadStartOffsetChanged)
40+
Q_PROPERTY(double selectedPadDuration READ selectedPadDuration NOTIFY selectedPadDurationChanged)
3841
Q_PROPERTY(bool channelMode READ channelMode WRITE setChannelMode NOTIFY channelModeChanged)
3942

4043
public:
@@ -62,6 +65,14 @@ class SamplerController : public QObject
6265
double selectedPadHpfCutoff() const;
6366
void setSelectedPadHpfCutoff(double cutoff);
6467

68+
int selectedPadStartOffsetSeconds() const;
69+
void setSelectedPadStartOffsetSeconds(int seconds);
70+
71+
int selectedPadStartOffsetMilliseconds() const;
72+
void setSelectedPadStartOffsetMilliseconds(int milliseconds);
73+
74+
double selectedPadDuration() const;
75+
6576
bool channelMode() const;
6677
void setChannelMode(bool enabled);
6778

@@ -85,6 +96,8 @@ class SamplerController : public QObject
8596
void selectedPadVolumeChanged();
8697
void selectedPadCutoffChanged();
8798
void selectedPadHpfCutoffChanged();
99+
void selectedPadStartOffsetChanged();
100+
void selectedPadDurationChanged();
88101
void channelModeChanged();
89102

90103
private:

src/common/constants.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ QString xmlKeySample()
691691

692692
QString xmlKeySamplePath() { return "path"; }
693693
QString xmlKeyChannelMode() { return "channelMode"; }
694+
QString xmlKeyStartOffset() { return "startOffset"; }
694695

695696
QString xmlValueFalse() { return "false"; }
696697

src/common/constants.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ QString xmlKeySampler();
207207
QString xmlKeySample();
208208
QString xmlKeySamplePath();
209209
QString xmlKeyChannelMode();
210+
QString xmlKeyStartOffset();
210211

211212
QString xmlValueFalse();
212213
QString xmlValueTrue();

src/domain/devices/sampler_device.cpp

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void SamplerDevice::processMidiNoteOn(uint8_t note, uint8_t velocity)
7373
{
7474
std::lock_guard<std::mutex> lock { m_mutex };
7575

76-
if (note >= 128 || !m_samples.at(note)) {
76+
if (note >= maxSamples || !m_samples.at(note)) {
7777
return;
7878
}
7979

@@ -91,7 +91,7 @@ void SamplerDevice::processMidiNoteOn(uint8_t note, uint8_t velocity)
9191
if (!voice.active) {
9292
voice.note = note;
9393
voice.sample = m_samples.at(note).get();
94-
voice.position = 0.0;
94+
voice.position = voice.sample->startOffset * voice.sample->sampleRate;
9595
voice.velocity = static_cast<float>(velocity) / 127.0f;
9696
voice.pan = m_globalPan;
9797
voice.volume = m_globalVolume;
@@ -157,7 +157,7 @@ void SamplerDevice::processMidiCc(uint8_t controller, uint8_t value, uint8_t cha
157157
if (m_channelMode) {
158158
// channel is 0-indexed (0-15)
159159
const size_t note = 36 + channel;
160-
if (note < 128 && m_samples.at(note)) {
160+
if (note < maxSamples && m_samples.at(note)) {
161161
if (controller == 10) { // Panning
162162
m_samples.at(note)->pan = static_cast<float>(value) / 127.0f;
163163
} else if (controller == 7) { // Volume
@@ -329,7 +329,7 @@ void SamplerDevice::processAudio(float * output, uint32_t nFrames, uint32_t samp
329329

330330
void SamplerDevice::loadSample(uint8_t note, const std::string & filePath)
331331
{
332-
if (note >= 128) {
332+
if (note >= maxSamples) {
333333
return;
334334
}
335335

@@ -371,7 +371,7 @@ void SamplerDevice::loadSample(uint8_t note, const std::string & filePath)
371371

372372
void SamplerDevice::clearSample(uint8_t note)
373373
{
374-
if (note >= 128) {
374+
if (note >= maxSamples) {
375375
return;
376376
}
377377
std::lock_guard<std::mutex> lock { m_mutex };
@@ -381,15 +381,15 @@ void SamplerDevice::clearSample(uint8_t note)
381381

382382
const SamplerDevice::Sample * SamplerDevice::sample(uint8_t note) const
383383
{
384-
if (note >= 128) {
384+
if (note >= maxSamples) {
385385
return nullptr;
386386
}
387387
return m_samples.at(note).get();
388388
}
389389

390390
std::string SamplerDevice::absoluteFilePath(uint8_t note) const
391391
{
392-
if (note >= 128 || !m_samples.at(note)) {
392+
if (note >= maxSamples || !m_samples.at(note)) {
393393
return "";
394394
}
395395

@@ -404,15 +404,15 @@ std::string SamplerDevice::absoluteFilePath(uint8_t note) const
404404
float SamplerDevice::samplePan(uint8_t note) const
405405
{
406406
std::lock_guard<std::mutex> lock { m_mutex };
407-
if (note >= 128 || !m_samples.at(note)) {
407+
if (note >= maxSamples || !m_samples.at(note)) {
408408
return 0.5f;
409409
}
410410
return m_samples.at(note)->pan;
411411
}
412412

413413
void SamplerDevice::setSamplePan(uint8_t note, float pan)
414414
{
415-
if (note >= 128) {
415+
if (note >= maxSamples) {
416416
return;
417417
}
418418
std::lock_guard<std::mutex> lock { m_mutex };
@@ -431,15 +431,15 @@ void SamplerDevice::setSamplePan(uint8_t note, float pan)
431431
float SamplerDevice::sampleVolume(uint8_t note) const
432432
{
433433
std::lock_guard<std::mutex> lock { m_mutex };
434-
if (note >= 128 || !m_samples.at(note)) {
434+
if (note >= maxSamples || !m_samples.at(note)) {
435435
return 1.0f;
436436
}
437437
return m_samples.at(note)->volume;
438438
}
439439

440440
void SamplerDevice::setSampleVolume(uint8_t note, float volume)
441441
{
442-
if (note >= 128) {
442+
if (note >= maxSamples) {
443443
return;
444444
}
445445
std::lock_guard<std::mutex> lock { m_mutex };
@@ -458,15 +458,15 @@ void SamplerDevice::setSampleVolume(uint8_t note, float volume)
458458
float SamplerDevice::sampleCutoff(uint8_t note) const
459459
{
460460
std::lock_guard<std::mutex> lock { m_mutex };
461-
if (note >= 128 || !m_samples.at(note)) {
461+
if (note >= maxSamples || !m_samples.at(note)) {
462462
return 1.0f;
463463
}
464464
return m_samples.at(note)->cutoff;
465465
}
466466

467467
void SamplerDevice::setSampleCutoff(uint8_t note, float cutoff)
468468
{
469-
if (note >= 128) {
469+
if (note >= maxSamples) {
470470
return;
471471
}
472472
std::lock_guard<std::mutex> lock { m_mutex };
@@ -485,15 +485,15 @@ void SamplerDevice::setSampleCutoff(uint8_t note, float cutoff)
485485
float SamplerDevice::sampleHpfCutoff(uint8_t note) const
486486
{
487487
std::lock_guard<std::mutex> lock { m_mutex };
488-
if (note >= 128 || !m_samples.at(note)) {
488+
if (note >= maxSamples || !m_samples.at(note)) {
489489
return 0.0f;
490490
}
491491
return m_samples.at(note)->hpfCutoff;
492492
}
493493

494494
void SamplerDevice::setSampleHpfCutoff(uint8_t note, float cutoff)
495495
{
496-
if (note >= 128) {
496+
if (note >= maxSamples) {
497497
return;
498498
}
499499
std::lock_guard<std::mutex> lock { m_mutex };
@@ -509,6 +509,37 @@ void SamplerDevice::setSampleHpfCutoff(uint8_t note, float cutoff)
509509
}
510510
}
511511

512+
double SamplerDevice::sampleStartOffset(uint8_t note) const
513+
{
514+
std::lock_guard<std::mutex> lock { m_mutex };
515+
if (note >= maxSamples || !m_samples.at(note)) {
516+
return 0.0;
517+
}
518+
return m_samples.at(note)->startOffset;
519+
}
520+
521+
void SamplerDevice::setSampleStartOffset(uint8_t note, double offset)
522+
{
523+
if (note >= maxSamples) {
524+
return;
525+
}
526+
std::lock_guard<std::mutex> lock { m_mutex };
527+
if (m_samples.at(note)) {
528+
m_samples.at(note)->startOffset = std::max(0.0, offset);
529+
emit dataChanged();
530+
}
531+
}
532+
533+
double SamplerDevice::sampleDuration(uint8_t note) const
534+
{
535+
std::lock_guard<std::mutex> lock { m_mutex };
536+
if (note >= maxSamples || !m_samples.at(note) || !m_samples.at(note)->data) {
537+
return 0.0;
538+
}
539+
const auto & s = m_samples.at(note);
540+
return static_cast<double>(s->data->size() / static_cast<size_t>(s->channels)) / static_cast<double>(s->sampleRate);
541+
}
542+
512543
bool SamplerDevice::channelMode() const
513544
{
514545
std::lock_guard<std::mutex> lock { m_mutex };
@@ -555,7 +586,7 @@ void SamplerDevice::serializeToXml(QXmlStreamWriter & writer) const
555586
writer.writeAttribute(Constants::NahdXml::xmlKeyId(), QString::number(id()));
556587
writer.writeAttribute(Constants::NahdXml::xmlKeyChannelMode(), m_channelMode ? Constants::NahdXml::xmlValueTrue() : Constants::NahdXml::xmlValueFalse());
557588

558-
for (uint8_t note = 0; note < 128; note++) {
589+
for (uint8_t note = 0; note < maxSamples; note++) {
559590
if (const auto & s = m_samples.at(note)) {
560591
writer.writeStartElement(Constants::NahdXml::xmlKeySample());
561592
writer.writeAttribute(Constants::NahdXml::xmlKeyNote(), QString::number(note));
@@ -573,6 +604,7 @@ void SamplerDevice::serializeToXml(QXmlStreamWriter & writer) const
573604
writer.writeAttribute(Constants::NahdXml::xmlKeyVolume(), QString::number(std::round(s->manualVolume * 100.0f)));
574605
writer.writeAttribute(Constants::NahdXml::xmlKeyCutoff(), QString::number(std::round(s->manualCutoff * 100.0f)));
575606
writer.writeAttribute(Constants::NahdXml::xmlKeyHpfCutoff(), QString::number(std::round(s->manualHpfCutoff * 100.0f)));
607+
writer.writeAttribute(Constants::NahdXml::xmlKeyStartOffset(), QString::number(std::round(s->startOffset * 1000.0)));
576608
writer.writeEndElement();
577609
}
578610
}
@@ -600,6 +632,7 @@ void SamplerDevice::deserializeFromXml(QXmlStreamReader & reader)
600632
const auto volume = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyVolume(), false);
601633
const auto cutoff = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyCutoff(), false);
602634
const auto hpfCutoff = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyHpfCutoff(), false);
635+
const auto startOffset = Utils::Xml::readIntAttribute(reader, Constants::NahdXml::xmlKeyStartOffset(), false);
603636
if (note.has_value()) {
604637
loadSample(static_cast<uint8_t>(note.value()), path.toStdString());
605638
if (pan.has_value()) {
@@ -614,6 +647,9 @@ void SamplerDevice::deserializeFromXml(QXmlStreamReader & reader)
614647
if (hpfCutoff.has_value()) {
615648
setSampleHpfCutoff(static_cast<uint8_t>(note.value()), static_cast<float>(hpfCutoff.value()) / 100.0f);
616649
}
650+
if (startOffset.has_value()) {
651+
setSampleStartOffset(static_cast<uint8_t>(note.value()), static_cast<double>(startOffset.value()) / 1000.0);
652+
}
617653
}
618654
}
619655
reader.readNext();
@@ -624,7 +660,7 @@ void SamplerDevice::deserializeFromXml(QXmlStreamReader & reader)
624660
void SamplerDevice::saveState()
625661
{
626662
std::lock_guard<std::mutex> lock { m_mutex };
627-
for (size_t i = 0; i < 128; ++i) {
663+
for (size_t i = 0; i < maxSamples; i++) {
628664
if (m_samples.at(i)) {
629665
m_savedSamples.at(i) = std::make_unique<Sample>(*m_samples.at(i));
630666
} else {
@@ -636,7 +672,7 @@ void SamplerDevice::saveState()
636672
void SamplerDevice::restoreState()
637673
{
638674
std::lock_guard<std::mutex> lock { m_mutex };
639-
for (size_t i = 0; i < 128; ++i) {
675+
for (size_t i = 0; i < maxSamples; i++) {
640676
m_samples.at(i) = std::move(m_savedSamples.at(i));
641677
m_savedSamples.at(i) = nullptr;
642678
}

src/domain/devices/sampler_device.hpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace noteahead {
3838
class SamplerDevice : public Device
3939
{
4040
public:
41+
static constexpr size_t maxSamples = 128;
4142
using AudioFileReaderU = std::unique_ptr<AudioFileReader>;
4243

4344
explicit SamplerDevice(AudioFileReaderU audioFileReader = nullptr);
@@ -69,6 +70,7 @@ class SamplerDevice : public Device
6970
float manualVolume = 1.0f;
7071
float manualCutoff = 1.0f;
7172
float manualHpfCutoff = 0.0f;
73+
double startOffset = 0.0; // in seconds
7274
};
7375

7476
void loadSample(uint8_t note, const std::string & filePath);
@@ -88,6 +90,11 @@ class SamplerDevice : public Device
8890
float sampleHpfCutoff(uint8_t note) const;
8991
void setSampleHpfCutoff(uint8_t note, float cutoff);
9092

93+
double sampleStartOffset(uint8_t note) const;
94+
void setSampleStartOffset(uint8_t note, double offset);
95+
96+
double sampleDuration(uint8_t note) const;
97+
9198
bool channelMode() const;
9299
void setChannelMode(bool enabled);
93100

@@ -127,8 +134,8 @@ class SamplerDevice : public Device
127134
float releaseGain = 1.0f;
128135
};
129136

130-
std::array<std::unique_ptr<Sample>, 128> m_samples;
131-
std::array<std::unique_ptr<Sample>, 128> m_savedSamples;
137+
std::array<std::unique_ptr<Sample>, maxSamples> m_samples;
138+
std::array<std::unique_ptr<Sample>, maxSamples> m_savedSamples;
132139
std::vector<Voice> m_voices;
133140
mutable std::mutex m_mutex;
134141

0 commit comments

Comments
 (0)