Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/packages/core/lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export function sample(
mode?: string;
startOffset?: number;
stopOffset?: number;
loopStartOffset?: number;
loopStopOffset?: number;
},
trigger: ElemNode,
rate: ElemNode,
Expand Down
2 changes: 2 additions & 0 deletions js/packages/core/lib/mc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export function sample(
mode?: string;
startOffset?: number;
stopOffset?: number;
loopStartOffset?: number;
loopStopOffset?: number;
playbackRate?: number;
},
gate: ElemNode,
Expand Down
2 changes: 1 addition & 1 deletion js/packages/web-renderer/raw/elementary-wasm.js

Large diffs are not rendered by default.

29 changes: 25 additions & 4 deletions runtime/elem/builtins/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "../Types.h"

#include "./helpers/Change.h"
#include "./helpers/SampleProperties.h"


namespace elem
Expand Down Expand Up @@ -72,6 +73,18 @@ namespace elem
stopOffset.store(static_cast<size_t>(vi));
}

if (key == Sample::kLoopStartOffset) {
if (auto const result = Sample::updateLoopProperty(val, loopStartOffset); result != ReturnCode::Ok()) {
return result;
}
}

if (key == Sample::kLoopStopOffset) {
if (auto const result = Sample::updateLoopProperty(val, loopStopOffset); result != ReturnCode::Ok()) {
return result;
}
}

return GraphNode<FloatType>::setProperty(key, val);
}

Expand Down Expand Up @@ -109,6 +122,8 @@ namespace elem
auto const wantsLoop = mode == Mode::Loop;
auto const ostart = startOffset.load();
auto const ostop = stopOffset.load();
auto const oLoopStart = loopStartOffset.load();
auto const oLoopStop = loopStopOffset.load();

// Optionally accept a second input signal specifying the playback rate
auto const hasPlaybackRateSignal = numChannels >= 2;
Expand All @@ -129,7 +144,8 @@ namespace elem
}

// Process both readers for the current sample
outputData[i] = readers[0].tick(ostart, ostop, rate, wantsLoop) + readers[1].tick(ostart, ostop, rate, wantsLoop);
outputData[i] = readers[0].tick(ostart, ostop, rate, wantsLoop, oLoopStart, oLoopStop)
+ readers[1].tick(ostart, ostop, rate, wantsLoop, oLoopStart, oLoopStop);
}
}

Expand All @@ -150,6 +166,8 @@ namespace elem
std::atomic<Mode> mode = Mode::Trigger;
std::atomic<size_t> startOffset = 0;
std::atomic<size_t> stopOffset = 0;
std::atomic<size_t> loopStartOffset = Sample::kLoopOffsetNull;
std::atomic<size_t> loopStopOffset = Sample::kLoopOffsetNull;
};

// A helper struct for reading from sample data with variable rate using
Expand All @@ -176,7 +194,7 @@ namespace elem
targetGain = FloatType(0);
}

