Skip to content

Commit 93f4f84

Browse files
authored
Merge pull request #9 from eshaz/multiple-sync
Multiple Sync
2 parents 0b006a1 + 8bca99e commit 93f4f84

15 files changed

+1878
-364
lines changed

README.md

+165-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# `synaudio`
22

3-
`synaudio` is a JavaScript library that finds the synchronization point between two similar audio clips.
4-
* Synchronize two audio clips by finding the sample offset with the [Pearson correlation coefficient](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient).
3+
`synaudio` is a JavaScript library that finds the synchronization points between two or more similar audio clips.
4+
* Synchronize two or more audio clips by finding the sample offsets with the [Pearson correlation coefficient](https://en.wikipedia.org/wiki/Pearson_correlation_coefficient).
55
* Correlation algorithm implemented as WebAssembly SIMD.
66
* Built in Web Worker implementations for concurrency.
77

@@ -34,7 +34,11 @@
3434
});
3535
```
3636

37-
1. Call the `sync`, `syncWorker`, or `syncWorkerConcurrent` method on the instance to find the synchronization point in samples between two audio clips.
37+
### Sync Two Clips
38+
39+
SynAudio can synchronize two clips: one base and one comparison. The comparison clip must be a subset of the base clip in order for there to be a valid match. If you don't know the ordering of the clips, see [Sync Multiple Clips](#sync-multiple-clips)
40+
41+
* Call the `sync`, `syncWorker`, or `syncWorkerConcurrent` method on the instance to find the synchronization point in samples between two audio clips.
3842

3943
* See the [API](#api) section below for details on these methods.
4044

@@ -55,17 +59,93 @@
5559
sampleOffset, // position relative to `base` where `comparison` matches best
5660
correlation, // covariance coefficient of the match [ ranging -1 (worst) to 1 (best) ]
5761
} = await synAudio.syncWorkerConcurrent(
58-
base, // audio data to use a base for the comparison
59-
comparison, // audio data to compare against the base
60-
4 // number of threads to spawn
62+
base, // audio data to use a base for the comparison
63+
comparison, // audio data to compare against the base
64+
4 // number of threads to spawn
65+
);
66+
```
67+
68+
### Sync Multiple Clips
69+
70+
`syncMultiple` will find the best linear match(es) between a set of two or more clips. Internally, SynAudio will determine the correlation of every order combination of each clip and then will find the path(s) in this graph where the correlation is the highest.
71+
72+
* Call the `syncMultiple` method on the instance to find the best synchronization path between two or more audio clips.
73+
74+
```js
75+
// example "clips" array
76+
const clips = [
77+
{
78+
name: "clip1",
79+
data: {
80+
channelData: [leftChannelFloat32Array, rightChannelFloat32Array]
81+
samplesDecoded: 64445
82+
}
83+
},
84+
{
85+
name: "clip2",
86+
data: {
87+
channelData: [leftChannelFloat32Array, rightChannelFloat32Array]
88+
samplesDecoded: 24323
89+
}
90+
},
91+
{
92+
name: "clip3",
93+
data: {
94+
channelData: [leftChannelFloat32Array, rightChannelFloat32Array]
95+
samplesDecoded: 45675
96+
}
97+
}
98+
];
99+
100+
const results = await synAudio.syncMultiple(
101+
clips, // array of clips to compare
102+
8 // number of threads to spawn
61103
);
62104
```
63105

106+
* The `results` object will contain a two dimensional array of of match groups containing matching clips. Each match group represents an ordered list of matching audio clips where each clip relates to the previous. The sample offset within each match group is relative to the first clip in the series.
107+
* In the below example, there are two match groups with the first group containing three clips, and the second containing two clips. There was no significant correlation _(no correlation >= `options.correlationThreshold`)_ found between the clips in the two match groups. If a clip were to exist that relates the two groups together, then the result would contain only one match group, and relate all other clips to the first one in sequential order.
108+
109+
```js
110+
// results example
111+
[
112+
// first match group (no relation to second group)
113+
[
114+
{
115+
name: "cut_1601425_Mpeg", // first clip in match
116+
sampleOffset: 0,
117+
},
118+
{
119+
name: "cut_2450800_Mpeg",
120+
correlation: 0.9846370220184326,
121+
sampleOffset: 849375, // position where this clip starts relative to the first clip
122+
},
123+
{
124+
name: "cut_2577070_Mpeg",
125+
correlation: 0.9878544973345423,
126+
sampleOffset: 975645, // position where this clip starts relative to the first clip
127+
},
128+
],
129+
// second match group (no relation to first group)
130+
[
131+
{
132+
name: "cut_194648_Mpeg",
133+
sampleOffset: 0,
134+
},
135+
{
136+
name: "cut_287549_Mpeg",
137+
correlation: 0.9885798096656799,
138+
sampleOffset: 92901, // position where this clip starts relative to the first clip
139+
},
140+
]
141+
]
142+
```
143+
64144
## API
65145

66146
## `SynAudio`
67147

68-
Class that that finds the synchronization point between two similar audio clips.
148+
Class that that finds the synchronization point between two or more similar audio clips.
69149

