SREAL commited on
Commit
8ab8c77
·
verified ·
1 Parent(s): e5cc123

I want to get rid of all the extra windows, just one window, lets make it compact and make the controls look more 'synth like'. I want to add a more comprehensive visual ADSR editor for the amp and for the filter envelope. also, i want it to have a semi 3-D oscilloscope aesthetic, kind if like a pip-boy from fallout. I have attached a reference image.

Browse files
Files changed (3) hide show
  1. index.html +161 -125
  2. script.js +186 -223
  3. style.css +95 -38
index.html CHANGED
@@ -10,168 +10,209 @@
10
  <script src="https://unpkg.com/feather-icons"></script>
11
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
12
  </head>
13
- <body class="bg-gray-900 text-white min-h-screen">
14
- <custom-navbar></custom-navbar>
15
-
16
  <div class="container mx-auto px-4 py-8">
17
  <header class="text-center mb-8">
18
- <h1 class="text-3xl md:text-4xl font-bold mb-2 bg-gradient-to-r from-gray-400 to-white bg-clip-text text-transparent">
19
- SynthWave Studio
20
  </h1>
21
- <p class="text-gray-400 text-sm max-w-2xl mx-auto">
22
- Polyphonic MIDI synthesizer with velocity-sensitive controls
23
- </p>
24
  </header>
25
 
26
- <!-- Main Synth Interface -->
27
- <div class="bg-gray-800 rounded-xl p-6 shadow-2xl border border-gray-700">
28
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
29
- <!-- Visualizer Section -->
30
  <div class="lg:col-span-2">
31
- <div class="bg-gray-900 rounded-lg p-4 border border-gray-700 shadow-inner">
32
- <div class="flex justify-between items-center mb-3">
33
- <h2 class="text-lg font-bold text-gray-300 flex items-center">
34
- <i data-feather="activity" class="mr-2 text-green-400"></i> Oscilloscope
35
- </h2>
36
- <div class="flex space-x-2">
37
- <button class="w-3 h-3 rounded-full bg-red-500"></button>
38
- <button class="w-3 h-3 rounded-full bg-yellow-500"></button>
39
- <button class="w-3 h-3 rounded-full bg-green-500"></button>
40
- </div>
41
- </div>
42
- <div class="relative">
43
- <canvas id="visualizer" class="w-full h-48 rounded bg-black border border-gray-700"></canvas>
44
- <div class="absolute inset-0 rounded pointer-events-none border border-gray-700"></div>
45
  </div>
46
  </div>
47
-
48
  <!-- ADSR Envelope Editors -->
49
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
50
- <!-- Amp Envelope -->
51
- <div class="bg-gray-900 rounded-lg p-4 border border-gray-700">
52
- <h3 class="font-semibold mb-3 text-gray-300 flex items-center">
53
- <i data-feather="volume-2" class="mr-2 text-blue-400"></i> Amp Envelope
54
  </h3>
55
- <canvas id="amp-envelope" class="w-full h-32 bg-gray-800 rounded border border-gray-700"></canvas>
56
- <div class="grid grid-cols-4 gap-2 mt-3">
 
 
57
  <div>
58
- <label class="block text-xs text-gray-400 mb-1">A</label>
59
- <input type="range" id="envelope-attack" min="0.01" max="2" value="0.1" step="0.01"
60
- class="w-full accent-blue-500">
 
61
  </div>
62
  <div>
63
- <label class="block text-xs text-gray-400 mb-1">D</label>
64
- <input type="range" id="envelope-decay" min="0.01" max="2" value="0.3" step="0.01"
65
- class="w-full accent-blue-500">
 
66
  </div>
67
  <div>
68
- <label class="block text-xs text-gray-400 mb-1">S</label>
69
  <input type="range" id="envelope-sustain" min="0" max="1" value="0.5" step="0.01"
70
- class="w-full accent-blue-500">
 
71
  </div>
72
  <div>
73
- <label class="block text-xs text-gray-400 mb-1">R</label>
74
- <input type="range" id="envelope-release" min="0.1" max="5" value="0.5" step="0.1"
75
- class="w-full accent-blue-500">
 
76
  </div>
77
  </div>
78
  </div>
79
-
80
- <!-- Filter Envelope -->
81
- <div class="bg-gray-900 rounded-lg p-4 border border-gray-700">
82
- <h3 class="font-semibold mb-3 text-gray-300 flex items-center">
83
- <i data-feather="filter" class="mr-2 text-purple-400"></i> Filter Envelope
84
  </h3>
85
- <canvas id="filter-envelope" class="w-full h-32 bg-gray-800 rounded border border-gray-700"></canvas>
86
- <div class="grid grid-cols-4 gap-2 mt-3">
 
 
87
  <div>
88
- <label class="block text-xs text-gray-400 mb-1">A</label>
89
- <input type="range" id="filter-env-attack" min="0.01" max="2" value="0.1" step="0.01"
90
- class="w-full accent-purple-500">
 
91
  </div>
92
  <div>
93
- <label class="block text-xs text-gray-400 mb-1">D</label>
94
- <input type="range" id="filter-env-decay" min="0.01" max="2" value="0.3" step="0.01"
95
- class="w-full accent-purple-500">
 
96
  </div>
97
  <div>
98
- <label class="block text-xs text-gray-400 mb-1">S</label>
99
- <input type="range" id="filter-env-sustain" min="0" max="1" value="0.5" step="0.01"
100
- class="w-full accent-purple-500">
 
101
  </div>
102
  <div>
103
- <label class="block text-xs text-gray-400 mb-1">R</label>
104
- <input type="range" id="filter-env-release" min="0.1" max="5" value="0.5" step="0.1"
105
- class="w-full accent-purple-500">
 
106
  </div>
107
  </div>
108
  </div>
109
  </div>
110
  </div>
111
-
112
- <!-- Controls Section -->
113
- <div class="bg-gray-900 rounded-lg p-4 border border-gray-700">
114
- <h2 class="text-lg font-bold mb-4 text-gray-300 flex items-center">
115
- <i data-feather="sliders" class="mr-2 text-yellow-400"></i> Synth Controls
116
- </h2>
117
-
118
- <div class="space-y-5">
119
- <!-- Oscillator Controls -->
120
- <div>
121
- <h3 class="font-semibold mb-2 text-gray-300 border-b border-gray-700 pb-1">Oscillator</h3>
122
- <div class="space-y-3">
123
- <div>
124
- <label class="block text-xs text-gray-400 mb-1">Wave Type</label>
125
- <select id="oscillator-type" class="w-full bg-gray-800 text-white rounded p-2 text-sm border border-gray-700">
126
- <option value="sawtooth">Sawtooth</option>
127
- <option value="sine">Sine</option>
128
- <option value="square">Square</option>
129
- <option value="triangle">Triangle</option>
130
- </select>
131
- </div>
132
- </div>
133
  </div>
134
 
135
- <!-- Filter Controls -->
136
- <div>
137
- <h3 class="font-semibold mb-2 text-gray-300 border-b border-gray-700 pb-1">Filter</h3>
138
- <div class="space-y-3">
139
- <div>
140
- <label class="block text-xs text-gray-400 mb-1">Cutoff</label>
141
- <input type="range" id="filter-frequency" min="50" max="5000" value="1000" step="1"
142
- class="w-full accent-purple-500">
143
- </div>
144
- <div>
145
- <label class="block text-xs text-gray-400 mb-1">Resonance</label>
146
- <input type="range" id="filter-q" min="0.1" max="10" value="1" step="0.1"
147
- class="w-full accent-purple-500">
148
- </div>
149
  </div>
150
  </div>
151
 
