motion-canvas-multitrack
Version:
A plug and play audio plugin for Motion Canvas
166 lines • 5.57 kB
JavaScript
import { array_min_max, map } from "./utils";
export const uid = () => {
return "id" + Math.random().toString(16).slice(2);
};
export const copy_audio = (s, d) => {
d.id = s.id;
d.name = s.name;
d.source = s.source;
d.active = s.active;
d.offset = s.offset;
d.duration = s.duration;
d.track_id = s.track_id;
d.buffer = s.buffer;
d.buffer_line = s.buffer_line;
d.recoding = s.recoding;
};
export const unlink_audio = (s) => {
return {
id: s.id,
name: s.name,
source: s.source,
active: s.active,
offset: s.offset,
duration: s.duration,
track_id: s.track_id,
buffer: s.buffer,
buffer_line: s.buffer_line,
recoding: s.recoding,
};
};
export const load_audios = async (paths, ctx, track_id) => {
const audios = [];
for await (const path of paths) {
audios.push(await load_audio(path, ctx, track_id));
}
return audios;
};
export const load_audio = async (path, ctx, track_id) => {
const buffer = await get_source_buffer(ctx, path);
if (!buffer) {
console.log("error in getting file");
return;
}
const file_name_rx = /[ \w-]+?(?=\.)/;
return build_audio(file_name_rx.exec(path)[0], buffer, path, track_id, false, false);
};
export const build_audio = (name, buffer, path, track_id, recoding, active) => {
return {
id: uid(),
name: name,
source: path,
offset: 0,
active: active,
track_id: track_id,
duration: buffer.duration,
recoding: recoding,
buffer: buffer,
buffer_line: ""
};
};
export const add_track = (is_main = false) => {
const id = is_main ? "default" : uid();
return {
id: id,
muted: false,
volume: 100,
main: is_main
};
};
export const get_source_buffer = async (ctx, path) => {
return await fetch(path)
.then(res => res.arrayBuffer())
.then(buffer => ctx.decodeAudioData(buffer))
.catch((e) => {
console.log(e);
return null;
});
};
export const audio_polyline = (buffer, lod) => {
const data = buffer.getChannelData(0);
const poly_step = 100 / data.length;
const min_max = array_min_max(Array.from(data));
const data_step = data.length / lod;
var line = "";
for (let i = 0; i < data.length; i += Math.round(data_step)) {
const x = (i * poly_step).toFixed(3);
const y = map(data[i], min_max.min, min_max.max, 10, 90).toFixed(3);
line += `${x},${y} `;
}
return line;
};
export const pause_play = (state, source, ctx, buffer, offset) => {
if (state)
play(source, ctx, buffer, offset);
else
stop(source);
};
export const build_sound_line = (buffer, n_points) => {
const point_size = buffer.length / n_points;
const points_lists = [];
for (let i = 0; i < buffer.length; i += point_size) {
points_lists.push(buffer.slice(i, i + point_size));
}
const chunks_avg = points_lists.map(section => {
return section.reduce((p, a) => p + a, 0) / section.length;
});
const min_max = array_min_max(chunks_avg);
const points = [];
const step = 1 / n_points;
for (let i = 0; i < chunks_avg.length; i++) {
const x = i * step;
const y = map(chunks_avg[i], min_max.min, min_max.max, 0, 1);
points.push([x, y]);
}
return points;
};
export const play = (source, ctx, buffer, offset) => {
source.current = ctx.createBufferSource();
source.current.buffer = buffer.current;
source.current.connect(ctx.destination);
source.current.start(0, offset);
};
export const stop = (source) => {
if (source.current) {
source.current.stop();
source.current.disconnect();
source.current = null;
}
};
export const build_buffer = async (ctx, buffer, audios, tracks, duration) => {
// Use max channel count across all audios, or fallback to stereo (2)
const maxChannels = Math.max(2, ...audios.map(a => a.buffer?.numberOfChannels ?? 0));
buffer.current = ctx.createBuffer(maxChannels, ctx.sampleRate * duration, ctx.sampleRate);
audios.forEach(audio => {
if (!audio.active || audio.recoding)
return;
const track = get_track(audio.track_id, tracks);
if (track.muted)
return;
const offset = Math.round(audio.offset * ctx.sampleRate);
const available_space = buffer.current.length - offset;
const len = Math.min(available_space, audio.buffer.length);
// Mix per channel dynamically
for (let ch = 0; ch < audio.buffer.numberOfChannels; ch++) {
const source = audio.buffer.getChannelData(ch);
const destination = buffer.current.getChannelData(ch);
for (let i = 0; i < len; i++) {
destination[i + offset] += source[i] * (track.volume / 100);
}
}
});
};
const get_track = (track_id, tracks) => {
return tracks.find(t => t.id == track_id);
};
export const blob_to_AudioBuffer = async (ctx, blob_part, mime_type) => {
const blob = new Blob(blob_part, { type: mime_type });
const buff = await blob.arrayBuffer();
const mono = await ctx.decodeAudioData(buff);
const length = ctx.sampleRate * mono.duration;
const stereo = ctx.createBuffer(2, length, ctx.sampleRate);
stereo.copyToChannel(mono.getChannelData(0), 0);
stereo.copyToChannel(mono.getChannelData(0), 1);
return stereo;
};
//# sourceMappingURL=wave.js.map