Merge branch 'main' into patterns-tab

This commit is contained in:
Felix Roos 2023-09-17 17:28:18 +02:00
commit 6c3173f45b
76 changed files with 2981 additions and 531 deletions

View file

@ -53,3 +53,7 @@
#code .cm-cursor {
border-left: 2px solid currentcolor !important;
}
#code .cm-foldGutter {
display: none !important;
}

View file

@ -5,15 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
import {
CodeMirror,
cx,
flash,
useHighlighting,
useStrudel,
useKeydown,
updateMiniLocations,
} from '@strudel.cycles/react';
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid';
@ -28,6 +20,8 @@ import { themes } from './themes.mjs';
import { settingsMap, useSettings, setLatestCode } from '../settings.mjs';
import Loader from './Loader';
import { settingPatterns } from '../settings.mjs';
import { code2hash, hash2code } from './helpers.mjs';
import { isTauri } from '../tauri.mjs';
const { latestCode } = settingsMap.get();
@ -43,14 +37,22 @@ const modules = [
import('@strudel.cycles/core'),
import('@strudel.cycles/tonal'),
import('@strudel.cycles/mini'),
import('@strudel.cycles/midi'),
import('@strudel.cycles/xen'),
import('@strudel.cycles/webaudio'),
import('@strudel.cycles/osc'),
import('@strudel.cycles/serial'),
import('@strudel.cycles/soundfonts'),
import('@strudel.cycles/csound'),
];
if (isTauri()) {
modules.concat([
import('@strudel/desktopbridge/loggerbridge.mjs'),
import('@strudel/desktopbridge/midibridge.mjs'),
import('@strudel/desktopbridge/oscbridge.mjs'),
]);
} else {
modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]);
}
const modulesLoading = evalScope(
controls, // sadly, this cannot be exported from core direclty
@ -73,11 +75,11 @@ async function initCode() {
try {
const initialUrl = window.location.href;
const hash = initialUrl.split('?')[1]?.split('#')?.[0];
const codeParam = window.location.href.split('#')[1];
const codeParam = window.location.href.split('#')[1] || '';
// looking like https://strudel.tidalcycles.org/?J01s5i1J0200 (fixed hash length)
if (codeParam) {
// looking like https://strudel.tidalcycles.org/#ImMzIGUzIg%3D%3D (hash length depends on code length)
return atob(decodeURIComponent(codeParam || ''));
return hash2code(codeParam);
} else if (hash) {
return supabase
.from('code')
@ -125,6 +127,8 @@ export function Repl({ embedded = false }) {
panelPosition,
} = useSettings();
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
useStrudel({
initialCode: '// LOADING...',
@ -140,13 +144,15 @@ export function Repl({ embedded = false }) {
setMiniLocations(meta.miniLocations);
setPending(false);
setLatestCode(code);
window.location.hash = '#' + encodeURIComponent(btoa(code));
window.location.hash = '#' + code2hash(code);
},
onEvalError: (err) => {
setPending(false);
},
onToggle: (play) => !play && cleanupDraw(false),
drawContext,
// drawTime: [0, 6],
paintOptions,
});
// init code

View file

@ -0,0 +1,25 @@
export function unicodeToBase64(text) {
const utf8Bytes = new TextEncoder().encode(text);
const base64String = btoa(String.fromCharCode(...utf8Bytes));
return base64String;
}
export function base64ToUnicode(base64String) {
const utf8Bytes = new Uint8Array(
atob(base64String)
.split('')
.map((char) => char.charCodeAt(0)),
);
const decodedText = new TextDecoder().decode(utf8Bytes);
return decodedText;
}
export function code2hash(code) {
return encodeURIComponent(unicodeToBase64(code));
//return '#' + encodeURIComponent(btoa(code));
}
export function hash2code(hash) {
return base64ToUnicode(decodeURIComponent(hash));
//return atob(decodeURIComponent(codeParam || ''));
}

View file

@ -1,5 +1,5 @@
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
import { registerSynthSounds, samples } from '@strudel.cycles/webaudio';
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
import './piano.mjs';
import './files.mjs';
@ -8,6 +8,7 @@ export async function prebake() {
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
await Promise.all([
registerSynthSounds(),
registerZZFXSounds(),
//registerSoundfonts(),
// need dynamic import here, because importing @strudel.cycles/soundfonts fails on server:
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically

View file

@ -34,6 +34,7 @@ import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
import bluescreen, { settings as bluescreenSettings } from '@strudel.cycles/react/src/themes/bluescreen';
import blackscreen, { settings as blackscreenSettings } from '@strudel.cycles/react/src/themes/blackscreen';
import whitescreen, { settings as whitescreenSettings } from '@strudel.cycles/react/src/themes/whitescreen';
import teletext, { settings as teletextSettings } from '@strudel.cycles/react/src/themes/teletext';
import algoboy, { settings as algoboySettings } from '@strudel.cycles/react/src/themes/algoboy';
import terminal, { settings as terminalSettings } from '@strudel.cycles/react/src/themes/terminal';
@ -42,6 +43,7 @@ export const themes = {
bluescreen,
blackscreen,
whitescreen,
teletext,
algoboy,
terminal,
abcdef,
@ -95,6 +97,7 @@ export const settings = {
bluescreen: bluescreenSettings,
blackscreen: blackscreenSettings,
whitescreen: whitescreenSettings,
teletext: teletextSettings,
algoboy: algoboySettings,
terminal: terminalSettings,
abcdef: {
@ -469,3 +472,11 @@ function getCircularReplacer() {
function stringifySafe(json) {
return JSON.stringify(json, getCircularReplacer());
}
export function injectStyle(rule) {
const newStyle = document.createElement('style');
document.head.appendChild(newStyle);
const styleSheet = newStyle.sheet;
const ruleIndex = styleSheet.insertRule(rule, 0);
return () => styleSheet.deleteRule(ruleIndex);
}

View file

@ -229,7 +229,9 @@ stack(
.add("0,.02")
.note().gain(.3)
.clip("<1@3 [.3 1]>/2")
.s('sawtooth').cutoff(600).color('#F8E71C'),
.cutoff(600)
.lpa(.2).lpenv(-4)
.s('sawtooth').color('#F8E71C'),
).fast(3/2)
//.pianoroll({fold:1})`;
@ -430,7 +432,7 @@ export const waa2 = `// "Waa2"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos
n(
note(
"a4 [a3 c3] a3 c3"
.sub("<7 12 5 12>".slow(2))
.off(1/4,x=>x.add(7))
@ -442,7 +444,7 @@ n(
.cutoff(cosine.range(500,4000).slow(16))
.gain(.5)
.room(.5)
`;
.lpa(.125).lpenv(-2).v("8:.125").fanchor(.25)`;
export const hyperpop = `// "Hyperpop"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@ -468,7 +470,9 @@ stack(
.note()
.s("sawtooth,square")
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
.apply(filter1),
.apply(filter1)
.lpa(.1).lpenv(2).ftype('24db')
,
"~@3 [<2 3>,<4 5>]"
.echo(4,1/16,.7)
.scale(scales)
@ -537,6 +541,7 @@ stack(
.s('sawtooth') // waveform
.gain(.4) // turn down
.cutoff(sine.slow(7).range(300,5000)) // automate cutoff
.lpa(.1).lpenv(-2)
//.hush()
,chord("<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>")
.dict('lefthand').voicing() // chords
@ -559,6 +564,7 @@ stack(
)
.slow(3/2)`;
/*
export const outroMusic = `// "Outro music"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos
@ -576,8 +582,8 @@ chord("<C^7 Am7 Dm7 G7>*2").dict('lefthand').anchor("G4").voicing()
.s("gm_epiano1:1")
.color('steelblue')
.stack(
n("<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2")
.scale('C3 major')
"<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2"
.scale('C3 major').note()
.s('sawtooth').color('brown')
)
.attack(0.05).decay(.1).sustain(.7)
@ -589,7 +595,8 @@ chord("<C^7 Am7 Dm7 G7>*2").dict('lefthand').anchor("G4").voicing()
.n(3).color('gray')
).slow(3/2)
//.pianoroll({autorange:1,vertical:1,fold:0})
`;
`;
*/
export const bassFuge = `// "Bass fuge"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
@ -772,7 +779,8 @@ stack(
.gain("0.4,0.4(5,8,-1)"),
note("<0 2 5 3>".scale('G1 minor')).struct("x(5,8,-1)")
.s('sawtooth').decay(.1).sustain(0),
.s('sawtooth').decay(.1).sustain(0)
.lpa(.1).lpenv(-4).lpf(800).lpq(8),
note("<G4 A4 Bb4 A4>,Bb3,D3").struct("~ x*2").s('square').clip(1)
.cutoff(sine.range(500,4000).slow(16)).resonance(10)
@ -803,14 +811,16 @@ stack(
sine.add(saw.slow(4)).range(0,7).segment(8)
.superimpose(x=>x.add(.1))
.scale('G0 minor').note()
.s("sawtooth").decay(.1).sustain(0)
.gain(.4).cutoff(perlin.range(300,3000).slow(8)).resonance(10)
.s("sawtooth")
.gain(.4).decay(.1).sustain(0)
.lpa(.1).lpenv(-4).lpq(10)
.cutoff(perlin.range(300,3000).slow(8))
.degradeBy("0 0.1 .5 .1")
.rarely(add(note("12")))
,
// chord
note("Bb3,D4".superimpose(x=>x.add(.2)))
.s('sawtooth').cutoff(1000).struct("<~@3 [~ x]>")
.s('sawtooth').lpf(1000).struct("<~@3 [~ x]>")
.decay(.05).sustain(.0).delay(.8).delaytime(.125).room(.8)
,
// alien
@ -827,8 +837,8 @@ note("c3 eb3 g3 bb3").palindrome()
.s('sawtooth')
.jux(x=>x.rev().color('green').s('sawtooth'))
.off(1/4, x=>x.add(note("<7 12>/2")).slow(2).late(.005).s('triangle'))
//.delay(.5)
.fast(1).cutoff(sine.range(200,2000).slow(8))
.lpf(sine.range(200,2000).slow(8))
.lpa(.2).lpenv(-2)
.decay(.05).sustain(0)
.room(.6)
.delay(.5).delaytime(.1).delayfeedback(.4)
@ -907,7 +917,13 @@ n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2)
.delay(.2)
.room(.5).pan(sine.range(.3,.6))
.s('piano')
.stack("<<A1 C2>!2 F2 [F2 E2]>".add.out("0 -5".fast(2)).add("0,.12").note().s('sawtooth').clip(1).cutoff(300))
.stack(
"<<A1 C2>!2 F2 F2>"
.add.out("0 -5".fast(2))
.add("0,.12").note()
.s('sawtooth').cutoff(180)
.lpa(.1).lpenv(2)
)
.slow(4)
.stack(s("bd*4, [~ [hh hh? hh?]]*2,~ [sd ~ [sd:2? bd?]]").bank('RolandTR909').gain(.5).slow(2))
`;