Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
aff8dac
feat: prepare headers for indexed connect
miloszwielgus Oct 10, 2025
fb20be1
feat: implement NodeConnections
miloszwielgus Oct 16, 2025
445194d
feat: adjusted cpp layer to indexed connect
miloszwielgus Oct 16, 2025
9221b15
feat: implement channel merger and splitter cpp layer
miloszwielgus Oct 16, 2025
612464b
feat: implement jsi layer for merger/splitter and indexed connect
miloszwielgus Oct 16, 2025
0e05c4e
fix: lint
miloszwielgus Oct 16, 2025
e09fcd8
feat: implement ts layer
miloszwielgus Oct 16, 2025
e5bc9e2
feat: implement example for testing
miloszwielgus Oct 16, 2025
f617171
fix: adjust AudioParam to indexed connect
miloszwielgus Oct 16, 2025
0fa83a5
fix: revert to originall SterePanner impl with small adjustment
miloszwielgus Oct 16, 2025
3b7aab0
fix: delete pointless resizes, lint
miloszwielgus Oct 16, 2025
8ce140f
feat: errors thrown closer to spec
miloszwielgus Oct 16, 2025
ecdd102
fix: lint
miloszwielgus Oct 16, 2025
c7b7f97
feat: web support for merger/splitter
miloszwielgus Oct 17, 2025
e72cf31
Merge remote-tracking branch 'main' into indexed-connect
miloszwielgus Oct 17, 2025
5ad73dd
fix: correctly initialize input processing buses
miloszwielgus Oct 20, 2025
21c98ca
fix: make processAudio void, comments, lint
miloszwielgus Oct 20, 2025
0035e95
fix: de-ref the example
miloszwielgus Oct 20, 2025
cc6e877
fix: delete unsued methods
miloszwielgus Oct 20, 2025
0e9a73b
feat: docs for indexed connect
miloszwielgus Oct 20, 2025
6c13ae9
fix: use RENDER_QUANTUM_SIZE to create needed buses, instead of curre…
miloszwielgus Oct 22, 2025
35a18dc
fix: nits
miloszwielgus Oct 23, 2025
de2f27e
fix: fixed internal summing / mixing
miloszwielgus Oct 23, 2025
e5b55f5
feat: add ocillator to the example
miloszwielgus Oct 23, 2025
5da3bc7
feat: docs for splitter/merger
miloszwielgus Oct 23, 2025
cab643e
refactor: make processInputAtIndex more readable
miloszwielgus Oct 23, 2025
8bb4b0e
feat: implement InvalidAccessError propagation on disconnect
miloszwielgus Oct 24, 2025
cae36e7
fix: nits, styling
miloszwielgus Oct 27, 2025
badbff5
Merge remote-tracking branch 'upstream/main' into indexed-connect
miloszwielgus Oct 27, 2025
1614fd0
Merge remote-tracking branch 'main' into indexed-connect
miloszwielgus Oct 27, 2025
5065d4c
feat: update web-audio-api coverage
miloszwielgus Oct 27, 2025
49a392b
fix: reallocate output bus if channel count does not match
miloszwielgus Oct 28, 2025
0cd0b3e
feat: merger/mergerless example
miloszwielgus Oct 28, 2025
1444374
fix: got rid of unnecesary getters in host objects
miloszwielgus Oct 28, 2025
4bbc411
fix: converage in alphabetical order
miloszwielgus Oct 28, 2025
a93ddfd
fix: unsigned -> unsigned int
miloszwielgus Oct 30, 2025
c288e4d
refactor: move connection types to a separate file
miloszwielgus Oct 30, 2025
cc10d04
fix: nitpicks
miloszwielgus Nov 3, 2025
ca71ad2
fix: comments in NodeConnections.h, lint
miloszwielgus Nov 3, 2025
aa2c248
fix: implicit bool casts, unsafe method delete
miloszwielgus Nov 13, 2025
86b3e71
chore: merge remote-tracking branch main into indexed-connect
miloszwielgus Nov 13, 2025
fc7d761
fix: temp fix to see if convolver works
miloszwielgus Nov 13, 2025
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
288 changes: 288 additions & 0 deletions apps/common-app/src/examples/MergerSplitter/MergerSplitter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import React, { useRef, useState, useEffect, FC, useMemo } from 'react';
import { Alert, ActivityIndicator } from 'react-native';
import {
AudioContext,
AudioBufferSourceNode,
AudioBuffer,
OscillatorNode,
} from 'react-native-audio-api';

