mirror of
https://codeberg.org/uzu/strudel
synced 2026-10-07 09:28:07 -04:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd12b49f4d | |||
| 18d4fe0785 | |||
| 4e38988777 | |||
| 308eda677c | |||
| fdd167d62d | |||
| 85bccdcc19 | |||
| f514cd85b1 | |||
| d53929cf68 | |||
| a4bd0ae100 | |||
| 283a071c86 | |||
| 6350cfdb9c | |||
| b72e71b0bd | |||
| 758b94776d | |||
| e62d5759e7 |
@@ -1,4 +1,5 @@
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
|
||||
@@ -73,8 +73,6 @@ export function registerControl(names, ...aliases) {
|
||||
*/
|
||||
export const { s, sound } = registerControl(['s', 'n', 'gain'], 'sound');
|
||||
|
||||
export const { rec } = registerControl(['rec', 'n']);
|
||||
|
||||
/**
|
||||
* Define a custom webaudio node to use as a sound source.
|
||||
*
|
||||
@@ -1527,7 +1525,7 @@ export const { cps } = registerControl('cps');
|
||||
* note("c a f e").s("piano").clip("<.5 1 2>")
|
||||
*
|
||||
*/
|
||||
export const { clip, legato } = registerControl('clip', 'legato');
|
||||
export const { clip } = registerControl('clip');
|
||||
|
||||
/**
|
||||
* Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration.
|
||||
|
||||
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
|
||||
if (fractions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const x = fractions.pop();
|
||||
return fractions.reduce(
|
||||
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
|
||||
fraction(1),
|
||||
x,
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1729,6 +1729,75 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres
|
||||
return pat._compress(span.begin, span.end);
|
||||
});
|
||||
|
||||
/**
|
||||
* fills in the gap between consecutive notes
|
||||
*
|
||||
* @name fill
|
||||
* @memberof Pattern
|
||||
* @param {Pattern | number | array} fill relative note length
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("sawtooth").euclid(11,16).fill("<1 .5>")._pianoroll()
|
||||
* @example
|
||||
* // second array parameter is "lookahead" which is the number of cycles in the future to query for the next event
|
||||
* s("supersaw").euclid(7,16).fill("1:<1 .15>")._pianoroll()
|
||||
*/
|
||||
export const fill = register('fill', function (legato, pat) {
|
||||
let multiplier = legato;
|
||||
let lookahead = 1;
|
||||
if (Array.isArray(legato)) {
|
||||
multiplier = legato[0] ?? 1;
|
||||
lookahead = legato[1] ?? lookahead;
|
||||
}
|
||||
|
||||
let spanEnd;
|
||||
let spanBegin;
|
||||
return pat
|
||||
.withQuerySpan((span) => {
|
||||
spanEnd = span.end;
|
||||
spanBegin = span.begin;
|
||||
|
||||
return span.withEnd((e) => {
|
||||
return e.add(lookahead);
|
||||
})
|
||||
// .withBegin(e => e.sub(lookahead));
|
||||
})
|
||||
.withHaps((haps) => {
|
||||
const newHaps = [];
|
||||
haps
|
||||
.sort((a, b) => {
|
||||
return a.part.begin.sub(b.part.begin).valueOf();
|
||||
})
|
||||
.forEach((hap, i) => {
|
||||
const nextHap = haps[i + 1];
|
||||
const partbegin = hap.part.begin;
|
||||
let partend = hap.part.end;
|
||||
const wholebegin = hap.whole.begin;
|
||||
// filter out haps that were not part of the original span
|
||||
if (wholebegin.gte(spanEnd) || wholebegin.lt(spanBegin)) {
|
||||
return;
|
||||
}
|
||||
let wholeend = hap.whole.end;
|
||||
if (nextHap != null) {
|
||||
partend = nextHap.part.begin;
|
||||
wholeend = nextHap.whole.begin;
|
||||
}
|
||||
|
||||
let wholeduration = wholeend.sub(wholebegin).mul(multiplier);
|
||||
let partduration = partend.sub(partbegin).mul(multiplier);
|
||||
|
||||
partend = partbegin.add(partduration);
|
||||
wholeend = wholebegin.add(wholeduration);
|
||||
|
||||
const newPart = new TimeSpan(partbegin, partend);
|
||||
const newWhole = new TimeSpan(wholebegin, wholeend);
|
||||
|
||||
newHaps.push(new Hap(newWhole, newPart, hap.value, hap.context));
|
||||
});
|
||||
return newHaps;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast.
|
||||
* @name fastGap
|
||||
@@ -2804,8 +2873,16 @@ export const s_tour = function (pat, ...many) {
|
||||
export const chop = register('chop', function (n, pat) {
|
||||
const slices = Array.from({ length: n }, (x, i) => i);
|
||||
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
|
||||
const merge = function (a, b) {
|
||||
if ('begin' in a && 'end' in a && a.begin !== undefined && a.end !== undefined) {
|
||||
const d = a.end - a.begin;
|
||||
b = { begin: a.begin + b.begin * d, end: a.begin + b.end * d };
|
||||
}
|
||||
// return a;
|
||||
return Object.assign({}, a, b);
|
||||
};
|
||||
const func = function (o) {
|
||||
return sequence(slice_objects.map((slice_o) => Object.assign({}, o, slice_o)));
|
||||
return sequence(slice_objects.map((slice_o) => merge(o, slice_o)));
|
||||
};
|
||||
return pat.squeezeBind(func).setTactus(__tactus ? Fraction(n).mulmaybe(pat.tactus) : undefined);
|
||||
});
|
||||
|
||||
@@ -937,6 +937,9 @@ describe('Pattern', () => {
|
||||
.firstCycle(),
|
||||
);
|
||||
});
|
||||
it('Can chop chops', () => {
|
||||
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
|
||||
});
|
||||
});
|
||||
describe('range', () => {
|
||||
it('Can be patterned', () => {
|
||||
@@ -1209,4 +1212,40 @@ describe('Pattern', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('s_expand', () => {
|
||||
it('can expand four things in half', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(0, 1, 2, 3).s_expand(1, 0.5),
|
||||
s_cat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).s_expand(0.5)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can expand five things in half', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(0, 1, 2, 3, 4).s_expand(1, 0.5),
|
||||
s_cat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).s_expand(0.5)),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('stepJoin', () => {
|
||||
it('can join a pattern with a tactus of 2', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(pure(pure('a')), pure(pure('b').setTactus(2))).stepJoin(),
|
||||
s_cat(pure('a'), pure('b').setTactus(2)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('can join a pattern with a tactus of 0.5', () => {
|
||||
expect(
|
||||
sameFirst(
|
||||
sequence(pure(pure('a')), pure(pure('b').setTactus(0.5))).stepJoin(),
|
||||
s_cat(pure('a'), pure('b').setTactus(0.5)),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,6 +61,14 @@ export class TimeSpan {
|
||||
// Applies given function to the end time of the timespan"""
|
||||
return new TimeSpan(this.begin, func_time(this.end));
|
||||
}
|
||||
withBegin(func_time) {
|
||||
// Applies given function to the end time of the timespan"""
|
||||
return new TimeSpan(func_time(this.begin), this.end);
|
||||
}
|
||||
withDuration(func_time) {
|
||||
const duration = this.end.sub(this.begin)
|
||||
return new TimeSpan(this.begin, this.begin.add(func_time(duration)))
|
||||
}
|
||||
|
||||
withCycle(func_time) {
|
||||
// Like withTime, but time is relative to relative to the cycle (i.e. the
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
|
||||
@@ -53,7 +53,7 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
|
||||
return silence;
|
||||
};
|
||||
|
||||
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear');
|
||||
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'smear');
|
||||
|
||||
export const rescale = register('rescale', function (f, pat) {
|
||||
return pat.mul(x(f).w(f).y(f).h(f));
|
||||
|
||||
@@ -6,6 +6,7 @@ import replace from '@rollup/plugin-replace';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { getAudioContext, registerSound } from './index.mjs';
|
||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
export const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
export const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
const loadCache = {}; // string: Promise<ArrayBuffer>
|
||||
|
||||
export const getCachedBuffer = (url) => bufferCache[url];
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import workletsUrl from './worklets.mjs?url';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
import { loadBuffer, bufferCache, loadCache, onTriggerSample } from './sampler.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
|
||||
export const soundMap = map();
|
||||
|
||||
@@ -95,7 +95,6 @@ function loadWorklets() {
|
||||
return workletsLoading;
|
||||
}
|
||||
|
||||
let stream;
|
||||
// this function should be called on first user interaction (to avoid console warning)
|
||||
export async function initAudio(options = {}) {
|
||||
const { disableWorklets = false } = options;
|
||||
@@ -113,7 +112,6 @@ export async function initAudio(options = {}) {
|
||||
} catch (err) {
|
||||
console.warn('could not load AudioWorklet effects', err);
|
||||
}
|
||||
stream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
|
||||
logger('[superdough] ready');
|
||||
}
|
||||
let audioReady;
|
||||
@@ -310,48 +308,6 @@ export function resetGlobalEffects() {
|
||||
analysersData = {};
|
||||
}
|
||||
|
||||
/* async */ function record(name, begin, hapDuration) {
|
||||
registerSound(
|
||||
name,
|
||||
() => {
|
||||
console.log('trigger recording before its ready...', getAudioContext().currentTime);
|
||||
},
|
||||
{},
|
||||
);
|
||||
const ac = getAudioContext();
|
||||
try {
|
||||
const inputNode = ac.createMediaStreamSource(stream);
|
||||
const samples = Math.round(hapDuration * ac.sampleRate);
|
||||
const options = { samples, begin, end: begin + hapDuration };
|
||||
const recorder = getWorklet(ac, 'recording-processor', {});
|
||||
recorder.port.postMessage(options);
|
||||
inputNode.connect(recorder);
|
||||
/* return */ new Promise((resolve) => {
|
||||
recorder.port.onmessage = async (e) => {
|
||||
const audioBuffer = ac.createBuffer(1, samples, ac.sampleRate);
|
||||
audioBuffer.getChannelData(0).set(e.data.buffer);
|
||||
const url = `rec:${name}`;
|
||||
bufferCache[url] = audioBuffer;
|
||||
loadCache[url] = audioBuffer;
|
||||
const value = [url];
|
||||
console.log('register recording', getAudioContext().currentTime);
|
||||
registerSound(name, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
|
||||
type: 'sample',
|
||||
samples: value,
|
||||
baseUrl: undefined,
|
||||
prebake: false,
|
||||
tag: undefined,
|
||||
});
|
||||
resolve(name);
|
||||
};
|
||||
});
|
||||
return recorder;
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
// reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
export const superdough = async (value, t, hapDuration) => {
|
||||
const ac = getAudioContext();
|
||||
if (typeof value !== 'object') {
|
||||
@@ -374,7 +330,6 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
// destructure
|
||||
let {
|
||||
s = getDefaultValue('s'),
|
||||
rec,
|
||||
bank,
|
||||
source,
|
||||
gain = getDefaultValue('gain'),
|
||||
@@ -456,19 +411,11 @@ export const superdough = async (value, t, hapDuration) => {
|
||||
if (bank && s) {
|
||||
s = `${bank}_${s}`;
|
||||
}
|
||||
if (rec && getSound(rec)) {
|
||||
s = rec;
|
||||
value.s = rec;
|
||||
}
|
||||
|
||||
// get source AudioNode
|
||||
let sourceNode;
|
||||
if (source) {
|
||||
sourceNode = source(t, value, hapDuration);
|
||||
} else if (rec && !getSound(rec)) {
|
||||
console.log('record', rec);
|
||||
const recorder = record(rec, t, hapDuration);
|
||||
sourceNode = recorder;
|
||||
} else if (getSound(s)) {
|
||||
const { onTrigger } = getSound(s);
|
||||
const soundHandle = await onTrigger(t, value, onended);
|
||||
|
||||
@@ -464,48 +464,3 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
|
||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||
|
||||
class RecordingProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
super();
|
||||
this.done = false;
|
||||
this.head = 0;
|
||||
this.nudge = 0.1;
|
||||
this.port.onmessage = (e) => {
|
||||
this.begin = e.data.begin + this.nudge;
|
||||
|
||||
this.samples = e.data.samples;
|
||||
this.buffer = new Float32Array(this.samples);
|
||||
};
|
||||
}
|
||||
process(inputs, outputs) {
|
||||
// noop if scheduled recording begin hasn't been reached
|
||||
// eslint-disable-next-line no-undef
|
||||
if (currentTime < this.begin) {
|
||||
return true;
|
||||
}
|
||||
if (!this.buffer) {
|
||||
console.log('buffer not ready..');
|
||||
return true;
|
||||
}
|
||||
// stop when the buffer is full
|
||||
if (!this.done && this.head >= this.samples) {
|
||||
this.done = true;
|
||||
this.port.postMessage({ buffer: this.buffer });
|
||||
return false;
|
||||
}
|
||||
|
||||
// so far only 1 channel
|
||||
const input = inputs[0];
|
||||
// const output = outputs[0];
|
||||
for (let i = 0; i < input[0].length; i++) {
|
||||
this.buffer[this.head] = input[0][i] * 0.25;
|
||||
/* output[0][i] = input[0][i];
|
||||
output[1][i] = input[0][i]; */
|
||||
this.head++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('recording-processor', RecordingProcessor);
|
||||
|
||||
@@ -5,6 +5,7 @@ import replace from '@rollup/plugin-replace';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [],
|
||||
build: {
|
||||
lib: {
|
||||
|
||||
@@ -3953,6 +3953,88 @@ exports[`runs examples > example "layer" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "legato" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:sawtooth ]",
|
||||
"[ 1/8 → 3/16 | s:sawtooth ]",
|
||||
"[ 3/16 → 5/16 | s:sawtooth ]",
|
||||
"[ 5/16 → 3/8 | s:sawtooth ]",
|
||||
"[ 3/8 → 1/2 | s:sawtooth ]",
|
||||
"[ 1/2 → 9/16 | s:sawtooth ]",
|
||||
"[ 9/16 → 11/16 | s:sawtooth ]",
|
||||
"[ 11/16 → 3/4 | s:sawtooth ]",
|
||||
"[ 3/4 → 7/8 | s:sawtooth ]",
|
||||
"[ 7/8 → 15/16 | s:sawtooth ]",
|
||||
"[ 15/16 → 1/1 | s:sawtooth ]",
|
||||
"[ 1/1 → 17/16 | s:sawtooth ]",
|
||||
"[ 9/8 → 37/32 | s:sawtooth ]",
|
||||
"[ 19/16 → 5/4 | s:sawtooth ]",
|
||||
"[ 21/16 → 43/32 | s:sawtooth ]",
|
||||
"[ 11/8 → 23/16 | s:sawtooth ]",
|
||||
"[ 3/2 → 49/32 | s:sawtooth ]",
|
||||
"[ 25/16 → 13/8 | s:sawtooth ]",
|
||||
"[ 27/16 → 55/32 | s:sawtooth ]",
|
||||
"[ 7/4 → 29/16 | s:sawtooth ]",
|
||||
"[ 15/8 → 61/32 | s:sawtooth ]",
|
||||
"[ 31/16 → 63/32 | s:sawtooth ]",
|
||||
"[ 2/1 → 17/8 | s:sawtooth ]",
|
||||
"[ 17/8 → 35/16 | s:sawtooth ]",
|
||||
"[ 35/16 → 37/16 | s:sawtooth ]",
|
||||
"[ 37/16 → 19/8 | s:sawtooth ]",
|
||||
"[ 19/8 → 5/2 | s:sawtooth ]",
|
||||
"[ 5/2 → 41/16 | s:sawtooth ]",
|
||||
"[ 41/16 → 43/16 | s:sawtooth ]",
|
||||
"[ 43/16 → 11/4 | s:sawtooth ]",
|
||||
"[ 11/4 → 23/8 | s:sawtooth ]",
|
||||
"[ 23/8 → 47/16 | s:sawtooth ]",
|
||||
"[ 47/16 → 3/1 | s:sawtooth ]",
|
||||
"[ 3/1 → 49/16 | s:sawtooth ]",
|
||||
"[ 25/8 → 101/32 | s:sawtooth ]",
|
||||
"[ 51/16 → 13/4 | s:sawtooth ]",
|
||||
"[ 53/16 → 107/32 | s:sawtooth ]",
|
||||
"[ 27/8 → 55/16 | s:sawtooth ]",
|
||||
"[ 7/2 → 113/32 | s:sawtooth ]",
|
||||
"[ 57/16 → 29/8 | s:sawtooth ]",
|
||||
"[ 59/16 → 119/32 | s:sawtooth ]",
|
||||
"[ 15/4 → 61/16 | s:sawtooth ]",
|
||||
"[ 31/8 → 125/32 | s:sawtooth ]",
|
||||
"[ 63/16 → 127/32 | s:sawtooth ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "legato" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 3/16 | s:supersaw ]",
|
||||
"[ 3/16 → 5/16 | s:supersaw ]",
|
||||
"[ 5/16 → 7/16 | s:supersaw ]",
|
||||
"[ 7/16 → 5/8 | s:supersaw ]",
|
||||
"[ 5/8 → 3/4 | s:supersaw ]",
|
||||
"[ 3/4 → 7/8 | s:supersaw ]",
|
||||
"[ 7/8 → 1/1 | s:supersaw ]",
|
||||
"[ 1/1 → 19/16 | s:supersaw ]",
|
||||
"[ 19/16 → 21/16 | s:supersaw ]",
|
||||
"[ 21/16 → 23/16 | s:supersaw ]",
|
||||
"[ 23/16 → 13/8 | s:supersaw ]",
|
||||
"[ 13/8 → 7/4 | s:supersaw ]",
|
||||
"[ 7/4 → 15/8 | s:supersaw ]",
|
||||
"[ 15/8 → 2/1 | s:supersaw ]",
|
||||
"[ 2/1 → 35/16 | s:supersaw ]",
|
||||
"[ 35/16 → 37/16 | s:supersaw ]",
|
||||
"[ 37/16 → 39/16 | s:supersaw ]",
|
||||
"[ 39/16 → 21/8 | s:supersaw ]",
|
||||
"[ 21/8 → 11/4 | s:supersaw ]",
|
||||
"[ 11/4 → 23/8 | s:supersaw ]",
|
||||
"[ 23/8 → 3/1 | s:supersaw ]",
|
||||
"[ 3/1 → 51/16 | s:supersaw ]",
|
||||
"[ 51/16 → 53/16 | s:supersaw ]",
|
||||
"[ 53/16 → 55/16 | s:supersaw ]",
|
||||
"[ 55/16 → 29/8 | s:supersaw ]",
|
||||
"[ 29/8 → 15/4 | s:supersaw ]",
|
||||
"[ 15/4 → 31/8 | s:supersaw ]",
|
||||
"[ 31/8 → 4/1 | s:supersaw ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "leslie" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]",
|
||||
|
||||
@@ -142,22 +142,29 @@ function openDB(config, onOpened) {
|
||||
async function processFilesForIDB(files) {
|
||||
return Promise.all(
|
||||
Array.from(files)
|
||||
.map((file) => {
|
||||
const title = file.name;
|
||||
.map((s) => {
|
||||
const title = s.name;
|
||||
|
||||
if (!isAudioFile(title)) {
|
||||
return;
|
||||
}
|
||||
const path = file.webkitRelativePath;
|
||||
let id = path?.length ? path : title;
|
||||
if (id == null || title == null || file == null) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
title,
|
||||
blob: file,
|
||||
id,
|
||||
};
|
||||
//create obscured url to file system that can be fetched
|
||||
const sUrl = URL.createObjectURL(s);
|
||||
//fetch the sound and turn it into a buffer array
|
||||
return fetch(sUrl).then((res) => {
|
||||
return res.blob().then((blob) => {
|
||||
const path = s.webkitRelativePath;
|
||||
let id = path?.length ? path : title;
|
||||
if (id == null || title == null || blob == null) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
title,
|
||||
blob,
|
||||
id,
|
||||
};
|
||||
});
|
||||
});
|
||||
})
|
||||
.filter(Boolean),
|
||||
).catch((error) => {
|
||||
|
||||
Reference in New Issue
Block a user