152
- <!-- Effects Controls -->
153
- <div>
154
- <h3 class="font-semibold mb-2 text-gray-300 border-b border-gray-700 pb-1">Effects</h3>
155
- <div class="space-y-3">
156
- <div>
157
- <label class="block text-xs text-gray-400 mb-1">Reverb</label>
158
- <input type="range" id="reverb-wet" min="0" max="1" value="0.3" step="0.01"
159
- class="w-full accent-green-500">
160
- </div>
161
- <div>
162
- <label class="block text-xs text-gray-400 mb-1">Distortion</label>
163
- <input type="range" id="distortion-wet" min="0" max="1" value="0.2" step="0.01"
164
- class="w-full accent-red-500">
165
- </div>
166
  </div>
167
  </div>
168
-
169
- <!-- MIDI Connection -->
170
- <div class="pt-2 border-t border-gray-700">
171
- <button id="midi-connect-btn" class="w-full bg-gray-800 hover:bg-gray-700 text-white font-bold py-2 px-3 rounded-lg transition duration-200 text-sm border border-gray-700">
172
- <i data-feather="power" class="mr-1"></i> Connect MIDI
173
- </button>
174
- <div class="mt-2 text-xs text-center" id="midi-status">Not connected</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  </div>
176
  </div>
177
  </div>
@@ -179,15 +220,10 @@
179
  </div>
180
  </div>
181
 
182
- <custom-footer></custom-footer>
183
-
184
- <script src="components/navbar.js"></script>
185
- <script src="components/footer.js"></script>
186
  <script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
187
  <script src="script.js"></script>
188
  <script>
189
  feather.replace();
190
  </script>
191
- <script src="https://deepsite.hf.co/deepsite-badge.js"></script>
192
  </body>
193
  </html>
 
10
  <script src="https://unpkg.com/feather-icons"></script>
11
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
12
  </head>
13
+ <body class="bg-black text-cyan-400 min-h-screen font-mono">
 
 
14
  <div class="container mx-auto px-4 py-8">
15
  <header class="text-center mb-8">
16
+ <h1 class="text-3xl md:text-5xl font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-purple-500">
17
+ SYNTHWAVE STUDIO
18
  </h1>
19
+ <p class="text-cyan-600 text-sm">POLYPHONIC MIDI SYNTHESIZER</p>
 
 
20
  </header>
21
 
22
+ <div class="bg-gray-900 border-2 border-cyan-700 rounded-xl p-6 shadow-2xl shadow-cyan-900/30">
23
+ <!-- Main Synth Interface -->
24
  <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
25
+ <!-- Oscilloscope Section -->
26
  <div class="lg:col-span-2">
27
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4 mb-6 relative synth-display">
28
+ <div class="absolute top-2 left-2 w-3 h-3 bg-green-500 rounded-full"></div>
29
+ <div class="absolute top-2 left-8 w-3 h-3 bg-yellow-500 rounded-full"></div>
30
+ <div class="absolute top-2 left-14 w-3 h-3 bg-red-500 rounded-full"></div>
31
+ <h2 class="text-lg font-bold text-cyan-300 mb-4 text-center">OSCILLOSCOPE</h2>
32
+ <div class="relative h-64 bg-black rounded-lg border-4 border-gray-900 shadow-inner">
33
+ <canvas id="visualizer" class="absolute inset-0 w-full h-full"></canvas>
34
+ <div class="absolute inset-0 border-2 border-cyan-700 rounded pointer-events-none"></div>
35
+ <div class="absolute top-2 left-2 text-cyan-600 text-xs">VOLTAGE: <span id="voltage-display">0.00V</span></div>
36
+ <div class="absolute top-2 right-2 text-cyan-600 text-xs">TIME: <span id="time-display">0.00ms</span></div>
 
 
 
 
37
  </div>
38
  </div>
39
+
40
  <!-- ADSR Envelope Editors -->
41
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
42
+ <!-- Amplitude ADSR -->
43
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
44
+ <h3 class="text-md font-bold text-cyan-300 mb-3 flex items-center">
45
+ <i data-feather="bar-chart-2" class="mr-2 text-cyan-500"></i> AMP ENVELOPE
46
  </h3>
47
+ <div class="relative h-40 bg-black border border-cyan-900 rounded mb-3">
48
+ <canvas id="amp-envelope-editor" class="w-full h-full"></canvas>
49
+ </div>
50
+ <div class="grid grid-cols-4 gap-2">
51
  <div>
52
+ <label class="block text-xs text-cyan-500 mb-1">ATTACK</label>
53
+ <input type="range" id="envelope-attack" min="0.01" max="5" value="0.1" step="0.01"
54
+ class="w-full accent-cyan-500">
55
+ <span id="envelope-attack-value" class="text-xs text-cyan-600">0.1 S</span>
56
  </div>
57
  <div>
58
+ <label class="block text-xs text-cyan-500 mb-1">DECAY</label>
59
+ <input type="range" id="envelope-decay" min="0.01" max="5" value="0.3" step="0.01"
60
+ class="w-full accent-cyan-500">
61
+ <span id="envelope-decay-value" class="text-xs text-cyan-600">0.3 S</span>
62
  </div>
63
  <div>
64
+ <label class="block text-xs text-cyan-500 mb-1">SUSTAIN</label>
65
  <input type="range" id="envelope-sustain" min="0" max="1" value="0.5" step="0.01"
66
+ class="w-full accent-cyan-500">
67
+ <span id="envelope-sustain-value" class="text-xs text-cyan-600">50%</span>
68
  </div>
69
  <div>
70
+ <label class="block text-xs text-cyan-500 mb-1">RELEASE</label>
71
+ <input type="range" id="envelope-release" min="0.1" max="10" value="1.0" step="0.1"
72
+ class="w-full accent-cyan-500">
73
+ <span id="envelope-release-value" class="text-xs text-cyan-600">1.0 S</span>
74
  </div>
75
  </div>
76
  </div>
77
+
78
+ <!-- Filter ADSR -->
79
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
80
+ <h3 class="text-md font-bold text-cyan-300 mb-3 flex items-center">
81
+ <i data-feather="bar-chart" class="mr-2 text-cyan-500"></i> FILTER ENVELOPE
82
  </h3>
83
+ <div class="relative h-40 bg-black border border-cyan-900 rounded mb-3">
84
+ <canvas id="filter-envelope-editor" class="w-full h-full"></canvas>
85
+ </div>
86
+ <div class="grid grid-cols-4 gap-2">
87
  <div>
88
+ <label class="block text-xs text-cyan-500 mb-1">ATTACK</label>
89
+ <input type="range" id="filter-attack" min="0.01" max="5" value="0.1" step="0.01"
90
+ class="w-full accent-cyan-500">
91
+ <span id="filter-attack-value" class="text-xs text-cyan-600">0.1 S</span>
92
  </div>
93
  <div>
94
+ <label class="block text-xs text-cyan-500 mb-1">DECAY</label>
95
+ <input type="range" id="filter-decay" min="0.01" max="5" value="0.3" step="0.01"
96
+ class="w-full accent-cyan-500">
97
+ <span id="filter-decay-value" class="text-xs text-cyan-600">0.3 S</span>
98
  </div>
99
  <div>
100
+ <label class="block text-xs text-cyan-500 mb-1">SUSTAIN</label>
101
+ <input type="range" id="filter-sustain" min="0" max="1" value="0.5" step="0.01"
102
+ class="w-full accent-cyan-500">
103
+ <span id="filter-sustain-value" class="text-xs text-cyan-600">50%</span>
104
  </div>
105
  <div>
106
+ <label class="block text-xs text-cyan-500 mb-1">RELEASE</label>
107
+ <input type="range" id="filter-release" min="0.1" max="10" value="1.0" step="0.1"
108
+ class="w-full accent-cyan-500">
109
+ <span id="filter-release-value" class="text-xs text-cyan-600">1.0 S</span>
110
  </div>
111
  </div>
112
  </div>
113
  </div>
114
  </div>
