Skip to content

Commit

Permalink
Multi-channel capture node
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-thompson committed Nov 11, 2024
1 parent cf09c1d commit 4ed4fd5
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 7 deletions.
18 changes: 18 additions & 0 deletions js/packages/core/lib/mc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,21 @@ export function table(

return unpack(createNode("mc.table", other, [resolve(t)]), channels);
}

export function capture(
props: {
name?: string;
channels: number,
},
g: ElemNode,
...args: Array<NodeRepr_t>
): Array<NodeRepr_t> {
let { channels, ...other } = props;

invariant(
typeof channels === "number" && channels > 0,
"Must provide a positive number channels prop",
);

return unpack(createNode("mc.capture", other, [resolve(g), ...args.map(resolve)]), channels);
}
141 changes: 141 additions & 0 deletions js/packages/offline-renderer/__tests__/__snapshots__/mc.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,146 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`mc capture 1`] = `
Array [
Float32Array [
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
1,
],
Float32Array [
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
],
Float32Array [
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
3,
],
Float32Array [
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
4,
],
]
`;

exports[`mc sampleseq 1`] = `
Float32Array [
0,
Expand Down
55 changes: 55 additions & 0 deletions js/packages/offline-renderer/__tests__/mc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,58 @@ test("mc sampleseq", async function () {

expect(outs[0]).toMatchSnapshot();
});

test("mc capture", async function () {
let core = new OfflineRenderer();

await core.initialize({
numInputChannels: 0,
numOutputChannels: 4,
blockSize: 32,
});

let [gate, setGateProps] = core.createRef("const", { value: 0 }, []);

core.render(
...el.mc.capture({ name: "test", channels: 4 }, gate, 1, 2, 3, 4),
);

// Ten blocks of data to get past the root node fade
let inps = [];
let outs = [
new Float32Array(32),
new Float32Array(32),
new Float32Array(32),
new Float32Array(32),
];

// Get past the fade-in
for (let i = 0; i < 1000; ++i) {
core.process(inps, outs);
}

let eventCallback = jest.fn();
core.on("mc.capture", eventCallback);

setGateProps({ value: 1 });
core.process(inps, outs);
expect(outs).toMatchSnapshot();

setGateProps({ value: 0 });
core.process(inps, outs);

expect(eventCallback.mock.calls).toHaveLength(1);
let args = eventCallback.mock.calls[0];
let evt = args[0];

expect(evt.data).toHaveLength(4);
expect(evt.source).toBe("test");

for (let i = 0; i < 4; ++i) {
expect(evt.data[i]).toHaveLength(32);

for (let j = 0; j < 32; ++j) {
expect(evt.data[i][j]).toBe(i + 1);
}
}
});
2 changes: 1 addition & 1 deletion js/packages/offline-renderer/elementary-wasm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/packages/web-renderer/raw/elementary-wasm.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions runtime/elem/DefaultNodeTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "builtins/SparSeq.h"
#include "builtins/SparSeq2.h"
#include "builtins/Table.h"
#include "builtins/mc/Capture.h"
#include "builtins/mc/SampleSeq.h"
#include "builtins/mc/Table.h"

Expand Down Expand Up @@ -123,6 +124,7 @@ namespace elem
callback("sampleseq", GenericNodeFactory<SampleSeqNode<FloatType>>());
callback("sampleseq2", GenericNodeFactory<SampleSeqWithStretchNode<FloatType>>());
callback("table", GenericNodeFactory<TableNode<FloatType>>());
callback("mc.capture", GenericNodeFactory<MCCaptureNode<FloatType>>());
callback("mc.sampleseq", GenericNodeFactory<StereoSampleSeqNode<FloatType>>());
callback("mc.sampleseq2", GenericNodeFactory<StereoSampleSeqWithStretchNode<FloatType>>());
callback("mc.table", GenericNodeFactory<StereoTableNode<FloatType>>());
Expand Down
4 changes: 4 additions & 0 deletions runtime/elem/GraphRenderSequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ namespace elem
std::vector<FloatType*> inputPtrs(inlets.size());
auto const numChildren = inlets.size();

// Gives the node a chance to prepare anything that might dynamically depend on
// the number of input signals
node->setProperty("_internal:numChildren", elem::js::Number(numChildren));

for (size_t j = 0; j < numChildren; ++j) {
auto const& inlet = inlets[j];
inputPtrs[j] = bufferMap.at({inlet.source, inlet.outletChannel});
Expand Down
14 changes: 9 additions & 5 deletions runtime/elem/MultiChannelRingBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace elem
~MultiChannelRingBuffer() = default;

//==============================================================================
void write (T const** data, size_t numChannels, size_t numSamples)
void write (T const** data, size_t numChannels, size_t numSamples, size_t readOffset = 0)
{
auto const w = writePos.load();
auto const r = readPos.load();
Expand All @@ -45,10 +45,10 @@ namespace elem
for (size_t i = 0; i < std::min(buffers.size(), numChannels); ++i) {
if (w + numSamples >= maxElements) {
auto const s1 = maxElements - w;
std::copy_n(data[i], s1, buffers[i].data() + w);
std::copy_n(data[i] + s1, numSamples - s1, buffers[i].data());
std::copy_n(data[i] + readOffset, s1, buffers[i].data() + w);
std::copy_n(data[i] + readOffset + s1, numSamples - s1, buffers[i].data());
} else {
std::copy_n(data[i], numSamples, buffers[i].data() + w);
std::copy_n(data[i] + readOffset, numSamples, buffers[i].data() + w);
}
}

Expand Down Expand Up @@ -90,6 +90,11 @@ namespace elem
return numFullSlots(r, w);
}

size_t getNumChannels()
{
return buffers.size();
}

private:
size_t numFullSlots(size_t const r, size_t const w)
{
Expand Down Expand Up @@ -126,4 +131,3 @@ namespace elem
};

} // namespace elem

43 changes: 43 additions & 0 deletions runtime/elem/Types.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <algorithm>
#include <sstream>
#include <unordered_map>

Expand Down Expand Up @@ -120,6 +121,48 @@ namespace elem
size_t _size;
};

//==============================================================================
template <typename FloatType>
class AudioBuffer {
public:
AudioBuffer() = default;

void resize(size_t newNumChannels, size_t newNumSamples) {
auto const newSize = newNumChannels * newNumSamples;

storage = std::vector<FloatType>(newSize);
numChannels = newNumChannels;
numSamples = newNumSamples;

for (size_t i = 0; i < newNumChannels; ++i) {
channelPtrs[i] = storage.data() + (i * newNumSamples);
}
}

size_t getNumChannels() {
return numChannels;
}

size_t getNumSamples() {
return numSamples;
}

FloatType** data() {
return channelPtrs.data();
}

void clear() {
std::fill_n(storage.data(), storage.size(), FloatType(0));
}

private:
std::array<FloatType*, 32> channelPtrs;
std::vector<FloatType> storage;

size_t numChannels = 0;
size_t numSamples = 0;
};

//==============================================================================
// A couple of quick helpers to provide an API similar to std::views::keys of C++20.
//
Expand Down
Loading

0 comments on commit 4ed4fd5

Please sign in to comment.