import { Container, Slider, Spacer, Button } from '../../components';

// test url pointing to my public repo, to be changed / deleted
const AUDIO_URL =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly could be added to audiodocs/static/audio with informative description -> what is it, number of channels, SR

'https://github.com/miloszwielgus/test-files/raw/refs/heads/main/output.m4a';

const INITIAL_GAIN = 0.5;
const labelWidth = 100;

const SplitterMerger: FC = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inconsistent file name with component name

const [isPlayingFile, setIsPlayingFile] = useState(false);
const [isPlayingOsc, setIsPlayingOsc] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const [gain1, setGain1] = useState(INITIAL_GAIN);
const [gain2, setGain2] = useState(INITIAL_GAIN);
const [gain3, setGain3] = useState(INITIAL_GAIN);
const [gain4, setGain4] = useState(INITIAL_GAIN);
const [gain5, setGain5] = useState(INITIAL_GAIN);
const [gain6, setGain6] = useState(INITIAL_GAIN);
/*
Uncomment all the merger lines and comment the gain connect to destintation lines to test the merger node.
The changes you will notice are that the channels are panned out correctly (channel 1st is left etc)
and that channel 4 is silent (effect of 6->2 downmixing)
*/
const {
context,
splitter,
// merger,
gainNode1,
gainNode2,
gainNode3,
gainNode4,
gainNode5,
gainNode6,
} = useMemo(() => {
const ctx = new AudioContext();

const splitNode = ctx.createChannelSplitter(6);
// const mergeNode = ctx.createChannelMerger(6);

const g1 = ctx.createGain();
g1.gain.value = INITIAL_GAIN;
const g2 = ctx.createGain();
g2.gain.value = INITIAL_GAIN;
const g3 = ctx.createGain();
g3.gain.value = INITIAL_GAIN;
const g4 = ctx.createGain();
g4.gain.value = INITIAL_GAIN;
const g5 = ctx.createGain();
g5.gain.value = INITIAL_GAIN;
const g6 = ctx.createGain();
g6.gain.value = INITIAL_GAIN;

splitNode.connect(g1, 0, 0);
// g1.connect(mergeNode, 0, 0);
g1.connect(ctx.destination);
splitNode.connect(g2, 1, 0);
// g2.connect(mergeNode, 0, 1);
g2.connect(ctx.destination);
splitNode.connect(g3, 2, 0);
// g3.connect(mergeNode, 0, 2);
g3.connect(ctx.destination);
splitNode.connect(g4, 3, 0);
// g4.connect(mergeNode, 0, 3);
g4.connect(ctx.destination);
splitNode.connect(g5, 4, 0);
// g5.connect(mergeNode, 0, 4);
g5.connect(ctx.destination);
splitNode.connect(g6, 5, 0);
g6.connect(ctx.destination);
// g6.connect(mergeNode, 0, 5);

// mergeNode.connect(ctx.destination);

return {
context: ctx,
splitter: splitNode,
// merger: mergeNode,
gainNode1: g1,
gainNode2: g2,
gainNode3: g3,
gainNode4: g4,
gainNode5: g5,
gainNode6: g6,
};
}, []);

const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);

const sourceNodeRef = useRef<AudioBufferSourceNode | null>(null);
const oscNodeRef = useRef<OscillatorNode | null>(null);

useEffect(() => {
const fetchAndDecodeAudio = async () => {
setIsLoading(true);
try {
const response = await fetch(AUDIO_URL);
const arrayBuffer = await response.arrayBuffer();
const buffer = await context.decodeAudioData(arrayBuffer);
setAudioBuffer(buffer);
} catch (error) {
console.error('Failed to fetch or decode audio:', error);
Alert.alert(
'Error',
'Could not load the audio file. Check network and URL.'
);
} finally {
setIsLoading(false);
}
};

fetchAndDecodeAudio();

return () => {
if (sourceNodeRef.current) {
sourceNodeRef.current.stop(0);
sourceNodeRef.current.disconnect();
sourceNodeRef.current = null;
}
if (oscNodeRef.current) {
oscNodeRef.current.stop(0);
oscNodeRef.current.disconnect();
oscNodeRef.current = null;
}
context.close();
};
}, [context]);