115
+
116
+ <!-- Controls Panel -->
117
+ <div class="space-y-6">
118
+ <!-- MIDI Section -->
119
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
120
+ <h2 class="text-lg font-bold mb-4 text-cyan-300 flex items-center">
121
+ <i data-feather="music" class="mr-2 text-cyan-500"></i> MIDI INTERFACE
122
+ </h2>
123
+
124
+ <div class="mb-4">
125
+ <button id="midi-connect-btn" class="w-full bg-gray-700 hover:bg-gray-600 text-cyan-300 font-bold py-2 px-4 rounded border border-cyan-700 transition duration-200 flex items-center justify-center">
126
+ <i data-feather="power" class="mr-2"></i> CONNECT DEVICE
127
+ </button>
 
 
 
 
 
 
 
 
 
128
  </div>
129
 
130
+ <div class="bg-gray-900 rounded p-3 mb-4 border border-cyan-900">
131
+ <h3 class="font-semibold mb-2 text-cyan-300">STATUS</h3>
132
+ <div id="midi-status" class="text-sm text-yellow-400">
133
+ NOT CONNECTED
 
 
 
 
 
 
 
 
 
 
134
  </div>
135
  </div>
136
 
137
+ <div class="bg-gray-900 rounded p-3 border border-cyan-900">
138
+ <h3 class="font-semibold mb-2 text-cyan-300">ACTIVE NOTES</h3>
139
+ <div id="active-notes" class="text-sm text-cyan-500 h-24 overflow-y-auto">
140
+ NO ACTIVE NOTES
 
 
 
 
 
 
 
 
 
 
141
  </div>
142
  </div>
143
+ </div>
144
+
145
+ <!-- Oscillator Controls -->
146
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
147
+ <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">OSCILLATOR</h3>
148
+ <div class="space-y-4">
149
+ <div>
150
+ <label class="block text-xs text-cyan-500 mb-1">WAVEFORM</label>
151
+ <select id="oscillator-type" class="w-full bg-gray-900 text-cyan-300 rounded p-2 border border-cyan-800">
152
+ <option value="sawtooth">SAWTOOTH</option>
153
+ <option value="sine">SINE</option>
154
+ <option value="square">SQUARE</option>
155
+ <option value="triangle">TRIANGLE</option>
156
+ </select>
157
+ </div>
158
+ <div>
159
+ <label class="block text-xs text-cyan-500 mb-1">DETUNE</label>
160
+ <input type="range" id="oscillator-detune" min="-50" max="50" value="0" step="1"
161
+ class="w-full accent-cyan-500">
162
+ <span id="oscillator-detune-value" class="text-xs text-cyan-600">0 CENTS</span>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <!-- Filter Controls -->
168
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
169
+ <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">FILTER</h3>
170
+ <div class="space-y-4">
171
+ <div>
172
+ <label class="block text-xs text-cyan-500 mb-1">CUTOFF</label>
173
+ <input type="range" id="filter-frequency" min="50" max="5000" value="1000" step="1"
174
+ class="w-full accent-cyan-500">
175
+ <span id="filter-frequency-value" class="text-xs text-cyan-600">1000 HZ</span>
176
+ </div>
177
+ <div>
178
+ <label class="block text-xs text-cyan-500 mb-1">RESONANCE</label>
179
+ <input type="range" id="filter-q" min="0.1" max="20" value="1" step="0.1"
180
+ class="w-full accent-cyan-500">
181
+ <span id="filter-q-value" class="text-xs text-cyan-600">1.0</span>
182
+ </div>
183
+ <div>
184
+ <label class="block text-xs text-cyan-500 mb-1">TYPE</label>
185
+ <select id="filter-type" class="w-full bg-gray-900 text-cyan-300 rounded p-2 border border-cyan-800">
186
+ <option value="lowpass">LOWPASS</option>
187
+ <option value="highpass">HIGHPASS</option>
188
+ <option value="bandpass">BANDPASS</option>
189
+ </select>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Effects Section -->
195
+ <div class="bg-gray-800 border border-cyan-800 rounded-lg p-4">
196
+ <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">EFFECTS</h3>
197
+ <div class="space-y-4">
198
+ <div>
199
+ <label class="block text-xs text-cyan-500 mb-1">REVERB</label>
200
+ <input type="range" id="reverb-wet" min="0" max="1" value="0.3" step="0.01"
201
+ class="w-full accent-cyan-500">
202
+ <span id="reverb-wet-value" class="text-xs text-cyan-600">30%</span>
203
+ </div>
204
+ <div>
205
+ <label class="block text-xs text-cyan-500 mb-1">DISTORTION</label>
206
+ <input type="range" id="distortion-wet" min="0" max="1" value="0.2" step="0.01"
207
+ class="w-full accent-cyan-500">
208
+ <span id="distortion-wet-value" class="text-xs text-cyan-600">20%</span>
209
+ </div>
210
+ <div>
211
+ <label class="block text-xs text-cyan-500 mb-1">DELAY</label>
212
+ <input type="range" id="delay-wet" min="0" max="1" value="0.25" step="0.01"
213
+ class="w-full accent-cyan-500">
214
+ <span id="delay-wet-value" class="text-xs text-cyan-600">25%</span>
215
+ </div>
216
  </div>
217
  </div>
218
  </div>
 
220
  </div>
221
  </div>
222
 
 
 
 
 
223
  <script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
224
  <script src="script.js"></script>
225
  <script>
226
  feather.replace();
227
  </script>
 
228
  </body>
229
  </html>
script.js CHANGED
@@ -1,20 +1,16 @@
1
 
2
- // Shared JavaScript across all pages
3
- console.log('SynthWave Studio loaded');
4
-
5
  // Tone.js setup
6
  let synth;
7
  let filter;
8
  let reverb;
9
  let distortion;
 
10
  let analyser;
 
11
  let filterEnvelope;
12
- let ampEnvelopeValues = { attack: 0.1, decay: 0.3, sustain: 0.5, release: 0.5 };
13
- let filterEnvelopeValues = { attack: 0.1, decay: 0.3, sustain: 0.5, release: 0.5 };
14
 
15
  // Initialize audio context on first interaction
16
  let audioContextStarted = false;
17
-
18
  function initAudioContext() {
19
  if (!audioContextStarted) {
20
  Tone.start();
@@ -36,21 +32,26 @@ function setupSynth() {
36
  wet: 0.2
37
  }).connect(reverb);
38
 
 
 
 
 
 
 
39
  // Create filter
40
  filter = new Tone.Filter({
41
  type: "lowpass",
42
  frequency: 1000,
43
  Q: 1
44
- }).connect(distortion);
45
 
46
  // Create filter envelope
47
  filterEnvelope = new Tone.Envelope({
48
  attack: 0.1,
49
  decay: 0.3,
50
  sustain: 0.5,
51
- release: 0.5
52
  });
53
- filterEnvelope.connect(filter.frequency);
54
 
55
  // Create main synth
56
  synth = new Tone.PolySynth(Tone.Synth, {
@@ -61,25 +62,27 @@ function setupSynth() {
61
  attack: 0.1,
62
  decay: 0.3,
63
  sustain: 0.5,
64
- release: 0.5
65
  }
66
  }).connect(filter);
67
 
 
 
 
68
  // Create analyzer for visualizer
69
- analyser = new Tone.Analyser("waveform", 1024);
70
  synth.connect(analyser);
71
 
72
  console.log('Synth initialized');
73
  }
74
-
75
  // MIDI handling
76
  let midiAccess = null;
77
  let midiInputs = [];
78
 
79
  function onMIDISuccess(midi) {
80
  midiAccess = midi;
81
- updateMIDIStatus('Connected');
82
- document.getElementById('midi-connect-btn').textContent = 'MIDI Connected';
83
  document.getElementById('midi-connect-btn').disabled = true;
84
 
85
  // Get inputs
@@ -94,7 +97,7 @@ function onMIDISuccess(midi) {
94
 
95
  function onMIDIFailure(msg) {
96
  console.error('Failed to get MIDI access - ' + msg);
97
- updateMIDIStatus('Connection failed: ' + msg, 'error');
98
  }
99
 
100
  function onMIDIStateChange(event) {
@@ -113,12 +116,12 @@ function onMIDIStateChange(event) {
113
  function setupMIDIInput(input) {
114
  midiInputs.push(input);
115
  input.addEventListener('midimessage', onMIDIMessage);
116
- updateMIDIStatus(`Connected to: ${input.name}`, 'success');
117
  }
118
 
119
  function removeMIDIInput(input) {
120
  midiInputs = midiInputs.filter(i => i.id !== input.id);
121
- updateMIDIStatus(`Disconnected: ${input.name}`, 'warning');
122
  }
123
 
124
  function onMIDIMessage(message) {
@@ -138,6 +141,8 @@ function onMIDIMessage(message) {
138
  noteOff(note);
139
  break;
140
  }
 
 
141
  }
142
 
143
  function noteOn(note, velocity) {
@@ -147,9 +152,7 @@ function noteOn(note, velocity) {
147
  const velocityNorm = velocity / 127;
148
 
149
  synth.triggerAttack(noteName, undefined, velocityNorm);
150
- if (filterEnvelope) {
151
- filterEnvelope.triggerAttack();
152
- }
153
 
154
  console.log(`Note On: ${noteName}, Velocity: ${velocity}`);
155
  }
@@ -159,13 +162,28 @@ function noteOff(note) {
159
 
160
  const noteName = Tone.Frequency(note, "midi").toNote();
161
  synth.triggerRelease(noteName);
162
- if (filterEnvelope) {
163
- filterEnvelope.triggerRelease();
164
- }
165
 
166
  console.log(`Note Off: ${noteName}`);
167
  }
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  function updateMIDIStatus(message, type = 'info') {
170
  const statusEl = document.getElementById('midi-status');
171
  statusEl.textContent = message;
@@ -182,7 +200,7 @@ function updateMIDIStatus(message, type = 'info') {
182
  statusEl.classList.add('text-red-400');
183
  break;
184
  default:
185
- statusEl.classList.add('text-blue-400');
186
  }
187
  }
188
 
@@ -195,105 +213,140 @@ function setupUIControls() {
195
  }
196
  });
197
 
 
 
 
 
 
 
 
 
 
198
  // Filter Frequency
199
  document.getElementById('filter-frequency').addEventListener('input', (e) => {
 
 
200
  if (filter) {
201
- filter.frequency.value = parseInt(e.target.value);
202
  }
203
  });
204
 
205
  // Filter Resonance (Q)
206
  document.getElementById('filter-q').addEventListener('input', (e) => {
 
 
 
 
 
 
 
 
 
207
  if (filter) {
208
- filter.Q.value = parseFloat(e.target.value);
209
  }
210
  });
211
 
212
- // Amp Envelope Controls
213
  document.getElementById('envelope-attack').addEventListener('input', (e) => {
214
  const value = parseFloat(e.target.value);
215
- ampEnvelopeValues.attack = value;
216
  if (synth) {
217
  synth.set({ envelope: { attack: value } });
218
  }
219
- drawAmpEnvelope();
220
  });
221
 
 
222
  document.getElementById('envelope-decay').addEventListener('input', (e) => {
223
  const value = parseFloat(e.target.value);
224
- ampEnvelopeValues.decay = value;
225
  if (synth) {
226
  synth.set({ envelope: { decay: value } });
227
  }
228
- drawAmpEnvelope();
229
  });
230
 
 
231
  document.getElementById('envelope-sustain').addEventListener('input', (e) => {
232
  const value = parseFloat(e.target.value);
233
- ampEnvelopeValues.sustain = value;
234
  if (synth) {
235
  synth.set({ envelope: { sustain: value } });
236
  }
237
- drawAmpEnvelope();
238
  });
239
-
240
  document.getElementById('envelope-release').addEventListener('input', (e) => {
241
  const value = parseFloat(e.target.value);
242
- ampEnvelopeValues.release = value;
243
  if (synth) {
244
  synth.set({ envelope: { release: value } });
245
  }
246
- drawAmpEnvelope();
247
  });
248
 
249
  // Filter Envelope Controls
250
- document.getElementById('filter-env-attack').addEventListener('input', (e) => {
251
  const value = parseFloat(e.target.value);
252
- filterEnvelopeValues.attack = value;
253
  if (filterEnvelope) {
254
  filterEnvelope.attack = value;
255
  }
256
- drawFilterEnvelope();
257
  });
258
 
259
- document.getElementById('filter-env-decay').addEventListener('input', (e) => {
260
  const value = parseFloat(e.target.value);
261
- filterEnvelopeValues.decay = value;
262
  if (filterEnvelope) {
263
  filterEnvelope.decay = value;
264
  }
265
- drawFilterEnvelope();
266
  });
267
 
268
- document.getElementById('filter-env-sustain').addEventListener('input', (e) => {
269
  const value = parseFloat(e.target.value);
270
- filterEnvelopeValues.sustain = value;
271
  if (filterEnvelope) {
272
  filterEnvelope.sustain = value;
273
  }
274
- drawFilterEnvelope();
275
  });
276
 
277
- document.getElementById('filter-env-release').addEventListener('input', (e) => {
278
  const value = parseFloat(e.target.value);
279
- filterEnvelopeValues.release = value;
280
  if (filterEnvelope) {
281
  filterEnvelope.release = value;
282
  }
283
- drawFilterEnvelope();
284
  });
285
 
286
  // Reverb Wet
287
  document.getElementById('reverb-wet').addEventListener('input', (e) => {
 
 
288
  if (reverb) {
289
- reverb.wet.value = parseFloat(e.target.value);
290
  }
291
  });
292
 
293
  // Distortion Wet
294
  document.getElementById('distortion-wet').addEventListener('input', (e) => {
 
 
295
  if (distortion) {
296
- distortion.wet.value = parseFloat(e.target.value);
 
 
 
 
 
 
 
 
 
297
  }
298
  });
299
  }
@@ -320,12 +373,8 @@ function setupVisualizer() {
320
  const values = analyser.getValue();
321
  ctx.clearRect(0, 0, canvas.width, canvas.height);
322
 
323
- // Draw 3D effect background
324
- ctx.fillStyle = '#000';
325
- ctx.fillRect(0, 0, canvas.width, canvas.height);
326
-
327
  // Draw grid
328
- ctx.strokeStyle = 'rgba(0, 255, 0, 0.1)';
329
  ctx.lineWidth = 1;
330
 
331
  // Vertical lines
@@ -344,10 +393,10 @@ function setupVisualizer() {
344
  ctx.stroke();
345
  }
346
 
347
- // Draw waveform with 3D effect
348
  ctx.beginPath();
349
  ctx.lineWidth = 2;
350
- ctx.strokeStyle = '#00ff00';
351
 
352
  const sliceWidth = canvas.width / values.length;
353
  let x = 0;
@@ -366,183 +415,99 @@ function setupVisualizer() {
366
  }
367
 
368
  ctx.stroke();
369
-
370
- // Draw glow effect
371
- ctx.shadowColor = '#00ff00';
372
- ctx.shadowBlur = 10;
373
- ctx.stroke();
374
- ctx.shadowBlur = 0;
375
-
376
  requestAnimationFrame(draw);
377
  }
378
 
379
  draw();
380
  }
381
 
382
- // Envelope Visualizers
383
- function setupEnvelopeVisualizers() {
384
- drawAmpEnvelope();
385
- drawFilterEnvelope();
386
- }
387
-
388
- function drawAmpEnvelope() {
389
- const canvas = document.getElementById('amp-envelope');
390
  const ctx = canvas.getContext('2d');
391
 
392
- canvas.width = canvas.clientWidth;
393
- canvas.height = canvas.clientHeight;
394
-
395
- ctx.clearRect(0, 0, canvas.width, canvas.height);
396
-
397
- // Draw background
398
- ctx.fillStyle = '#1f2937';
399
- ctx.fillRect(0, 0, canvas.width, canvas.height);
400
-
401
- // Draw grid
402
- ctx.strokeStyle = 'rgba(59, 130, 246, 0.2)';
403
- ctx.lineWidth = 1;
404
-
405
- // Vertical lines
406
- for (let x = 0; x < canvas.width; x += 20) {
407
- ctx.beginPath();
408
- ctx.moveTo(x, 0);
409
- ctx.lineTo(x, canvas.height);
410
- ctx.stroke();
411
- }
412
-
413
- // Horizontal lines
414
- for (let y = 0; y < canvas.height; y += 20) {
415
- ctx.beginPath();
416
- ctx.moveTo(0, y);
417
- ctx.lineTo(canvas.width, y);
418
- ctx.stroke();
419
  }
420
 
421
- // Draw envelope
422
- ctx.beginPath();
423
- ctx.lineWidth = 3;
424
- ctx.strokeStyle = '#3b82f6';
425
-
426
- const padding = 20;
427
- const width = canvas.width - padding * 2;
428
- const height = canvas.height - padding * 2;
429
-
430
- // Calculate points
431
- const attackX = padding + width * 0.2 * (ampEnvelopeValues.attack / 2);
432
- const decayX = attackX + width * 0.3 * (ampEnvelopeValues.decay / 2);
433
- const sustainY = padding + height * (1 - ampEnvelopeValues.sustain);
434
- const releaseX = canvas.width - padding - width * 0.2 * (ampEnvelopeValues.release / 5);
435
-
436
- // Draw envelope path
437
- ctx.moveTo(padding, canvas.height - padding);
438
- ctx.lineTo(attackX, padding);
439
- ctx.lineTo(decayX, sustainY);
440
- ctx.lineTo(releaseX, sustainY);
441
- ctx.lineTo(canvas.width - padding, canvas.height - padding);
442
-
443
- ctx.stroke();
444
-
445
- // Draw points
446
- ctx.fillStyle = '#3b82f6';
447
- ctx.beginPath();
448
- ctx.arc(padding, canvas.height - padding, 4, 0, Math.PI * 2);
449
- ctx.fill();
450
-
451
- ctx.beginPath();
452
- ctx.arc(attackX, padding, 4, 0, Math.PI * 2);
453
- ctx.fill();
454
-
455
- ctx.beginPath();
456
- ctx.arc(decayX, sustainY, 4, 0, Math.PI * 2);
457
- ctx.fill();
458
-
459
- ctx.beginPath();
460
- ctx.arc(releaseX, sustainY, 4, 0, Math.PI * 2);
461
- ctx.fill();
462
-
463
- ctx.beginPath();
464
- ctx.arc(canvas.width - padding, canvas.height - padding, 4, 0, Math.PI * 2);
465
- ctx.fill();
466
- }
467
-
468
- function drawFilterEnvelope() {
469
- const canvas = document.getElementById('filter-envelope');
470
- const ctx = canvas.getContext('2d');
471
-
472
- canvas.width = canvas.clientWidth;
473
- canvas.height = canvas.clientHeight;
474
-
475
- ctx.clearRect(0, 0, canvas.width, canvas.height);
476
-
477
- // Draw background
478
- ctx.fillStyle = '#1f2937';
479
- ctx.fillRect(0, 0, canvas.width, canvas.height);
480
-
481
- // Draw grid
482
- ctx.strokeStyle = 'rgba(139, 92, 246, 0.2)';
483
- ctx.lineWidth = 1;
484
 
485
- // Vertical lines
486
- for (let x = 0; x < canvas.width; x += 20) {
487
- ctx.beginPath();
488
- ctx.moveTo(x, 0);
489
- ctx.lineTo(x, canvas.height);
490
- ctx.stroke();
491
- }
492
 
493
- // Horizontal lines
494
- for (let y = 0; y < canvas.height; y += 20) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  ctx.beginPath();
496
- ctx.moveTo(0, y);
497
- ctx.lineTo(canvas.width, y);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  ctx.stroke();
 
 
 
 
 
 
 
 
499
  }
500
 
501
- // Draw envelope
502
- ctx.beginPath();
503
- ctx.lineWidth = 3;
504
- ctx.strokeStyle = '#8b5cf6';
505
-
506
- const padding = 20;
507
- const width = canvas.width - padding * 2;
508
- const height = canvas.height - padding * 2;
509
-
510
- // Calculate points
511
- const attackX = padding + width * 0.2 * (filterEnvelopeValues.attack / 2);
512
- const decayX = attackX + width * 0.3 * (filterEnvelopeValues.decay / 2);
513
- const sustainY = padding + height * (1 - filterEnvelopeValues.sustain);
514
- const releaseX = canvas.width - padding - width * 0.2 * (filterEnvelopeValues.release / 5);
515
-
516
- // Draw envelope path
517
- ctx.moveTo(padding, canvas.height - padding);
518
- ctx.lineTo(attackX, padding);
519
- ctx.lineTo(decayX, sustainY);
520
- ctx.lineTo(releaseX, sustainY);
521
- ctx.lineTo(canvas.width - padding, canvas.height - padding);
522
-
523
- ctx.stroke();
524
-
525
- // Draw points
526
- ctx.fillStyle = '#8b5cf6';
527
- ctx.beginPath();
528
- ctx.arc(padding, canvas.height - padding, 4, 0, Math.PI * 2);
529
- ctx.fill();
530
-
531
- ctx.beginPath();
532
- ctx.arc(attackX, padding, 4, 0, Math.PI * 2);
533
- ctx.fill();
534
-
535
- ctx.beginPath();
536
- ctx.arc(decayX, sustainY, 4, 0, Math.PI * 2);
537
- ctx.fill();
538
-
539
- ctx.beginPath();
540
- ctx.arc(releaseX, sustainY, 4, 0, Math.PI * 2);
541
- ctx.fill();
542
-
543
- ctx.beginPath();
544
- ctx.arc(canvas.width - padding, canvas.height - padding, 4, 0, Math.PI * 2);
545
- ctx.fill();
546
  }
547
 
548
  // Initialize everything when page loads
@@ -550,11 +515,9 @@ document.addEventListener('DOMContentLoaded', () => {
550
  // Setup UI controls
551
  setupUIControls();
552
 
553
- // Setup visualizer
554
  setupVisualizer();
555
-
556
- // Setup envelope visualizers
557
- setupEnvelopeVisualizers();
558
 
559
  // Setup synth
560
  setupSynth();
@@ -566,7 +529,7 @@ document.addEventListener('DOMContentLoaded', () => {
566
  navigator.requestMIDIAccess()
567
  .then(onMIDISuccess, onMIDIFailure);
568
  } else {
569
- updateMIDIStatus('WebMIDI is not supported in this browser', 'error');
570
  }
571
  });
572
  });
 
1
 
 
 
 
2
  // Tone.js setup
3
  let synth;
4
  let filter;
5
  let reverb;
6
  let distortion;
7
+ let delay;
8
  let analyser;
9
+ let activeNotes = new Map();
10
  let filterEnvelope;
 
 
11
 
12
  // Initialize audio context on first interaction
13
  let audioContextStarted = false;
 
14
  function initAudioContext() {
15
  if (!audioContextStarted) {
16
  Tone.start();
 
32
  wet: 0.2
33
  }).connect(reverb);
34
 
35
+ delay = new Tone.FeedbackDelay({
36
+ delayTime: 0.25,
37
+ feedback: 0.5,
38
+ wet: 0.25
39
+ }).connect(distortion);
40
+
41
  // Create filter
42
  filter = new Tone.Filter({
43
  type: "lowpass",
44
  frequency: 1000,
45
  Q: 1
46
+ }).connect(delay);
47
 
48
  // Create filter envelope
49
  filterEnvelope = new Tone.Envelope({
50
  attack: 0.1,
51
  decay: 0.3,
52
  sustain: 0.5,
53
+ release: 1.0
54
  });
 
55
 
56
  // Create main synth
57
  synth = new Tone.PolySynth(Tone.Synth, {
 
62
  attack: 0.1,
63
  decay: 0.3,
64
  sustain: 0.5,
65
+ release: 1.0
66
  }
67
  }).connect(filter);
68
 
69
+ // Modulate filter frequency with envelope
70
+ filterEnvelope.connect(filter.frequency);
71
+
72
  // Create analyzer for visualizer
73
+ analyser = new Tone.Analyser("fft", 64);
74
  synth.connect(analyser);
75
 
76
  console.log('Synth initialized');
77
  }
 
78
  // MIDI handling
79
  let midiAccess = null;
80
  let midiInputs = [];
81
 
82
  function onMIDISuccess(midi) {
83
  midiAccess = midi;
84
+ updateMIDIStatus('CONNECTED');
85
+ document.getElementById('midi-connect-btn').textContent = 'DEVICE CONNECTED';
86
  document.getElementById('midi-connect-btn').disabled = true;
87
 
88
  // Get inputs
 
97
 
98
  function onMIDIFailure(msg) {
99
  console.error('Failed to get MIDI access - ' + msg);
100
+ updateMIDIStatus('CONNECTION FAILED: ' + msg, 'error');
101
  }
102
 
103
  function onMIDIStateChange(event) {
 
116
  function setupMIDIInput(input) {
117
  midiInputs.push(input);
118
  input.addEventListener('midimessage', onMIDIMessage);
119
+ updateMIDIStatus(`CONNECTED TO: ${input.name}`, 'success');
120
  }
121
 
122
  function removeMIDIInput(input) {
123
  midiInputs = midiInputs.filter(i => i.id !== input.id);
124
+ updateMIDIStatus(`DISCONNECTED: ${input.name}`, 'warning');
125
  }
126
 
127
  function onMIDIMessage(message) {
 
141
  noteOff(note);
142
  break;
143
  }
144
+
145
+ updateActiveNotesDisplay();
146
  }
147
 
148
  function noteOn(note, velocity) {
 
152
  const velocityNorm = velocity / 127;
153
 
154
  synth.triggerAttack(noteName, undefined, velocityNorm);
155
+ activeNotes.set(note, { name: noteName, velocity: velocity });
 
 
156
 
157
  console.log(`Note On: ${noteName}, Velocity: ${velocity}`);
158
  }
 
162
 
163
  const noteName = Tone.Frequency(note, "midi").toNote();
164
  synth.triggerRelease(noteName);
165
+ activeNotes.delete(note);
 
 
166
 
167
  console.log(`Note Off: ${noteName}`);
168
  }
169
 
170
+ function updateActiveNotesDisplay() {
171
+ const container = document.getElementById('active-notes');
172
+ if (activeNotes.size === 0) {
173
+ container.innerHTML = '<div class="text-cyan-700">NO ACTIVE NOTES</div>';
174
+ return;
175
+ }
176
+
177
+ let html = '';
178
+ activeNotes.forEach((note, key) => {
179
+ html += `<div class="py-1 px-2 rounded mb-1 bg-gray-900 border border-cyan-900 flex justify-between">
180
+ <span>${note.name}</span>
181
+ <span class="text-cyan-600">VEL: ${note.velocity}</span>
182
+ </div>`;
183
+ });
184
+ container.innerHTML = html;
185
+ }
186
+
187
  function updateMIDIStatus(message, type = 'info') {
188
  const statusEl = document.getElementById('midi-status');
189
  statusEl.textContent = message;
 
200
  statusEl.classList.add('text-red-400');
201
  break;
202
  default:
203
+ statusEl.classList.add('text-cyan-400');
204
  }
205
  }
206
 
 
213
  }
214
  });
215
 
216
+ // Oscillator Detune
217
+ document.getElementById('oscillator-detune').addEventListener('input', (e) => {
218
+ const value = parseInt(e.target.value);
219
+ document.getElementById('oscillator-detune-value').textContent = `${value} CENTS`;
220
+ if (synth) {
221
+ synth.set({ oscillator: { detune: value } });
222
+ }
223
+ });
224
+
225
  // Filter Frequency
226
  document.getElementById('filter-frequency').addEventListener('input', (e) => {
227
+ const value = parseInt(e.target.value);
228
+ document.getElementById('filter-frequency-value').textContent = `${value} HZ`;
229
  if (filter) {
230
+ filter.frequency.value = value;
231
  }
232
  });
233
 
234
  // Filter Resonance (Q)
235
  document.getElementById('filter-q').addEventListener('input', (e) => {
236
+ const value = parseFloat(e.target.value);
237
+ document.getElementById('filter-q-value').textContent = value.toFixed(1);
238
+ if (filter) {
239
+ filter.Q.value = value;
240
+ }
241
+ });
242
+
243
+ // Filter Type
244
+ document.getElementById('filter-type').addEventListener('change', (e) => {
245
  if (filter) {
246
+ filter.type = e.target.value;
247
  }
248
  });
249
 
250
+ // Envelope Attack
251
  document.getElementById('envelope-attack').addEventListener('input', (e) => {
252
  const value = parseFloat(e.target.value);
253
+ document.getElementById('envelope-attack-value').textContent = `${value.toFixed(2)} S`;
254
  if (synth) {
255
  synth.set({ envelope: { attack: value } });
256
  }
257
+ drawADSR(); // Update ADSR visualization
258
  });
259
 
260
+ // Envelope Decay
261
  document.getElementById('envelope-decay').addEventListener('input', (e) => {
262
  const value = parseFloat(e.target.value);
263
+ document.getElementById('envelope-decay-value').textContent = `${value.toFixed(2)} S`;
264
  if (synth) {
265
  synth.set({ envelope: { decay: value } });
266
  }
267
+ drawADSR(); // Update ADSR visualization
268
  });
269
 
270
+ // Envelope Sustain
271
  document.getElementById('envelope-sustain').addEventListener('input', (e) => {
272
  const value = parseFloat(e.target.value);
273
+ document.getElementById('envelope-sustain-value').textContent = `${Math.round(value * 100)}%`;
274
  if (synth) {
275
  synth.set({ envelope: { sustain: value } });
276
  }
277
+ drawADSR(); // Update ADSR visualization
278
  });
279
+ // Envelope Release
280
  document.getElementById('envelope-release').addEventListener('input', (e) => {
281
  const value = parseFloat(e.target.value);
282
+ document.getElementById('envelope-release-value').textContent = `${value.toFixed(1)} S`;
283
  if (synth) {
284
  synth.set({ envelope: { release: value } });
285
  }
286
+ drawADSREnvelope(); // Update ADSR visualization
287
  });
288
 
289
  // Filter Envelope Controls
290
+ document.getElementById('filter-attack').addEventListener('input', (e) => {
291
  const value = parseFloat(e.target.value);
292
+ document.getElementById('filter-attack-value').textContent = `${value.toFixed(2)} S`;
293
  if (filterEnvelope) {
294
  filterEnvelope.attack = value;
295
  }
296
+ drawFilterEnvelope(); // Update filter ADSR visualization
297
  });
298
 
299
+ document.getElementById('filter-decay').addEventListener('input', (e) => {
300
  const value = parseFloat(e.target.value);
301
+ document.getElementById('filter-decay-value').textContent = `${value.toFixed(2)} S`;
302
  if (filterEnvelope) {
303
  filterEnvelope.decay = value;
304
  }
305
+ drawFilterEnvelope(); // Update filter ADSR visualization
306
  });
307
 
308
+ document.getElementById('filter-sustain').addEventListener('input', (e) => {
309
  const value = parseFloat(e.target.value);
310
+ document.getElementById('filter-sustain-value').textContent = `${Math.round(value * 100)}%`;
311
  if (filterEnvelope) {
312
  filterEnvelope.sustain = value;
313
  }
314
+ drawFilterEnvelope(); // Update filter ADSR visualization
315
  });
316
 
317
+ document.getElementById('filter-release').addEventListener('input', (e) => {
318
  const value = parseFloat(e.target.value);
319
+ document.getElementById('filter-release-value').textContent = `${value.toFixed(1)} S`;
320
  if (filterEnvelope) {
321
  filterEnvelope.release = value;
322
  }
323
+ drawFilterEnvelope(); // Update filter ADSR visualization
324
  });
325
 
326
  // Reverb Wet
327
  document.getElementById('reverb-wet').addEventListener('input', (e) => {
328
+ const value = parseFloat(e.target.value);
329
+ document.getElementById('reverb-wet-value').textContent = `${Math.round(value * 100)}%`;
330
  if (reverb) {
331
+ reverb.wet.value = value;
332
  }
333
  });
334
 
335
  // Distortion Wet
336
  document.getElementById('distortion-wet').addEventListener('input', (e) => {
337
+ const value = parseFloat(e.target.value);
338
+ document.getElementById('distortion-wet-value').textContent = `${Math.round(value * 100)}%`;
339
  if (distortion) {
340
+ distortion.wet.value = value;
341
+ }
342
+ });
343
+
344
+ // Delay Wet
345
+ document.getElementById('delay-wet').addEventListener('input', (e) => {
346
+ const value = parseFloat(e.target.value);
347
+ document.getElementById('delay-wet-value').textContent = `${Math.round(value * 100)}%`;
348
+ if (delay) {
349
+ delay.wet.value = value;
350
  }
351
  });
352
  }
 
373
  const values = analyser.getValue();
374
  ctx.clearRect(0, 0, canvas.width, canvas.height);
375
 
 
 
 
 
376
  // Draw grid
377
+ ctx.strokeStyle = 'rgba(6, 182, 212, 0.1)';
378
  ctx.lineWidth = 1;
379
 
380
  // Vertical lines
 
393
  ctx.stroke();
394
  }
395
 
396
+ // Draw waveform
397
  ctx.beginPath();
398
  ctx.lineWidth = 2;
399
+ ctx.strokeStyle = '#06b6d4'; // cyan-500
400
 
401
  const sliceWidth = canvas.width / values.length;
402
  let x = 0;
 
415
  }
416
 
417
  ctx.stroke();
 
 
 
 
 
 
 
418
  requestAnimationFrame(draw);
419
  }
420
 
421
  draw();
422
  }
423
 
424
+ // ADSR Visualizer
425
+ function setupADSRVisualizer() {
426
+ const canvas = document.getElementById('adsr-visualizer');
 
 
 
 
 
427
  const ctx = canvas.getContext('2d');
428
 
429
+ function resizeCanvas() {
430
+ canvas.width = canvas.clientWidth;
431
+ canvas.height = canvas.clientHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  }
433
 
434
+ window.addEventListener('resize', resizeCanvas);
435
+ resizeCanvas();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
+ // Initial draw
438
+ drawADSR();
 
 
 
 
 
439
 
440
+ function drawADSR() {
441
+ if (!canvas.width || !canvas.height) return;
442
+
443
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
444
+
445
+ // Draw grid
446
+ ctx.strokeStyle = 'rgba(6, 182, 212, 0.1)';
447
+ ctx.lineWidth = 1;
448
+
449
+ // Vertical lines
450
+ for (let x = 0; x < canvas.width; x += 20) {
451
+ ctx.beginPath();
452
+ ctx.moveTo(x, 0);
453
+ ctx.lineTo(x, canvas.height);
454
+ ctx.stroke();
455
+ }
456
+
457
+ // Horizontal lines
458
+ for (let y = 0; y < canvas.height; y += 20) {
459
+ ctx.beginPath();
460
+ ctx.moveTo(0, y);
461
+ ctx.lineTo(canvas.width, y);
462
+ ctx.stroke();
463
+ }
464
+
465
+ // Draw ADSR envelope
466
+ const attack = parseFloat(document.getElementById('envelope-attack').value);
467
+ const decay = parseFloat(document.getElementById('envelope-decay').value);
468
+ const sustain = parseFloat(document.getElementById('envelope-sustain').value);
469
+ const release = parseFloat(document.getElementById('envelope-release').value);
470
+
471
+ // Normalize values for visualization
472
+ const totalTime = attack + decay + 2 + release; // Add some time for sustain and release visualization
473
+ const attackWidth = (attack / totalTime) * canvas.width;
474
+ const decayWidth = (decay / totalTime) * canvas.width;
475
+ const sustainWidth = (2 / totalTime) * canvas.width;
476
+ const releaseWidth = (release / totalTime) * canvas.width;
477
+
478
  ctx.beginPath();
479
+ ctx.lineWidth = 3;
480
+ ctx.strokeStyle = '#06b6d4'; // cyan-500
481
+
482
+ // Start at 0
483
+ ctx.moveTo(0, canvas.height);
484
+
485
+ // Attack
486
+ ctx.lineTo(attackWidth, 0);
487
+
488
+ // Decay
489
+ const sustainHeight = canvas.height - (sustain * canvas.height);
490
+ ctx.lineTo(attackWidth + decayWidth, sustainHeight);
491
+
492
+ // Sustain
493
+ ctx.lineTo(attackWidth + decayWidth + sustainWidth, sustainHeight);
494
+
495
+ // Release
496
+ ctx.lineTo(attackWidth + decayWidth + sustainWidth + releaseWidth, canvas.height);
497
+
498
  ctx.stroke();
499
+
500
+ // Draw labels
501
+ ctx.fillStyle = '#06b6d4';
502
+ ctx.font = '10px monospace';
503
+ ctx.fillText('A', 5, canvas.height - 5);
504
+ ctx.fillText('D', attackWidth - 10, canvas.height - 5);
505
+ ctx.fillText('S', attackWidth + decayWidth - 10, sustainHeight - 5);
506
+ ctx.fillText('R', attackWidth + decayWidth + sustainWidth + releaseWidth - 15, canvas.height - 5);
507
  }
508
 
509
+ // Make drawADSR accessible globally for updates
510
+ window.drawADSR = drawADSR;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
 
513
  // Initialize everything when page loads
 
515
  // Setup UI controls
516
  setupUIControls();
517
 
518
+ // Setup visualizers
519
  setupVisualizer();
520
+ setupADSRVisualizer();
 
 
521
 
522
  // Setup synth
523
  setupSynth();
 
529
  navigator.requestMIDIAccess()
530
  .then(onMIDISuccess, onMIDIFailure);
531
  } else {
532
+ updateMIDIStatus('WEBMIDI NOT SUPPORTED', 'error');
533
  }
534
  });
535
  });
style.css CHANGED
@@ -1,21 +1,22 @@
1
 
2
- /* Shared styles across all pages */
3
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&family=Roboto:wght@300;400;500;700&display=swap');
4
 
5
  :root {
6
- --primary-color: #6b7280; /* gray-500 */
7
- --secondary-color: #ffffff; /* white */
 
 
 
8
  }
9
 
10
  body {
11
- font-family: 'Roboto', sans-serif;
12
- background-color: #111827; /* gray-900 */
13
- color: var(--secondary-color);
14
  margin: 0;
15
  padding: 0;
16
  min-height: 100vh;
17
- display: flex;
18
- flex-direction: column;
19
  }
20
 
21
  h1, h2, h3, h4, h5, h6 {
@@ -32,7 +33,7 @@ h1, h2, h3, h4, h5, h6 {
32
  }
33
 
34
  ::-webkit-scrollbar-track {
35
- background: #1f2937; /* gray-800 */
36
  }
37
 
38
  ::-webkit-scrollbar-thumb {
@@ -41,17 +42,19 @@ h1, h2, h3, h4, h5, h6 {
41
  }
42
 
43
  ::-webkit-scrollbar-thumb:hover {
44
- background: #4b5563; /* gray-600 */
45
  }
46
 
47
  /* Button styles */
48
  button {
49
  transition: all 0.2s ease;
 
 
 
50
  }
51
 
52
  button:hover {
53
- transform: translateY(-1px);
54
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
55
  }
56
 
57
  /* Input range styling */
@@ -59,7 +62,7 @@ input[type="range"] {
59
  -webkit-appearance: none;
60
  height: 6px;
61
  border-radius: 3px;
62
- background: #374151; /* gray-700 */
63
  outline: none;
64
  }
65
 
@@ -70,7 +73,7 @@ input[type="range"]::-webkit-slider-thumb {
70
  border-radius: 50%;
71
  background: var(--primary-color);
72
  cursor: pointer;
73
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
74
  }
75
 
76
  input[type="range"]::-moz-range-thumb {
@@ -79,48 +82,89 @@ input[type="range"]::-moz-range-thumb {
79
  border-radius: 50%;
80
  background: var(--primary-color);
81
  cursor: pointer;
82
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
83
  border: none;
84
  }
85
 
86
  /* Select styling */
87
  select {
88
  appearance: none;
89
- background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
90
  background-repeat: no-repeat;
91
  background-position: right 0.5rem center;
92
  background-size: 1em;
 
 
 
93
  }
94
 
95
  /* Canvas styling */
96
- #visualizer, #amp-envelope, #filter-envelope {
97
  width: 100%;
98
- background: #000;
99
  border-radius: 0.25rem;
100
  }
101
 
102
- #visualizer {
103
- height: 192px;
 
104
  }
105
 
106
- #amp-envelope, #filter-envelope {
107
- height: 128px;
108
  }
109
 
110
- /* Envelope canvas styling */
111
- #amp-envelope, #filter-envelope {
112
- cursor: pointer;
113
  }
114
 
115
- /* Active note styling */
116
- .note-active {
117
- background-color: #374151 !important; /* gray-700 */
118
- border-left: 3px solid var(--primary-color);
119
  }
120
 
121
- /* Compact control labels */
122
- label {
123
- font-size: 0.75rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
 
126
  /* Responsive adjustments */
@@ -130,18 +174,31 @@ label {
130
  }
131
 
132
  h1 {
133
- font-size: 1.5rem;
134
  }
135
 
136
  .grid {
137
  gap: 1rem;
138
  }
139
 
140
- #visualizer {
141
- height: 150px;
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
 
144
- #amp-envelope, #filter-envelope {
145
- height: 100px;
 
146
  }
147
  }
 
1
 
2
+ /* SynthWave Studio Styles */
3
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&family=Roboto+Mono:wght@300;400;500;700&display=swap');
4
 
5
  :root {
6
+ --primary-color: #06b6d4; /* cyan-500 */
7
+ --secondary-color: #ffffff;
8
+ --background-color: #000000;
9
+ --panel-color: #111827; /* gray-900 */
10
+ --border-color: #0891b2; /* cyan-700 */
11
  }
12
 
13
  body {
14
+ font-family: 'Roboto Mono', monospace;
15
+ background-color: var(--background-color);
16
+ color: var(--primary-color);
17
  margin: 0;
18
  padding: 0;
19
  min-height: 100vh;
 
 
20
  }
21
 
22
  h1, h2, h3, h4, h5, h6 {
 
33
  }
34
 
35
  ::-webkit-scrollbar-track {
36
+ background: #0f172a; /* gray-900 */
37
  }
38
 
39
  ::-webkit-scrollbar-thumb {
 
42
  }
43
 
44
  ::-webkit-scrollbar-thumb:hover {
45
+ background: #0891b2; /* cyan-600 */
46
  }
47
 
48
  /* Button styles */
49
  button {
50
  transition: all 0.2s ease;
51
+ text-transform: uppercase;
52
+ letter-spacing: 1px;
53
+ font-size: 0.75rem;
54
  }
55
 
56
  button:hover {
57
+ box-shadow: 0 0 10px rgba(6, 182, 212, 0.5);
 
58
  }
59
 
60
  /* Input range styling */
 
62
  -webkit-appearance: none;
63
  height: 6px;
64
  border-radius: 3px;
65
+ background: #0f172a; /* gray-900 */
66
  outline: none;
67
  }
68
 
 
73
  border-radius: 50%;
74
  background: var(--primary-color);
75
  cursor: pointer;
76
+ box-shadow: 0 0 5px rgba(6, 182, 212, 0.8);
77
  }
78
 
79
  input[type="range"]::-moz-range-thumb {
 
82
  border-radius: 50%;
83
  background: var(--primary-color);
84
  cursor: pointer;
85
+ box-shadow: 0 0 5px rgba(6, 182, 212, 0.8);
86
  border: none;
87
  }
88
 
89
  /* Select styling */
90
  select {
91
  appearance: none;
92
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2306b6d4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
93
  background-repeat: no-repeat;
94
  background-position: right 0.5rem center;
95
  background-size: 1em;
96
+ text-transform: uppercase;
97
+ letter-spacing: 1px;
98
+ font-size: 0.75rem;
99
  }
100
 
101
  /* Canvas styling */
102
+ #visualizer, #amp-envelope-editor, #filter-envelope-editor {
103
  width: 100%;
104
+ background: #000000;
105
  border-radius: 0.25rem;
106
  }
107
 
108
+ /* Panel styling */
109
+ .bg-gray-900 {
110
+ background-color: #0f172a; /* darker gray */
111
  }
112
 
113
+ .border-cyan-800 {
114
+ border-color: #164e63; /* darker cyan */
115
  }
116
 
117
+ .border-cyan-900 {
118
+ border-color: #083344; /* darkest cyan */
 
119
  }
120
 
121
+ /* Text colors */
122
+ .text-cyan-300 {
123
+ color: #67e8f9;
 
124
  }
125
 
126
+ .text-cyan-500 {
127
+ color: #06b6d4;
128
+ }
129
+
130
+ .text-cyan-600 {
131
+ color: #0891b2;
132
+ }
133
+
134
+ .text-cyan-700 {
135
+ color: #0e7490;
136
+ }
137
+
138
+ /* Synth display styling */
139
+ .synth-display {
140
+ background:
141
+ linear-gradient(rgba(6, 182, 212, 0.05) 1px, transparent 1px),
142
+ linear-gradient(90deg, rgba(6, 182, 212, 0.05) 1px, transparent 1px);
143
+ background-size: 20px 20px;
144
+ box-shadow:
145
+ inset 0 0 15px rgba(6, 182, 212, 0.1),
146
+ 0 0 10px rgba(6, 182, 212, 0.2);
147
+ border: 2px solid #0891b2;
148
+ position: relative;
149
+ overflow: hidden;
150
+ }
151
+
152
+ .synth-display::before {
153
+ content: "";
154
+ position: absolute;
155
+ top: 0;
156
+ left: 0;
157
+ right: 0;
158
+ bottom: 0;
159
+ background:
160
+ radial-gradient(circle at 20% 20%, rgba(6, 182, 212, 0.1) 0%, transparent 20%),
161
+ radial-gradient(circle at 80% 80%, rgba(6, 182, 212, 0.1) 0%, transparent 20%);
162
+ pointer-events: none;
163
+ }
164
+
165
+ /* Glowing effect for controls */
166
+ .glow {
167
+ box-shadow: 0 0 8px rgba(6, 182, 212, 0.5);
168
  }
169
 
170
  /* Responsive adjustments */
 
174
  }
175
 
176
  h1 {
177
+ font-size: 2rem;
178
  }
179
 
180
  .grid {
181
  gap: 1rem;
182
  }
183
 
184
+ .text-lg {
185
+ font-size: 1rem;
186
+ }
187
+
188
+ .text-md {
189
+ font-size: 0.875rem;
190
+ }
191
+
192
+ .text-sm {
193
+ font-size: 0.75rem;
194
+ }
195
+
196
+ .text-xs {
197
+ font-size: 0.625rem;
198
  }
199
 
200
+ /* Adjust ADSR editors for mobile */
201
+ .grid-cols-4 {
202
+ grid-template-columns: repeat(2, minmax(0, 1fr));
203
  }
204
  }