diff --git a/website/src/pages/vanilla/mini.astro b/website/src/pages/vanilla/mini.astro new file mode 100644 index 00000000..77dd58b6 --- /dev/null +++ b/website/src/pages/vanilla/mini.astro @@ -0,0 +1,65 @@ +--- +import HeadCommonNew from '../../components/HeadCommonNew.astro'; +--- + + + + + Strudel Vanilla REPL + + +

vanilli repl

+ ,6)/2").bank('RolandTR707') + , + s("~ sd:<0 1>").bank('RolandTR707').room("<0 .5>") + .lastOf(8, x=>x.segment("12").end(.2).gain(isaw)) + , + s("[tb ~ tb]").bank('RolandTR707') + .clip(0).release(.08).room(.2) + ).off(-1/6, x=>x.speed(.7).gain(.2).degrade()) + , + stack( + note(",6) ~!2 [f1?]*2>") + .s("sawtooth").lpf(perlin.range(400,1000)) + .lpa(.1).lpenv(-3).room(.2) + .lpq(8).noise(.2) + .add(note("0,.1")) + , + chord("<~ Gm9 ~!2>") + .dict('ireal').voicing() + .s("sawtooth").vib("2:.1") + .lpf(1000).lpa(.1).lpenv(-4) + .room(.5) + , + n(run(3)).chord("/8") + .dict('ireal-ext') + .off(1/2, add(n(4))) + .voicing() + .clip(.1).release(.05) + .s("sine").jux(rev) + .sometimesBy(sine.slow(16), add(note(12))) + .room(.75) + .lpf(sine.range(200,2000).slow(16)) + .gain(saw.slow(4).div(2)) + ).add(note(perlin.range(0,.5))) + )`}> + + + diff --git a/website/src/repl/vanilla/strudel-editor.mjs b/website/src/repl/vanilla/strudel-editor.mjs new file mode 100644 index 00000000..85acc72e --- /dev/null +++ b/website/src/repl/vanilla/strudel-editor.mjs @@ -0,0 +1,132 @@ +import { getDrawContext, silence, controls, evalScope, hash2code, code2hash } from '@strudel.cycles/core'; +import { StrudelMirror } from '@strudel/codemirror'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { registerSoundfonts } from '@strudel.cycles/soundfonts'; +import { + getAudioContext, + webaudioOutput, + registerSynthSounds, + registerZZFXSounds, + samples, +} from '@strudel.cycles/webaudio'; + +function camelToKebab(camelCaseString) { + return camelCaseString.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} +function kebabToCamel(kebabCaseString) { + return kebabCaseString.replace(/-([a-z])/g, function (match, group) { + return group.toUpperCase(); + }); +} + +const initialSettings = { + keybindings: 'strudelTheme', + isLineNumbersDisplayed: true, + isActiveLineHighlighted: true, + isAutoCompletionEnabled: false, + isPatternHighlightingEnabled: true, + isFlashEnabled: true, + isTooltipEnabled: false, + isLineWrappingEnabled: false, + theme: 'teletext', + fontFamily: 'monospace', + fontSize: 18, +}; +const settingAttributes = Object.keys(initialSettings).map(camelToKebab); +const parseAttribute = (name, value) => { + const camel = kebabToCamel(name); + const type = typeof initialSettings[camel]; + // console.log('type', type, name); + if (type === 'boolean') { + return ['1', 'true'].includes(value); + } + if (type === 'number') { + return Number(value); + } + return value; +}; +// console.log('attributes', settingAttributes); + +class StrudelEditor extends HTMLElement { + static observedAttributes = ['code', ...settingAttributes]; + settings = initialSettings; + constructor() { + super(); + } + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'code') { + this.code = newValue; + this.editor?.setCode(initialCode); + } else if (settingAttributes.includes(name)) { + const camel = kebabToCamel(name); + this.settings[camel] = parseAttribute(name, newValue); + // console.log('name', name, newValue, camel, this.settings[camel]); + this.editor?.updateSettings(this.settings); + } + } + connectedCallback() { + const drawContext = getDrawContext(); + const drawTime = [-2, 2]; + this.container = document.createElement('div'); + this.appendChild(this.container); + this.editor = new StrudelMirror({ + defaultOutput: webaudioOutput, + getTime: () => getAudioContext().currentTime, + transpiler, + root: this.container, + initialCode: '// LOADING', + pattern: silence, + settings: this.settings, + drawTime, + onDraw: (haps, time, frame, painters) => { + painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); + painters?.forEach((painter) => { + // ctx time haps drawTime paintOptions + painter(drawContext, time, haps, drawTime, { clear: false }); + }); + }, + prebake: async () => { + // populate scope / lazy load modules + const modulesLoading = evalScope( + import('@strudel.cycles/core'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/webaudio'), + import('@strudel/codemirror'), + import('@strudel/hydra'), + import('@strudel.cycles/soundfonts'), + // import('@strudel.cycles/xen'), + // import('@strudel.cycles/serial'), + // import('@strudel.cycles/csound'), + /* import('@strudel.cycles/midi'), */ + // import('@strudel.cycles/osc'), + controls, // sadly, this cannot be exported from core directly (yet) + ); + // load samples + const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/'; + await Promise.all([ + modulesLoading, + registerSynthSounds(), + registerZZFXSounds(), + registerSoundfonts(), + samples(`${ds}/tidal-drum-machines.json`), + samples(`${ds}/piano.json`), + samples(`${ds}/Dirt-Samples.json`), + samples(`${ds}/EmuSP12.json`), + samples(`${ds}/vcsl.json`), + ]); + }, + afterEval: ({ code }) => { + window.location.hash = '#' + code2hash(code); + }, + }); + // init settings + this.editor.updateSettings(this.settings); + this.editor.setCode(this.code); + // settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); + // onEvent('strudel-toggle-play', () => this.editor.toggle()); + } + // Element functionality written in here +} + +customElements.define('strudel-editor', StrudelEditor);