const handlePlayPauseFile = () => {
if (isPlayingFile) {
if (sourceNodeRef.current) {
sourceNodeRef.current.stop(0);
sourceNodeRef.current.disconnect();
sourceNodeRef.current = null;
}
setIsPlayingFile(false);
} else {
if (!audioBuffer) return;

const sourceNode = context.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.loop = true;

sourceNode.connect(splitter);

sourceNode.start(0);
sourceNodeRef.current = sourceNode;
setIsPlayingFile(true);
}
};

const handlePlayPauseOsc = () => {
if (isPlayingOsc) {
if (oscNodeRef.current) {
oscNodeRef.current.stop(0);
oscNodeRef.current.disconnect();
oscNodeRef.current = null;
}
setIsPlayingOsc(false);
} else {
const osc = context.createOscillator();
osc.type = 'sine';
osc.frequency.value = 440; // A4

osc.connect(splitter);

osc.start(0);
oscNodeRef.current = osc;
setIsPlayingOsc(true);
}
};

const handleGain1Change = (value: number) => {
setGain1(value);
gainNode1.gain.value = value;
};
const handleGain2Change = (value: number) => {
setGain2(value);
gainNode2.gain.value = value;
};
const handleGain3Change = (value: number) => {
setGain3(value);
gainNode3.gain.value = value;
};
const handleGain4Change = (value: number) => {
setGain4(value);
gainNode4.gain.value = value;
};
const handleGain5Change = (value: number) => {
setGain5(value);
gainNode5.gain.value = value;
};
const handleGain6Change = (value: number) => {
setGain6(value);
gainNode6.gain.value = value;
};

return (
<Container centered>
{isLoading && <ActivityIndicator size="large" color="#FFFFFF" />}
<Button
onPress={handlePlayPauseFile}
title={isPlayingFile ? 'Stop File' : 'Play File'}
disabled={isLoading || !audioBuffer}
/>
<Spacer.Vertical size={15} />
<Button
onPress={handlePlayPauseOsc}
title={isPlayingOsc ? 'Stop Oscillator' : 'Play Oscillator'}
disabled={isLoading}
/>
<Spacer.Vertical size={30} />

<Slider
label="Gain Path 1"
value={gain1}
onValueChange={handleGain1Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={15} />
<Slider
label="Gain Path 2"
value={gain2}
onValueChange={handleGain2Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={15} />
<Slider
label="Gain Path 3"
value={gain3}
onValueChange={handleGain3Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={15} />
<Slider
label="Gain Path 4"
value={gain4}
onValueChange={handleGain4Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={15} />
<Slider
label="Gain Path 5"
value={gain5}
onValueChange={handleGain5Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={15} />
<Slider
label="Gain Path 6"
value={gain6}
onValueChange={handleGain6Change}
min={0.0}
max={1.5}
step={0.01}
minLabelWidth={labelWidth}
/>
<Spacer.Vertical size={30} />
</Container>
);
};

export default SplitterMerger;
1 change: 1 addition & 0 deletions apps/common-app/src/examples/MergerSplitter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MergerSplitter';
8 changes: 8 additions & 0 deletions apps/common-app/src/examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Record from './Record/Record';
import PlaybackSpeed from './PlaybackSpeed/PlaybackSpeed';
import Worklets from './Worklets/Worklets';
import Streaming from './Streaming/Streaming';
import SplitterMerger from './MergerSplitter/MergerSplitter';

type NavigationParamList = {
Oscillator: undefined;
Expand All @@ -25,6 +26,7 @@ type NavigationParamList = {
Record: undefined;
Worklets: undefined;
Streamer: undefined;
SplitterMerger: undefined;
};

export type ExampleKey = keyof NavigationParamList;
Expand Down Expand Up @@ -104,4 +106,10 @@ export const Examples: Example[] = [
subtitle: 'Stream audio from a URL',
screen: Streaming,
},
{
key: 'SplitterMerger',
title: 'Channel Splitter and Merger',
subtitle: 'Split and merge audio channels',
screen: SplitterMerger,
},
] as const;
Loading