Skip to content

Commit

Permalink
Dynamically assign output channels to mc nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
nick-thompson committed Oct 24, 2024
1 parent fc56661 commit a8923b8
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,45 @@ Float32Array [
42,
]
`;

exports[`mc table 2`] = `
Float32Array [
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
42,
]
`;

exports[`mc table 3`] = `
Float32Array [
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
27,
]
`;
82 changes: 58 additions & 24 deletions js/packages/offline-renderer/__tests__/mc.test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import OfflineRenderer from '../index';
import { el } from '@elemaudio/core';
import OfflineRenderer from "../index";
import { el } from "@elemaudio/core";


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

await core.initialize({
numInputChannels: 0,
numOutputChannels: 1,
virtualFileSystem: {
'/v/ones': Float32Array.from([1, 1, 1]),
'/v/stereo': [
"/v/ones": Float32Array.from([1, 1, 1]),
"/v/stereo": [
Float32Array.from([27, 27, 27]),
Float32Array.from([15, 15, 15]),
],
},
});

// Graph
await core.render(el.add(...el.mc.table({path: '/v/stereo', channels: 2}, 0)));
await core.render(
el.add(...el.mc.table({ path: "/v/stereo", channels: 2 }, 0)),
);

// Ten blocks of data
let inps = [];
Expand All @@ -34,36 +35,69 @@ test('mc table', async function() {
core.process(inps, outs);

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

// Let's try a graph unpacking more channels than the resource has
await core.render(
el.add(...el.mc.table({ path: "/v/stereo", channels: 3 }, 0)),
);

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

// Process another small block
core.process(inps, outs);
expect(outs[0]).toMatchSnapshot();

// And again unpacking fewer channels
await core.render(
el.add(...el.mc.table({ path: "/v/stereo", channels: 1 }, 0)),
);

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

// Process another small block
core.process(inps, outs);
expect(outs[0]).toMatchSnapshot();
});

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

await core.initialize({
numInputChannels: 0,
numOutputChannels: 1,
virtualFileSystem: {
'/v/stereo': [
Float32Array.from(Array.from({length: 128}).fill(27)),
Float32Array.from(Array.from({length: 128}).fill(15)),
"/v/stereo": [
Float32Array.from(Array.from({ length: 128 }).fill(27)),
Float32Array.from(Array.from({ length: 128 }).fill(15)),
],
},
});

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

core.render(
el.add(...el.mc.sampleseq({
path: '/v/stereo',
channels: 2,
duration: 128,
seq: [
{ time: 0, value: 0 },
{ time: 128, value: 1 },
{ time: 256, value: 0 },
{ time: 512, value: 1 },
]
}, time))
el.add(
...el.mc.sampleseq(
{
path: "/v/stereo",
channels: 2,
duration: 128,
seq: [
{ time: 0, value: 0 },
{ time: 128, value: 1 },
{ time: 256, value: 0 },
{ time: 512, value: 1 },
],
},
time,
),
),
);

// Ten blocks of data to get past the root node fade
Expand All @@ -79,7 +113,7 @@ test('mc sampleseq', async function() {
expect(outs[0]).toMatchSnapshot();

// Jump in time
setTimeProps({value: 520});
setTimeProps({ value: 520 });

// Spin for a few blocks and we should see the gain fade resolve and
// emit the constant sum of the two channels
Expand Down
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.

37 changes: 28 additions & 9 deletions runtime/elem/GraphRenderSequence.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
namespace elem
{

//==============================================================================
// Returns the number of output channels given a set of outlet connections
inline size_t getRequiredOutputChannels(std::vector<OutletConnection> const& outlets) {
size_t numOuts = 1;

for (auto const& connection : outlets) {
// Outlet channels are zero indexed, hence the +1 for required channel count
numOuts = std::max(numOuts, connection.outletChannel + 1);
}

return numOuts;
}

//==============================================================================
// A simple struct representing the audio processing inputs given to the runtime
// by the host application.
Expand Down Expand Up @@ -91,7 +104,7 @@ namespace elem
, bufferMap(bm)
{}

void push(BufferAllocator<FloatType>& ba, std::shared_ptr<GraphNode<FloatType>>& node)
void push(BufferAllocator<FloatType>& ba, std::shared_ptr<GraphNode<FloatType>>& node, std::vector<OutletConnection> const& outlets)
{
// First we update our node and tap registry to make sure we can easily visit them
// for tap promotion and event propagation
Expand All @@ -102,14 +115,14 @@ namespace elem
}

// Next we prepare the render operation
auto const numOuts = node->getNumOutputChannels();
auto const numOuts = getRequiredOutputChannels(outlets);
std::vector<FloatType*> outputPtrs(numOuts);

for (size_t i = 0; i < numOuts; ++i) {
bufferMap.insert({{node->getId(), i}, ba.next()});
outputPtrs[i] = bufferMap.at({node->getId(), i});
}

renderOps.push_back([=, active = rootPtr->active(), outputPtrs = std::move(outputPtrs)](HostContext<FloatType>& ctx) mutable {
node->process(BlockContext<FloatType> {
ctx.inputData,
Expand All @@ -123,8 +136,13 @@ namespace elem
});
}

void push(BufferAllocator<FloatType>& ba, std::shared_ptr<GraphNode<FloatType>>& node, std::vector<std::pair<NodeId, size_t>> const& children)
void push(BufferAllocator<FloatType>& ba, std::shared_ptr<GraphNode<FloatType>>& node, std::vector<InletConnection> const& inlets, std::vector<OutletConnection> const& outlets)
{
// Check if we're dealing with a leaf node
if (inlets.size() == 0) {
return push(ba, node, outlets);
}

// First we update our node and tap registry to make sure we can easily visit them
// for tap promotion and event propagation
nodeList.push_back(node);
Expand All @@ -134,7 +152,7 @@ namespace elem
}

// Next we prepare the render operation
auto const numOuts = node->getNumOutputChannels();
auto const numOuts = getRequiredOutputChannels(outlets);
std::vector<FloatType*> outputPtrs(numOuts);

for (size_t i = 0; i < numOuts; ++i) {
Expand All @@ -143,13 +161,14 @@ namespace elem
}

// Allocate room for the child pointers here, gets moved into the lambda capture group below
std::vector<FloatType*> inputPtrs(children.size());
auto const numChildren = children.size();
std::vector<FloatType*> inputPtrs(inlets.size());
auto const numChildren = inlets.size();

for (size_t j = 0; j < numChildren; ++j) {
inputPtrs[j] = bufferMap.at(children[j]);
auto const& inlet = inlets[j];
inputPtrs[j] = bufferMap.at({inlet.source, inlet.outletChannel});
}

renderOps.push_back([=, active = rootPtr->active(), outputPtrs = std::move(outputPtrs), inputPtrs = std::move(inputPtrs)](HostContext<FloatType>& ctx) mutable {
node->process(BlockContext<FloatType> {
const_cast<const FloatType**>(inputPtrs.data()),
Expand Down
Loading

0 comments on commit a8923b8

Please sign in to comment.