mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-04 06:12:22 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64f16c4f33 | |||
| ffbbc43c89 | |||
| 086596c47c | |||
| fdde05e751 | |||
| 694162a7b1 | |||
| cbcb25b063 | |||
| 0d08225983 | |||
| 00872bd1de | |||
| b7d98dd370 | |||
| 0ecacae71e | |||
| 8cd497abb2 | |||
| 070055d2ef | |||
| e500ebe268 | |||
| 3a7ec18d46 | |||
| a2b8407fbb | |||
| ac1fd88d8d | |||
| 9f8143e062 | |||
| cdc5e5f3f3 | |||
| bad498a1e5 | |||
| 4de0c11f8c | |||
| f360d394a7 |
@@ -57,7 +57,7 @@ export class Cyclist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// query the pattern for events
|
// query the pattern for events
|
||||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'cyclist' });
|
||||||
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
if (hap.hasOnset()) {
|
if (hap.hasOnset()) {
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ export class NeoCyclist {
|
|||||||
if (this.started === false) {
|
if (this.started === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps, cyclist: 'neocyclist' });
|
||||||
const haps = this.pattern.queryArc(begin, end, { _cps: this.cps });
|
|
||||||
haps.forEach((hap) => {
|
haps.forEach((hap) => {
|
||||||
if (hap.hasOnset()) {
|
if (hap.hasOnset()) {
|
||||||
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
const timeUntilTrigger = cycleToSeconds(hap.whole.begin - this.cycle, this.cps);
|
||||||
|
|||||||
@@ -98,10 +98,7 @@ export class Pattern {
|
|||||||
|
|
||||||
// runs func on query state
|
// runs func on query state
|
||||||
withState(func) {
|
withState(func) {
|
||||||
return this.withHaps((haps, state) => {
|
return new Pattern((state) => this.query(func(state)));
|
||||||
func(state);
|
|
||||||
return haps;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -214,7 +214,10 @@ export function repl({
|
|||||||
}
|
}
|
||||||
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||||
if (Object.keys(pPatterns).length) {
|
if (Object.keys(pPatterns).length) {
|
||||||
let patterns = Object.values(pPatterns);
|
let patterns = [];
|
||||||
|
for (const [key, value] of Object.entries(pPatterns)) {
|
||||||
|
patterns.push(value.withState((state) => state.setControls({ id: key })));
|
||||||
|
}
|
||||||
if (eachTransform) {
|
if (eachTransform) {
|
||||||
// Explicit lambda so only element (not index and array) are passed
|
// Explicit lambda so only element (not index and array) are passed
|
||||||
patterns = patterns.map((x) => eachTransform(x));
|
patterns = patterns.map((x) => eachTransform(x));
|
||||||
@@ -228,6 +231,7 @@ export function repl({
|
|||||||
pattern = allTransforms[i](pattern);
|
pattern = allTransforms[i](pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPattern(pattern)) {
|
if (!isPattern(pattern)) {
|
||||||
const message = `got "${typeof evaluated}" instead of pattern`;
|
const message = `got "${typeof evaluated}" instead of pattern`;
|
||||||
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ export class State {
|
|||||||
return this.setSpan(func(this.span));
|
return this.setSpan(func(this.span));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns new State with different controls
|
// Returns new State with added controls.
|
||||||
setControls(controls) {
|
setControls(controls) {
|
||||||
return new State(this.span, controls);
|
return new State(this.span, { ...this.controls, ...controls });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ Pattern.prototype.mqtt = function (
|
|||||||
cx.connect(props);
|
cx.connect(props);
|
||||||
}
|
}
|
||||||
return this.withHap((hap) => {
|
return this.withHap((hap) => {
|
||||||
const onTrigger = (t_deprecate, hap, currentTime, cps, targetTime) => {
|
const onTrigger = (hap, currentTime, cps, targetTime) => {
|
||||||
let msg_topic = topic;
|
let msg_topic = topic;
|
||||||
if (!cx || !cx.isConnected()) {
|
if (!cx || !cx.isConnected()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -135,12 +135,14 @@ export class SuperdoughOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
this.disconnect();
|
||||||
|
this.initializeAudio();
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
this.channelMerger.disconnect();
|
this.channelMerger.disconnect();
|
||||||
this.destinationGain.disconnect();
|
this.destinationGain.disconnect();
|
||||||
this.destinationGain = null;
|
this.destinationGain = null;
|
||||||
this.channelMerger = null;
|
this.channelMerger = null;
|
||||||
this.nodes = {};
|
|
||||||
this.initializeAudio();
|
|
||||||
}
|
}
|
||||||
connectToDestination = (input, channels = [0, 1]) => {
|
connectToDestination = (input, channels = [0, 1]) => {
|
||||||
//This upmix can be removed if correct channel counts are set throughout the app,
|
//This upmix can be removed if correct channel counts are set throughout the app,
|
||||||
@@ -172,6 +174,7 @@ export class SuperdoughAudioController {
|
|||||||
Array.from(this.nodes).forEach((node) => {
|
Array.from(this.nodes).forEach((node) => {
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
});
|
});
|
||||||
|
this.nodes = {};
|
||||||
this.output.reset();
|
this.output.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
destroyAudioWorkletNode,
|
destroyAudioWorkletNode,
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getLfo,
|
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
@@ -14,7 +13,6 @@ import {
|
|||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
const WT_MAX_MIP_LEVELS = 6;
|
|
||||||
export const Warpmode = Object.freeze({
|
export const Warpmode = Object.freeze({
|
||||||
NONE: 0,
|
NONE: 0,
|
||||||
ASYM: 1,
|
ASYM: 1,
|
||||||
@@ -40,39 +38,25 @@ export const Warpmode = Object.freeze({
|
|||||||
FLIP: 21,
|
FLIP: 21,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadWavetableFrames(url, label, frameLen = 2048) {
|
const seenKeys = new Set();
|
||||||
const buf = await loadBuffer(url, label);
|
async function getPayload(url, label, frameLen = 2048) {
|
||||||
const ch0 = buf.getChannelData(0);
|
const key = `${url},${frameLen}`;
|
||||||
const total = ch0.length;
|
if (!seenKeys.has(key)) {
|
||||||
const numFrames = Math.max(1, Math.floor(total / frameLen));
|
const buf = await loadBuffer(url, label);
|
||||||
const frames = new Array(numFrames);
|
const ch0 = buf.getChannelData(0);
|
||||||
for (let i = 0; i < numFrames; i++) {
|
const total = ch0.length;
|
||||||
const start = i * frameLen;
|
const numFrames = Math.max(1, Math.floor(total / frameLen));
|
||||||
frames[i] = ch0.subarray(start, start + frameLen);
|
const frames = new Array(numFrames);
|
||||||
|
for (let i = 0; i < numFrames; i++) {
|
||||||
|
const start = i * frameLen;
|
||||||
|
frames[i] = ch0.subarray(start, start + frameLen);
|
||||||
|
}
|
||||||
|
seenKeys.add(key);
|
||||||
|
return { frames, frameLen, numFrames, key };
|
||||||
}
|
}
|
||||||
|
return { frameLen, key }; // worklet will use the cached version
|
||||||
// build mipmaps
|
|
||||||
const mipmaps = [frames];
|
|
||||||
let levelFrames = frames;
|
|
||||||
for (let level = 1; level < WT_MAX_MIP_LEVELS; level++) {
|
|
||||||
const prevLen = levelFrames[0].length;
|
|
||||||
if (prevLen <= 32) break;
|
|
||||||
const nextLen = prevLen >> 1;
|
|
||||||
const next = levelFrames.map((src) => {
|
|
||||||
const out = new Float32Array(nextLen);
|
|
||||||
for (let j = 0; j < nextLen; j++) {
|
|
||||||
out[j] = (src[2 * j] + src[2 * j + 1]) / 2;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
});
|
|
||||||
mipmaps.push(next);
|
|
||||||
levelFrames = next;
|
|
||||||
}
|
|
||||||
return { frames, mipmaps, frameLen, numFrames };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadCache = {};
|
|
||||||
|
|
||||||
function humanFileSize(bytes, si) {
|
function humanFileSize(bytes, si) {
|
||||||
var thresh = si ? 1000 : 1024;
|
var thresh = si ? 1000 : 1024;
|
||||||
if (bytes < thresh) return bytes + ' B';
|
if (bytes < thresh) return bytes + ' B';
|
||||||
@@ -117,6 +101,7 @@ async function decodeAtNativeRate(arr) {
|
|||||||
return await tempAC.decodeAudioData(arr);
|
return await tempAC.decodeAudioData(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadCache = {};
|
||||||
const loadBuffer = (url, label) => {
|
const loadBuffer = (url, label) => {
|
||||||
url = url.replace('#', '%23');
|
url = url.replace('#', '%23');
|
||||||
if (!loadCache[url]) {
|
if (!loadCache[url]) {
|
||||||
@@ -222,7 +207,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||||
const { s, n = 0, duration } = value;
|
const { s, n = 0, duration, clip } = value;
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
|
||||||
let { warpmode } = value;
|
let { warpmode } = value;
|
||||||
@@ -231,8 +216,11 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
}
|
}
|
||||||
const frequency = getFrequencyFromValue(value);
|
const frequency = getFrequencyFromValue(value);
|
||||||
const { url, label } = getCommonSampleInfo(value, tables);
|
const { url, label } = getCommonSampleInfo(value, tables);
|
||||||
const payload = await loadWavetableFrames(url, label, frameLen);
|
const payload = await getPayload(url, label, frameLen);
|
||||||
const holdEnd = t + duration;
|
let holdEnd = t + duration;
|
||||||
|
if (clip !== undefined) {
|
||||||
|
holdEnd = Math.min(t + clip * duration, holdEnd);
|
||||||
|
}
|
||||||
const endWithRelease = holdEnd + release;
|
const endWithRelease = holdEnd + release;
|
||||||
const envEnd = endWithRelease + 0.01;
|
const envEnd = endWithRelease + 0.01;
|
||||||
const source = getWorklet(
|
const source = getWorklet(
|
||||||
@@ -252,7 +240,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
},
|
},
|
||||||
{ outputChannelCount: [2] },
|
{ outputChannelCount: [2] },
|
||||||
);
|
);
|
||||||
source.port.postMessage({ type: 'tables', payload });
|
source.port.postMessage({ type: 'table', payload });
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||||
return;
|
return;
|
||||||
@@ -322,13 +310,12 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
||||||
const envGain = ac.createGain();
|
const envGain = ac.createGain();
|
||||||
const node = source.connect(envGain);
|
const node = source.connect(envGain);
|
||||||
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||||
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
||||||
const handle = { node, source };
|
const handle = { node, source };
|
||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
source.disconnect();
|
|
||||||
destroyAudioWorkletNode(source);
|
destroyAudioWorkletNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class LFOProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(_inputs, outputs, parameters) {
|
||||||
const begin = parameters['begin'][0];
|
const begin = parameters['begin'][0];
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
@@ -510,7 +510,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(input, outputs, params) {
|
process(_input, outputs, params) {
|
||||||
if (currentTime <= params.begin[0]) {
|
if (currentTime <= params.begin[0]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1043,6 +1043,7 @@ function brownian(x, oct = 4) {
|
|||||||
return (sum / norm) * 2 - 1;
|
return (sum / norm) * 2 - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tablesCache = {};
|
||||||
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
@@ -1061,31 +1062,40 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.tables = null;
|
|
||||||
this.frameLen = 0;
|
this.frameLen = 0;
|
||||||
this.numFrames = 0;
|
this.numFrames = 0;
|
||||||
this.phase = [];
|
this.phase = [];
|
||||||
this.syncRatio = 1;
|
this.invSR = 1 / sampleRate;
|
||||||
|
|
||||||
this.port.onmessage = (e) => {
|
this.port.onmessage = (e) => {
|
||||||
const { type, payload } = e.data || {};
|
const { type, payload } = e.data || {};
|
||||||
if (type === 'tables') {
|
if (type === 'table') {
|
||||||
this.tables = payload.mipmaps;
|
const key = payload.key;
|
||||||
this.frameLen = payload.frameLen;
|
this.frameLen = payload.frameLen;
|
||||||
|
if (!tablesCache[key]) {
|
||||||
|
const tables = [payload.frames];
|
||||||
|
let table = tables[0];
|
||||||
|
for (let level = 1; level < 1; level++) {
|
||||||
|
const nextLen = table.length >> 1;
|
||||||
|
const nextTable = table.map((frame) => {
|
||||||
|
const avg = new Float32Array(nextLen);
|
||||||
|
for (let i = 0; i < nextLen; i++) {
|
||||||
|
avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2;
|
||||||
|
}
|
||||||
|
return avg;
|
||||||
|
});
|
||||||
|
tables.push(nextTable);
|
||||||
|
table = nextTable;
|
||||||
|
if (nextLen <= 32) break;
|
||||||
|
}
|
||||||
|
tablesCache[key] = tables;
|
||||||
|
}
|
||||||
|
this.tables = tablesCache[key];
|
||||||
this.numFrames = this.tables[0].length;
|
this.numFrames = this.tables[0].length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_chooseMip(dphi) {
|
|
||||||
const approxHarm = Math.min(64, 1 / Math.max(1e-6, dphi));
|
|
||||||
let level = 0;
|
|
||||||
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
|
||||||
level++;
|
|
||||||
}
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
_mirror(x) {
|
_mirror(x) {
|
||||||
return 1 - Math.abs(2 * x - 1);
|
return 1 - Math.abs(2 * x - 1);
|
||||||
}
|
}
|
||||||
@@ -1222,14 +1232,25 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_sampleFrame(frame, phase) {
|
_sampleFrame(frame, phase) {
|
||||||
const pos = phase * frame.length;
|
const len = frame.length;
|
||||||
|
const pos = phase * len;
|
||||||
const i = pos | 0;
|
const i = pos | 0;
|
||||||
const frac = pos - i;
|
const frac = pos - i;
|
||||||
const a = frame[i % frame.length];
|
const a = frame[i];
|
||||||
const b = frame[(i + 1) % frame.length];
|
const i1 = i + 1 < len ? i + 1 : 0; // fast wrap
|
||||||
|
const b = frame[i1];
|
||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_chooseMip(dphi) {
|
||||||
|
const approxHarm = clamp(dphi, 1e-6, 64);
|
||||||
|
let level = 0;
|
||||||
|
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
process(_inputs, outputs, parameters) {
|
process(_inputs, outputs, parameters) {
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1239,29 +1260,27 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
const outL = outputs[0][0];
|
const outL = outputs[0][0];
|
||||||
const outR = outputs[0][1] || outputs[0][0];
|
const outR = outputs[0][1] || outputs[0][0];
|
||||||
|
|
||||||
if (!this.tables) {
|
if (!this.tables) {
|
||||||
outL.fill(0);
|
outL.fill(0);
|
||||||
if (outR !== outL) outR.set(outL);
|
if (outR !== outL) outR.set(outL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < outL.length; i++) {
|
for (let i = 0; i < outL.length; i++) {
|
||||||
const detune = pv(parameters.detune, i);
|
const detune = pv(parameters.detune, i);
|
||||||
const tablePos = pv(parameters.position, i);
|
const tablePos = clamp(pv(parameters.position, i), 0, 1);
|
||||||
const idx = tablePos * (this.numFrames - 1);
|
const idx = tablePos * (this.numFrames - 1);
|
||||||
const fIdx = idx | 0;
|
const fIdx = idx | 0;
|
||||||
const frac = idx - fIdx;
|
const frac = idx - fIdx;
|
||||||
const warpAmount = pv(parameters.warp, i);
|
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||||
const warpMode = pv(parameters.warpMode, i);
|
const warpMode = pv(parameters.warpMode, i);
|
||||||
const voices = pv(parameters.voices, i);
|
const voices = pv(parameters.voices, i);
|
||||||
const phaseRand = pv(parameters.phaserand, i);
|
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||||
const spread = voices > 1 ? pv(parameters.spread, i) : 0;
|
const spread = voices > 1 ? clamp(pv(parameters.spread, i), 0, 1) : 0;
|
||||||
const gain1 = Math.sqrt(0.5 - 0.5 * spread);
|
const gain1 = Math.sqrt(0.5 - 0.5 * spread);
|
||||||
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
const gain2 = Math.sqrt(0.5 + 0.5 * spread);
|
||||||
let f = pv(parameters.frequency, i);
|
let f = pv(parameters.frequency, i);
|
||||||
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||||
const normalizer = 0.3 / Math.sqrt(voices);
|
const normalizer = 1 / Math.sqrt(voices);
|
||||||
for (let n = 0; n < voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
const isOdd = (n & 1) == 1;
|
const isOdd = (n & 1) == 1;
|
||||||
let gainL = gain1;
|
let gainL = gain1;
|
||||||
@@ -1272,7 +1291,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
gainR = gain1;
|
gainR = gain1;
|
||||||
}
|
}
|
||||||
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
const fVoice = applySemitoneDetuneToFrequency(f, getUnisonDetune(voices, detune, n)); // voice detune
|
||||||
const dPhase = fVoice / sampleRate;
|
const dPhase = fVoice * this.invSR;
|
||||||
const level = this._chooseMip(dPhase);
|
const level = this._chooseMip(dPhase);
|
||||||
const table = this.tables[level];
|
const table = this.tables[level];
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,21 @@ layout: ../../layouts/MainLayout.astro
|
|||||||
|
|
||||||
This Guide shows you the different ways to get started with using Strudel in your own project.
|
This Guide shows you the different ways to get started with using Strudel in your own project.
|
||||||
|
|
||||||
|
## Respect the license
|
||||||
|
|
||||||
|
First, please take a moment to understand Strudel's free/open source license,
|
||||||
|
[AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.en.html).
|
||||||
|
|
||||||
|
Here is a lay summary, but check the license for legal definitions and responsibilities.
|
||||||
|
|
||||||
|
- You can distribute modified versions if you keep track of the changes and the date you made them.
|
||||||
|
- You must license derivative work under the same license.
|
||||||
|
- Source code must be distributed along with web publication.
|
||||||
|
|
||||||
|
Among other things, it means that when you share your work, the whole application must be shared under the same free/open source license, or one compatible with it. This is because we want Strudel to stay free/open source. In other words, you are not permitted to distribute integrations of Strudel with libraries or other code that does not have a compatible free/open source license.
|
||||||
|
|
||||||
|
This also applies to clones informed by reading Strudel's source code, as legally speaking, that counts as a 'derivative work'. Again, please [read the licence](https://www.gnu.org/licenses/agpl-3.0.en.html) for details.
|
||||||
|
|
||||||
## Embedding the Strudel REPL
|
## Embedding the Strudel REPL
|
||||||
|
|
||||||
There are 3 quick ways to embed strudel in your website:
|
There are 3 quick ways to embed strudel in your website:
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ export function Reference() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowCaseSearch = search.toLowerCase();
|
const lowerCaseSearch = search.toLowerCase();
|
||||||
return (
|
return (
|
||||||
entry.name.toLowerCase().includes(lowCaseSearch) ||
|
entry.name.toLowerCase().includes(lowerCaseSearch) ||
|
||||||
(entry.synonyms?.some((s) => s.includes(lowCaseSearch)) ?? false)
|
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [search]);
|
}, [search]);
|
||||||
|
|||||||
Reference in New Issue
Block a user