Merge branch 'main' into atfornesmain
This commit is contained in:
commit
71b4e14dd3
17 changed files with 249 additions and 127 deletions
|
|
@ -1214,6 +1214,16 @@ const generic_params = [
|
|||
* @name waveloss
|
||||
*/
|
||||
['waveloss'],
|
||||
/*
|
||||
* Noise crackle density
|
||||
*
|
||||
* @name density
|
||||
* @param {number | Pattern} density between 0 and x
|
||||
* @example
|
||||
* s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(4))
|
||||
*
|
||||
*/
|
||||
['density'],
|
||||
// TODO: midi effects?
|
||||
['dur'],
|
||||
// ['modwheel'],
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
import { getDrawContext } from '@strudel.cycles/core';
|
||||
|
||||
let audio = false;
|
||||
let hydra;
|
||||
|
||||
function appendCanvas(c) {
|
||||
const { canvas: testCanvas } = getDrawContext();
|
||||
c.canvas.id = 'hydra-canvas';
|
||||
c.canvas.style.position = 'absolute';
|
||||
c.canvas.style.position = 'fixed';
|
||||
c.canvas.style.top = '0px';
|
||||
testCanvas.after(c.canvas);
|
||||
return testCanvas;
|
||||
}
|
||||
|
||||
export async function initHydra(config) {
|
||||
audio = config?.audio || false;
|
||||
export async function initHydra(options = {}) {
|
||||
//load and init hydra
|
||||
if (!document.getElementById('hydra-canvas')) {
|
||||
await import('https://unpkg.com/hydra-synth');
|
||||
hydra = new Hydra({ detectAudio: audio });
|
||||
const { src = 'https://unpkg.com/hydra-synth', ...opts } = options;
|
||||
await import(src);
|
||||
|
||||
hydra = new Hydra(opts);
|
||||
appendCanvas(hydra);
|
||||
// s0.init({ src: hydraCanvas }); // whats that?
|
||||
}
|
||||
|
||||
// if config.audio is true
|
||||
// if options.detectAudio is true
|
||||
// and current canvas des not detect audio
|
||||
if (config?.audio && !hydra.detectAudio) {
|
||||
if (options?.detectAudio && !hydra?.detectAudio) {
|
||||
//remove previous canvas without audio detection
|
||||
document.getElementById('hydra-canvas').remove();
|
||||
// create and append a new audio responsive canvas
|
||||
hydra = new Hydra({ detectAudio: audio });
|
||||
appendCanvas(hydra);
|
||||
return initHydra(options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,23 @@ import { createRoot } from 'react-dom/client';
|
|||
import jsdoc from '../../../../doc.json';
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
const getDocSynonyms = (doc) => [getDocLabel(doc), ...(doc.synonyms || [])];
|
||||
const getInnerText = (html) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
export function Autocomplete({ doc }) {
|
||||
export function Autocomplete({ doc, label = getDocLabel(doc) }) {
|
||||
const synonyms = getDocSynonyms(doc).filter((a) => a !== label);
|
||||
return (
|
||||
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
||||
<h3 className="pt-0 mt-0">{label}</h3>{' '}
|
||||
{!!synonyms.length && (
|
||||
<span>
|
||||
Synonyms: <code>{synonyms.join(', ')}</code>
|
||||
</span>
|
||||
)}
|
||||
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||
<ul>
|
||||
{doc.params?.map(({ name, type, description }, i) => (
|
||||
|
|
@ -48,18 +55,24 @@ const jsdocCompletions = jsdoc.docs
|
|||
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||
)
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
.map((doc) /*: Completion */ => ({
|
||||
label: getDocLabel(doc),
|
||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||
info: () => {
|
||||
const node = document.createElement('div');
|
||||
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||
// .. using renderToStaticMarkup
|
||||
createRoot(node).render(<Autocomplete doc={doc} />);
|
||||
return node;
|
||||
},
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
}));
|
||||
.reduce(
|
||||
(acc, doc) /*: Completion */ =>
|
||||
acc.concat(
|
||||
[getDocLabel(doc), ...(doc.synonyms || [])].map((label) => ({
|
||||
label,
|
||||
// detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
|
||||
info: () => {
|
||||
const node = document.createElement('div');
|
||||
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||
// .. using renderToStaticMarkup
|
||||
createRoot(node).render(<Autocomplete doc={doc} label={label} />);
|
||||
return node;
|
||||
},
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
})),
|
||||
),
|
||||
[],
|
||||
);
|
||||
|
||||
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||
let word = context.matchBefore(/\w*/);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ window.addEventListener(
|
|||
export const strudelTooltip = hoverTooltip(
|
||||
(view, pos, side) => {
|
||||
// Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips
|
||||
if (!ctrlDown) {
|
||||
return null;
|
||||
}
|
||||
let { from, to, text } = view.state.doc.lineAt(pos);
|
||||
let start = pos,
|
||||
end = pos;
|
||||
|
|
@ -47,11 +50,13 @@ export const strudelTooltip = hoverTooltip(
|
|||
// Get entry from Strudel documentation
|
||||
let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0];
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
if (!ctrlDown) {
|
||||
return null;
|
||||
// Try for synonyms
|
||||
entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0];
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
pos: start,
|
||||
end,
|
||||
|
|
@ -60,7 +65,7 @@ export const strudelTooltip = hoverTooltip(
|
|||
create(view) {
|
||||
let dom = document.createElement('div');
|
||||
dom.className = 'strudel-tooltip';
|
||||
createRoot(dom).render(<Autocomplete doc={entry} />);
|
||||
createRoot(dom).render(<Autocomplete doc={entry} label={word} />);
|
||||
return { dom };
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs';
|
|||
let noiseCache = {};
|
||||
|
||||
// lazy generates noise buffers and keeps them forever
|
||||
function getNoiseBuffer(type) {
|
||||
function getNoiseBuffer(type, density) {
|
||||
const ac = getAudioContext();
|
||||
if (noiseCache[type]) {
|
||||
return noiseCache[type];
|
||||
|
|
@ -34,17 +34,26 @@ function getNoiseBuffer(type) {
|
|||
output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
|
||||
output[i] *= 0.11;
|
||||
b6 = white * 0.115926;
|
||||
} else if (type === 'crackle') {
|
||||
const probability = density * 0.01;
|
||||
if (Math.random() < probability) {
|
||||
output[i] = Math.random() * 2 - 1;
|
||||
} else {
|
||||
output[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
noiseCache[type] = noiseBuffer;
|
||||
|
||||
// Prevent caching to randomize crackles
|
||||
if (type !== 'crackle') noiseCache[type] = noiseBuffer;
|
||||
return noiseBuffer;
|
||||
}
|
||||
|
||||
// expects one of noises as type
|
||||
export function getNoiseOscillator(type = 'white', t) {
|
||||
export function getNoiseOscillator(type = 'white', t, density = 0.02) {
|
||||
const ac = getAudioContext();
|
||||
const o = ac.createBufferSource();
|
||||
o.buffer = getNoiseBuffer(type);
|
||||
o.buffer = getNoiseBuffer(type, density);
|
||||
o.loop = true;
|
||||
o.start(t);
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ export const superdough = async (value, deadline, hapDuration) => {
|
|||
source,
|
||||
gain = 0.8,
|
||||
postgain = 1,
|
||||
density = 0.03,
|
||||
// filters
|
||||
ftype = '12db',
|
||||
fanchor = 0.5,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const fm = (osc, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
|||
};
|
||||
|
||||
const waveforms = ['sine', 'square', 'triangle', 'sawtooth'];
|
||||
const noises = ['pink', 'white', 'brown'];
|
||||
const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||
|
||||
export function registerSynthSounds() {
|
||||
[...waveforms, ...noises].forEach((s) => {
|
||||
|
|
@ -36,7 +36,8 @@ export function registerSynthSounds() {
|
|||
if (waveforms.includes(s)) {
|
||||
sound = getOscillator(s, t, value);
|
||||
} else {
|
||||
sound = getNoiseOscillator(s, t);
|
||||
let { density } = value;
|
||||
sound = getNoiseOscillator(s, t, density);
|
||||
}
|
||||
|
||||
let { node: o, stop, triggerRelease } = sound;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue