forked from oamaok/modulate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a7b87dc
Showing
51 changed files
with
4,453 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
dist | ||
data/database.sqlite3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
dist | ||
client/generated | ||
server/migrations/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"semi": false, | ||
"singleQuote": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
const fs = require('fs/promises') | ||
const path = require('path') | ||
const esbuild = require('esbuild') | ||
const chokidar = require('chokidar') | ||
|
||
const recursiveCopy = async (srcDir, destDir) => { | ||
const absoluteSrc = path.resolve(__dirname, srcDir) | ||
const absoluteDest = path.resolve(__dirname, destDir) | ||
let entryStack = await fs.readdir(srcDir) | ||
let entry | ||
|
||
while ((entry = entryStack.pop())) { | ||
const entryPath = path.resolve(absoluteSrc, entry) | ||
const stats = await fs.stat(entryPath) | ||
|
||
const destPath = path.join(absoluteDest, entry) | ||
if (stats.isDirectory()) { | ||
try { | ||
const destStats = await fs.stat(destPath) | ||
if (!destStats.isDirectory()) { | ||
throw new Error( | ||
`Destination path exists and is not a directory: ${destPath}` | ||
) | ||
} | ||
} catch (err) { | ||
await fs.mkdir(destPath) | ||
} | ||
|
||
entryStack.push( | ||
...(await fs.readdir(entryPath)).map((e) => path.join(entry, e)) | ||
) | ||
} else { | ||
await fs.copyFile(entryPath, destPath) | ||
} | ||
} | ||
} | ||
|
||
const buildClient = async () => { | ||
console.time('Build client') | ||
|
||
await Promise.all([ | ||
esbuild.build({ | ||
entryPoints: ['./client/src/index.tsx'], | ||
bundle: true, | ||
outfile: './dist/client/assets/main.js', | ||
incremental: true, | ||
jsxFactory: 'h', | ||
jsxFragment: 'Fragment', | ||
minify: false, | ||
define: { | ||
// 'process.env.NODE_ENV': '"production"', | ||
}, | ||
}), | ||
recursiveCopy('./client/static', './dist/client'), | ||
]) | ||
|
||
console.timeEnd('Build client') | ||
} | ||
|
||
const buildWorklets = async () => { | ||
console.time('Build worklets') | ||
|
||
const worklets = (await fs.readdir('./client/worklets')) | ||
.filter((worklet) => worklet.endsWith('.ts')) | ||
.map((worklet) => worklet.split('.')[0]) | ||
|
||
await Promise.all( | ||
worklets.map((worklet) => | ||
esbuild.build({ | ||
entryPoints: [`./client/worklets/${worklet}.ts`], | ||
bundle: true, | ||
outfile: `./dist/client/worklets/${worklet}.js`, | ||
incremental: true, | ||
minify: false, | ||
}) | ||
) | ||
) | ||
|
||
await fs.writeFile( | ||
'./client/src/generated/worklets.ts', | ||
` | ||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
// | ||
// THIS IS A GENERATED FILE, DO NOT EDIT MANUALLY | ||
// | ||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | ||
${worklets | ||
.map((worklet) => `import ${worklet} from '../../worklets/${worklet}'`) | ||
.join('\n')} | ||
export const workletNames = ${JSON.stringify(worklets)} as const | ||
export type Worklets = { | ||
${worklets.map((worklet) => ` ${worklet}: typeof ${worklet}`).join('\n')} | ||
} | ||
` | ||
) | ||
console.timeEnd('Build worklets') | ||
} | ||
|
||
const buildServer = () => {} | ||
|
||
;(async () => { | ||
await buildWorklets() | ||
await buildClient() | ||
await buildServer() | ||
|
||
chokidar | ||
.watch('./worklets/*', { | ||
persistent: true, | ||
ignoreInitial: true, | ||
}) | ||
.on('all', buildWorklets) | ||
|
||
chokidar | ||
.watch('./client/**/*', { | ||
persistent: true, | ||
ignoreInitial: true, | ||
}) | ||
.on('all', buildClient) | ||
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
type Method = 'GET' | 'POST' | ||
type Options = { | ||
body?: any | ||
params?: any | ||
} | ||
|
||
const request = (method: Method, endpoint: string, { body }: Options) => { | ||
const token = localStorage.getItem('token') | ||
|
||
return fetch(endpoint, { | ||
method, | ||
body: body ? JSON.stringify(body) : undefined, | ||
credentials: 'include', | ||
headers: { | ||
Accept: 'application/json', | ||
Authorization: token ? `Bearer ${token}` : '', | ||
}, | ||
}).then((res) => res.json()) | ||
} | ||
|
||
const get = (endpoint: string, options?: Options) => { | ||
return request('GET', endpoint, { ...options }) | ||
} | ||
|
||
const post = (endpoint: string, options?: Options) => { | ||
return request('POST', endpoint, { ...options }) | ||
} | ||
|
||
export const getIdentity = () => { | ||
return get('/api/identity') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { workletNames, WorkletNode } from './worklets' | ||
|
||
let audioContext: AudioContext | null = null | ||
|
||
export const initializeAudio = async () => { | ||
audioContext = new AudioContext() | ||
for (const worklet of workletNames) { | ||
const path = `/worklets/${worklet}.js` | ||
try { | ||
await audioContext.audioWorklet.addModule(path) | ||
} catch (err) { | ||
throw new Error(`Failed to load audio worklet: ${path}`) | ||
} | ||
} | ||
} | ||
|
||
export const getAudioContext = () => { | ||
if (!audioContext) { | ||
throw new Error('AudioContext not initialized') | ||
} | ||
|
||
return audioContext | ||
} | ||
|
||
export const controlVoltageToFrequency = (voltage: number) => { | ||
return 13.75 * 2 ** voltage | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { h, useEffect } from 'kaiku' | ||
import state, { | ||
getCableConnectionCandidate, | ||
getSocketPosition, | ||
releaseActiveCable, | ||
} from '../state' | ||
import CablePath from './CablePath' | ||
|
||
const ActiveCable = () => { | ||
useEffect(() => { | ||
if (state.activeCable) { | ||
document.addEventListener('mouseup', releaseActiveCable) | ||
|
||
return () => { | ||
document.removeEventListener('mouseup', releaseActiveCable) | ||
} | ||
} | ||
}) | ||
|
||
if (!state.activeCable) return null | ||
|
||
const candidateSocket = getCableConnectionCandidate() | ||
|
||
const a = getSocketPosition(state.activeCable.from.socket) | ||
const b = candidateSocket ? getSocketPosition(candidateSocket) : state.cursor | ||
|
||
const from = state.activeCable.from.socket.type === 'output' ? a : b | ||
const to = state.activeCable.from.socket.type === 'output' ? b : a | ||
|
||
return <CablePath from={from} to={to} /> | ||
} | ||
|
||
export default ActiveCable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { h, useEffect } from 'kaiku' | ||
import { getRegisteredSocket, getSockets } from '../sockets' | ||
import { getSocketPosition } from '../state' | ||
import { ConnectedSocket } from '../types' | ||
import CablePath from './CablePath' | ||
|
||
type Props = { | ||
from: ConnectedSocket | ||
to: ConnectedSocket | ||
} | ||
|
||
const Cable = ({ from, to }: Props) => { | ||
useEffect(() => { | ||
const outputSocket = getRegisteredSocket(from.moduleId, from.name) | ||
const inputSocket = getRegisteredSocket(to.moduleId, to.name) | ||
|
||
if (outputSocket.type === 'input') { | ||
throw new Error('invalid outputSocket type') | ||
} | ||
|
||
if (inputSocket.type === 'output') { | ||
throw new Error('invalid inputSocket type') | ||
} | ||
|
||
if (inputSocket.node instanceof AudioNode) { | ||
outputSocket.node.connect(inputSocket.node) | ||
} else { | ||
outputSocket.node.connect(inputSocket.node) | ||
} | ||
|
||
return () => { | ||
const outputSocket = getRegisteredSocket(from.moduleId, from.name) | ||
const inputSocket = getRegisteredSocket(to.moduleId, to.name) | ||
|
||
if (outputSocket.type === 'input') { | ||
throw new Error('invalid outputSocket type') | ||
} | ||
|
||
if (inputSocket.type === 'output') { | ||
throw new Error('invalid inputSocket type') | ||
} | ||
|
||
if (inputSocket.node instanceof AudioNode) { | ||
outputSocket.node.disconnect(inputSocket.node) | ||
} else { | ||
outputSocket.node.disconnect(inputSocket.node) | ||
} | ||
} | ||
}) | ||
|
||
return <CablePath from={getSocketPosition(from)} to={getSocketPosition(to)} /> | ||
} | ||
|
||
export default Cable |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { h } from 'kaiku' | ||
import { Vec2 } from '../types' | ||
|
||
type Props = { | ||
from: Vec2 | ||
to: Vec2 | ||
} | ||
|
||
const CablePath = ({ from, to }: Props) => { | ||
const controlPointOffset = Math.floor(Math.sqrt(Math.abs(from.y - to.y)) * 10) | ||
|
||
return ( | ||
<path | ||
d={` | ||
M ${from.x} ${from.y} | ||
C ${from.x + controlPointOffset} ${from.y} | ||
${to.x - controlPointOffset} ${to.y} | ||
${to.x} ${to.y} | ||
`} | ||
stroke-width="3" | ||
stroke-linecap="round" | ||
stroke="rgba(255,255,255,0.5)" | ||
fill="transparent" | ||
/> | ||
) | ||
} | ||
|
||
export default CablePath |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { h } from 'kaiku' | ||
import state from '../state' | ||
|
||
import ActiveCable from './ActiveCable' | ||
import Cable from './Cable' | ||
|
||
const Cables = () => ( | ||
<div className="cables"> | ||
<svg viewBox={`0 0 ${state.viewport.width} ${state.viewport.height}`}> | ||
{state.cables.map(({ id, from, to }) => ( | ||
<Cable key={id} from={from.socket} to={to.socket} /> | ||
))} | ||
<ActiveCable /> | ||
</svg> | ||
</div> | ||
) | ||
|
||
export default Cables |
Oops, something went wrong.