70150
```js
71151
new SynAudio({
@@ -79,6 +159,7 @@ new SynAudio({
79159
declare interface SynAudioOptions {
80160
correlationSampleSize?: number; // default 11025
81161
initialGranularity?: number; // default 16
162+
correlationThreshold?: number; // default 0.5
82163
}
83164
```
84165
* `correlationSampleSize` *optional, defaults to 11025*
@@ -87,56 +168,81 @@ declare interface SynAudioOptions {
87168
* `initialGranularity` *optional, defaults to 16*
88169
* Number of samples to jump while performing the first pass search
89170
* Higher numbers will decrease accuracy at the benefit of much faster computation
171+
* `correlationThreshold` *optional, defaults to 0.5*
172+
* Threshold that will filter out any low correlation matches
173+
* Only applicable to `syncMultiple`
90174

91175

92176
### Methods
93177
```ts
94178
declare class SynAudio {
95179
constructor(options?: SynAudioOptions);
96180

181+
/*
182+
* Two Clips
183+
*/
97184
public async sync(
98185
base: PCMAudio,
99186
comparison: PCMAudio
100-
): Promise<SynAudioResult>;
187+
): Promise<TwoClipMatch>;
101188

102-
public async syncWorker(
189+
public syncWorker(
103190
base: PCMAudio,
104191
comparison: PCMAudio
105-
): Promise<SynAudioResult>;
192+
): Promise<TwoClipMatch>;
106193

107-
public async syncWorkerConcurrent(
194+
public syncWorkerConcurrent(
108195
base: PCMAudio,
109196
comparison: PCMAudio,
110197
threads?: number // default 1
111-
): Promise<SynAudioResult>;
198+
): Promise<TwoClipMatch>;
199+
200+
/*
201+
* Multiple Clips
202+
*/
203+
public syncMultiple(
204+
clips: AudioClip[],
205+
threads?: number // default 8
206+
): Promise<MultipleClipMatchList[]>;
112207
}
113208
```
114-
115-
* `sync(base: PCMAudio, comparison: PCMAudio): SynAudioResult`
209+
### Two Clips
210+
* `sync(base: PCMAudio, comparison: PCMAudio): Promise<TwoClipMatch>`
116211
* Executes on the main thread.
117212
* Parameters
118213
* `base` Audio data to compare against
119214
* `comparison` Audio data to use as a comparison
120215
* Returns
121-
* `SynAudioResult` containing the `correlation` and `sampleOffset`
122-
* `syncWorker(base: PCMAudio, comparison: PCMAudio): SynAudioResult`
216+
* Promise resolving to `TwoClipMatch` that contains the `correlation` and `sampleOffset`
217+
* `syncWorker(base: PCMAudio, comparison: PCMAudio): Promise<TwoClipMatch>`
123218
* Execute in a separate thread as a web worker.
124219
* Parameters
125220
* `base` Audio data to compare against
126221
* `comparison` Audio data to use as a comparison
127222
* Returns
128-
* `SynAudioResult` containing the `correlation` and `sampleOffset`
129-
* `syncWorkerConcurrent(base: PCMAudio, comparison: PCMAudio, threads: number): SynAudioResult`
223+
* Promise resolving to `TwoClipMatch` that contains the `correlation` and `sampleOffset`
224+
* `syncWorkerConcurrent(base: PCMAudio, comparison: PCMAudio, threads: number): Promise<TwoClipMatch>`
130225
* Splits the incoming data into chunks and spawns multiple workers that execute concurrently.
131226
* Parameters
132227
* `base` Audio data to compare against
133228
* `comparison` Audio data to use as a comparison
134229
* `threads` Number of threads to spawn *optional, defaults to 1*
135230
* Returns
136-
* `SynAudioResult` containing the `correlation` and `sampleOffset`
231+
* Promise resolving to `TwoClipMatch` that contains the `correlation` and `sampleOffset`
232+
233+
### Multiple Clips
234+
235+
* `syncMultiple(clips: AudioClip[], threads?: number): Promise<MultipleClipMatch[][]>`
236+
* Executes on the main thread.
237+
* Parameters
238+
* `clips` Array of `AudioClip`(s) to compare
239+
* `threads` Maximum number of threads to spawn *optional, defaults to 8*
240+
* Returns
241+
* Promise resolving to `MultipleClipMatchList[]` Two dimensional array containing a list of each matching audio clip groups
137242

138243
### Types
139244

245+
#### Input Types
140246
```ts
141247
interface PCMAudio {
142248
channelData: Float32Array[];
@@ -151,7 +257,20 @@ interface PCMAudio {
151257
* Total number of samples in a single audio channel
152258

153259
```ts
154-
interface SynAudioResult {
260+
interface AudioClip {
261+
name: string;
262+
data: PCMAudio;
263+
}
264+
```
265+
* `name`
266+
* Name of the audio clip
267+
* `data`
268+
* Audio data for the clip
269+
270+
#### Return Types
271+
272+
```ts
273+
interface TwoClipMatch {
155274
correlation: number;
156275
sampleOffset: number;
157276
}
@@ -160,4 +279,29 @@ interface SynAudioResult {
160279
* Correlation coefficient of the `base` and `comparison` audio at the `sampleOffset`
161280
* Ranging from -1 (worst) to 1 (best)
162281
* `sampleOffset`
163-
* Number of samples relative to `base` where `comparison` has the highest correlation
282+
* Number of samples relative to `base` where `comparison` has the highest correlation
283+
284+
285+
```ts
286+
declare type MultipleClipMatchList =
287+
| []
288+
| [MultipleClipMatchFirst, ...MultipleClipMatch];
289+
290+
declare interface MultipleClipMatchFirst {
291+
name: string;
292+
sampleOffset: 0;
293+
}
294+
295+
declare interface MultipleClipMatch {
296+
name: string;
297+
correlation: number;
298+
sampleOffset: number;
299+
}
300+
```
301+
* `name`
302+
* Name of the matching clip
303+
* `correlation`
304+
* Correlation coefficient between the previous clip and this cli
305+
* Ranging from -1 (worst) to 1 (best)
306+
* `sampleOffset`
307+
* Number of samples relative to the root clip (first clip in the match)
170 KB
Binary file not shown.

0 commit comments

Comments
 (0)