FloatType tick (size_t const startOffset, size_t const stopOffset, FloatType const stepSize, bool const wantsLoop)
FloatType tick (size_t const startOffset, size_t const stopOffset, FloatType const stepSize, bool const wantsLoop, size_t const loopStartOffset, size_t const loopStopOffset)
{
if (sourceBuffer == nullptr || pos < 0.0 || (gain == FloatType(0) && targetGain == FloatType(0)))
return FloatType(0);
Expand All @@ -185,12 +203,15 @@ namespace elem
auto* sourceData = bufferView.data();
size_t const sourceLength = bufferView.size();

if (pos >= (double) (sourceLength - stopOffset)) {
const size_t startOffsetFinal = loopStartOffset != Sample::kLoopOffsetNull ? loopStartOffset : startOffset;
const size_t stopOffsetFinal = loopStopOffset != Sample::kLoopOffsetNull ? loopStopOffset : stopOffset;

if (pos >= (double) (sourceLength - stopOffsetFinal)) {
if (!wantsLoop) {
return FloatType(0);
}

pos = (double) startOffset;
pos = (double) startOffsetFinal;
}

// Linear interpolation on the buffer read
Expand Down
37 changes: 37 additions & 0 deletions runtime/elem/builtins/helpers/SampleProperties.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#pragma once

#import "../../Types.h"
#import "../../Value.h"

namespace elem
{
namespace Sample
{
static constexpr size_t kLoopOffsetNull = -1;
static constexpr const char* kLoopStartOffset = "loopStartOffset";
static constexpr const char* kLoopStopOffset = "loopStopOffset";

inline int updateLoopProperty(js::Value const& val, std::atomic<size_t>& property)
{
if (val.isNumber())
{
auto const v = (js::Number)val;
auto const vi = static_cast<int>(v);

if (vi < 0)
return ReturnCode::InvalidPropertyValue();

property.store(static_cast<size_t>(vi));
return ReturnCode::Ok();
}

if (val.isUndefined() || val.isNull())
{
property.store(kLoopOffsetNull);
return ReturnCode::Ok();
}

return ReturnCode::InvalidPropertyValue();
}
}
}
39 changes: 29 additions & 10 deletions runtime/elem/builtins/mc/Sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "../helpers/Change.h"
#include "../helpers/GainFade.h"
#include "../helpers/FloatUtils.h"

#include "../helpers/SampleProperties.h"

namespace elem
{
Expand Down Expand Up @@ -81,6 +81,19 @@ namespace elem
playbackRate.store((js::Number) val);
}

if (key == Sample::kLoopStartOffset) {
if (auto const result = Sample::updateLoopProperty(val, loopStartOffset); result != ReturnCode::Ok()) {
return result;
}
}

if (key == Sample::kLoopStopOffset) {
if (auto const result = Sample::updateLoopProperty(val, loopStopOffset); result != ReturnCode::Ok()) {
return result;
}
}


return GraphNode<FloatType>::setProperty(key, val);
}

Expand Down Expand Up @@ -126,6 +139,8 @@ namespace elem
auto const wantsLoop = mode == Mode::Loop;
auto const ostart = startOffset.load();
auto const ostop = stopOffset.load();
auto const oLoopStart = loopStartOffset.load();
auto const oLoopStop = loopStopOffset.load();
auto const rate = playbackRate.load();

size_t i = 0;
Expand All @@ -136,8 +151,8 @@ namespace elem

if (cv > FloatType(0.5)) {
// Read from [i, j]
readers[0].sumInto(outputData, numOuts, i, j - i, rate);
readers[1].sumInto(outputData, numOuts, i, j - i, rate);
readers[0].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);
readers[1].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);

// Update voice state
readers[currentReader & 1].noteOff();
Expand All @@ -150,8 +165,8 @@ namespace elem
// If we're in trigger mode then we can ignore falling edges
if (cv < FloatType(-0.5) && playbackMode != Mode::Trigger) {
// Read from [i, j]
readers[0].sumInto(outputData, numOuts, i, j - i, rate);
readers[1].sumInto(outputData, numOuts, i, j - i, rate);
readers[0].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);
readers[1].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);

// Update voice state
readers[currentReader & 1].noteOff();
Expand All @@ -162,8 +177,8 @@ namespace elem
}
}

readers[0].sumInto(outputData, numOuts, i, j - i, rate);
readers[1].sumInto(outputData, numOuts, i, j - i, rate);
readers[0].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);
readers[1].sumInto(outputData, numOuts, i, j - i, rate, oLoopStart, oLoopStop);
}

SingleWriterSingleReaderQueue<SharedResourcePtr> bufferQueue;
Expand All @@ -183,6 +198,8 @@ namespace elem
std::atomic<Mode> mode = Mode::Trigger;
std::atomic<size_t> startOffset = 0;
std::atomic<size_t> stopOffset = 0;
std::atomic<size_t> loopStartOffset = Sample::kLoopOffsetNull;
std::atomic<size_t> loopStopOffset = Sample::kLoopOffsetNull;
std::atomic<double> playbackRate = 1.0;
};

Expand Down Expand Up @@ -236,18 +253,20 @@ namespace elem
return lerp(static_cast<float>(alpha), data[left], data[right]);
}

void sumInto(FloatType** outputData, size_t numOuts, size_t writeOffset, size_t numSamples, double playbackRate)
void sumInto(FloatType** outputData, size_t numOuts, size_t writeOffset, size_t numSamples, double playbackRate, size_t const loopStartOffset, size_t const loopStopOffset)
{
elem::GainFade<FloatType> localFade(gainFade);

double readStart = startOffset;
double const readStart = loopStartOffset != Sample::kLoopOffsetNull ? static_cast<double>(loopStartOffset) : startOffset;
const double stopOffsetFinal = loopStopOffset != Sample::kLoopOffsetNull ? static_cast<double>(loopStopOffset) : stopOffset;

double readStop = 0;

for (size_t i = 0; i < std::min(numOuts, sourceBuffer->numChannels()); ++i) {
auto bufferView = sourceBuffer->getChannelData(i);
size_t const sourceLength = bufferView.size();

readStop = static_cast<double>(sourceLength) - stopOffset;
readStop = static_cast<double>(sourceLength) - stopOffsetFinal;

// Reinitialize the local copy to match our member instance
localFade = gainFade;
Expand Down