├── src ├── components │ ├── audio-context │ │ ├── node.module.sass │ │ ├── views │ │ │ ├── debug │ │ │ │ ├── index.ts │ │ │ │ └── Debug.tsx │ │ │ ├── demo │ │ │ │ ├── index.ts │ │ │ │ └── Demo.tsx │ │ │ ├── menu │ │ │ │ └── index.ts │ │ │ └── project │ │ │ │ ├── index.ts │ │ │ │ ├── plugins │ │ │ │ ├── detectControlls.ts │ │ │ │ ├── detectMount.ts │ │ │ │ └── detectState.ts │ │ │ │ ├── Project.tsx │ │ │ │ └── ProjectControll.tsx │ │ ├── examples │ │ │ ├── index.ts │ │ │ ├── piano.txt │ │ │ ├── club.txt │ │ │ ├── out.txt │ │ │ ├── random.txt │ │ │ ├── super.txt │ │ │ ├── clubs.txt │ │ │ └── car.txt │ │ ├── lib │ │ │ ├── frequencies.ts │ │ │ ├── start.ts │ │ │ ├── avgArray.ts │ │ │ ├── BasePort.tsx │ │ │ ├── pipe.ts │ │ │ ├── refValue.ts │ │ │ ├── line.ts │ │ │ ├── Toggle.tsx │ │ │ ├── BaseNode.tsx │ │ │ ├── Select.tsx │ │ │ ├── Number.tsx │ │ │ └── Range.tsx │ │ ├── assets │ │ │ ├── convolver │ │ │ │ ├── bell.ogg │ │ │ │ ├── church.ogg │ │ │ │ ├── forest.ogg │ │ │ │ ├── irHall.ogg │ │ │ │ ├── london.ogg │ │ │ │ ├── metal.ogg │ │ │ │ ├── spring.ogg │ │ │ │ ├── tank.ogg │ │ │ │ ├── elemente.ogg │ │ │ │ ├── artificial.ogg │ │ │ │ ├── impulsional.ogg │ │ │ │ └── index.ts │ │ │ └── drums │ │ │ │ ├── kit │ │ │ │ ├── hats │ │ │ │ │ ├── hat 01.mp3 │ │ │ │ │ ├── hat 02.mp3 │ │ │ │ │ ├── hat 03.mp3 │ │ │ │ │ ├── hat 04.mp3 │ │ │ │ │ ├── hat 05.mp3 │ │ │ │ │ ├── hat 06.mp3 │ │ │ │ │ ├── hat 07.mp3 │ │ │ │ │ ├── hat 08.mp3 │ │ │ │ │ ├── hat 09.mp3 │ │ │ │ │ └── hat 10.mp3 │ │ │ │ ├── kick │ │ │ │ │ ├── kick 01.mp3 │ │ │ │ │ ├── kick 02.mp3 │ │ │ │ │ ├── kick 03.mp3 │ │ │ │ │ ├── kick 04.mp3 │ │ │ │ │ ├── kick 05.mp3 │ │ │ │ │ ├── kick 06.mp3 │ │ │ │ │ ├── kick 07.mp3 │ │ │ │ │ ├── kick 08.mp3 │ │ │ │ │ ├── kick 09.mp3 │ │ │ │ │ ├── kick 10.mp3 │ │ │ │ │ ├── kick 11.mp3 │ │ │ │ │ └── kick 12.mp3 │ │ │ │ ├── perc │ │ │ │ │ └── perc 01.mp3 │ │ │ │ └── snare │ │ │ │ │ ├── snare 01.mp3 │ │ │ │ │ ├── snare 02.mp3 │ │ │ │ │ ├── snare 03.mp3 │ │ │ │ │ ├── snare 04.mp3 │ │ │ │ │ ├── snare 05.mp3 │ │ │ │ │ ├── snare 06.mp3 │ │ │ │ │ ├── snare 07.mp3 │ │ │ │ │ ├── snare 08.mp3 │ │ │ │ │ ├── snare 09.mp3 │ │ │ │ │ ├── snare 10.mp3 │ │ │ │ │ ├── snare 11.mp3 │ │ │ │ │ ├── snare 12.mp3 │ │ │ │ │ ├── snare 13.mp3 │ │ │ │ │ ├── snare 14.mp3 │ │ │ │ │ ├── snare 15.mp3 │ │ │ │ │ ├── snare 16.mp3 │ │ │ │ │ ├── snare 17.mp3 │ │ │ │ │ ├── snare 18.mp3 │ │ │ │ │ └── snare 19.mp3 │ │ │ │ └── index.ts │ │ ├── _ports.ts │ │ ├── AudioProject.tsx │ │ ├── worklet │ │ │ ├── ValueProcessor.ts │ │ │ ├── MemoryProcessor.ts │ │ │ ├── ImpulseProcessor.ts │ │ │ ├── SequenceProcessor.ts │ │ │ └── TimerProcessor.ts │ │ ├── index.tsx │ │ ├── nodes │ │ │ ├── Destination.tsx │ │ │ ├── Slider.tsx │ │ │ ├── Toggle.tsx │ │ │ ├── Gain.tsx │ │ │ ├── StereoPanner.tsx │ │ │ ├── ShowValue.tsx │ │ │ ├── Delay.tsx │ │ │ ├── Note.tsx │ │ │ ├── Memory.tsx │ │ │ ├── Inpulse.tsx │ │ │ ├── Input.tsx │ │ │ ├── ProjectValue.tsx │ │ │ ├── Timer.tsx │ │ │ ├── Recorder.tsx │ │ │ ├── Turner.tsx │ │ │ ├── Player.tsx │ │ │ ├── Convolver.tsx │ │ │ ├── Analyzer.tsx │ │ │ ├── Smoothing.tsx │ │ │ ├── BiSlideer.tsx │ │ │ ├── Math.tsx │ │ │ ├── ChannelSplitter.tsx │ │ │ ├── DynamicsCompressor.tsx │ │ │ ├── Panner.tsx │ │ │ ├── Oscilloscope.tsx │ │ │ └── Drums.tsx │ │ ├── _groups.ts │ │ ├── ctx.ts │ │ ├── ports │ │ │ ├── AudioPort.tsx │ │ │ └── SignalPort.tsx │ │ ├── _nodes.ts │ │ └── styles.module.sass │ ├── node-editor │ │ ├── node-back │ │ │ ├── index.ts │ │ │ ├── plugins │ │ │ │ ├── detectResize.ts │ │ │ │ └── detectUpdate.ts │ │ │ ├── NodeBack.tsx │ │ │ └── BackVariants.tsx │ │ ├── node-hud │ │ │ ├── index.ts │ │ │ ├── NodeHud.module.sass │ │ │ ├── NodeHud.tsx │ │ │ └── HudPortal.tsx │ │ ├── node-item │ │ │ ├── index.ts │ │ │ ├── plugins │ │ │ │ ├── detectMount.ts │ │ │ │ ├── detectResize.ts │ │ │ │ ├── detectViewDiv.ts │ │ │ │ ├── detectSelect.ts │ │ │ │ ├── detectView.ts │ │ │ │ ├── detectCopy.ts │ │ │ │ └── detectDrag.ts │ │ │ ├── NodeItem.module.sass │ │ │ └── NodeItem.tsx │ │ ├── node-map │ │ │ ├── index.ts │ │ │ ├── NodeMap.module.sass │ │ │ ├── plugins │ │ │ │ ├── detectResize.ts │ │ │ │ ├── detectWheel.ts │ │ │ │ └── detectDrag.ts │ │ │ └── NodeMap.tsx │ │ ├── node-port │ │ │ ├── index.ts │ │ │ ├── plugins │ │ │ │ ├── detectRemove.ts │ │ │ │ ├── detectMount.ts │ │ │ │ ├── detectConnect.ts │ │ │ │ ├── detectDrag.ts │ │ │ │ ├── detectHover.ts │ │ │ │ └── detectPosition.ts │ │ │ ├── NodePort.module.sass │ │ │ └── NodePort.tsx │ │ ├── node-lines │ │ │ ├── index.ts │ │ │ ├── NodeLinesForward.tsx │ │ │ ├── NodeLineForward.tsx │ │ │ ├── NodeLinesBack.tsx │ │ │ ├── NodeLineBack.tsx │ │ │ ├── NodeLine.tsx │ │ │ └── NodeLines.tsx │ │ ├── node-selection │ │ │ ├── index.ts │ │ │ ├── plugins │ │ │ │ ├── detectShift.ts │ │ │ │ ├── detectSelect.ts │ │ │ │ └── detectDrag.ts │ │ │ ├── NodeSelectionView.tsx │ │ │ └── NodeSelection.tsx │ │ ├── node-list │ │ │ ├── index.ts │ │ │ ├── NodeList.tsx │ │ │ └── NodeListItem.tsx │ │ ├── node-layers │ │ │ ├── index.ts │ │ │ ├── NodeLayers.tsx │ │ │ └── NodeLayersItem.tsx │ │ ├── node-project │ │ │ ├── index.ts │ │ │ ├── utils │ │ │ │ ├── LoadEvent.ts │ │ │ │ └── copyPack.ts │ │ │ └── plugins │ │ │ │ └── detectDelete.ts │ │ ├── node-view │ │ │ ├── NodeView.module.sass │ │ │ ├── plugins │ │ │ │ ├── detectViewSvg.ts │ │ │ │ └── detectViewDiv.ts │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── node-back-v2 │ │ │ └── index.tsx │ └── canvas │ │ └── index.tsx ├── library │ ├── loader │ │ ├── index.ts │ │ └── audio.ts │ ├── signals │ │ ├── index.ts │ │ └── signalRef.ts │ ├── fv │ │ └── index.ts │ ├── dispose │ │ └── index.ts │ ├── frames │ │ └── index.ts │ ├── base64 │ │ └── index.ts │ ├── function │ │ └── index.ts │ ├── gzip │ │ └── index.ts │ └── dom │ │ └── index.ts ├── fonts │ └── icons │ │ ├── fonts │ │ ├── icons.ttf │ │ └── icons.woff │ │ ├── Read Me.txt │ │ └── demo-files │ │ ├── demo.js │ │ └── demo.css ├── App.tsx ├── index.html ├── index.ts └── index.sass ├── screen ├── screen1.png ├── screen2.png ├── screen3.png └── screen4.png ├── tsconfig.node.json ├── .gitignore ├── .github └── workflows │ └── main.yml ├── vite.config.ts ├── tsconfig.json ├── LICENSE └── package.json /src/components/audio-context/node.module.sass: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/library/loader/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./audio"; -------------------------------------------------------------------------------- /src/library/signals/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./signalRef"; -------------------------------------------------------------------------------- /src/components/audio-context/views/debug/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Debug"; -------------------------------------------------------------------------------- /src/components/audio-context/views/demo/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Demo"; -------------------------------------------------------------------------------- /src/components/audio-context/views/menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Menu"; -------------------------------------------------------------------------------- /src/components/node-editor/node-back/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeBack"; -------------------------------------------------------------------------------- /src/components/node-editor/node-hud/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeHud"; -------------------------------------------------------------------------------- /src/components/node-editor/node-item/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeItem"; -------------------------------------------------------------------------------- /src/components/node-editor/node-map/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeMap"; -------------------------------------------------------------------------------- /src/components/node-editor/node-port/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodePort"; -------------------------------------------------------------------------------- /src/components/audio-context/views/project/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Project"; -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeLines"; -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeSelection"; -------------------------------------------------------------------------------- /screen/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/screen/screen1.png -------------------------------------------------------------------------------- /screen/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/screen/screen2.png -------------------------------------------------------------------------------- /screen/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/screen/screen3.png -------------------------------------------------------------------------------- /screen/screen4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/screen/screen4.png -------------------------------------------------------------------------------- /src/components/audio-context/examples/index.ts: -------------------------------------------------------------------------------- 1 | export const examples = import.meta.glob('./*.txt'); -------------------------------------------------------------------------------- /src/components/node-editor/node-list/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeList"; 2 | export * from "./NodeListItem"; -------------------------------------------------------------------------------- /src/fonts/icons/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/fonts/icons/fonts/icons.ttf -------------------------------------------------------------------------------- /src/components/node-editor/node-layers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeLayers"; 2 | export * from "./NodeLayersItem"; -------------------------------------------------------------------------------- /src/components/node-editor/node-project/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./NodeProject"; 2 | export * from "./utils/LoadEvent"; -------------------------------------------------------------------------------- /src/fonts/icons/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/fonts/icons/fonts/icons.woff -------------------------------------------------------------------------------- /src/components/audio-context/lib/frequencies.ts: -------------------------------------------------------------------------------- 1 | export const frequencies = [0, 32, 64, 128, 256, 512, 1024, 2048, 4090, 8192, 16384, 22000]; -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/bell.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/bell.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/church.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/church.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/forest.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/forest.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/irHall.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/irHall.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/london.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/london.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/metal.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/metal.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/spring.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/spring.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/tank.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/tank.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/elemente.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/elemente.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/artificial.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/artificial.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/impulsional.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/convolver/impulsional.ogg -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 01.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 02.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 03.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 04.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 05.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 05.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 06.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 06.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 07.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 07.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 08.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 08.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 09.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 09.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/hats/hat 10.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/hats/hat 10.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/convolver/index.ts: -------------------------------------------------------------------------------- 1 | import { audioLoader } from "$library/loader"; 2 | export default audioLoader( 3 | import.meta.glob("./*.ogg") 4 | ); 5 | -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/index.ts: -------------------------------------------------------------------------------- 1 | import { audioLoader } from "$library/loader"; 2 | export default audioLoader( 3 | import.meta.glob("./**/*.mp3") 4 | ); 5 | -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 01.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 02.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 03.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 04.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 05.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 05.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 06.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 06.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 07.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 07.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 08.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 08.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 09.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 09.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 10.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 10.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 11.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 11.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/kick/kick 12.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/kick/kick 12.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/perc/perc 01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/perc/perc 01.mp3 -------------------------------------------------------------------------------- /src/components/node-editor/node-map/NodeMap.module.sass: -------------------------------------------------------------------------------- 1 | .map 2 | width: 100% 3 | height: 100% 4 | position: relative 5 | overflow: hidden 6 | transform-origin: 0 0 7 | -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 01.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 01.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 02.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 02.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 03.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 03.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 04.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 04.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 05.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 05.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 06.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 06.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 07.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 07.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 08.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 08.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 09.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 09.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 10.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 10.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 11.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 11.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 12.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 12.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 13.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 13.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 14.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 14.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 15.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 15.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 16.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 16.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 17.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 17.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 18.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 18.mp3 -------------------------------------------------------------------------------- /src/components/audio-context/assets/drums/kit/snare/snare 19.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicimpa/w-nodes/HEAD/src/components/audio-context/assets/drums/kit/snare/snare 19.mp3 -------------------------------------------------------------------------------- /src/library/fv/index.ts: -------------------------------------------------------------------------------- 1 | export type TFV = T | ((...args: A) => T); 2 | export const fv = (v: TFV, ...args: A) => ( 3 | v instanceof Function ? v(...args) : v 4 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectRemove.ts: -------------------------------------------------------------------------------- 1 | import { NodePort } from "../NodePort"; 2 | 3 | export default (ctx: NodePort) => { 4 | 5 | return () => { 6 | ctx.lines.disconnect(ctx); 7 | }; 8 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectMount.ts: -------------------------------------------------------------------------------- 1 | import { NodePort } from "../NodePort"; 2 | 3 | export default (ctx: NodePort) => { 4 | ctx.item.ports.add(ctx); 5 | return () => ctx.item.ports.delete(ctx); 6 | }; -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { AudioContext } from "$components/audio-context"; 2 | import { Editor } from "$components/node-editor"; 3 | 4 | 5 | export const App = () => ( 6 | 7 | 8 | 9 | ); 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/audio-context/_ports.ts: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "./ports/AudioPort"; 2 | import { BasePort } from "./lib/BasePort"; 3 | import { SignalPort } from "./ports/SignalPort"; 4 | 5 | export default [ 6 | AudioPort, 7 | SignalPort, 8 | ] as (typeof BasePort)[]; -------------------------------------------------------------------------------- /src/components/node-editor/node-port/NodePort.module.sass: -------------------------------------------------------------------------------- 1 | .port 2 | --color: #999 3 | width: 15px 4 | height: 15px 5 | border-radius: 100% 6 | background-color: var(--color) 7 | display: inline-block 8 | border: 2px solid rgba(0,0,0,0.6) 9 | cursor: pointer 10 | -------------------------------------------------------------------------------- /src/components/node-editor/node-hud/NodeHud.module.sass: -------------------------------------------------------------------------------- 1 | .hud 2 | width: 100% 3 | height: 100% 4 | position: relative 5 | 6 | .items 7 | position: absolute 8 | inset: 0 9 | pointer-events: none 10 | 11 | .item 12 | display: contents 13 | pointer-events: all 14 | -------------------------------------------------------------------------------- /src/components/node-editor/node-project/utils/LoadEvent.ts: -------------------------------------------------------------------------------- 1 | export class LoadEvent extends KeyboardEvent { 2 | constructor(public loadCode: string) { 3 | super('keydown', { code: 'KeyV', ctrlKey: true }); 4 | } 5 | 6 | static emit(code: string) { 7 | dispatchEvent(new this(code)); 8 | } 9 | } -------------------------------------------------------------------------------- /src/components/audio-context/views/project/plugins/detectControlls.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "../Project"; 2 | import { effect } from "@preact/signals-react"; 3 | 4 | export default (ctx: Project) => { 5 | effect(() => { 6 | if (!ctx.controlls && ctx.run) { 7 | ctx.run = 0; 8 | } 9 | }); 10 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectMount.ts: -------------------------------------------------------------------------------- 1 | import { NodeItem } from "../NodeItem"; 2 | 3 | export default (ctx: NodeItem) => { 4 | ctx.list.items.add(ctx); 5 | ctx.props.onMount?.(ctx); 6 | return () => { 7 | ctx.list.items.delete(ctx); 8 | ctx.props.onDestroy?.(ctx); 9 | }; 10 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-view/NodeView.module.sass: -------------------------------------------------------------------------------- 1 | .view 2 | position: absolute 3 | inset: 0 4 | pointer-events: none 5 | overflow: hidden 6 | transform-origin: 0 0 7 | will-change: transform 8 | 9 | .viewSvg 10 | pointer-events: none 11 | position: absolute 12 | inset: 0 13 | image-rendering: crisp-edges 14 | -------------------------------------------------------------------------------- /src/library/dispose/index.ts: -------------------------------------------------------------------------------- 1 | export type TDispose = (() => void) | null | undefined | void; 2 | 3 | export function dispose(...args: TDispose[]) { 4 | return () => { 5 | args.forEach((_dispose) => { 6 | try { 7 | _dispose?.(); 8 | } catch (e) { 9 | console.error(e); 10 | } 11 | }); 12 | }; 13 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "allowJs": true 10 | }, 11 | "include": [ 12 | "vite.config.ts", 13 | "main.js" 14 | ] 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectConnect.ts: -------------------------------------------------------------------------------- 1 | import { NodePort } from "../NodePort"; 2 | import { effect } from "@preact/signals-react"; 3 | import { elementEvents } from "@vicimpa/events"; 4 | 5 | export default (ctx: NodePort) => ( 6 | effect(() => ( 7 | elementEvents(ctx.port.value, 'mouseup', () => { 8 | ctx.lines.connect(ctx); 9 | }) 10 | )) 11 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-view/plugins/detectViewSvg.ts: -------------------------------------------------------------------------------- 1 | import type { NodeView } from ".."; 2 | import { effect } from "@preact/signals-react"; 3 | 4 | export default (ctx: NodeView) => ( 5 | effect(() => { 6 | const { map } = ctx; 7 | const { value: svg } = ctx.svg; 8 | if (!svg) return; 9 | Object.assign(svg.viewBox.baseVal, map.view.toJSON()); 10 | }) 11 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-project/utils/copyPack.ts: -------------------------------------------------------------------------------- 1 | import { makeDataPack, t } from "@vicimpa/data-pack"; 2 | 3 | export const copyPack = makeDataPack( 4 | t.obj({ 5 | nodes: t.array(t.uint()), 6 | connect: t.array( 7 | t.tuple( 8 | t.tuple(t.uint(), t.uint()), 9 | t.tuple(t.uint(), t.uint()), 10 | ) 11 | ), 12 | configs: t.array(t.map()) 13 | }) 14 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-project/plugins/detectDelete.ts: -------------------------------------------------------------------------------- 1 | import { NodeProject } from "../NodeProject"; 2 | import { windowEvents } from "@vicimpa/events"; 3 | 4 | const deleteKeys = ['Backspace', 'Delete']; 5 | 6 | export default (ctx: NodeProject) => ( 7 | windowEvents('keydown', e => { 8 | if (deleteKeys.includes(e.key)) { 9 | ctx.destroy(...ctx.selection.select); 10 | } 11 | }) 12 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-item/NodeItem.module.sass: -------------------------------------------------------------------------------- 1 | .itemDiv 2 | position: absolute 3 | will-change: transform 4 | 5 | .item 6 | pointer-events: none 7 | 8 | .contain 9 | width: 1000px 10 | height: 1000px 11 | 12 | .fill 13 | display: inline-block 14 | width: auto 15 | height: auto 16 | 17 | [data-drag] 18 | cursor: grab 19 | 20 | [data-drag], [data-controll] 21 | pointer-events: all 22 | -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/plugins/detectShift.ts: -------------------------------------------------------------------------------- 1 | import { NodeSelection } from "../NodeSelection"; 2 | import { dispose } from "$library/dispose"; 3 | import { windowEvents } from "@vicimpa/events"; 4 | 5 | export default (ctx: NodeSelection) => ( 6 | dispose( 7 | windowEvents(['mousedown', 'mouseup', 'mousemove', 'mousedown', 'mouseup'], (e) => { 8 | ctx.shiftKey = e.shiftKey; 9 | }) 10 | ) 11 | ); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WebAudioNodes Sandbox 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/audio-context/lib/start.ts: -------------------------------------------------------------------------------- 1 | import { ctx } from "../ctx"; 2 | 3 | export interface Startable { 4 | start?(n?: number): any; 5 | end?(): any; 6 | stop?(): any; 7 | } 8 | 9 | export const start = (target?: T) => { 10 | var dispose = target?.start?.(ctx.currentTime); 11 | return () => { 12 | dispose instanceof Function && dispose(); 13 | target?.stop?.(); 14 | target?.end?.(); 15 | }; 16 | }; -------------------------------------------------------------------------------- /src/components/audio-context/views/project/plugins/detectMount.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "../Project"; 2 | import { dispose } from "$library/dispose"; 3 | import { pipe } from "$components/audio-context/lib/pipe"; 4 | import { start } from "$components/audio-context/lib/start"; 5 | 6 | export default (ctx: Project) => ( 7 | dispose( 8 | pipe(ctx.time, ctx.timeView), 9 | start(ctx.timeView), 10 | () => ctx.time.destroy(), 11 | ) 12 | ); -------------------------------------------------------------------------------- /src/components/audio-context/AudioProject.tsx: -------------------------------------------------------------------------------- 1 | import { NodeProject } from "$components/node-editor"; 2 | import { ReactNode } from "react"; 3 | import _nodes from "./_nodes"; 4 | import _ports from "./_ports"; 5 | import { provide } from "@vicimpa/react-decorators"; 6 | 7 | @provide() 8 | export class AudioProject extends NodeProject { 9 | nodes = _nodes; 10 | ports = _ports; 11 | 12 | render(): ReactNode { 13 | return ( 14 | super.render() 15 | ); 16 | } 17 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectResize.ts: -------------------------------------------------------------------------------- 1 | import type { NodeItem } from ".."; 2 | import { effect } from "@preact/signals-react"; 3 | import { resizeObserver } from "@vicimpa/observers"; 4 | 5 | export default (ctx: NodeItem) => ( 6 | effect(() => ( 7 | resizeObserver(ctx.fillRef.value, ({ contentRect }) => { 8 | ctx.width = contentRect.width + ctx.padding * 2; 9 | ctx.height = contentRect.height + ctx.padding * 2; 10 | }) 11 | )) 12 | ); -------------------------------------------------------------------------------- /src/library/frames/index.ts: -------------------------------------------------------------------------------- 1 | type TFrameListener = (dtime: number, time: number) => any; 2 | 3 | export function frames(frame: T) { 4 | var run = true; 5 | var time = performance.now(); 6 | 7 | function loop(newTime: number) { 8 | if (!run) return; 9 | requestAnimationFrame(loop); 10 | frame(newTime - time, time = newTime); 11 | } 12 | 13 | requestAnimationFrame(loop); 14 | 15 | return function () { 16 | run = false; 17 | }; 18 | } -------------------------------------------------------------------------------- /src/components/audio-context/worklet/ValueProcessor.ts: -------------------------------------------------------------------------------- 1 | import { defineWorklet } from "../lib/defineWorklet"; 2 | 3 | export default await defineWorklet({ 4 | params: { 5 | value: { 6 | defaultValue: 0 7 | }, 8 | }, 9 | options: { 10 | numberOfInputs: 0, 11 | numberOfOutputs: 1, 12 | outputChannelCount: [1] 13 | }, 14 | loop(outL) { 15 | for (var i = 0; i < this.numFrames; i++) { 16 | outL[i] = this.param('value', i); 17 | } 18 | } 19 | }); -------------------------------------------------------------------------------- /src/components/audio-context/lib/avgArray.ts: -------------------------------------------------------------------------------- 1 | export const avgArray = (array: number[], count = 0): number[] => { 2 | if (count <= 0) return array; 3 | 4 | array = Array.from( 5 | { length: array.length + array.length - 1 }, 6 | (_, i) => { 7 | if (i & 1) { 8 | return ( 9 | (array[Math.floor(i / 2)] + array[Math.ceil(i / 2)]) / 2 10 | ); 11 | } 12 | 13 | return array[Math.floor(i / 2)]; 14 | } 15 | ); 16 | 17 | return avgArray(array, count - 1); 18 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-back/plugins/detectResize.ts: -------------------------------------------------------------------------------- 1 | import { NodeBack } from ".."; 2 | import { effect } from "@preact/signals-react"; 3 | import { resizeObserver } from "@vicimpa/observers"; 4 | 5 | export default (ctx: NodeBack) => ( 6 | effect(() => ( 7 | resizeObserver(ctx.group.value, ({ contentRect }) => { 8 | if (!ctx.pattern.value) return; 9 | ctx.pattern.value.width.baseVal.value = contentRect.width; 10 | ctx.pattern.value.height.baseVal.value = contentRect.height; 11 | }) 12 | )) 13 | ); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from "$App"; 2 | import { createElement } from "react"; 3 | import { createRoot } from "react-dom/client"; 4 | import { dom } from "$library/dom"; 5 | 6 | createRoot( 7 | dom('div', { 8 | style: { 9 | display: 'contents', 10 | border: 'none' 11 | }, 12 | ref(elem) { 13 | document.body.appendChild(elem); 14 | } 15 | }) 16 | ).render( 17 | createElement(App) 18 | ); 19 | 20 | document.addEventListener("dragover", function (event) { 21 | event.preventDefault(); 22 | }, false); -------------------------------------------------------------------------------- /src/components/audio-context/index.tsx: -------------------------------------------------------------------------------- 1 | import { AudioProject } from "./AudioProject"; 2 | import { Debug } from "./views/debug/Debug"; 3 | import { Demo } from "./views/demo"; 4 | import { Menu } from "./views/menu"; 5 | import { Project } from "./views/project"; 6 | import { PropsWithChildren } from "react"; 7 | 8 | export const AudioContext = ({ children }: PropsWithChildren) => ( 9 | 10 | 11 | 12 | 13 | 14 | {children} 15 | 16 | 17 | ); -------------------------------------------------------------------------------- /src/components/audio-context/lib/BasePort.tsx: -------------------------------------------------------------------------------- 1 | import { NodePort } from "$components/node-editor"; 2 | import s from "../styles.module.sass"; 3 | 4 | export class BasePort extends NodePort { 5 | title: string = ''; 6 | color = '#555'; 7 | output = !!this.props.output; 8 | 9 | render() { 10 | this.title = this.props.title ?? (this.props.output ? 'out' : 'in'); 11 | return ( 12 |
13 | {super.render()} 14 | {this.title} 15 |
16 | ); 17 | } 18 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectViewDiv.ts: -------------------------------------------------------------------------------- 1 | import type { NodeItem } from ".."; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { effect } from "@preact/signals-react"; 4 | 5 | export default (ctx: NodeItem) => ( 6 | effect(() => { 7 | const { value: div } = ctx.viewDivRef; 8 | 9 | if (!div) return; 10 | 11 | const size = Vec2.fromSize(ctx); 12 | const _ctx = new Vec2(ctx).round(); 13 | const pos = size.cdiv(2).times(-1).plus(_ctx); 14 | div.style.transform = `translate(${pos.x}px, ${pos.y}px)`; 15 | }) 16 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectDrag.ts: -------------------------------------------------------------------------------- 1 | import { NodePort } from "../NodePort"; 2 | import { effect } from "@preact/signals-react"; 3 | import { elementEvents } from "@vicimpa/events"; 4 | 5 | export default (ctx: NodePort) => ( 6 | effect(() => { 7 | return elementEvents(ctx.port.value, 'mousedown', (event) => { 8 | if (event.button !== 0) return; 9 | event.preventDefault(); 10 | ctx.lines.fromStart(event, ...(event.metaKey || event.ctrlKey) ? ( 11 | ctx.lines.disconnect(ctx) 12 | ) : [ctx]); 13 | }); 14 | }) 15 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-map/plugins/detectResize.ts: -------------------------------------------------------------------------------- 1 | import { batch, effect } from "@preact/signals-react"; 2 | 3 | import type { NodeMap } from ".."; 4 | import { resizeObserver } from "@vicimpa/observers"; 5 | 6 | export default (ctx: NodeMap) => ( 7 | effect(() => ( 8 | resizeObserver(ctx.div.value, (entry) => { 9 | batch(() => { 10 | ctx.top = entry.contentRect.top; 11 | ctx.left = entry.contentRect.left; 12 | ctx.width = entry.contentRect.width; 13 | ctx.height = entry.contentRect.height; 14 | }); 15 | }) 16 | )) 17 | ); -------------------------------------------------------------------------------- /src/index.sass: -------------------------------------------------------------------------------- 1 | html, body 2 | width: 100vw 3 | height: 100vh 4 | 5 | * 6 | margin: 0 7 | padding: 0 8 | box-sizing: border-box 9 | outline: none 10 | user-select: none 11 | shape-rendering: optimizeSpeed 12 | text-rendering: optimizeSpeed 13 | image-rendering: optimizeSpeed 14 | 15 | body 16 | background-color: #444 17 | color: #fff 18 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif 19 | font-size: 16px 20 | 21 | button:not(:disabled) 22 | cursor: pointer 23 | -------------------------------------------------------------------------------- /src/library/base64/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | fromBuffer(buffer: ArrayBuffer) { 3 | var binaryString = ''; 4 | var bytes = new Uint8Array(buffer); 5 | 6 | bytes.forEach(byte => { 7 | binaryString += String.fromCharCode(byte); 8 | }); 9 | 10 | return btoa(binaryString); 11 | }, 12 | toBuffer(base64: string) { 13 | var binaryString = atob(base64); 14 | var bytes = new Uint8Array(binaryString.length); 15 | 16 | bytes.forEach((_, i) => { 17 | bytes[i] = binaryString.charCodeAt(i); 18 | }); 19 | 20 | return bytes.buffer; 21 | } 22 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-hud/NodeHud.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | 3 | import { provide } from "@vicimpa/react-decorators"; 4 | import s from "./NodeHud.module.sass"; 5 | import { signalRef } from "$library/signals"; 6 | 7 | @provide() 8 | export class NodeHud extends Component { 9 | items = signalRef(); 10 | 11 | render(): ReactNode { 12 | return ( 13 |
14 | {this.props.children} 15 |
16 |
17 | ); 18 | } 19 | } -------------------------------------------------------------------------------- /src/library/signals/signalRef.ts: -------------------------------------------------------------------------------- 1 | import { Signal, signal } from "@preact/signals-react"; 2 | 3 | import { createRef, RefObject } from "react"; 4 | 5 | export type SignalRef = Signal & RefObject; 6 | 7 | export const signalRef = () => { 8 | return Object.defineProperty( 9 | Object.assign( 10 | signal(), 11 | createRef() 12 | ), 13 | 'current', 14 | { 15 | get(this: Signal) { 16 | return this.value; 17 | }, 18 | set(this: Signal, v: T) { 19 | this.value = v; 20 | }, 21 | } 22 | ) as SignalRef; 23 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/NodeLinesForward.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, createElement } from "react"; 2 | 3 | import { NodeLineForward } from "./NodeLineForward"; 4 | import { NodeLines } from "./NodeLines"; 5 | import { useInject } from "@vicimpa/react-decorators"; 6 | import { useSignals } from "@preact/signals-react/runtime"; 7 | 8 | export const NodeLinesForward = () => { 9 | const lines = useInject(NodeLines); 10 | 11 | useSignals(); 12 | 13 | if (!lines.from.length) 14 | return null; 15 | 16 | return createElement(Fragment, {}, ...lines.from.map(from => ( 17 | 18 | ))); 19 | }; -------------------------------------------------------------------------------- /src/library/function/index.ts: -------------------------------------------------------------------------------- 1 | export function renameClass any)>(target: T, name: string): T { 2 | return ({ [name]: class extends target { } })[name]; 3 | } 4 | 5 | export function renameFunction any>(target: T, name: string): T { 6 | return ({ [name]: function (...args: any[]) { return target.apply(this, args); } })[name] as T; 7 | } 8 | 9 | export const name = any>(name: string) => { 10 | return (target: T) => renameClass(target, name); 11 | }; 12 | 13 | export const delay = (n = 0) => new Promise(resolve => setTimeout(resolve, n)); -------------------------------------------------------------------------------- /src/components/node-editor/node-view/plugins/detectViewDiv.ts: -------------------------------------------------------------------------------- 1 | import type { NodeView } from ".."; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { effect } from "@preact/signals-react"; 4 | 5 | export default (ctx: NodeView) => ( 6 | effect(() => { 7 | const { map } = ctx; 8 | const { value: div } = ctx.div; 9 | if (!div) return; 10 | 11 | const { s } = map; 12 | const { rect } = map; 13 | const offset = Vec2.fromSize(rect) 14 | .div(-2) 15 | .div(1, s) 16 | .plus(map.x, map.y) 17 | .times(s); 18 | 19 | div.style.transform = `matrix(${s}, 0, 0, ${s}, ${-offset.x}, ${-offset.y})`; 20 | }) 21 | ); -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Destination.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { ctx } from "../ctx"; 4 | import { dispose } from "$library/dispose"; 5 | import { group } from "../_groups"; 6 | import { name } from "$library/function"; 7 | import { pipe } from "../lib/pipe"; 8 | 9 | @name('Destination') 10 | @group('output') 11 | export default class extends BaseNode { 12 | #in = ctx.createGain(); 13 | 14 | _connect = () => ( 15 | dispose( 16 | pipe(this.#in, ctx.destination) 17 | ) 18 | ); 19 | 20 | input = ( 21 | 22 | ); 23 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/NodeLineForward.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { NodeLine } from "./NodeLine"; 3 | import { NodeLines } from "./NodeLines"; 4 | import { NodePort } from "../node-port"; 5 | import { useInject } from "@vicimpa/react-decorators"; 6 | import { useSignals } from "@preact/signals-react/runtime"; 7 | 8 | export const NodeLineForward: FC<{ from: NodePort; }> = ({ from }) => { 9 | const lines = useInject(NodeLines); 10 | 11 | useSignals(); 12 | 13 | if (!lines.mouse) 14 | return null; 15 | 16 | return ( 17 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/audio-context/views/debug/Debug.tsx: -------------------------------------------------------------------------------- 1 | import { created, using } from "$components/audio-context/lib/defineWorklet"; 2 | 3 | import { HudPortal } from "$components/node-editor/node-hud/HudPortal"; 4 | 5 | export const Debug = () => { 6 | 7 | return ( 8 | 9 |
20 |

CustomNodes: {using}/{created}

21 |
22 |
23 | ); 24 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectSelect.ts: -------------------------------------------------------------------------------- 1 | import { effect, untracked } from "@preact/signals-react"; 2 | 3 | import { NodeItem } from "../NodeItem"; 4 | 5 | export default (ctx: NodeItem) => ( 6 | effect(() => { 7 | var select = ctx.selection.select.includes(ctx); 8 | 9 | if (select !== untracked(() => ctx.select)) 10 | ctx.select = select; 11 | }), 12 | effect(() => { 13 | if (ctx.select) 14 | ctx.itemRef.current?.up(); 15 | else { 16 | if (ctx.fillRef.current) { 17 | ctx.fillRef.current.querySelectorAll('*') 18 | .forEach(e => e instanceof HTMLElement && e.blur()); 19 | } 20 | } 21 | }) 22 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectView.ts: -------------------------------------------------------------------------------- 1 | import type { NodeItem } from ".."; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { effect } from "@preact/signals-react"; 4 | 5 | export default (ctx: NodeItem) => ( 6 | effect(() => { 7 | if (!ctx.viewRef.value) 8 | return; 9 | 10 | const size = Vec2.fromSize(ctx); 11 | const _ctx = new Vec2(ctx).round(); 12 | const pos = size.cdiv(2).times(-1).plus(_ctx); 13 | 14 | ctx.viewRef.value.x.baseVal.value = pos.x; 15 | ctx.viewRef.value.y.baseVal.value = pos.y; 16 | ctx.viewRef.value.width.baseVal.value = size.x; 17 | ctx.viewRef.value.height.baseVal.value = size.y; 18 | }) 19 | ); -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | 17 | - name: Install 18 | run: | 19 | npm i 20 | 21 | - name: Build 22 | run: | 23 | npm run build 24 | 25 | - name: Deploy 26 | uses: crazy-max/ghaction-github-pages@v4 27 | with: 28 | target_branch: gh-pages 29 | build_dir: dist 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Slider.tsx: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "../lib/BaseNode"; 2 | import { Range } from "../lib/Range"; 3 | import { SignalNode } from "../lib/signalNode"; 4 | import { SignalPort } from "../ports/SignalPort"; 5 | import { group } from "../_groups"; 6 | import { name } from "$library/function"; 7 | import { store } from "$components/node-editor"; 8 | 9 | @name('Slider') 10 | @group('controll') 11 | export default class extends BaseNode { 12 | @store _value = new SignalNode(0, { min: 0, max: 1 }); 13 | 14 | output = ( 15 | 16 | ); 17 | 18 | _view = () => ( 19 | 20 | ); 21 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/NodeLinesBack.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, createElement } from "react"; 2 | 3 | import { NodeLineBack } from "./NodeLineBack"; 4 | import { NodeLines } from "./NodeLines"; 5 | import { useInject } from "@vicimpa/react-decorators"; 6 | import { useSignals } from "@preact/signals-react/runtime"; 7 | 8 | export const NodeLinesBack = () => { 9 | const lines = useInject(NodeLines); 10 | 11 | useSignals(); 12 | 13 | if (!lines.connects.length) 14 | return null; 15 | 16 | return createElement(Fragment, {}, ...lines.connects.map(connect => { 17 | const [from, to] = connect; 18 | return ; 19 | })); 20 | }; -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import paths from "vite-tsconfig-paths"; 3 | import react from '@vitejs/plugin-react-swc'; 4 | 5 | export default defineConfig({ 6 | root: './src', 7 | publicDir: '../public', 8 | base: './', 9 | css: { 10 | preprocessorOptions: { 11 | scss: { 12 | api: 'modern' 13 | }, 14 | sass: { 15 | api: 'modern' 16 | } 17 | } 18 | }, 19 | build: { 20 | emptyOutDir: true, 21 | outDir: '../dist', 22 | target: 'esnext', 23 | }, 24 | server: { 25 | host: '0.0.0.0' 26 | }, 27 | plugins: [ 28 | paths({ root: '..' }), 29 | react({ plugins: [], tsDecorators: true }), 30 | ], 31 | }); 32 | -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Toggle.tsx: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "../lib/BaseNode"; 2 | import { SignalNode } from "../lib/signalNode"; 3 | import { SignalPort } from "../ports/SignalPort"; 4 | import { Toggle } from "../lib/Toggle"; 5 | import { group } from "../_groups"; 6 | import { name } from "$library/function"; 7 | import { store } from "$components/node-editor"; 8 | 9 | @name('Toggle') 10 | @group('controll') 11 | export default class extends BaseNode { 12 | @store _toggle = new SignalNode(0, { default: 0 }); 13 | 14 | output = ( 15 | 16 | ); 17 | 18 | _view = () => ( 19 | <> 20 | 21 | 22 | ); 23 | } -------------------------------------------------------------------------------- /src/components/audio-context/worklet/MemoryProcessor.ts: -------------------------------------------------------------------------------- 1 | import { defineWorklet } from "../lib/defineWorklet"; 2 | 3 | export default await defineWorklet({ 4 | params: { 5 | value: { 6 | defaultValue: 0 7 | }, 8 | write: { 9 | defaultValue: 0, 10 | minValue: 0, 11 | } 12 | }, 13 | context: { 14 | value: 0, 15 | }, 16 | options: { 17 | numberOfInputs: 0, 18 | numberOfOutputs: 1 19 | }, 20 | loop(outL) { 21 | for (var i = 0; i < this.numFrames; i++) { 22 | var value = this.param('value', i); 23 | var write = this.param('write', i); 24 | 25 | if (write) { 26 | this.context.value = value; 27 | } 28 | 29 | outL[i] = this.context.value; 30 | } 31 | } 32 | }); -------------------------------------------------------------------------------- /src/components/audio-context/lib/pipe.ts: -------------------------------------------------------------------------------- 1 | import { SignalNode } from "./signalNode"; 2 | 3 | export const pipe = ( 4 | from: AudioNode | SignalNode, 5 | to: AudioNode | AudioParam | SignalNode, 6 | ...append: [output?: number, input?: number] 7 | ) => { 8 | if (from instanceof SignalNode) 9 | from = from.node; 10 | 11 | if (to instanceof AudioNode) 12 | from.connect(to, ...append); 13 | else if (to instanceof SignalNode) { 14 | from.connect(to.node.offset); 15 | to.addInput(from); 16 | } else 17 | from.connect(to); 18 | 19 | return () => { 20 | if (to instanceof SignalNode) { 21 | from.disconnect(to.node.offset); 22 | to.deleteInput(from); 23 | } else 24 | from.disconnect(to as AudioNode); 25 | }; 26 | }; -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectHover.ts: -------------------------------------------------------------------------------- 1 | import { elementEvents, windowEvents } from "@vicimpa/events"; 2 | 3 | import { NodePort } from "../NodePort"; 4 | import { dispose } from "$library/dispose"; 5 | import { effect } from "@preact/signals-react"; 6 | 7 | export default (ctx: NodePort) => ( 8 | effect(() => ( 9 | dispose( 10 | elementEvents(ctx.port.value, 'mouseenter', () => { 11 | ctx.hover = true; 12 | }), 13 | elementEvents(ctx.port.value, 'mouseover', () => { 14 | ctx.hover = true; 15 | }), 16 | elementEvents(ctx.port.value, 'mouseout', () => { 17 | ctx.hover = false; 18 | }), 19 | windowEvents('blur', () => { 20 | ctx.hover = false; 21 | }) 22 | ) 23 | )) 24 | ); -------------------------------------------------------------------------------- /src/fonts/icons/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/docs/#local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /src/components/node-editor/node-list/NodeList.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | 3 | import { NodeItem } from "../node-item"; 4 | import { NodeView } from "../node-view"; 5 | import { provide } from "@vicimpa/react-decorators"; 6 | import { signalRef } from "$library/signals"; 7 | 8 | @provide() 9 | export class NodeList extends Component { 10 | group = signalRef(); 11 | 12 | items = new Set(); 13 | 14 | render(): ReactNode { 15 | return ( 16 | 17 |
18 | 21 | 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-layers/NodeLayers.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | 3 | import { NodeView } from "../node-view"; 4 | import { provide } from "@vicimpa/react-decorators"; 5 | import { signalRef } from "$library/signals"; 6 | 7 | @provide() 8 | export class NodeLayers extends Component { 9 | pre = signalRef(); 10 | post = signalRef(); 11 | 12 | render(): ReactNode { 13 | return ( 14 | <> 15 | 16 | 17 | 18 | 19 | {this.props.children} 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /src/components/audio-context/lib/refValue.ts: -------------------------------------------------------------------------------- 1 | type RefKeys = { 2 | [K in keyof T]: T[K] extends number ? K : never 3 | }[keyof T]; 4 | 5 | export class RefValue { 6 | value: number; 7 | readonly defaultValue: number; 8 | get minValue() { return Number.MIN_VALUE; }; 9 | get maxValue() { return Number.MAX_VALUE; }; 10 | 11 | constructor() { 12 | throw new Error('Can not construct this object'); 13 | } 14 | } 15 | 16 | export function refValue(target: T, key: RefKeys) { 17 | return Object.setPrototypeOf({ 18 | defaultValue: target[key], 19 | get value() { 20 | return target[key]; 21 | }, 22 | set value(v) { 23 | target[key] = v; 24 | }, 25 | maxValue: Number.MAX_VALUE, 26 | minValue: Number.MIN_VALUE, 27 | }, RefValue.prototype) as RefValue; 28 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/NodeSelectionView.tsx: -------------------------------------------------------------------------------- 1 | import { NodeLayersItem } from "../node-layers"; 2 | import { NodeSelection } from "./NodeSelection"; 3 | import { useInject } from "@vicimpa/react-decorators"; 4 | import { useSignals } from "@preact/signals-react/runtime"; 5 | 6 | export const NodeSelectionView = () => { 7 | useSignals(); 8 | const select = useInject(NodeSelection); 9 | 10 | if (!select.view) 11 | return null; 12 | 13 | const [from, to] = select.view; 14 | const size = to.cminus(from); 15 | 16 | return ( 17 | 18 | 26 | 27 | ); 28 | }; -------------------------------------------------------------------------------- /src/components/audio-context/views/project/plugins/detectState.ts: -------------------------------------------------------------------------------- 1 | import { Project } from "../Project"; 2 | import { dispose } from "$library/dispose"; 3 | import { effect } from "@preact/signals-react"; 4 | 5 | export default (ctx: Project) => ( 6 | dispose( 7 | effect(() => { 8 | switch (ctx.run) { 9 | case 0: { 10 | ctx.time.revert.value = 1; 11 | ctx.time.start.value = 0; 12 | break; 13 | } 14 | case 1: { 15 | ctx.time.revert.value = 1; 16 | ctx.time.start.value = 1; 17 | break; 18 | } 19 | case 2: { 20 | ctx.time.revert.value = 0; 21 | ctx.time.start.value = 0; 22 | break; 23 | } 24 | } 25 | }), 26 | effect(() => { 27 | ctx.time.speed.value = ctx.temp / 60 * 4; 28 | }) 29 | ) 30 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-map/plugins/detectWheel.ts: -------------------------------------------------------------------------------- 1 | import type { NodeMap } from ".."; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { clamp } from "@vicimpa/math"; 4 | import { effect } from "@preact/signals-react"; 5 | import { elementEvents } from "@vicimpa/events"; 6 | 7 | export default (ctx: NodeMap) => ( 8 | effect(() => ( 9 | elementEvents(ctx.div.value, 'wheel', (e) => { 10 | if (ctx.move) return; 11 | 12 | e.preventDefault(); 13 | e.stopPropagation(); 14 | 15 | if (!e.ctrlKey) { 16 | const delta = Vec2.fromDeltaXY(e); 17 | 18 | delta 19 | .div(ctx.s) 20 | .plus(ctx) 21 | .toObject(ctx); 22 | 23 | return; 24 | } 25 | 26 | ctx.toScale(v => ( 27 | clamp(v - e.deltaY * v * .001, .05, 2) 28 | ), Vec2.fromPageXY(e)); 29 | }) 30 | )) 31 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-map/plugins/detectDrag.ts: -------------------------------------------------------------------------------- 1 | import type { NodeMap } from ".."; 2 | import { vec2, Vec2 } from "@vicimpa/lib-vec2"; 3 | import { effect } from "@preact/signals-react"; 4 | import { elementEvents } from "@vicimpa/events"; 5 | import { makeDrag } from "@vicimpa/easy-drag"; 6 | 7 | const drag = makeDrag<[ctx: NodeMap]>((_, ctx) => { 8 | const start = new Vec2(ctx); 9 | ctx.move = true; 10 | if (ctx.div.value) 11 | ctx.div.value.style.cursor = 'move'; 12 | return ({ delta }) => { 13 | vec2(delta).div(ctx.s) 14 | .plus(start) 15 | .toObject(ctx); 16 | 17 | return () => { 18 | ctx.move = false; 19 | if (ctx.div.value) 20 | ctx.div.value.style.cursor = 'default'; 21 | }; 22 | }; 23 | }, 1); 24 | 25 | export default (ctx: NodeMap) => ( 26 | effect(() => elementEvents(ctx.div.value, 'mousedown', (e) => drag(e, ctx))) 27 | ); -------------------------------------------------------------------------------- /src/components/audio-context/lib/line.ts: -------------------------------------------------------------------------------- 1 | import { Vec2 } from "@vicimpa/lib-vec2"; 2 | 3 | export type LineOptions = { 4 | size?: number; 5 | color?: string; 6 | dash?: number[]; 7 | }; 8 | 9 | export const line = ( 10 | ctx: CanvasRenderingContext2D, 11 | fn: (move: (...args: [x: number, y: number] | [vec: Vec2]) => void) => void | undefined | Path2D, 12 | options?: LineOptions 13 | ) => { 14 | var first = true; 15 | var { size = 1, color = '#fff', dash = [] } = options ?? {}; 16 | ctx.beginPath(); 17 | const path = fn((...args) => { 18 | if (first) { 19 | ctx.moveTo(...args); 20 | first = false; 21 | return; 22 | } 23 | 24 | ctx.lineTo(...args); 25 | }); 26 | ctx.lineWidth = size; 27 | ctx.strokeStyle = color; 28 | ctx.setLineDash(dash); 29 | if (path) 30 | ctx.stroke(path); 31 | else 32 | ctx.stroke(); 33 | ctx.closePath(); 34 | }; -------------------------------------------------------------------------------- /src/components/audio-context/_groups.ts: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "./lib/BaseNode"; 2 | 3 | export const groups = { 4 | 'input': 'Input/Source', 5 | 'output': 'Output/Destination', 6 | 'analyze': 'Analyze/Test/Show', 7 | 'controll': 'Controll/Value', 8 | 'base': 'Default/Base/Effect', 9 | 'custom': 'Custom/Worklet', 10 | 'ungroup': 'Ungroup/Other', 11 | 'high': 'High/Kit/Synth' 12 | } as const; 13 | 14 | const keys = Object.keys(groups); 15 | 16 | const symbol = Symbol('group'); 17 | 18 | export const groupIndex = (group: keyof typeof groups) => keys.indexOf(group); 19 | 20 | export const group = (group: keyof typeof groups) => { 21 | return (target: T) => { 22 | (target as any)[symbol] = group; 23 | }; 24 | }; 25 | 26 | export const getGroup = (target: T): keyof typeof groups => { 27 | return (target as any)[symbol] ?? 'ungroup'; 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/node-editor/node-item/plugins/detectCopy.ts: -------------------------------------------------------------------------------- 1 | import { NodeItem } from "../NodeItem"; 2 | import { effect } from "@preact/signals-react"; 3 | import { elementEvents } from "@vicimpa/events"; 4 | 5 | var storeType: typeof NodeItem | null; 6 | var storeConfig: object | null; 7 | 8 | export default (ctx: NodeItem) => ( 9 | effect(() => ( 10 | elementEvents(ctx.fillRef.value, 'mousedown', e => { 11 | if (!e.metaKey && !e.ctrlKey) 12 | return; 13 | 14 | const type = ctx.project.nodes.find(e => ctx instanceof e); 15 | 16 | if (!type) 17 | return; 18 | 19 | if (e.button === 0) { 20 | if (storeType === type && storeConfig) { 21 | ctx.project.restore(ctx, storeConfig); 22 | } 23 | } 24 | 25 | if (e.button === 2) { 26 | storeType = type; 27 | storeConfig = ctx.project.save(ctx); 28 | } 29 | }) 30 | )) 31 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-back/plugins/detectUpdate.ts: -------------------------------------------------------------------------------- 1 | import { NodeBack } from ".."; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { dispose } from "$library/dispose"; 4 | import { effect } from "@preact/signals-react"; 5 | 6 | export default (ctx: NodeBack) => ( 7 | dispose( 8 | effect(() => { 9 | const pos = new Vec2(ctx) 10 | .times(-1) 11 | .times(ctx.block) 12 | .minus(ctx.block / 2); 13 | 14 | const size = Vec2.fromSize(ctx) 15 | .clampMax(1) 16 | .plus(ctx) 17 | .times(ctx.block); 18 | 19 | if (!ctx.rect.value) 20 | return; 21 | 22 | const rect = new DOMRect(...pos, ...size); 23 | ctx.rect.value.x.baseVal.value = rect.x; 24 | ctx.rect.value.y.baseVal.value = rect.y; 25 | ctx.rect.value.width.baseVal.value = rect.width; 26 | ctx.rect.value.height.baseVal.value = rect.height; 27 | }) 28 | ) 29 | ); -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Gain.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { Range } from "../lib/Range"; 4 | import { SignalNode } from "../lib/signalNode"; 5 | import { ctx } from "../ctx"; 6 | import { group } from "../_groups"; 7 | import { name } from "$library/function"; 8 | import { store } from "$components/node-editor"; 9 | 10 | @name('Gain') 11 | @group('base') 12 | export default class extends BaseNode { 13 | #gain = new GainNode(ctx); 14 | 15 | @store _gain = new SignalNode(this.#gain.gain, { min: 0, max: 1.5 }); 16 | 17 | 18 | input = ( 19 | 20 | ); 21 | 22 | output = ( 23 | 24 | ); 25 | 26 | _view = () => ( 27 | <> 28 | 33 | 34 | ); 35 | } -------------------------------------------------------------------------------- /src/components/audio-context/nodes/StereoPanner.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { Range } from "../lib/Range"; 4 | import { SignalNode } from "../lib/signalNode"; 5 | import { ctx } from "../ctx"; 6 | import { group } from "../_groups"; 7 | import { name } from "$library/function"; 8 | import { store } from "$components/node-editor"; 9 | 10 | @name('StereoPanner') 11 | @group('base') 12 | export default class extends BaseNode { 13 | #stereo = new StereoPannerNode(ctx); 14 | 15 | @store _pan = new SignalNode(this.#stereo.pan); 16 | 17 | input = ( 18 | 19 | ); 20 | 21 | output = ( 22 | 23 | ); 24 | 25 | _view = () => ( 26 | <> 27 | 32 | 33 | ); 34 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-hud/HudPortal.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | 3 | import { NodeHud } from "./NodeHud"; 4 | import { connect } from "@vicimpa/react-decorators"; 5 | import { createPortal } from "react-dom"; 6 | import { dom } from "$library/dom"; 7 | import { effect } from "@preact/signals-react"; 8 | import { inject } from "@vicimpa/react-decorators"; 9 | import s from "./NodeHud.module.sass"; 10 | 11 | @connect((ctx) => ( 12 | effect(() => { 13 | const { value: items } = ctx.hud.items; 14 | if (!items) return; 15 | items.appendChild(ctx.elem); 16 | return () => ctx.elem.remove(); 17 | }) 18 | )) 19 | export class HudPortal extends Component { 20 | @inject(() => NodeHud) hud!: NodeHud; 21 | 22 | elem = dom('div', { className: s.item }); 23 | 24 | render(): ReactNode { 25 | return createPortal(this.props.children, this.elem); 26 | } 27 | } -------------------------------------------------------------------------------- /src/components/audio-context/nodes/ShowValue.tsx: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "../lib/BaseNode"; 2 | import { SignalNode } from "../lib/signalNode"; 3 | import { SignalPort } from "../ports/SignalPort"; 4 | import { group } from "../_groups"; 5 | import { name } from "$library/function"; 6 | 7 | @name('ShowValue') 8 | @group('analyze') 9 | export default class extends BaseNode { 10 | _in = new SignalNode(0, { default: 0 }); 11 | 12 | input = ( 13 | 14 | ); 15 | 16 | _view = () => ( 17 |
24 |

31 | {this._in} 32 |

33 |
34 | ); 35 | } -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Delay.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { Range } from "../lib/Range"; 4 | import { SignalNode } from "../lib/signalNode"; 5 | import { ctx } from "../ctx"; 6 | import { group } from "../_groups"; 7 | import { name } from "$library/function"; 8 | import { store } from "$components/node-editor"; 9 | 10 | @name('Delay') 11 | @group('base') 12 | export default class extends BaseNode { 13 | #delay = new DelayNode(ctx); 14 | 15 | @store _gain = new SignalNode(this.#delay.delayTime, { min: 0, max: 10 }); 16 | 17 | input = ( 18 | 19 | ); 20 | 21 | output = ( 22 | 23 | ); 24 | 25 | _view = () => ( 26 | <> 27 | 33 | 34 | ); 35 | } -------------------------------------------------------------------------------- /src/components/audio-context/worklet/ImpulseProcessor.ts: -------------------------------------------------------------------------------- 1 | import { defineWorklet } from "../lib/defineWorklet"; 2 | 3 | export default await defineWorklet({ 4 | params: { 5 | value: { 6 | defaultValue: 0 7 | }, 8 | length: { 9 | defaultValue: 1, 10 | minValue: 0, 11 | } 12 | }, 13 | context: { 14 | times: 0, 15 | preview: 0, 16 | }, 17 | options: { 18 | numberOfInputs: 0, 19 | numberOfOutputs: 1 20 | }, 21 | loop(outL) { 22 | for (var i = 0; i < this.numFrames; i++) { 23 | var value = this.param('value', i); 24 | var length = this.param('length', i); 25 | var result = 0; 26 | 27 | if (value !== this.context.preview) { 28 | this.context.preview = value; 29 | this.context.times = length; 30 | } 31 | 32 | if (this.context.times > 0) { 33 | this.context.times--; 34 | result = this.context.preview; 35 | } 36 | 37 | outL[i] = result; 38 | } 39 | } 40 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": [ 6 | "ESNext", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "module": "ESNext", 11 | "skipLibCheck": true, 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "experimentalDecorators": true, 23 | "types": [ 24 | "vite/client" 25 | ], 26 | "paths": { 27 | "$": [ 28 | "./src/" 29 | ], 30 | "$*": [ 31 | "./src/*" 32 | ] 33 | }, 34 | "plugins": [ 35 | { 36 | "name": "typescript-plugin-css-modules" 37 | } 38 | ] 39 | }, 40 | "include": [ 41 | "./src" 42 | ] 43 | } -------------------------------------------------------------------------------- /src/components/audio-context/lib/Toggle.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import { SignalNode } from "./signalNode"; 3 | import { SignalPort } from "../ports/SignalPort"; 4 | import { useComputed } from "@preact/signals-react"; 5 | 6 | export const Toggle: FC<{ value: SignalNode, label?: string; }> = ({ value, label = 'Ebabled' }) => { 7 | 8 | return useComputed(() => { 9 | var connected = value.connected; 10 | 11 | return ( 12 |
13 | 14 | 20 |
21 | ); 22 | }); 23 | }; -------------------------------------------------------------------------------- /src/library/loader/audio.ts: -------------------------------------------------------------------------------- 1 | import { TypeValue, t } from "@vicimpa/data-pack"; 2 | 3 | const moduleDTO = t.obj({ 4 | default: t.str() 5 | }); 6 | 7 | export function audioLoader(glob: Record Promise>) { 8 | return Object.entries(glob) 9 | .reduce((acc, [key, value]) => { 10 | var result: Promise; 11 | 12 | async function load(ctx: AudioContext) { 13 | const _module = await value(); 14 | if (!moduleDTO.equal(_module)) { 15 | throw new Error('Error'); 16 | } 17 | const module = _module as TypeValue; 18 | const response = await fetch(module.default); 19 | return ctx.decodeAudioData(await response.arrayBuffer()); 20 | } 21 | 22 | acc[key] = function (ctx) { 23 | if (result) 24 | return result; 25 | return result = load(ctx); 26 | }; 27 | 28 | return acc; 29 | }, {} as { [key: string]: (ctx: AudioContext) => Promise; }); 30 | } -------------------------------------------------------------------------------- /src/components/audio-context/worklet/SequenceProcessor.ts: -------------------------------------------------------------------------------- 1 | import { defineWorklet } from "../lib/defineWorklet"; 2 | 3 | export default await defineWorklet({ 4 | params: { 5 | time: { 6 | defaultValue: 0, 7 | } 8 | }, 9 | props: { 10 | seqence: 0, 11 | value: 1, 12 | }, 13 | context: { 14 | frame: 0 15 | }, 16 | options: { 17 | numberOfInputs: 0, 18 | numberOfOutputs: 1 19 | }, 20 | loop(outL) { 21 | for (let i = 0; i < this.numFrames; i++) { 22 | var frame = this.param('time', i); 23 | 24 | if (frame <= 0 || frame >= 16) 25 | continue; 26 | 27 | frame |= 0; 28 | 29 | var seqence = +this.props.seqence; 30 | var value = isNaN(seqence) ? 0 : seqence & 0xFFFF; 31 | 32 | if (this.context.frame !== frame) { 33 | this.context.frame = frame; 34 | continue; 35 | } 36 | 37 | var frameValue = (value >>> frame) & 1; 38 | 39 | outL[i] = frameValue * (this.props.value ?? 1); 40 | } 41 | }, 42 | }); -------------------------------------------------------------------------------- /src/components/audio-context/ctx.ts: -------------------------------------------------------------------------------- 1 | import { dispose } from "$library/dispose"; 2 | import { windowEvents } from "@vicimpa/events"; 3 | 4 | declare global { 5 | var _ctx: AudioContext | undefined; 6 | var _empty: GainNode | undefined; 7 | } 8 | 9 | export const ctx = globalThis._ctx ?? (globalThis._ctx = new AudioContext()); 10 | export const empty = globalThis._empty ?? (globalThis._empty = ctx.createGain(), globalThis._empty.connect(ctx.destination), globalThis._empty); 11 | 12 | empty.gain.value = 0; 13 | 14 | function resumeContextOnInteraction(audioContext: AudioContext) { 15 | if (audioContext.state === "suspended") { 16 | const resume = async () => { 17 | await audioContext.resume(); 18 | 19 | if (audioContext.state === "running") { 20 | _dispose(); 21 | } 22 | }; 23 | 24 | const _dispose = dispose( 25 | windowEvents('touchend', resume), 26 | windowEvents('click', resume), 27 | windowEvents('keydown', resume), 28 | ); 29 | } 30 | } 31 | resumeContextOnInteraction(ctx); -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Note.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { group } from "../_groups"; 4 | import { name } from "$library/function"; 5 | import { store } from "$components/node-editor"; 6 | import { signalRef } from "$library/signals"; 7 | import { dom } from "$library/dom"; 8 | 9 | @name('Note') 10 | @group('high') 11 | export default class extends BaseNode { 12 | area = dom('textarea', { onkeydown: e => (!e.ctrlKey && !e.metaKey) && e.stopPropagation() }); 13 | 14 | @store get sizeWidth() { return this.area.offsetWidth; }; 15 | set sizeWidth(v) { this.area.style.width = v + 'px'; } 16 | @store get sizeHeight() { return this.area.offsetHeight; }; 17 | set sizeHeight(v) { this.area.style.height = v + 'px'; } 18 | @store get contentText() { return this.area.value; }; 19 | set contentText(v) { this.area.value = v; } 20 | 21 | ref = signalRef(); 22 | 23 | _view = () => ( 24 |
{ r?.appendChild(this.area); }} /> 25 | ); 26 | }; -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Memory.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import MemoryProcessor from "../worklet/MemoryProcessor"; 4 | import { Number } from "../lib/Number"; 5 | import { SignalNode } from "../lib/signalNode"; 6 | import { dispose } from "$library/dispose"; 7 | import { group } from "../_groups"; 8 | import { name } from "$library/function"; 9 | import { store } from "$components/node-editor"; 10 | 11 | @name('Memory') 12 | @group('custom') 13 | export default class extends BaseNode { 14 | #processor = new MemoryProcessor(); 15 | 16 | @store _value = new SignalNode(this.#processor.value); 17 | @store _write = new SignalNode(this.#processor.write); 18 | 19 | _connect = () => dispose( 20 | () => this.#processor.destroy() 21 | ); 22 | 23 | output = ( 24 | 25 | ); 26 | 27 | _view = () => ( 28 | <> 29 | 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Inpulse.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import ImpulseProcessor from "../worklet/ImpulseProcessor"; 4 | import { Number } from "../lib/Number"; 5 | import { SignalNode } from "../lib/signalNode"; 6 | import { dispose } from "$library/dispose"; 7 | import { group } from "../_groups"; 8 | import { name } from "$library/function"; 9 | import { store } from "$components/node-editor"; 10 | 11 | @name('Inpulse') 12 | @group('custom') 13 | export default class extends BaseNode { 14 | #processor = new ImpulseProcessor(); 15 | 16 | @store _value = new SignalNode(this.#processor.value); 17 | @store _length = new SignalNode(this.#processor.length); 18 | 19 | _connect = () => dispose( 20 | () => this.#processor.destroy() 21 | ); 22 | 23 | output = ( 24 | 25 | ); 26 | 27 | _view = () => ( 28 | <> 29 | 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 vic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/fonts/icons/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!('boxShadow' in document.body.style)) { 2 | document.body.setAttribute('class', 'noBoxShadow'); 3 | } 4 | 5 | document.body.addEventListener("click", function(e) { 6 | var target = e.target; 7 | if (target.tagName === "INPUT" && 8 | target.getAttribute('class').indexOf('liga') === -1) { 9 | target.select(); 10 | } 11 | }); 12 | 13 | (function() { 14 | var fontSize = document.getElementById('fontSize'), 15 | testDrive = document.getElementById('testDrive'), 16 | testText = document.getElementById('testText'); 17 | function updateTest() { 18 | testDrive.innerHTML = testText.value || String.fromCharCode(160); 19 | if (window.icomoonLiga) { 20 | window.icomoonLiga(testDrive); 21 | } 22 | } 23 | function updateSize() { 24 | testDrive.style.fontSize = fontSize.value + 'px'; 25 | } 26 | fontSize.addEventListener('change', updateSize, false); 27 | testText.addEventListener('input', updateTest, false); 28 | testText.addEventListener('change', updateTest, false); 29 | updateSize(); 30 | }()); 31 | -------------------------------------------------------------------------------- /src/components/node-editor/index.tsx: -------------------------------------------------------------------------------- 1 | import { NodeBack2 } from "./node-back-v2"; 2 | import { NodeHud } from "./node-hud"; 3 | import { NodeLayers } from "./node-layers"; 4 | import { NodeLines } from "./node-lines"; 5 | import { NodeList } from "./node-list"; 6 | import { NodeMap } from "./node-map"; 7 | import { NodeSelection } from "./node-selection"; 8 | import { PropsWithChildren } from "react"; 9 | 10 | export * from "./node-back"; 11 | export * from "./node-hud"; 12 | export * from "./node-item"; 13 | export * from "./node-layers"; 14 | export * from "./node-lines"; 15 | export * from "./node-list"; 16 | export * from "./node-map"; 17 | export * from "./node-port"; 18 | export * from "./node-project"; 19 | export * from "./node-selection"; 20 | 21 | export const Editor = ({ children }: PropsWithChildren) => ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | -------------------------------------------------------------------------------- /src/components/audio-context/worklet/TimerProcessor.ts: -------------------------------------------------------------------------------- 1 | import { defineWorklet } from "../lib/defineWorklet"; 2 | 3 | export default await defineWorklet({ 4 | params: { 5 | start: { 6 | defaultValue: 0, 7 | minValue: 0, 8 | maxValue: 1 9 | }, 10 | loop: { 11 | defaultValue: 0, 12 | minValue: 0, 13 | }, 14 | revert: { 15 | defaultValue: 1 16 | }, 17 | speed: { 18 | defaultValue: 1 19 | }, 20 | }, 21 | context: { 22 | time: 0 23 | }, 24 | options: { 25 | numberOfInputs: 0, 26 | numberOfOutputs: 1 27 | }, 28 | loop(outL) { 29 | for (var i = 0; i < this.numFrames; i++) { 30 | var start = this.param('start', i); 31 | var loop = this.param('loop', i); 32 | var speed = this.param('speed', i); 33 | var revert = this.param('revert', i); 34 | 35 | if (start) 36 | this.context.time += (1 / this.sampleRate) * speed; 37 | else if (revert > 0) 38 | this.context.time = 0; 39 | 40 | if (loop > 0) 41 | this.context.time %= loop; 42 | 43 | var result = this.context.time; 44 | 45 | outL[i] = result; 46 | } 47 | } 48 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "w-nodes", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@preact/signals-react": "^3.3.1", 13 | "@vicimpa/data-buffer": "^0.0.7", 14 | "@vicimpa/data-pack": "^0.0.13", 15 | "@vicimpa/decorators": "^0.0.15", 16 | "@vicimpa/easy-drag": "^0.1.12", 17 | "@vicimpa/events": "^0.0.13", 18 | "@vicimpa/lib-vec2": "^0.4.8", 19 | "@vicimpa/math": "^0.0.10", 20 | "@vicimpa/observers": "^0.0.9", 21 | "@vicimpa/react-decorators": "^0.1.12", 22 | "@vicimpa/rsp": "^0.1.9", 23 | "@vicimpa/week-store": "^0.0.10", 24 | "react": "19.1.1", 25 | "react-dom": "19.1.1", 26 | "styled-components": "^6.1.19" 27 | }, 28 | "devDependencies": { 29 | "@types/react": "19.2.2", 30 | "@types/react-dom": "19.2.2", 31 | "@vitejs/plugin-react-swc": "^4.1.0", 32 | "sass-embedded": "^1.93.2", 33 | "typescript": "^5.9.3", 34 | "typescript-plugin-css-modules": "^5.2.0", 35 | "vite": "^7.1.10", 36 | "vite-tsconfig-paths": "^5.1.4" 37 | } 38 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-port/plugins/detectPosition.ts: -------------------------------------------------------------------------------- 1 | import { NodePort } from "../NodePort"; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { dispose } from "$library/dispose"; 4 | import { effect } from "@preact/signals-react"; 5 | import { intersectionObserver } from "@vicimpa/observers"; 6 | 7 | export default (ctx: NodePort) => ( 8 | effect(() => { 9 | const { value: port } = ctx.port; 10 | const { value: fill } = ctx.item.fillRef; 11 | 12 | if (!port || !fill) return; 13 | 14 | const { map } = ctx; 15 | 16 | const getPoint = () => { 17 | const rect = port.getBoundingClientRect(); 18 | const point = new Vec2(ctx); 19 | const newPoint = map 20 | .offset(Vec2.fromSize(rect).times(.5).plus(rect)) 21 | .ceil(); 22 | 23 | if (!point.equal(newPoint)) 24 | newPoint.toObject(ctx); 25 | }; 26 | 27 | 28 | return dispose( 29 | intersectionObserver(port, () => { 30 | getPoint(); 31 | }), 32 | effect(() => { 33 | ctx.item.x; 34 | ctx.item.y; 35 | ctx.item.width; 36 | ctx.item.height; 37 | getPoint(); 38 | }) 39 | ); 40 | }) 41 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/plugins/detectSelect.ts: -------------------------------------------------------------------------------- 1 | import { NodeSelection } from "../NodeSelection"; 2 | import { Vec2 } from "@vicimpa/lib-vec2"; 3 | import { dispose } from "$library/dispose"; 4 | import { effect } from "@preact/signals-react"; 5 | import { windowEvents } from "@vicimpa/events"; 6 | 7 | export default (ctx: NodeSelection) => ( 8 | dispose( 9 | effect(() => { 10 | if (!ctx.view) { 11 | ctx._select = null; 12 | return; 13 | } 14 | 15 | var [from, to] = ctx.view; 16 | 17 | if (ctx.shiftKey && !ctx._select) 18 | ctx._select = [...ctx.select]; 19 | 20 | ctx.toSelect(...[...ctx.list.items].filter(node => { 21 | const double = Vec2.fromSize(node).times(.5).minus(node.padding); 22 | const a = new Vec2(node).minus(double); 23 | const b = new Vec2(node).plus(double); 24 | return a.x < to.x && b.x > from.x && a.y < to.y && b.y > from.y; 25 | })); 26 | }), 27 | windowEvents('keydown', e => { 28 | if ((e.metaKey || e.ctrlKey) && e.code == 'KeyA') { 29 | e.preventDefault(); 30 | ctx.select = [...ctx.list.items]; 31 | 32 | } 33 | }) 34 | ) 35 | ); -------------------------------------------------------------------------------- /src/components/node-editor/node-view/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren } from "react"; 2 | 3 | import { NodeMap } from "../node-map"; 4 | import { connect } from "@vicimpa/react-decorators"; 5 | import detectViewDiv from "./plugins/detectViewDiv"; 6 | import detectViewSvg from "./plugins/detectViewSvg"; 7 | import { inject } from "@vicimpa/react-decorators"; 8 | import s from "./NodeView.module.sass"; 9 | import { signalRef } from "$library/signals"; 10 | 11 | export type NodeViewProps = { 12 | type: 'div' | 'svg'; 13 | } & PropsWithChildren; 14 | 15 | @connect(detectViewSvg, detectViewDiv) 16 | export class NodeView extends Component { 17 | @inject(() => NodeMap) map!: NodeMap; 18 | 19 | svg = signalRef(); 20 | div = signalRef(); 21 | 22 | render() { 23 | if (this.props.type === "div") 24 | return ( 25 |
26 |
27 | {this.props.children} 28 |
29 |
30 | ); 31 | 32 | return ( 33 | 34 | {this.props.children} 35 | 36 | ); 37 | } 38 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-layers/NodeLayersItem.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | import { inject, provide } from "@vicimpa/react-decorators"; 3 | import { prop, reactive } from "@vicimpa/decorators"; 4 | 5 | import { NodeLayers } from "./NodeLayers"; 6 | import { connect } from "@vicimpa/react-decorators"; 7 | import { createPortal } from "react-dom"; 8 | import { effect } from "@preact/signals-react"; 9 | import { svg } from "$library/dom"; 10 | 11 | @provide() 12 | @connect((ctx: NodeLayersItem) => ( 13 | effect(() => { 14 | const ref = ctx.post ? ctx.layers.post : ctx.layers.pre; 15 | if (!ref.value) return; 16 | 17 | ref.value.appendChild(ctx.elem); 18 | ctx.elem.setAttribute('data-nodelist-item', ''); 19 | 20 | return () => { 21 | ctx.elem.remove(); 22 | }; 23 | }) 24 | )) 25 | @reactive() 26 | export class NodeLayersItem extends Component> { 27 | @inject(() => NodeLayers) layers!: NodeLayers; 28 | 29 | elem = svg('g', {}); 30 | 31 | @prop post = this.props.post ?? false; 32 | 33 | render(): ReactNode { 34 | this.post = this.props.post ?? false; 35 | return createPortal(this.props.children, this.elem); 36 | } 37 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/NodeLineBack.tsx: -------------------------------------------------------------------------------- 1 | import { FC, MouseEvent, useCallback } from "react"; 2 | import { NodeConnect, NodeLines } from "./NodeLines"; 3 | 4 | import { NodeLayersItem } from "../node-layers"; 5 | import { NodeLine } from "./NodeLine"; 6 | import { NodeMap } from "../node-map"; 7 | import { useInject } from "@vicimpa/react-decorators"; 8 | import { useSignals } from "@preact/signals-react/runtime"; 9 | 10 | export const NodeLineBack: FC<{ connect: NodeConnect; }> = ({ connect }) => { 11 | useSignals(); 12 | 13 | const map = useInject(NodeMap); 14 | const lines = useInject(NodeLines); 15 | 16 | const mouseDown = useCallback((e: MouseEvent) => { 17 | if (e.button !== 0) return; 18 | const vec = map.offset(e); 19 | const { port } = connect 20 | .map(port => ({ port, distance: vec.distance(port) })) 21 | .sort((a, b) => b.distance - a.distance)[0]; 22 | 23 | lines.disconnect(connect); 24 | lines.fromStart(e.nativeEvent, port); 25 | }, [connect]); 26 | 27 | const [from, to] = connect; 28 | 29 | 30 | if (from.hover || to.hover) 31 | return ( 32 | 33 | 34 | 35 | ); 36 | 37 | return ( 38 | 39 | ); 40 | }; -------------------------------------------------------------------------------- /src/components/audio-context/views/project/Project.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | import { connect, provide } from "@vicimpa/react-decorators"; 3 | import { prop, reactive } from "@vicimpa/decorators"; 4 | 5 | import { ProjectControll } from "./ProjectControll"; 6 | import { SignalNode } from "$components/audio-context/lib/signalNode"; 7 | import TimerProcessor from "$components/audio-context/worklet/TimerProcessor"; 8 | import { computed } from "@preact/signals-react"; 9 | import detectControlls from "./plugins/detectControlls"; 10 | import detectMount from "./plugins/detectMount"; 11 | import detectState from "./plugins/detectState"; 12 | 13 | @provide() 14 | @connect( 15 | detectMount, 16 | detectState, 17 | detectControlls, 18 | ) 19 | @reactive() 20 | export class Project extends Component { 21 | @prop run = 0 as 0 | 1 | 2; 22 | @prop temp = 120; 23 | @prop length = 1; 24 | @prop controlls = 0; 25 | 26 | time = new TimerProcessor(); 27 | timeView = new SignalNode(0, { default: 0 }); 28 | 29 | _view = computed(() => { 30 | if (!this.controlls) 31 | return null; 32 | 33 | return ; 34 | }); 35 | 36 | render(): ReactNode { 37 | return ( 38 | <> 39 | {this.props.children} 40 | {this._view} 41 | 42 | ); 43 | } 44 | } -------------------------------------------------------------------------------- /src/components/audio-context/ports/AudioPort.tsx: -------------------------------------------------------------------------------- 1 | import { BasePort } from "../lib/BasePort"; 2 | import { SignalNode } from "../lib/signalNode"; 3 | import { SignalPort } from "./SignalPort"; 4 | 5 | export class AudioPort extends BasePort { 6 | color = '#44f'; 7 | value: AudioNode = this.props.value; 8 | 9 | _onConnect(port: AudioPort | SignalPort) { 10 | if (!this.value) return false; 11 | if (!port.value) return false; 12 | if (!this.props.output) return true; 13 | 14 | if (port.value instanceof AudioNode) { 15 | this.value.connect(port.value); 16 | return true; 17 | } 18 | 19 | if (port.value instanceof SignalNode) { 20 | this.value.connect(port.value.node.offset); 21 | port.value.addInput(this.value); 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | _onDisconnect(port: AudioPort | SignalPort) { 29 | if (!this.props.output) 30 | return true; 31 | 32 | if (this.value && port.value) { 33 | if (port.value instanceof AudioNode) { 34 | this.value.disconnect(port.value); 35 | return true; 36 | } 37 | 38 | if (port.value instanceof SignalNode) { 39 | port.value.deleteInput(this.value); 40 | try { 41 | this.value.disconnect(port.value.node.offset); 42 | } catch (e) { } 43 | return true; 44 | } 45 | } 46 | 47 | return true; 48 | } 49 | } -------------------------------------------------------------------------------- /src/library/gzip/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | async encode(data: ArrayBuffer) { 3 | const compressionStream = new CompressionStream('gzip'); 4 | const writableStream = compressionStream.writable; 5 | const writer = writableStream.getWriter(); 6 | 7 | writer.write(data); 8 | writer.close(); 9 | 10 | const compressedStream = compressionStream.readable; 11 | const compressedChunks = []; 12 | 13 | const reader = compressedStream.getReader(); 14 | 15 | var result: ReadableStreamReadResult; 16 | 17 | while (!(result = await reader.read()).done) { 18 | compressedChunks.push(result.value); 19 | } 20 | 21 | return new Blob(compressedChunks).arrayBuffer(); 22 | 23 | }, 24 | async decode(data: ArrayBuffer) { 25 | const decompressionStream = new DecompressionStream('gzip'); 26 | const writableStream = decompressionStream.writable; 27 | const writer = writableStream.getWriter(); 28 | 29 | writer.write(new Uint8Array(data)); 30 | writer.close(); 31 | 32 | const decompressedStream = decompressionStream.readable; 33 | const decompressedChunks = []; 34 | 35 | const reader = decompressedStream.getReader(); 36 | 37 | var result: ReadableStreamReadResult; 38 | 39 | while (!(result = await reader.read()).done) { 40 | decompressedChunks.push(result.value); 41 | } 42 | 43 | return new Blob(decompressedChunks).arrayBuffer(); 44 | } 45 | }; -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Input.tsx: -------------------------------------------------------------------------------- 1 | import { prop, reactive, } from "@vicimpa/decorators"; 2 | 3 | import { AudioPort } from "../ports/AudioPort"; 4 | import { BaseNode } from "../lib/BaseNode"; 5 | import { connect } from "@vicimpa/react-decorators"; 6 | import { ctx } from "../ctx"; 7 | import { effect } from "@preact/signals-react"; 8 | import { group } from "../_groups"; 9 | import { name } from "$library/function"; 10 | 11 | @name('Input') 12 | @group('input') 13 | @reactive() 14 | @connect(self => ( 15 | effect(() => { 16 | if (!self.stream) 17 | return; 18 | 19 | self.stream.connect(self._gain); 20 | return () => { 21 | self.stream?.disconnect(self._gain); 22 | }; 23 | }) 24 | )) 25 | export default class extends BaseNode { 26 | _gain = new GainNode(ctx); 27 | 28 | @prop stream: MediaStreamAudioSourceNode | null = null; 29 | @prop devices: MediaDeviceInfo[] = []; 30 | 31 | componentDidMount(): void { 32 | navigator.mediaDevices.getUserMedia({ audio: true, video: false }) 33 | .then(media => { 34 | this.stream = ctx.createMediaStreamSource(media); 35 | }); 36 | 37 | navigator.mediaDevices.enumerateDevices() 38 | .then(devices => { 39 | this.devices = devices.filter(e => e.kind === 'audioinput'); 40 | }); 41 | } 42 | 43 | output = ( 44 | 45 | ); 46 | 47 | _view = () => ( 48 | <> 49 | 50 | ); 51 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/plugins/detectDrag.ts: -------------------------------------------------------------------------------- 1 | import { max, min } from "@vicimpa/math"; 2 | 3 | import { NodeSelection } from "../NodeSelection"; 4 | import { vec2, Vec2 } from "@vicimpa/lib-vec2"; 5 | import { effect } from "@preact/signals-react"; 6 | import { elementEvents } from "@vicimpa/events"; 7 | import { frames } from "$library/frames"; 8 | import { makeDrag } from "@vicimpa/easy-drag"; 9 | 10 | const select = makeDrag<[ctx: NodeSelection]>(({ current: now }, ctx) => { 11 | const { map } = ctx; 12 | 13 | const current = vec2(now); 14 | const a = map.offset(current); 15 | 16 | const dispose = frames((dtime) => { 17 | map.calcViewTransitionVec(current, dtime) 18 | .toObject(map); 19 | 20 | const b = map.offset(current); 21 | 22 | const from = new Vec2(min(a.x, b.x), min(a.y, b.y)); 23 | const to = new Vec2(max(a.x, b.x), max(a.y, b.y)); 24 | 25 | if (!from.equal(from) || to.equal(to)) { 26 | ctx.view = [from, to]; 27 | } 28 | }); 29 | 30 | return ({ current: newCurrent }) => { 31 | current.set(newCurrent); 32 | 33 | return () => { 34 | ctx.view = null; 35 | dispose(); 36 | }; 37 | }; 38 | }); 39 | 40 | export default (ctx: NodeSelection) => ( 41 | effect(() => elementEvents(ctx.map.div.value, 'mousedown', e => { 42 | if (!e.button) { 43 | if (!e.shiftKey && e.target !== ctx.map.div.value) 44 | return; 45 | select(e, ctx); 46 | } 47 | })) 48 | ); -------------------------------------------------------------------------------- /src/components/audio-context/nodes/ProjectValue.tsx: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "../lib/BaseNode"; 2 | import { Project } from "../views/project"; 3 | import { SignalNode } from "../lib/signalNode"; 4 | import { SignalPort } from "../ports/SignalPort"; 5 | import { connect } from "@vicimpa/react-decorators"; 6 | import { dispose } from "$library/dispose"; 7 | import { effect } from "@preact/signals-react"; 8 | import { group } from "../_groups"; 9 | import { inject } from "@vicimpa/react-decorators"; 10 | import { name } from "$library/function"; 11 | import { pipe } from "../lib/pipe"; 12 | import { store } from "$components/node-editor"; 13 | 14 | @name('Project') 15 | @connect(ctx => ( 16 | dispose( 17 | effect(() => { 18 | ctx._run.value = ctx.source.run & 1; 19 | }), 20 | pipe(ctx.source.time, ctx._time), 21 | ( 22 | ctx.source.controlls++, 23 | () => { ctx.source.controlls--; } 24 | ) 25 | ) 26 | )) 27 | @group('high') 28 | export default class extends BaseNode { 29 | @inject(() => Project) source!: Project; 30 | 31 | @store 32 | get temp() { 33 | return this.source.temp; 34 | } 35 | set temp(v) { 36 | this.source.temp = v; 37 | } 38 | 39 | _run = new SignalNode(0, { default: 0 }); 40 | _time = new SignalNode(0, { default: 0 }); 41 | 42 | output = ( 43 | <> 44 | 45 | 46 | 47 | ); 48 | } -------------------------------------------------------------------------------- /src/components/audio-context/examples/piano.txt: -------------------------------------------------------------------------------- 1 | H4sIAAAAAAAAE+3TfUwTZxwH8Ht6hZbyIm8KIbA1F4wMK0xkTjdefhkQBJwgYUGBUdpylAvQ0t51UmRO2YuGBIt6LB0LEcRljGwElxQ3s5eTqSMKCYkTNqdDtmS+hE3dSyRZxOXpXbfLJWb+sX+W7Enu7vf5fe957vL0+pk6wZBAEmp1kIoMGkPBY8iHfOgUOoVOo9NoEuH+JJpBKlyQM+iSv/PQ2/4P/uvBX7+xGt9xCc37MY9uo6DbaAktoU5Vp6rb/zl0q86qhoODCYIIIghCRRCEWqr/rQOvhwiCSCTEgaQjMALPDAxSmhcYOAuWGWcamXGmlRlnITLjTCczzkJlxlmYzDgLlxlnETLjbIXMOIuUGWdRMuMsWmacxciMs1iZcbZSZpytkmok7Q8pWSVdSakOOF52LyHtoXxekMLBCmsU1iocorBO4VCFwxQOVzhC4RUKRyocpXC0wjEKxyq8UuFVMuM9jJP6cZLjJZ9VNdFutq2aKqHd2ykDlc9YGS6DMmBXBrxBdIF4KQ+0nxJdEfBG0TsDflr0C+KlKNDeLLo04CdFl1EG6jmnydJEc1vpBo4yUAUOl6n57245Y23kqBeNnLuVNjY4aYexnuZcNtposnDMSzRr2sXZ7Vyj0WpibEab3dliamba6bR0k5NjGhgLY2pOs1utLNNOVzL1XCMuttB4TYvdxtE2roJu4yoaGVZPm1i3vpUx2expel2Zk2ZZPddI6/FG6d12l56lab3d5u858Xx9dk430mm1IVqEtOIQy384kw+pxbPmEVaQzcUPJbRaUnUvhNSgvSnEIw7dAfQRiiQjUM0D/K+YUeUTxHsTA2pzRpdPTzlgDHLTK4s3C6OFC+2xVa2wV3el6ISah/B+x628mF5BqIifGrw2CDmdd/Nq53uF463ZurkhITfjZL3rlSP98MM3uWzUyV5hd+xo4UK5A9Jrwq76Ur3QMZCbNZt9WPji95QaT5UTMu/dSaX+OALPPLGsLh/qEZ/nZOHyoekdXxV44UBaw089411iv4+DqgXOE7GOh8Raz/qJgf3CloX22NGLLjDneYZ0pTysTu898Wl1h/g+j++Cg3WGNe0bvHCB+jXp+s02YTL7x+G5Mh6WS1L3OK6Zhempqamp39zwyfvnj5qNXkg8001PXN4prJ6l+oZ38NAzO6xxnzOD/32plyE1JOvjtdVeWBpcjpv2WsH/Xt/tgT5+9MuWbTyEJdVr2dndcPFKwrt3i3hYXLNxcN3SGyDuwz4YSbbEzCZ74cZIflNHaZe4blUndN2pOfd5HQ8pi7889m1Sj7iu81Uwb3on4/izXhh5c5v65+8Pif2+1yDr7WKaquXha4/+g5sf8iDuw+swfv9CMqrjIdq0jzq26S0Q92E/PL/2wVhXphf40Ksa3/0+uMF6yNEyHs60kOsTr/cDnl64cD43M2exwRd2FCy3zIlzObwwXrydifEeg60udfpSyWHhT+czyuSjCQAA -------------------------------------------------------------------------------- /src/components/node-editor/node-list/NodeListItem.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren, ReactNode } from "react"; 2 | import { inject, provide } from "@vicimpa/react-decorators"; 3 | 4 | import { NodeList } from "./NodeList"; 5 | import { connect } from "@vicimpa/react-decorators"; 6 | import { createPortal } from "react-dom"; 7 | import { dom } from "$library/dom"; 8 | import { effect } from "@preact/signals-react"; 9 | 10 | var index = 0; 11 | 12 | @provide() 13 | @connect((ctx: NodeListItem) => ( 14 | effect(() => { 15 | const { value: group } = ctx.list.group; 16 | if (!group) return; 17 | 18 | group.appendChild(ctx.elem); 19 | ctx.elem.setAttribute('data-nodelist-item', ''); 20 | 21 | return () => { 22 | ctx.elem.remove(); 23 | }; 24 | }) 25 | )) 26 | export class NodeListItem extends Component { 27 | @inject(() => NodeList) list!: NodeList; 28 | index: number = 0; 29 | 30 | elem = dom('div', { style: { position: 'absolute', zIndex: `${this.index = index++}` } }); 31 | 32 | up() { 33 | if (this.index !== index) 34 | this.elem.style.zIndex = `${this.index = index++}`; 35 | // const { value: group } = this.list.group; 36 | 37 | // if (!group) return; 38 | // if (!group?.contains(this.elem)) return; 39 | // if (group.lastChild === this.elem) return; 40 | // group.appendChild(this.elem); 41 | } 42 | 43 | render(): ReactNode { 44 | return createPortal(this.props.children, this.elem); 45 | } 46 | } -------------------------------------------------------------------------------- /src/components/audio-context/ports/SignalPort.tsx: -------------------------------------------------------------------------------- 1 | import { prop, reactive } from "@vicimpa/decorators"; 2 | 3 | import { AudioPort } from "./AudioPort"; 4 | import { BasePort } from "../lib/BasePort"; 5 | import { SignalNode } from "../lib/signalNode"; 6 | import { connect } from "@vicimpa/react-decorators"; 7 | import { start } from "../lib/start"; 8 | 9 | @connect(ctx => start(ctx.value)) 10 | @reactive() 11 | export class SignalPort extends BasePort { 12 | @prop color = '#4f4'; 13 | value: SignalNode = this.props.value; 14 | 15 | _onConnect(port: AudioPort | SignalPort) { 16 | if (!this.value) return false; 17 | if (!port.value) return false; 18 | if (!this.props.output) return true; 19 | 20 | if (port.value instanceof AudioNode) { 21 | this.value.node.connect(port.value); 22 | return true; 23 | } 24 | 25 | if (port.value instanceof SignalNode) { 26 | this.value.node.connect(port.value.node.offset); 27 | port.value.addInput(this.value); 28 | return true; 29 | } 30 | 31 | return false; 32 | } 33 | 34 | _onDisconnect(port: AudioPort | SignalPort) { 35 | if (!this.props.output) return true; 36 | 37 | if (port.value instanceof AudioNode) { 38 | this.value.node.disconnect(port.value); 39 | } 40 | 41 | if (port.value instanceof SignalNode) { 42 | port.value.deleteInput(this.value); 43 | try { 44 | this.value.node.disconnect(port.value.node.offset); 45 | } catch (e) { } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/components/canvas/index.tsx: -------------------------------------------------------------------------------- 1 | import { effect, useComputed } from "@preact/signals-react"; 2 | import { useEffect, useMemo, JSX } from "react"; 3 | 4 | import { frames } from "$library/frames"; 5 | import { signalRef } from "$library/signals"; 6 | 7 | export type TCanvasCtx = { 8 | can: HTMLCanvasElement; 9 | ctx: CanvasRenderingContext2D; 10 | dtime: number; 11 | time: number; 12 | }; 13 | 14 | export type TCanvasProps = { 15 | draw?: (ctx: CanvasRenderingContext2D, can: HTMLCanvasElement) => any; 16 | loop?: (ctx: TCanvasCtx) => any; 17 | } & Omit; 18 | 19 | export const Canvas = ({ draw, loop, ...props }: TCanvasProps) => { 20 | const ref = useMemo(() => signalRef(), []); 21 | const ctx = useComputed(() => ref.value?.getContext('2d')); 22 | 23 | useEffect(() => ( 24 | effect(() => { 25 | if (!ref.value || !ctx.value) 26 | return; 27 | 28 | draw?.(ctx.value, ref.value); 29 | 30 | if (!loop) 31 | return; 32 | 33 | const _loop = loop; 34 | 35 | const _ctx: TCanvasCtx = { 36 | can: ref.value, 37 | ctx: ctx.value, 38 | dtime: 0, 39 | time: 0 40 | }; 41 | 42 | return frames((dtime, time) => { 43 | _ctx.dtime = dtime; 44 | _ctx.time = time; 45 | _loop(_ctx); 46 | }); 47 | }) 48 | ), [draw, loop]); 49 | 50 | return ( 51 | { ref.value = instance; }} {...props} /> 52 | ); 53 | }; -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Timer.tsx: -------------------------------------------------------------------------------- 1 | import { AudioPort } from "../ports/AudioPort"; 2 | import { BaseNode } from "../lib/BaseNode"; 3 | import { Number } from "../lib/Number"; 4 | import { SignalNode } from "../lib/signalNode"; 5 | import TimerProcessor from "../worklet/TimerProcessor"; 6 | import { Toggle } from "../lib/Toggle"; 7 | import { dispose } from "$library/dispose"; 8 | import { group } from "../_groups"; 9 | import { name } from "$library/function"; 10 | import { reactive } from "@vicimpa/decorators"; 11 | import { store } from "$components/node-editor"; 12 | 13 | @name('Timer') 14 | @group('custom') 15 | @reactive() 16 | export default class extends BaseNode { 17 | #src = new TimerProcessor(); 18 | 19 | @store _start = new SignalNode(this.#src.start, { default: 0, min: 0, max: 1 }); 20 | @store _loop = new SignalNode(this.#src.loop, { default: 0, min: 0 }); 21 | @store _speed = new SignalNode(this.#src.speed, { default: 1, min: 0 }); 22 | @store _revert = new SignalNode(this.#src.revert, { default: 1, min: 0 }); 23 | 24 | _connect = () => dispose( 25 | () => this.#src.destroy() 26 | ); 27 | 28 | output = ( 29 | 30 | ); 31 | 32 | _view = () => ( 33 | <> 34 | 35 | 36 | 39 | 40 | 43 | 44 | 45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /src/library/dom/index.ts: -------------------------------------------------------------------------------- 1 | type HTMLS = HTMLElementTagNameMap; 2 | type SVGS = SVGElementTagNameMap; 3 | 4 | type PropsKeys = { 5 | [K in keyof E]: E[K] extends string | number | boolean ? K : never 6 | }[keyof E]; 7 | 8 | type EventsKeys = { 9 | [K in keyof E]: K extends `on${string}` ? K : never 10 | }[keyof E]; 11 | 12 | type Props = { 13 | [K in PropsKeys]?: E[K] 14 | } & { 15 | [K in EventsKeys]?: E[K] 16 | } & { 17 | ref?: (elem: E) => any; 18 | } & ( 19 | E extends { style: any; } ? { 20 | style?: Partial; 21 | } : {} 22 | ); 23 | 24 | export const dom = (tag: K, { ref, style, ...props }: Props, ...children: Element[]) => { 25 | const elem = document.createElement(tag); 26 | Object.assign(elem, props); 27 | Object.assign(elem.style, style); 28 | children.forEach(_e => elem.appendChild(_e)); 29 | return (ref?.(elem), elem); 30 | }; 31 | 32 | export const svg = (tag: K, { ref, style, ...props }: Props, ...children: Element[]) => { 33 | const elem = document.createElementNS('http://www.w3.org/2000/svg', tag); 34 | Object.assign(elem, props); 35 | Object.assign(elem.style, style); 36 | children.forEach(_e => elem.appendChild(_e)); 37 | return (ref?.(elem), elem); 38 | }; 39 | 40 | export function selectText(element: HTMLElement) { 41 | const selection = window.getSelection()!; 42 | const range = document.createRange(); 43 | range.selectNodeContents(element); 44 | selection.removeAllRanges(); 45 | selection.addRange(range); 46 | } 47 | -------------------------------------------------------------------------------- /src/components/node-editor/node-selection/NodeSelection.tsx: -------------------------------------------------------------------------------- 1 | import { Component, PropsWithChildren } from "react"; 2 | import { inject, provide } from "@vicimpa/react-decorators"; 3 | import { prop, reactive } from "@vicimpa/decorators"; 4 | 5 | import { NodeItem } from "../node-item"; 6 | import { NodeList } from "../node-list"; 7 | import { NodeMap } from "../node-map"; 8 | import { NodeSelectionView } from "./NodeSelectionView"; 9 | import { Vec2 } from "@vicimpa/lib-vec2"; 10 | import { connect } from "@vicimpa/react-decorators"; 11 | import detectDrag from "./plugins/detectDrag"; 12 | import detectSelect from "./plugins/detectSelect"; 13 | import detectShift from "./plugins/detectShift"; 14 | 15 | @provide() 16 | @connect(detectShift, detectDrag, detectSelect) 17 | @reactive() 18 | export class NodeSelection extends Component { 19 | @inject(() => NodeMap) map!: NodeMap; 20 | @inject(() => NodeList) list!: NodeList; 21 | 22 | @prop _shiftCount = 0; 23 | shiftKey = false; 24 | 25 | _select: NodeItem[] | null = null; 26 | 27 | @prop view: [from: Vec2, to: Vec2] | null = null; 28 | @prop select: NodeItem[] = []; 29 | 30 | toSelect(...items: NodeItem[]) { 31 | if (!this.shiftKey) { 32 | this.select = items; 33 | return; 34 | } 35 | 36 | var selected = new Set(this._select ?? this.select); 37 | 38 | for (var item of items) { 39 | if (selected.has(item)) { 40 | selected.delete(item); 41 | } else { 42 | selected.add(item); 43 | } 44 | } 45 | 46 | this.select = [...selected]; 47 | } 48 | 49 | render() { 50 | return ( 51 | <> 52 | {this.props.children} 53 | 54 | 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /src/components/audio-context/lib/BaseNode.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from "react"; 2 | 3 | import { NodeItem } from "$components/node-editor"; 4 | import { computed } from "@preact/signals-react"; 5 | import { connect } from "@vicimpa/react-decorators"; 6 | import { reactive } from "@vicimpa/decorators"; 7 | import rsp from "@vicimpa/rsp"; 8 | import s from "../styles.module.sass"; 9 | 10 | @connect(ctx => ctx._connect()) 11 | @reactive() 12 | export class BaseNode extends NodeItem { 13 | title: string = Object.getPrototypeOf(this)?.constructor?.name ?? 'BaseNode'; 14 | 15 | padding = 10; 16 | 17 | head: ReactNode = null; 18 | input: ReactNode = null; 19 | output: ReactNode = null; 20 | 21 | canCreate = true; 22 | canRemove = true; 23 | isSelect = computed(() => this.select || undefined); 24 | 25 | _connect: () => (() => void) | void = () => { }; 26 | _view: FC = () => null; 27 | 28 | view = () => ( 29 | 34 |
35 | 36 | {this.title} 37 | 38 |
39 | {this.head} 40 | { 41 | this.canRemove && 42 |
45 |
46 |
47 | {this.input && ( 48 |
49 | {this.input} 50 |
51 | )} 52 |
53 | {} 54 |
55 | {this.output && ( 56 |
57 | {this.output} 58 |
59 | )} 60 |
61 |
62 | ); 63 | } -------------------------------------------------------------------------------- /src/components/node-editor/node-lines/NodeLine.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC, MouseEvent } from "react"; 2 | 3 | import { NodePort } from "../node-port"; 4 | import { Vec2 } from "@vicimpa/lib-vec2"; 5 | import { max } from "@vicimpa/math"; 6 | import { useSignals } from "@preact/signals-react/runtime"; 7 | 8 | export type TNodeLineProps = { 9 | from: NodePort | Vec2; 10 | to: NodePort | Vec2; 11 | color?: string; 12 | size?: number; 13 | onMouseDown?: (event: MouseEvent) => void; 14 | }; 15 | 16 | function drawBezierCurve(from: Vec2 | NodePort, to: Vec2 | NodePort) { 17 | const step1 = new Vec2(from); 18 | const step2 = new Vec2(to); 19 | 20 | const distance = max(step1.distance(to) * .5, 50); 21 | 22 | if (from instanceof NodePort) 23 | step1.plus(distance * (+!!from.props.output * 2 - 1), 0); 24 | 25 | if (to instanceof NodePort) 26 | step2.plus(distance * (+!!to.props.output * 2 - 1), 0); 27 | 28 | return `M ${from.x} ${from.y} C ${step1.x} ${step1.y}, ${step2.x} ${step2.y}, ${to.x} ${to.y}`; 29 | } 30 | 31 | export const NodeLine: FC = ({ 32 | from, 33 | to, 34 | color = '#fff', 35 | size = 3, 36 | onMouseDown, 37 | }) => { 38 | useSignals(); 39 | 40 | const style: CSSProperties = { 41 | pointerEvents: onMouseDown ? 'visibleStroke' : 'none', 42 | cursor: 'pointer' 43 | }; 44 | 45 | const path = drawBezierCurve(from, to); 46 | color = from instanceof NodePort ? from.color : to instanceof NodePort ? to.color : '#999'; 47 | 48 | return ( 49 | 59 | ); 60 | }; -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Recorder.tsx: -------------------------------------------------------------------------------- 1 | import { prop, reactive, } from "@vicimpa/decorators"; 2 | 3 | import { AudioPort } from "../ports/AudioPort"; 4 | import { BaseNode } from "../lib/BaseNode"; 5 | import { computed } from "@preact/signals-react"; 6 | import { connect } from "@vicimpa/react-decorators"; 7 | import { ctx } from "../ctx"; 8 | import { frames } from "$library/frames"; 9 | import { group } from "../_groups"; 10 | import { name } from "$library/function"; 11 | 12 | @name('Recorder') 13 | @group('output') 14 | @connect(self => frames(() => { 15 | let isStart = self.start !== -1; 16 | 17 | if (isStart && self.stream.state === 'paused') 18 | self.start = -1; 19 | 20 | if (!isStart && self.stream.state === 'recording') 21 | self.start = performance.now(); 22 | 23 | if (!isStart) 24 | self.remaining = 0; 25 | 26 | if (isStart) 27 | self.remaining = performance.now() - self.start; 28 | })) 29 | @reactive() 30 | export default class extends BaseNode { 31 | #in = ctx.createMediaStreamDestination(); 32 | stream = new MediaRecorder(this.#in.stream); 33 | 34 | @prop start = -1; 35 | @prop remaining = 0; 36 | @prop records = []; 37 | 38 | input = ( 39 | 40 | ); 41 | 42 | _timer = computed(() => { 43 | let { remaining: time } = this; 44 | 45 | let ms = `${(time | 0) % 1000}`.padStart(3, '0'); 46 | let s = `${((time /= 1000) | 0) % 60}`.padStart(2, '0'); 47 | let m = `${(time /= 60) | 0}`.padStart(2, '0'); 48 | 49 | return ( 50 |

{m}:{s}:{ms}

51 | ); 52 | }); 53 | 54 | _view = () => ( 55 | <> 56 | {this._timer} 57 | 58 | 59 | 60 | ); 61 | } -------------------------------------------------------------------------------- /src/components/audio-context/examples/club.txt: -------------------------------------------------------------------------------- 1 | H4sIAAAAAAAAE4WUf1BUVRTH731vF3ZXkB8iLki5atJai7GuIKTBjSYhR7FABVl2Xm93L8uTfbuw7+2CzDD+SNFRLLRSxB85GOU4qTWDmT/SBzVOjmLlj8xmZLQxscSMKZWSnObt3tUX2HT/eOd8zvfcc8/9sbuHHvf0uAigVut0cXF6fRJFJ42nx6fKDiV/1KlGY1qaRXYt+fmFdGGR7GqK5seNmr9wYakuqrRMDpTZaJtdduwcmU3MsEzO/ZjJilXcgcBSFVAvbYQyNcImOCy/CTZDFVA3w9ZgTivcA8lqj48O3d4euD+Ysh92wP9qswMeHL7wQXgU/k9vR2Hn8GAnPBFeiD4BTwdnR5yGZyFQn4UX4UXYEwypQqHgsffAXtgL+2E/HAxqg/AU1adWAwBUAAAKADAGAJAMAIgEACSRuIpwJNETiQ+JFrZyndgh+YkKX45rAQDxAAANACAFPBo0qQOIjSL9hLVRCk0eCUSHxMYodIrsAShqJBOfJqxWMCQ9hdeS4xEKpgkr96smepi1hCPI/MghPFrBNNk/IHnKfjWE44ZwWNcS1hHWER5BOGzHkr5GEBut0CkFRxEbPeTc4wmPJHoMiY8k8XA/scTGET12iB6vuD9ILEXuL3yPkLyr8DnJrCd6ooIpwhR5m4DMA4RpxT2H31aYnyB8imLEJdWYqfDhGsaJRb8HM6xD5AK4TmBrRa9XrGRcLOfhnG7Og4UA6/ZjwZqRnp0+3ZSZnZ2eYTJbMsxZ02xWs8lsMttEzFcz1YydcTDYh3mnz88LVnO6aarF5uQC1kyLKTNj+tQsmzXNMs2UZkm3MRU+lseCXJ31ebwiZjxeH8+6uXo85TnWJ3IVnINj3VO8LhfDe52Yxz4XZu0Cz3n8gsjxWHC4Wb6aCXbGCCLrExmhlq1mOE8A+0SBq8clnFOslJ0CzLkqRYfXI2KPOB/XiS+5/XaDwLNut4H3C5zDUMuJlQY7KwgGedPYafBh1iEavB5DFeeoMgicy8O6dbpXfVgQDBOKq1kHnmCo8PpC07dAbSSEmtCAOp1GAzUaLZSHRhOiyEeBsA0mammFAIIFwiZYUvuvWg8tPTxGVg+VhhoNTR3SwkgoZQGgqgu+Rzr0a5M/dI/ybwQ+fNpQ9mHQoSLGzmobXQYBiI3tD+QBIOWGbHfXGlWytKB9snsDGqjaVvVHG5YKrtYn7Mv/OXffPstyKqoFpR+dkZR9vxiNvNb+9beqdjQHHaD5r8qR1eai1AMrkL5ne9O2zytQ81r73Zu5WApygwtd+eX5UUapOKRfXoxunZvUVTBzPfpm61NZxzc1oGB9fQuKul0460xOLWLMiYdn0C0o/cstu8svNiBwIUY/s2GetAD9tvF+UT3aPXCJa+4IoDzq173r7tUj/etreme3+RGnG+jVl6xChf0dO7sd76BJ5upZcZtXI6bb8lPbsXUIpkpz70SvQoldI07mGHegD258b3zS5kGTy9/qumO8lntlqzHjet9mNGV3/pz4BBcaHJfw++0L76ENjYOfDESvRK1b5HEy1zC28LgushW9MZC6/sGKFagAfDcps3gDOjR+4phdnB3Z302m85o3oSvbf/iMn5YndfTEvry2cCuScl885U97QXpyo+HS+dOb0MTeV5ZeP16DDhfd3Pv2vFb00ewH80BMpVR+pKTzSOIupG+JYfrMiyTvj3XL7r25A0mL/rqzeHUJqklpOseX70Q7+p8937ncTu7pau75v/987dOWNlRa1pe/vx2j9bcO7Lx34uOcZaZl2pRnFkhzB96/3MitRJWumjNr8tvR9Gvo7rHuJSjn1hdCqfNDtCRlduYNawD9A64xXRLhCAAA -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Turner.tsx: -------------------------------------------------------------------------------- 1 | import { BaseNode } from "../lib/BaseNode"; 2 | import { Canvas } from "$components/canvas"; 3 | import { Range } from "../lib/Range"; 4 | import { SignalNode } from "../lib/signalNode"; 5 | import { SignalPort } from "../ports/SignalPort"; 6 | import { group } from "../_groups"; 7 | import { line } from "../lib/line"; 8 | import { name } from "$library/function"; 9 | import { store } from "$components/node-editor"; 10 | 11 | @name('Turner') 12 | @group('analyze') 13 | export default class extends BaseNode { 14 | _in = new SignalNode(0, { default: 0 }); 15 | 16 | @store _sensitivity = new SignalNode(1, { default: 1, min: 0.0001, max: 10 }); 17 | @store _speed = new SignalNode(1, { default: 1, min: 0.01, max: 10 }); 18 | 19 | _data: [x: number, y: number][] = Array.from({ length: 1000 }, (_, i) => [0, i]); 20 | 21 | input = ( 22 | 23 | ); 24 | 25 | _view = () => ( 26 | <> 27 | { 28 | var { width, height } = can; 29 | const data = this._data; 30 | 31 | data.unshift([ 32 | this._in.value * height * .4, 33 | this._speed.value * dtime * .1 34 | ]); 35 | 36 | ctx.resetTransform(); 37 | ctx.clearRect(0, 0, width, height); 38 | ctx.setTransform(-1, 0, 0, -1, width, height * .5); 39 | 40 | line(ctx, (m) => { 41 | for (var i = 0, x = 0; x <= width + 100 && i < data.length; x += data[i][1] ?? 0, i++) 42 | m(x, data[i][0] * this._sensitivity.value); 43 | data.splice(i); 44 | }, { size: 2, color: '#777' }); 45 | }} /> 46 | 47 | 48 | 49 | ); 50 | } -------------------------------------------------------------------------------- /src/components/audio-context/nodes/Player.tsx: -------------------------------------------------------------------------------- 1 | import { ctx, empty } from "../ctx"; 2 | import { prop, reactive, } from "@vicimpa/decorators"; 3 | 4 | import { AudioPort } from "../ports/AudioPort"; 5 | import { BaseNode } from "../lib/BaseNode"; 6 | import { dispose } from "$library/dispose"; 7 | import { effect } from "@preact/signals-react"; 8 | import { group } from "../_groups"; 9 | import { name } from "$library/function"; 10 | import { pipe } from "../lib/pipe"; 11 | import { signalRef } from "$library/signals"; 12 | 13 | @name('Player') 14 | @group('input') 15 | @reactive() 16 | export default class extends BaseNode { 17 | #gain = new GainNode(ctx); 18 | 19 | @prop fileList: FileList | null = null; 20 | 21 | audio = signalRef(); 22 | 23 | _connect = () => ( 24 | dispose( 25 | pipe(this.#gain, empty), 26 | effect(() => { 27 | if (!this.audio.value) 28 | return; 29 | 30 | if (!this.fileList) 31 | return; 32 | 33 | this.fileList.item(0)?.arrayBuffer() 34 | .then(buffer => new Blob([buffer])) 35 | .then(blob => URL.createObjectURL(blob)) 36 | .then(url => this.audio.value!.src = url); 37 | }), 38 | effect(() => { 39 | if (!this.audio.value) 40 | return; 41 | 42 | const media = ctx.createMediaElementSource( 43 | this.audio.value 44 | ); 45 | 46 | media.connect(this.#gain); 47 | 48 | return () => { 49 | media.disconnect(this.#gain); 50 | }; 51 | }) 52 | ) 53 | ); 54 | 55 | output = ( 56 | 57 | ); 58 | 59 | _view = () => ( 60 | <> 61 | e.currentTarget.blur()} onChange={e => this.fileList = e.target.files} type="file" /> 62 |