├── .gitignore
├── src
├── icons
│ ├── icon-128.png
│ ├── icon-144.png
│ ├── icon-152.png
│ ├── icon-192.png
│ ├── icon-256.png
│ ├── icon-512.png
│ └── icon.svg
├── waveform.ts
├── AudioNode.ts
├── Playback.ts
├── getFrequency.ts
├── manifest.json
├── sw.ts
├── keys.ts
├── Controls.ts
├── audioRecorder.ts
├── createSynthControls.ts
├── Slider.ts
├── midi.ts
├── ToneGenerator.ts
├── AudioTrack.ts
├── index.html
├── style.css
└── script.ts
├── tsconfig.json
├── index.d.ts
├── README.md
├── gh-redirect
└── index.html
├── test
├── helpers.ts
└── e2e
│ ├── key-controls.spec.ts
│ ├── virtual-keyboard.spec.ts
│ └── slider.spec.ts
├── package.json
├── .github
└── workflows
│ ├── playwright.yml
│ └── static.yml
├── esbuild.js
├── playwright.config.ts
├── webmidi.d.ts
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | playwright-report
4 | test-results
--------------------------------------------------------------------------------
/src/icons/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-128.png
--------------------------------------------------------------------------------
/src/icons/icon-144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-144.png
--------------------------------------------------------------------------------
/src/icons/icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-152.png
--------------------------------------------------------------------------------
/src/icons/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-192.png
--------------------------------------------------------------------------------
/src/icons/icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-256.png
--------------------------------------------------------------------------------
/src/icons/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamschulz/js-synth/HEAD/src/icons/icon-512.png
--------------------------------------------------------------------------------
/src/waveform.ts:
--------------------------------------------------------------------------------
1 | export type Waveform = "sine" | "square" | "triangle" | "sawtooth" | "noise";
2 |
--------------------------------------------------------------------------------
/src/AudioNode.ts:
--------------------------------------------------------------------------------
1 | export type MyAudioNode = {
2 | node: OscillatorNode | AudioBufferSourceNode;
3 | release: GainNode;
4 | };
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "typeRoots": ["index.d.ts"],
4 | "noEmit": true,
5 | "allowImportingTsExtensions": true,
6 | "lib": ["ES2024", "dom"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import { Main } from "./src/script";
2 |
3 | declare global {
4 | interface Window {
5 | Main: Main;
6 | }
7 |
8 | interface Navigator {
9 | keyboard: {
10 | getLayoutMap: () => Promise<{
11 | get: (string) => string;
12 | }>;
13 | };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-synth
2 |
3 | a synthesizer PWA written in javascript
4 |
5 | 
6 |
7 | - uses the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)
8 | - demo: https://jssynth.xyz
9 |
10 | - Build: `npm run build`
11 | - Test:
12 | - `npx playwright install`, `npx playwright install-dependencies`
13 | - `npm run test`
14 |
--------------------------------------------------------------------------------
/gh-redirect/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | JSSynth
9 |
10 |
11 |
12 |
13 | Redirect
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/Playback.ts:
--------------------------------------------------------------------------------
1 | import { RecNote } from "./signalRecorder";
2 |
3 | export class Playback {
4 | notes: RecNote[];
5 | ctx: AudioContext;
6 |
7 | constructor(notes: RecNote[]) {
8 | this.notes = notes;
9 | this.ctx = new window.AudioContext();
10 | }
11 |
12 | public play() {
13 | this.notes.forEach((note) => {
14 | window.setTimeout(() => {
15 | console.log("on", note.key);
16 | }, note.start);
17 |
18 | window.setTimeout(() => {
19 | console.log("off", note.key);
20 | }, note.stop);
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/helpers.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 |
3 | export function sleep(time: number): Promise {
4 | return new Promise((resolve) => setTimeout(resolve, time));
5 | }
6 |
7 | export async function getActiveNotes(page: Page): Promise