From 3b65cfd5c78fefeeed6a233dbffaf3e17cdaf9eb Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Mon, 31 Jul 2023 21:56:43 +0200 Subject: [PATCH] experimental strudel audio engine support --- package.json | 1 + src/API.ts | 77 +++++++++++++++++++++++++++++++++------------------- yarn.lock | 20 ++++++++++++++ 3 files changed, 70 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 5202ab7..6885687 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@codemirror/lang-javascript": "^6.1.9", "@codemirror/theme-one-dark": "^6.1.2", "@replit/codemirror-vim": "^6.0.14", + "@strudel.cycles/webaudio": "^0.8.2", "autoprefixer": "^10.4.14", "codemirror": "^6.0.1", "postcss": "^8.4.27", diff --git a/src/API.ts b/src/API.ts index 9cb4568..fc6483e 100644 --- a/src/API.ts +++ b/src/API.ts @@ -3,14 +3,28 @@ import { scale } from './Scales'; import { tryEvaluate } from "./Evaluator"; import { MidiConnection } from "./IO/MidiConnection"; // @ts-ignore +import { webaudioOutput, samples } from '@strudel.cycles/webaudio'; +// @ts-ignore import { ZZFX, zzfx } from "zzfx"; + + +const sound = (value: any) => ({ + value, + context: {}, + ensureObjectValue: () => { } +}); + export class UserAPI { variables: { [key: string]: any } = {} MidiConnection: MidiConnection = new MidiConnection() + strudelSound = webaudioOutput() + load: samples - constructor(public app: Editor) { } + constructor(public app: Editor) { + this.load = samples("github:tidalcycles/Dirt-Samples/master"); + } // ============================================================= // Utility functions @@ -27,13 +41,15 @@ export class UserAPI { } r = this.rate - script(...args: number[]): void { - args.forEach(arg => { tryEvaluate(this.app, this.app.universes[ - this.app.selected_universe].locals[arg]) }) + script(...args: number[]): void { + args.forEach(arg => { + tryEvaluate(this.app, this.app.universes[ + this.app.selected_universe].locals[arg]) + }) } s = this.script - clearscript(script: number): void { + clearscript(script: number): void { this.app.universes[this.app.selected_universe].locals[script] = { candidate: '', committed: '', evaluations: 0 } @@ -42,8 +58,8 @@ export class UserAPI { copyscript(from: number, to: number): void { // Copy a script to another script - this.app.universes[this.app.selected_universe].locals[to] = - this.app.universes[this.app.selected_universe].locals[from] + this.app.universes[this.app.selected_universe].locals[to] = + this.app.universes[this.app.selected_universe].locals[from] } cps = this.copyscript @@ -55,7 +71,7 @@ export class UserAPI { public midi_outputs(): void { console.log(this.MidiConnection.listMidiOutputs()) } - + public midi_output(outputName: string): void { if (!outputName) { console.log(this.MidiConnection.getCurrentMidiPort()) @@ -69,7 +85,7 @@ export class UserAPI { } public note(note: number, channel: number, velocity: number, duration: number): void { - this.MidiConnection.sendMidiNote( note, channel, velocity, duration) + this.MidiConnection.sendMidiNote(note, channel, velocity, duration) } public midi_clock(): void { @@ -97,7 +113,7 @@ export class UserAPI { } } v = this.variable - + public delete_variable(name: string): void { delete this.variables[name] } @@ -111,7 +127,7 @@ export class UserAPI { // ============================================================= // Small algorithmic functions // ============================================================= - + pick(...array: T[]): T { return array[Math.floor(Math.random() * array.length)] } seqbeat(...array: T[]): T { return array[this.app.clock.time_position.beat % array.length] } seqbar(...array: T[]): T { return array[this.app.clock.time_position.bar % array.length] } @@ -130,30 +146,30 @@ export class UserAPI { // ============================================================= public quantize(value: number, quantization: number[]): number { - if (quantization.length === 0) { return value } - let closest = quantization[0] - quantization.forEach(q => { - if (Math.abs(q - value) < Math.abs(closest - value)) { closest = q } - }) - return closest + if (quantization.length === 0) { return value } + let closest = quantization[0] + quantization.forEach(q => { + if (Math.abs(q - value) < Math.abs(closest - value)) { closest = q } + }) + return closest } quant = this.quantize public clamp(value: number, min: number, max: number): number { - return Math.min(Math.max(value, min), max) + return Math.min(Math.max(value, min), max) } cmp = this.clamp // ============================================================= // Transport functions // ============================================================= - - bpm(bpm: number):void { - this.app.clock.bpm = bpm + + bpm(bpm: number): void { + this.app.clock.bpm = bpm } - time_signature(numerator: number, denominator: number):void { - this.app.clock.time_signature = [ numerator, denominator ] + time_signature(numerator: number, denominator: number): void { + this.app.clock.time_signature = [numerator, denominator] } // ============================================================= @@ -182,8 +198,8 @@ export class UserAPI { get e7() { return this.app.universes[this.app.selected_universe].locals[6].evaluations } get e8() { return this.app.universes[this.app.selected_universe].locals[7].evaluations } get e9() { return this.app.universes[this.app.selected_universe].locals[8].evaluations } - public evaluations_number(index:number): number { - return this.app.universes[this.app.selected_universe].locals[index].evaluations + public evaluations_number(index: number): number { + return this.app.universes[this.app.selected_universe].locals[index].evaluations } e = this.evaluations_number @@ -214,14 +230,14 @@ export class UserAPI { return final_pulses.some(p => p == true) } - every(...n: number[]): boolean { + every(...n: number[]): boolean { return n.some(n => this.i % n === 0) } - + mod(...pulse: number[]): boolean { return pulse.some(p => this.app.clock.time_position.pulse % p === 0) } modbar(...bar: number[]): boolean { return bar.some(b => this.app.clock.time_position.bar % b === 0) } - + // ============================================================= // Trivial functions // ============================================================= @@ -229,4 +245,9 @@ export class UserAPI { // Small ZZFX interface for playing with this synth zzfx = (...thing: number[]) => zzfx(...thing); + playSound = async (values: object) => { + await this.load; + webaudioOutput(sound(values), 0.01) + } + } diff --git a/yarn.lock b/yarn.lock index f5a173f..d5a3296 100644 --- a/yarn.lock +++ b/yarn.lock @@ -301,6 +301,21 @@ resolved "https://registry.yarnpkg.com/@replit/codemirror-vim/-/codemirror-vim-6.0.14.tgz#8f44740b0497406b551726946c9b30f21c867671" integrity sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA== +"@strudel.cycles/core@0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@strudel.cycles/core/-/core-0.8.2.tgz#62e957a3636b39938d1c4ecc3fd766d02fc523bb" + integrity sha512-rmtrDfy6S/NH9HgFy4rVRRAwVXzYvR85Qwqr/WQqEZQh6OCBW9yXQQWGOZYiBi85Y2FZqwWppBImyGVA6ZzuyA== + dependencies: + fraction.js "^4.2.0" + +"@strudel.cycles/webaudio@^0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@strudel.cycles/webaudio/-/webaudio-0.8.2.tgz#90e7254544d9601f4365a21f3e0dcdfbc952aded" + integrity sha512-IdjSuqd8BUYJ8BOl3lJ8uJCR4G5g0W1YryhPp3uUQ7NaVDlaM4ZnC8qejEI8yoBF+ntWKHInjf5qpj7v5+ttbA== + dependencies: + "@strudel.cycles/core" "0.8.2" + nanostores "^0.8.1" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -651,6 +666,11 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanostores@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/nanostores/-/nanostores-0.8.1.tgz#963577028ac10eeb50bec376535f4762ab5af9be" + integrity sha512-1ZCfQtII2XeFDrtqXL2cdQ/diGrLxzRB3YMyQjn8m7GSGQrJfGST2iuqMpWnS/ZlifhtjgR/SX0Jy6Uij6lRLA== + node-releases@^2.0.12: version "2.0.13" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"