Compare commits

..

11 Commits

Author SHA1 Message Date
Jade (Rose) Rowland 42d7781d44 Merge branch 'main' into ziglets 2024-02-25 21:49:18 -05:00
Felix Roos a746bf5461 add assemblyscript version 2024-01-10 22:08:41 +01:00
Felix Roos 700f0be0ba optimize c output 2024-01-10 21:49:04 +01:00
Felix Roos cb59d632a8 add c info to readme 2024-01-10 21:45:21 +01:00
Felix Roos 9fd86458e6 add csaw 2024-01-10 21:44:16 +01:00
Felix Roos 3cd49065c8 prettierignore superdough-wasm 2024-01-10 21:32:20 +01:00
Felix Roos a7726e2334 add rust + shrink zig wasm file 2024-01-10 21:31:22 +01:00
Felix Roos 26793aec91 frequency control (bad) 2024-01-10 18:27:40 +01:00
Felix Roos 7c4ed7e05f cleanup 2024-01-10 18:14:49 +01:00
Felix Roos 93cfe9802c zig dsp poc 2024-01-10 18:11:05 +01:00
Felix Roos 0106a129c9 add minimal zig wasm testing ground 2024-01-10 17:04:10 +01:00
48 changed files with 1940 additions and 1595 deletions
+1
View File
@@ -10,4 +10,5 @@ paper
pnpm-lock.yaml
pnpm-workspace.yaml
**/dev-dist
superdough-wasm
website/.astro
+1 -2
View File
@@ -16,7 +16,6 @@
</div>
<div id="output"></div>
<script type="module">
// TODO: refactor to use newer version without controls import
import { controls, repl, evalScope } from 'https://cdn.skypack.dev/@strudel/core@0.11.0';
import { mini } from 'https://cdn.skypack.dev/@strudel/mini@0.11.0';
import { transpiler } from 'https://cdn.skypack.dev/@strudel/transpiler@0.11.0';
@@ -54,7 +53,7 @@
function getTune() {
return `samples('github:tidalcycles/dirt-samples')
setcps(1);
stack(
// amen
n("0 1 2 3 4 5 6 7")
+2 -1
View File
@@ -1,6 +1,6 @@
import { StrudelMirror } from '@strudel/codemirror';
import { funk42 } from './tunes';
import { drawPianoroll, evalScope } from '@strudel/core';
import { drawPianoroll, evalScope, controls } from '@strudel/core';
import './style.css';
import { initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
@@ -25,6 +25,7 @@ const editor = new StrudelMirror({
prebake: async () => {
initAudioOnFirstClick(); // needed to make the browser happy (don't await this here..)
const loadModules = evalScope(
controls,
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/tonal'),
+9 -4
View File
@@ -1,5 +1,5 @@
import { repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick, registerSynthSounds } from '@strudel/webaudio';
import { controls, repl, evalScope } from '@strudel/core';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { transpiler } from '@strudel/transpiler';
import tune from './tune.mjs';
@@ -7,9 +7,14 @@ const ctx = getAudioContext();
const input = document.getElementById('text');
input.innerHTML = tune;
initAudioOnFirstClick();
registerSynthSounds();
evalScope(import('@strudel/core'), import('@strudel/mini'), import('@strudel/webaudio'), import('@strudel/tonal'));
evalScope(
controls,
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/webaudio'),
import('@strudel/tonal'),
);
const { evaluate } = repl({
defaultOutput: webaudioOutput,
+1 -1
View File
@@ -1,5 +1,5 @@
export default `samples('github:tidalcycles/dirt-samples')
setcps(1)
stack(
// amen
n("0 1 2 3 4 5 6 7")
+3 -1
View File
@@ -1,4 +1,6 @@
import { Pattern, getDrawContext, silence, register, pure, createParams } from './index.mjs';
import { Pattern, getDrawContext, silence, register, pure } from './index.mjs';
import controls from './controls.mjs'; // do not import from index.mjs as it breaks for some reason..
const { createParams } = controls;
let clearColor = '#22222210';
+1420 -1445
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -134,7 +134,7 @@ export class Drawer {
this.lastFrame = phase;
this.visibleHaps = (this.visibleHaps || [])
// filter out haps that are too far in the past (think left edge of screen for pianoroll)
.filter((h) => h.endClipped >= phase - lookbehind - lookahead)
.filter((h) => h.whole?.end >= phase - lookbehind - lookahead)
// add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead;
+1 -7
View File
@@ -41,17 +41,11 @@ const _bjork = function (n, x) {
};
export const bjork = function (ons, steps) {
const inverted = ons < 0;
ons = Math.abs(ons);
const offs = steps - ons;
const x = Array(ons).fill([1]);
const y = Array(offs).fill([0]);
const result = _bjork([ons, offs], [x, y]);
const p = flatten(result[1][0]).concat(flatten(result[1][1]));
if (inverted) {
return p.map((x) => (x === 0 ? 1 : 0));
}
return p;
return flatten(result[1][0]).concat(flatten(result[1][1]));
};
/**
+1 -11
View File
@@ -3,7 +3,6 @@ hap.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/packages/core/hap.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Fraction from './fraction.mjs';
export class Hap {
/*
@@ -33,16 +32,7 @@ export class Hap {
}
get duration() {
let duration;
if (typeof this.value?.duration === 'number') {
duration = Fraction(this.value.duration);
} else {
duration = this.whole.end.sub(this.whole.begin);
}
if (typeof this.value?.clip === 'number') {
return duration.mul(this.value.clip);
}
return duration;
return this.whole.end.sub(this.whole.begin).mul(typeof this.value?.clip === 'number' ? this.value?.clip : 1);
}
get endClipped() {
+1 -2
View File
@@ -4,12 +4,11 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as controls from './controls.mjs'; // legacy
import controls from './controls.mjs';
export * from './euclid.mjs';
import Fraction from './fraction.mjs';
import { logger } from './logger.mjs';
export { Fraction, controls };
export * from './controls.mjs';
export * from './hap.mjs';
export * from './pattern.mjs';
export * from './signal.mjs';
-10
View File
@@ -1,16 +1,6 @@
export const logKey = 'strudel.log';
let debounce = 1000,
lastMessage,
lastTime;
export function logger(message, type, data = {}) {
let t = performance.now();
if (lastMessage === message && t - lastTime < debounce) {
return;
}
lastMessage = message;
lastTime = t;
console.log(`%c${message}`, 'background-color: black;color:white;border-radius:15px');
if (typeof document !== 'undefined' && typeof CustomEvent !== 'undefined') {
document.dispatchEvent(
+21
View File
@@ -2203,6 +2203,12 @@ export const bypass = register('bypass', function (on, pat) {
*/
export const ribbon = register('ribbon', (offset, cycles, pat) => pat.early(offset).restart(pure(1).slow(cycles)));
// sets absolute duration of haps
// TODO - fix
export const duration = register('duration', function (value, pat) {
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(value)));
});
export const hsla = register('hsla', (h, s, l, a, pat) => {
return pat.color(`hsla(${h}turn,${s * 100}%,${l * 100}%,${a})`);
});
@@ -2235,6 +2241,21 @@ export const velocity = register('velocity', function (velocity, pat) {
return pat.withContext((context) => ({ ...context, velocity: (context.velocity || 1) * velocity }));
});
/**
*
* Multiplies the hap duration with the given factor.
* With samples, `clip` might be a better function to use ([more info](https://github.com/tidalcycles/strudel/pull/598))
* @name legato
* @memberof Pattern
* @example
* note("c3 eb3 g3 c4").legato("<.25 .5 1 2>")
*/
// TODO - fix
export const legato = register('legato', function (value, pat) {
value = Fraction(value);
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
});
//////////////////////////////////////////////////////////////////////
// Control-related functions, i.e. ones that manipulate patterns of
// objects
+4 -65
View File
@@ -244,68 +244,9 @@ export const pickmodF = register('pickmodF', function (lookup, funcs, pat) {
return pat.apply(pickmod(lookup, funcs));
});
/** * Similar to `pick`, but it applies an outerJoin instead of an innerJoin.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickOut = register('pickOut', function (lookup, pat) {
return _pick(lookup, pat, false).outerJoin();
});
/** * The same as `pickOut`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodOut = register('pickmodOut', function (lookup, pat) {
return _pick(lookup, pat, true).outerJoin();
});
/** * Similar to `pick`, but the choosen pattern is restarted when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickRestart = register('pickRestart', function (lookup, pat) {
return _pick(lookup, pat, false).trigzeroJoin();
});
/** * The same as `pickRestart`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodRestart = register('pickmodRestart', function (lookup, pat) {
return _pick(lookup, pat, true).trigzeroJoin();
});
/** * Similar to `pick`, but the choosen pattern is reset when its index is triggered.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickReset = register('pickReset', function (lookup, pat) {
return _pick(lookup, pat, false).trigJoin();
});
/** * The same as `pickReset`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const pickmodReset = register('pickmodReset', function (lookup, pat) {
return _pick(lookup, pat, true).trigJoin();
});
/**
/** * Picks patterns (or plain values) either from a list (by index) or a lookup table (by name).
* Similar to `pick`, but cycles are squeezed into the target ('inhabited') pattern.
* @name inhabit
* @synonyms pickSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
@@ -316,23 +257,21 @@ export const pickmodReset = register('pickmodReset', function (lookup, pat) {
* @example
* s("a@2 [a b] a".inhabit({a: "bd(3,8)", b: "sd sd"})).slow(4)
*/
export const { inhabit, pickSqueeze } = register(['inhabit', 'pickSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
export const inhabit = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
});
/** * The same as `inhabit`, but if you pick a number greater than the size of the list,
* it wraps around, rather than sticking at the maximum value.
* For example, if you pick the fifth pattern of a list of three, you'll get the
* second one.
* @name inhabitmod
* @synonyms pickmodSqueeze
* @param {Pattern} pat
* @param {*} xs
* @returns {Pattern}
*/
export const { inhabitmod, pickmodSqueeze } = register(['inhabitmod', 'pickmodSqueeze'], function (lookup, pat) {
return _pick(lookup, pat, true).squeezeJoin();
export const inhabitmod = register('inhabit', function (lookup, pat) {
return _pick(lookup, pat, false).squeezeJoin();
});
/**
+5 -5
View File
@@ -4,23 +4,23 @@ Copyright (C) 2023 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { s } from '../controls.mjs';
import controls from '../controls.mjs';
import { mini } from '../../mini/mini.mjs';
import { describe, it, expect } from 'vitest';
describe('controls', () => {
it('should support controls', () => {
expect(s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
expect(controls.s('bd').firstCycleValues).toEqual([{ s: 'bd' }]);
});
it('should support compound controls', () => {
expect(s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
expect(controls.s(mini('bd:3')).firstCycleValues).toEqual([{ s: 'bd', n: 3 }]);
expect(controls.s(mini('bd:3 sd:4:1.4')).firstCycleValues).toEqual([
{ s: 'bd', n: 3 },
{ s: 'sd', n: 4, gain: 1.4 },
]);
});
it('should support ignore extra elements in compound controls', () => {
expect(s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
expect(controls.s(mini('bd:3:0.4 sd:4:0.5:3:17')).firstCycleValues).toEqual([
{ s: 'bd', n: 3, gain: 0.4 },
{ s: 'sd', n: 4, gain: 0.5 },
]);
+2 -1
View File
@@ -51,8 +51,9 @@ import {
import { steady } from '../signal.mjs';
import { n, s } from '../controls.mjs';
import controls from '../controls.mjs';
const { n, s } = controls;
const st = (begin, end) => new State(ts(begin, end));
const ts = (begin, end) => new TimeSpan(Fraction(begin), Fraction(end));
const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, context);
+2 -1
View File
@@ -6,7 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th
import { describe, it, expect } from 'vitest';
import { map, valued, mul } from '../value.mjs';
import { n } from '../controls.mjs';
import controls from '../controls.mjs';
const { n } = controls;
describe('Value', () => {
it('unionWith', () => {
+2 -1
View File
@@ -1,4 +1,4 @@
import { noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { controls, noteToMidi, valueToMidi, Pattern, evalScope } from '@strudel/core';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel/webaudio';
import * as core from '@strudel/core';
@@ -17,6 +17,7 @@ export async function prebake() {
// import('@strudel/serial'),
// import('@strudel/csound'),
// import('@strudel/osc'),
controls, // sadly, this cannot be exported from core directly (yet)
);
// load samples
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
+1
View File
@@ -0,0 +1 @@
rustsaw/target
+57
View File
@@ -0,0 +1,57 @@
# superdough-wasm
This is just a very early experiment to find out how to run wasm in an AudioWorklet.
WASM can be compiled from several languages, which are tested here..
## zig
<https://dev.to/sleibrock/webassembly-with-zig-part-1-4onm>
```sh
# (re)compile dsp.zig
brew install zig # prequisite
cd zigsaw
zig build-lib zigsaw.zig -target wasm32-freestanding -dynamic -rdynamic -O ReleaseSmall # build
npx http-server .. -o # run
```
wasm file size: 690B
## rust
<https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm>
```sh
# https://www.rust-lang.org/tools/install
cargo install wasm-pack # prequisite
cd rustsaw
wasm-pack build --target bundler # build
npx http-server .. -o # run
```
wasm file size: 653B
## c
<https://emscripten.org/docs/getting_started/Tutorial.html>
```sh
# brew install emscripten
cd csaw
emcc -O2 csaw.c -o csaw # build
npx http-server .. -o
```
wasm file size: 680B
## assemblyscript
<https://www.assemblyscript.org/getting-started.html#setting-up-a-new-project>
```sh
cd ascsaw
# npm i
npm run asbuild # build
```
wasm file size: 122B !
@@ -0,0 +1,22 @@
{
"targets": {
"debug": {
"outFile": "build/debug.wasm",
"textFile": "build/debug.wat",
"sourceMap": true,
"debug": true
},
"release": {
"outFile": "build/release.wasm",
"textFile": "build/release.wat",
"sourceMap": true,
"optimizeLevel": 3,
"shrinkLevel": 2,
"converge": false,
"noAssert": false
}
},
"options": {
"bindings": "esm"
}
}
@@ -0,0 +1,3 @@
export function saw(t: f64, f: f64): f64 {
return (((f * t * 1.0) % 1.0) - 0.5) * 2.0;
}
@@ -0,0 +1,6 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": [
"./**/*.ts"
]
}
@@ -0,0 +1,2 @@
*
!.gitignore
+54
View File
@@ -0,0 +1,54 @@
{
"name": "ascsaw",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ascsaw",
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
"assemblyscript": "^0.27.22"
}
},
"node_modules/assemblyscript": {
"version": "0.27.22",
"resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.22.tgz",
"integrity": "sha512-6ClobsR4Hxn6K0daYp/+n9qWTqVbpdVeSGSVDqRvUEz66vvFb8atS6nLm+fnQ54JXuXmzLQy0uWYYgB8G59btQ==",
"dev": true,
"dependencies": {
"binaryen": "116.0.0-nightly.20231102",
"long": "^5.2.1"
},
"bin": {
"asc": "bin/asc.js",
"asinit": "bin/asinit.js"
},
"engines": {
"node": ">=16",
"npm": ">=7"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/assemblyscript"
}
},
"node_modules/binaryen": {
"version": "116.0.0-nightly.20231102",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0-nightly.20231102.tgz",
"integrity": "sha512-aPU9tlKdw/gcXx6u4PxtDgOtGjg/ZKnYdk23ctYb70GxZgPhWnGWmnBt01aV5dt5yFFo2V4rbB7SzpSFhViFQA==",
"dev": true,
"bin": {
"wasm-opt": "bin/wasm-opt",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
"dev": true
}
}
}
@@ -0,0 +1,25 @@
{
"name": "ascsaw",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "node tests",
"asbuild:debug": "asc assembly/index.ts --target debug",
"asbuild:release": "asc assembly/index.ts --target release",
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
"start": "npx serve ."
},
"author": "",
"license": "ISC",
"devDependencies": {
"assemblyscript": "^0.27.22"
},
"type": "module",
"exports": {
".": {
"import": "./build/release.js",
"types": "./build/release.d.ts"
}
}
}
+1
View File
@@ -0,0 +1 @@
csaw
+7
View File
@@ -0,0 +1,7 @@
#include <math.h>
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
double saw(double t, double f) {
return fmod((f * t * 1.0), 1.0) - 0.5 * 2.0;
}
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<title>WASM AudioWorklet Demo</title>
</head>
<body>
<button id="play">play</button>
<input type="range" min="55" max="880" id="freq" />
</body>
<script src="./main.js"></script>
</html>
+24
View File
@@ -0,0 +1,24 @@
let ac;
document.getElementById('play').addEventListener('click', async () => {
ac = ac || new AudioContext();
await ac.resume();
await ac.audioWorklet.addModule('./worklet.js');
const node = new AudioWorkletNode(ac, 'saw-processor');
//let res = await fetch('./zigsaw/zigsaw.wasm');
// let res = await fetch('./csaw/csaw.wasm');
let res = await fetch('./ascsaw/build/release.wasm');
//let res = await fetch('./rustsaw/pkg/rustsaw_bg.wasm');
const buffer = await res.arrayBuffer();
node.port.onmessage = (e) => {
if (e.data === 'OK') {
console.log('worklet ready');
}
};
node.port.postMessage({ webassembly: buffer });
node.connect(ac.destination);
document.getElementById('freq').addEventListener('input', async (e) => {
node.port.postMessage({ frequency: e.target.value });
});
});
+123
View File
@@ -0,0 +1,123 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bumpalo"
version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustsaw"
version = "0.1.0"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "syn"
version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasm-bindgen"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
@@ -0,0 +1,13 @@
[package]
name = "rustsaw"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
@@ -0,0 +1,6 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn saw(t: f64, f: f64) -> f64 {
return (((f * t * 1.0) % 1.0) - 0.5) * 2.0;
}
+38
View File
@@ -0,0 +1,38 @@
class SawProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.t = 0; // samples passed
this.f = 110;
this.port.onmessage = (e) => {
const key = Object.keys(e.data)[0];
const value = e.data[key];
switch (key) {
case 'webassembly':
WebAssembly.instantiate(value, this.importObject).then((result) => {
this.api = result.instance.exports;
this.port.postMessage('OK');
});
break;
case 'frequency':
this.f = value;
}
};
}
process(inputs, outputs, parameters) {
if (this.api) {
const output = outputs[0];
for (let i = 0; i < output[0].length; i++) {
let t = this.t;
let out = 0;
out = this.api.saw(t / 44100, this.f);
output.forEach((channel) => {
channel[i] = out;
});
this.t++;
}
}
return true;
}
}
registerProcessor('saw-processor', SawProcessor);
Binary file not shown.
@@ -0,0 +1,5 @@
const std = @import("std");
export fn saw(t: f64, f: f64) f64 {
return ((@mod(f * t, 1.0)) - 0.5) * 2.0;
}
+1 -1
View File
@@ -251,7 +251,7 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
nudge = 0, // TODO: is this in seconds?
cut,
loop,
clip = undefined, // if set, samples will be cut off when the hap ends
clip = undefined, // if 1, samples will be cut off when the hap ends
n = 0,
note,
speed = 1, // sample playback speed
-1
View File
@@ -518,7 +518,6 @@ export const superdough = async (value, deadline, hapDuration) => {
// toDisconnect = all the node that should be disconnected in onended callback
// this is crucial for performance
toDisconnect = chain.concat([delaySend, reverbSend, analyserSend]);
return { mute: (dt) => post.gain.linearRampToValueAtTime(0, ac.currentTime + dt) };
};
export const superdoughTrigger = (t, hap, ct, cps) => superdough(hap, t - ct, hap.duration / cps, cps);
+2 -1
View File
@@ -7,9 +7,10 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes
import { pure, n, seq } from '@strudel/core';
import { pure, controls, seq } from '@strudel/core';
import { describe, it, expect } from 'vitest';
import { mini } from '../../mini/mini.mjs';
const { n } = controls;
describe('tonal', () => {
it('Should run tonal functions ', () => {
-7
View File
@@ -103,8 +103,6 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
const semitones = typeof interval === 'string' ? Interval.semitones(interval) || 0 : interval;
return hap.withValue(() => hap.value + semitones);
}
if (typeof hap.value === 'object')
return hap.withValue(() => ({ ...hap.value, note: Note.simplify(Note.transpose(hap.value.note, interval)) }));
// TODO: move simplify to player to preserve enharmonics
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
@@ -135,11 +133,6 @@ export const scaleTranspose = register('scaleTranspose', function (offset /* : n
if (!hap.context.scale) {
throw new Error('can only use scaleTranspose after .scale');
}
if (typeof hap.value === 'object')
return hap.withValue(() => ({
...hap.value,
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
}));
if (typeof hap.value !== 'string') {
throw new Error('can only use scaleTranspose with notes');
}
+2 -1
View File
@@ -5,7 +5,7 @@ export * from '@strudel/transpiler';
export * from '@strudel/mini';
export * from '@strudel/tonal';
export * from '@strudel/webaudio';
import { Pattern, evalScope } from '@strudel/core';
import { Pattern, evalScope, controls } from '@strudel/core';
import { initAudioOnFirstClick, registerSynthSounds, webaudioScheduler } from '@strudel/webaudio';
// import { registerSoundfonts } from '@strudel/soundfonts';
import { evaluate as _evaluate } from '@strudel/transpiler';
@@ -15,6 +15,7 @@ import { miniAllStrings } from '@strudel/mini';
export async function defaultPrebake() {
const loadModules = evalScope(
evalScope,
controls,
import('@strudel/core'),
import('@strudel/mini'),
import('@strudel/tonal'),
+2 -1
View File
@@ -12,8 +12,9 @@ npm i @strudel/webaudio --save
## Example
```js
import { repl, note } from "@strudel/core";
import { repl, controls } from "@strudel/core";
import { initAudioOnFirstClick, getAudioContext, webaudioOutput } from "@strudel/webaudio";
const { note } = controls;
initAudioOnFirstClick();
const ctx = getAudioContext();
+1
View File
@@ -15,6 +15,7 @@ const hap2value = (hap) => {
return { ...hap.value, velocity: hap.context.velocity };
};
// TODO: bind logger
export const webaudioOutputTrigger = (t, hap, ct, cps) => superdough(hap2value(hap), t - ct, hap.duration / cps, cps);
export const webaudioOutput = (hap, deadline, hapDuration) => superdough(hap2value(hap), deadline, hapDuration);
+42 -21
View File
@@ -2001,27 +2001,6 @@ exports[`runs examples > example "dry" example index 0 1`] = `
]
`;
exports[`runs examples > example "duration" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]",
"[ 1/4 → 1/2 | note:a s:piano duration:0.5 ]",
"[ 1/2 → 3/4 | note:f s:piano duration:0.5 ]",
"[ 3/4 → 1/1 | note:e s:piano duration:0.5 ]",
"[ 1/1 → 5/4 | note:c s:piano duration:1 ]",
"[ 5/4 → 3/2 | note:a s:piano duration:1 ]",
"[ 3/2 → 7/4 | note:f s:piano duration:1 ]",
"[ 7/4 → 2/1 | note:e s:piano duration:1 ]",
"[ 2/1 → 9/4 | note:c s:piano duration:2 ]",
"[ 9/4 → 5/2 | note:a s:piano duration:2 ]",
"[ 5/2 → 11/4 | note:f s:piano duration:2 ]",
"[ 11/4 → 3/1 | note:e s:piano duration:2 ]",
"[ 3/1 → 13/4 | note:c s:piano duration:0.5 ]",
"[ 13/4 → 7/2 | note:a s:piano duration:0.5 ]",
"[ 7/2 → 15/4 | note:f s:piano duration:0.5 ]",
"[ 15/4 → 4/1 | note:e s:piano duration:0.5 ]",
]
`;
exports[`runs examples > example "early" example index 0 1`] = `
[
"[ -1/10 ⇜ (0/1 → 2/5) | s:hh ]",
@@ -3542,6 +3521,48 @@ exports[`runs examples > example "layer" example index 0 1`] = `
]
`;
exports[`runs examples > example "legato" example index 0 1`] = `
[
"[ 0/1 → 1/32 | c4 ]",
"[ 1/4 → 9/32 | eb4 ]",
"[ 1/2 → 17/32 | g4 ]",
"[ 3/4 → 25/32 | bb4 ]",
"[ 1/1 → 17/16 | c4 ]",
"[ 5/4 → 21/16 | eb4 ]",
"[ 3/2 → 25/16 | g4 ]",
"[ 7/4 → 29/16 | bb4 ]",
"[ 2/1 → 17/8 | c4 ]",
"[ 9/4 → 19/8 | eb4 ]",
"[ 5/2 → 21/8 | g4 ]",
"[ 11/4 → 23/8 | bb4 ]",
"[ 3/1 → 51/16 | c4 ]",
"[ 13/4 → 55/16 | eb4 ]",
"[ 7/2 → 59/16 | g4 ]",
"[ 15/4 → 63/16 | bb4 ]",
]
`;
exports[`runs examples > example "legato" example index 0 2`] = `
[
"[ 0/1 → 1/16 | note:c3 ]",
"[ 1/4 → 5/16 | note:eb3 ]",
"[ 1/2 → 9/16 | note:g3 ]",
"[ 3/4 → 13/16 | note:c4 ]",
"[ 1/1 → 9/8 | note:c3 ]",
"[ 5/4 → 11/8 | note:eb3 ]",
"[ 3/2 → 13/8 | note:g3 ]",
"[ 7/4 → 15/8 | note:c4 ]",
"[ 2/1 → 9/4 | note:c3 ]",
"[ 9/4 → 5/2 | note:eb3 ]",
"[ 5/2 → 11/4 | note:g3 ]",
"[ 11/4 → 3/1 | note:c4 ]",
"[ 3/1 → 7/2 | note:c3 ]",
"[ 13/4 → 15/4 | note:eb3 ]",
"[ 7/2 → 4/1 | note:g3 ]",
"[ 15/4 → 17/4 | note:c4 ]",
]
`;
exports[`runs examples > example "leslie" example index 0 1`] = `
[
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]",
+4 -1
View File
@@ -7,6 +7,7 @@ import { evaluate } from '@strudel/transpiler';
import { evalScope } from '@strudel/core';
import * as strudel from '@strudel/core';
import * as webaudio from '@strudel/webaudio';
import controls from '@strudel/core/controls.mjs';
// import gist from '@strudel/core/gist.js';
import { mini, m } from '@strudel/mini/mini.mjs';
// import * as voicingHelpers from '@strudel/tonal/voicings.mjs';
@@ -20,6 +21,7 @@ import '@strudel/xen/xen.mjs';
// import '@strudel/osc/osc.mjs';
// import '@strudel/webaudio/webaudio.mjs';
// import '@strudel/serial/serial.mjs';
// import controls from '@strudel/core/controls.mjs';
import '../website/src/repl/piano';
class MockedNode {
@@ -151,9 +153,10 @@ evalScope(
strudel,
toneHelpersMocked,
uiHelpersMocked,
controls,
webaudio,
tonalHelpers,
/*
/* controls,
toneHelpers,
voicingHelpers,
drawHelpers,
+5 -1
View File
@@ -34,7 +34,11 @@ Some of these have equivalent operators in the Mini Notation:
<JsDoc client:idle name="Pattern.late" h={0} />
## clip / legato
## legato
<JsDoc client:idle name="Pattern.legato" h={0} />
## clip
<JsDoc client:idle name="clip" h={0} />
+6 -2
View File
@@ -1,4 +1,4 @@
import { evalScope, hash2code, logger } from '@strudel/core';
import { controls, evalScope, hash2code, logger } from '@strudel/core';
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '@strudel/webaudio';
@@ -92,7 +92,11 @@ export function loadModules() {
modules = modules.concat([import('@strudel/midi'), import('@strudel/osc')]);
}
return evalScope(settingPatterns, ...modules);
return evalScope(
controls, // sadly, this cannot be exported from core direclty
settingPatterns,
...modules,
);
}
let lastShared;