Compare commits

..

9 Commits

Author SHA1 Message Date
froos 401491fdba Merge branch 'main' into daslyfe/defaultgain 2025-10-20 20:37:50 +02:00
Felix Roos ef13c45496 fix: also adjust gain curve for 1.2 2025-07-05 13:04:51 +02:00
Felix Roos 1a279ef536 Merge remote-tracking branch 'origin/main' into daslyfe/defaultgain 2025-07-05 12:55:26 +02:00
Felix Roos dc62fea714 set default gain to .8 if version tag is set <= 1.2 2025-07-05 12:47:58 +02:00
daslyfe 940d36436b Merge branch 'main' into daslyfe/defaultgain 2025-06-28 22:08:14 +02:00
Jade (Rose) Rowland 3a483c0669 working 2025-06-27 16:12:06 -04:00
Jade (Rose) Rowland 0df20ca5bf gainlin 2025-06-27 16:12:00 -04:00
Jade (Rose) Rowland 39b9d2b9b5 Merge branch 'main' into daslyfe/defaultgain 2025-06-27 16:00:41 -04:00
Jade (Rose) Rowland 91d7a09c06 default to 1 2025-04-27 16:30:20 -04:00
6 changed files with 104 additions and 82 deletions
+12 -17
View File
@@ -396,6 +396,18 @@ export const { velocity } = registerControl('velocity');
*
*/
export const { gain } = registerControl('gain');
/**
* Applies a linear gain adjustment
*
* @name gainlinear
* @synonyms gainlin
* @param {number | Pattern} amount gainlin
* @example
* s("hh*8").gainlin(".4!2 1 .4!2 1 .4 1").fast(2)
*
*/
export const { gainlinear, gainlin } = registerControl('gainlinear', 'gainlin');
/**
* Gain applied after all effects have been processed.
*
@@ -2320,23 +2332,6 @@ export const { miditouch } = registerControl('miditouch');
// TODO: what is this?
export const { polyTouch } = registerControl('polyTouch');
/**
* Checks if a control name exists in the controlAlias map.
* @name hasControlName
* @param {string} alias The control name to check
* @returns {boolean} True if the control name exists, false otherwise
*/
export const hasControlName = (alias) => {
// Check if the name exists as a key (alias) or value (main control name)
return controlAlias.has(alias) || Array.from(controlAlias.values()).includes(alias);
};
/**
* Gets the control name from the controlAlias map.
* @name getControlName
* @param {string} alias The control name to get
* @returns {string} The control name
*/
export const getControlName = (alias) => {
if (controlAlias.has(alias)) {
return controlAlias.get(alias);
-8
View File
@@ -8,14 +8,6 @@ This package adds midi functionality to strudel Patterns.
npm i @strudel/midi --save
```
## Enabling MIDI for Local Development in Chrome
1. Open Chrome and navigate to `chrome://flags`
2. Search for "Insecure origins treated as secure"
3. In the text field that appears, add your development origin (e.g., http://localhost:3000)
4. Enable the flag
5. Restart Chrome
## Available Controls
The following MIDI controls are available:
+7 -40
View File
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
import * as _WebMidi from 'webmidi';
import { Pattern, getEventOffsetMs, isPattern, logger, ref } from '@strudel/core';
import { noteToMidi, hasControlName, getControlName, registerControl } from '@strudel/core';
import { noteToMidi, getControlName } from '@strudel/core';
import { Note } from 'webmidi';
// if you use WebMidi from outside of this package, make sure to import that instance:
@@ -100,34 +100,10 @@ export const midicontrolMap = new Map();
function unifyMapping(mapping) {
return Object.fromEntries(
Object.entries(mapping).map(([key, mapping]) => {
// Convert number to object with ccn property
if (typeof mapping === 'number') {
mapping = { ccn: mapping };
}
// Get the non-aliased control name from the key
const controlName = getControlName(key);
// Check if the key or controlName already exists in the controlAlias map
if (hasControlName(key) || hasControlName(controlName)) {
// Show warning and carry on.
logger(`[midimap] '[${key}, ${controlName}]' overwrites a Strudel API.`);
// Throw error to stop the music
//throw new Error(`[midimap] '${key}' overwrites a Strudel API.`);
}
// Register the control in the midicontrolMap if it doesn't exist
if (!midicontrolMap.has(controlName)) {
try {
registerControl(controlName);
} catch (err) {
throw new Error(`[midimap] Failed to register midimap control '${controlName}': ${err.message}`);
}
} else {
logger(`[midimap] '${controlName}' already registered as a midimap control. Skipping registration.`);
}
return [controlName, mapping];
return [getControlName(key), mapping];
}),
);
}
@@ -186,14 +162,7 @@ export async function midimaps(map) {
map = await loadCache[map];
}
if (typeof map === 'object') {
Object.entries(map).forEach(([name, mapping]) => {
try {
midicontrolMap.set(name, unifyMapping(mapping));
} catch (err) {
logger(`[midi] Error setting midimap '${name}': ${err.message}`);
throw err;
}
});
Object.entries(map).forEach(([name, mapping]) => midicontrolMap.set(name, unifyMapping(mapping)));
}
}
@@ -355,18 +324,18 @@ Pattern.prototype.midi = function (midiport, options = {}) {
const device = getDevice(midiConfig.midiport, outputs);
const otherOutputs = outputs.filter((o) => o.name !== device.name);
logger(
`[midi] Midi enabled! Using "${device.name}". ${
`Midi enabled! Using "${device.name}". ${
otherOutputs?.length ? `Also available: ${getMidiDeviceNamesString(otherOutputs)}` : ''
}`,
);
},
onDisconnected: ({ outputs }) =>
logger(`[midi] Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
logger(`Midi device disconnected! Available: ${getMidiDeviceNamesString(outputs)}`),
});
return this.onTrigger((hap, currentTime, cps, targetTime) => {
if (!WebMidi.enabled) {
logger('[midi] Midi not enabled');
logger('Midi not enabled');
return;
}
hap.ensureObjectValue();
@@ -414,9 +383,7 @@ Pattern.prototype.midi = function (midiport, options = {}) {
ccs.forEach(({ ccn, ccv }) => sendCC(ccn, ccv, device, midichan, timeOffsetString));
} else if (midimap !== 'default') {
// Add warning when a non-existent midimap is specified
throw new Error(
`[midimap] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`,
);
logger(`[midi] midimap "${midimap}" not found! Available maps: ${[...midicontrolMap.keys()].join(', ')}`);
}
// Handle note
+16 -15
View File
@@ -37,7 +37,7 @@ export function registerSound(key, onTrigger, data = {}) {
soundMap.setKey(key, { onTrigger, data });
}
let gainCurveFunc = (val) => val;
let gainCurveFunc = (val) => Math.pow(val, 2);
export function applyGainCurve(val) {
return gainCurveFunc(val);
@@ -141,9 +141,9 @@ export const getAudioDevices = async () => {
return devicesMap;
};
let defaultDefaultValues = {
const defaultDefaultValues = {
s: 'triangle',
gain: 0.8,
gain: 1,
postgain: 1,
density: '.03',
ftype: '12db',
@@ -166,17 +166,6 @@ let defaultDefaultValues = {
fft: 8,
};
const defaultDefaultDefaultValues = Object.freeze({ ...defaultDefaultValues });
export function setDefault(control, value) {
// const main = getControlName(control); // we cant do this because superdough is independent of strudel/core
defaultDefaultValues[control] = value;
}
export function resetDefaults() {
defaultDefaultValues = { ...defaultDefaultDefaultValues };
}
let defaultControls = new Map(Object.entries(defaultDefaultValues));
export function setDefaultValue(key, value) {
@@ -195,9 +184,17 @@ export function resetDefaultValues() {
}
export function setVersionDefaults(version) {
resetDefaultValues();
if (version === '1.0') {
const v = Number(version);
if (v <= 1.0) {
setDefaultValue('fanchor', 0.5);
}
if (v <= 1.2) {
setDefaultValue('gain', 0.8);
setGainCurve((v) => v);
} else {
setDefaultValue('gain', 1);
setGainCurve((v) => Math.pow(v, 2));
}
}
export const resetLoadedSounds = () => soundMap.set({});
@@ -406,6 +403,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
bank,
source,
gain = getDefaultValue('gain'),
gainlinear,
postgain = getDefaultValue('postgain'),
density = getDefaultValue('density'),
duckorbit,
@@ -503,6 +501,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
velocity = applyGainCurve(velocity);
tremolodepth = applyGainCurve(tremolodepth);
gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future
if (gainlinear != null) {
gain *= gainlinear;
}
const end = t + hapDuration;
const endWithRelease = end + release;
+69
View File
@@ -4527,6 +4527,75 @@ exports[`runs examples > example "gain" example index 0 1`] = `
]
`;
exports[`runs examples > example "gainlinear" example index 0 1`] = `
[
"[ 0/1 → 1/16 | s:hh gainlinear:0.4 ]",
"[ 1/16 → 1/8 | s:hh gainlinear:0.4 ]",
"[ 1/8 → 3/16 | s:hh gainlinear:1 ]",
"[ 3/16 → 1/4 | s:hh gainlinear:0.4 ]",
"[ 1/4 → 5/16 | s:hh gainlinear:0.4 ]",
"[ 5/16 → 3/8 | s:hh gainlinear:1 ]",
"[ 3/8 → 7/16 | s:hh gainlinear:0.4 ]",
"[ 7/16 → 1/2 | s:hh gainlinear:1 ]",
"[ 1/2 → 9/16 | s:hh gainlinear:0.4 ]",
"[ 9/16 → 5/8 | s:hh gainlinear:0.4 ]",
"[ 5/8 → 11/16 | s:hh gainlinear:1 ]",
"[ 11/16 → 3/4 | s:hh gainlinear:0.4 ]",
"[ 3/4 → 13/16 | s:hh gainlinear:0.4 ]",
"[ 13/16 → 7/8 | s:hh gainlinear:1 ]",
"[ 7/8 → 15/16 | s:hh gainlinear:0.4 ]",
"[ 15/16 → 1/1 | s:hh gainlinear:1 ]",
"[ 1/1 → 17/16 | s:hh gainlinear:0.4 ]",
"[ 17/16 → 9/8 | s:hh gainlinear:0.4 ]",
"[ 9/8 → 19/16 | s:hh gainlinear:1 ]",
"[ 19/16 → 5/4 | s:hh gainlinear:0.4 ]",
"[ 5/4 → 21/16 | s:hh gainlinear:0.4 ]",
"[ 21/16 → 11/8 | s:hh gainlinear:1 ]",
"[ 11/8 → 23/16 | s:hh gainlinear:0.4 ]",
"[ 23/16 → 3/2 | s:hh gainlinear:1 ]",
"[ 3/2 → 25/16 | s:hh gainlinear:0.4 ]",
"[ 25/16 → 13/8 | s:hh gainlinear:0.4 ]",
"[ 13/8 → 27/16 | s:hh gainlinear:1 ]",
"[ 27/16 → 7/4 | s:hh gainlinear:0.4 ]",
"[ 7/4 → 29/16 | s:hh gainlinear:0.4 ]",
"[ 29/16 → 15/8 | s:hh gainlinear:1 ]",
"[ 15/8 → 31/16 | s:hh gainlinear:0.4 ]",
"[ 31/16 → 2/1 | s:hh gainlinear:1 ]",
"[ 2/1 → 33/16 | s:hh gainlinear:0.4 ]",
"[ 33/16 → 17/8 | s:hh gainlinear:0.4 ]",
"[ 17/8 → 35/16 | s:hh gainlinear:1 ]",
"[ 35/16 → 9/4 | s:hh gainlinear:0.4 ]",
"[ 9/4 → 37/16 | s:hh gainlinear:0.4 ]",
"[ 37/16 → 19/8 | s:hh gainlinear:1 ]",
"[ 19/8 → 39/16 | s:hh gainlinear:0.4 ]",
"[ 39/16 → 5/2 | s:hh gainlinear:1 ]",
"[ 5/2 → 41/16 | s:hh gainlinear:0.4 ]",
"[ 41/16 → 21/8 | s:hh gainlinear:0.4 ]",
"[ 21/8 → 43/16 | s:hh gainlinear:1 ]",
"[ 43/16 → 11/4 | s:hh gainlinear:0.4 ]",
"[ 11/4 → 45/16 | s:hh gainlinear:0.4 ]",
"[ 45/16 → 23/8 | s:hh gainlinear:1 ]",
"[ 23/8 → 47/16 | s:hh gainlinear:0.4 ]",
"[ 47/16 → 3/1 | s:hh gainlinear:1 ]",
"[ 3/1 → 49/16 | s:hh gainlinear:0.4 ]",
"[ 49/16 → 25/8 | s:hh gainlinear:0.4 ]",
"[ 25/8 → 51/16 | s:hh gainlinear:1 ]",
"[ 51/16 → 13/4 | s:hh gainlinear:0.4 ]",
"[ 13/4 → 53/16 | s:hh gainlinear:0.4 ]",
"[ 53/16 → 27/8 | s:hh gainlinear:1 ]",
"[ 27/8 → 55/16 | s:hh gainlinear:0.4 ]",
"[ 55/16 → 7/2 | s:hh gainlinear:1 ]",
"[ 7/2 → 57/16 | s:hh gainlinear:0.4 ]",
"[ 57/16 → 29/8 | s:hh gainlinear:0.4 ]",
"[ 29/8 → 59/16 | s:hh gainlinear:1 ]",
"[ 59/16 → 15/4 | s:hh gainlinear:0.4 ]",
"[ 15/4 → 61/16 | s:hh gainlinear:0.4 ]",
"[ 61/16 → 31/8 | s:hh gainlinear:1 ]",
"[ 31/8 → 63/16 | s:hh gainlinear:0.4 ]",
"[ 63/16 → 4/1 | s:hh gainlinear:1 ]",
]
`;
exports[`runs examples > example "gap" example index 0 1`] = `[]`;
exports[`runs examples > example "grow" example index 0 1`] = `
-2
View File
@@ -13,7 +13,6 @@ import {
resetGlobalEffects,
resetLoadedSounds,
initAudioOnFirstClick,
resetDefaults,
} from '@strudel/webaudio';
import { setVersionDefaultsFrom } from './util.mjs';
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
@@ -182,7 +181,6 @@ export function useReplContext() {
const resetEditor = async () => {
(await getModule('@strudel/tonal'))?.resetVoicings();
resetDefaults();
resetGlobalEffects();
clearCanvas();
clearHydra();