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

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 (4) hide show
  1. about.html +114 -0
  2. index.html +148 -150
  3. script.js +253 -170
  4. style.css +38 -58
about.html ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>About - SynthWave Studio</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ </head>
12
+ <body class="bg-gray-900 text-white min-h-screen">
13
+ <custom-navbar></custom-navbar>
14
+
15
+ <div class="container mx-auto px-4 py-8">
16
+ <header class="text-center mb-12">
17
+ <h1 class="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-gray-400 to-white bg-clip-text text-transparent">
18
+ About SynthWave Studio
19
+ </h1>
20
+ <p class="text-gray-400 text-lg max-w-2xl mx-auto">
21
+ A modern web-based polyphonic synthesizer
22
+ </p>
23
+ </header>
24
+
25
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg mb-8">
26
+ <div class="flex flex-col md:flex-row gap-8">
27
+ <div class="md:w-1/3">
28
+ <img src="https://huggingface.co/spaces/SREAL/synthwave-studio/resolve/main/images/mios.png" alt="SynthWave Studio Interface" class="rounded-lg w-full">
29
+ </div>
30
+ <div class="md:w-2/3">
31
+ <h2 class="text-2xl font-bold mb-4 text-gray-300">Features</h2>
32
+ <ul class="space-y-2 text-gray-400">
33
+ <li class="flex items-start">
34
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
35
+ <span>Polyphonic MIDI synthesizer with velocity sensitivity</span>
36
+ </li>
37
+ <li class="flex items-start">
38
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
39
+ <span>Real-time audio visualization with 3D oscilloscope</span>
40
+ </li>
41
+ <li class="flex items-start">
42
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
43
+ <span>Comprehensive ADSR envelope editors for amp and filter</span>
44
+ </li>
45
+ <li class="flex items-start">
46
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
47
+ <span>Filter controls with resonance adjustment</span>
48
+ </li>
49
+ <li class="flex items-start">
50
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
51
+ <span>Effects processing including reverb and distortion</span>
52
+ </li>
53
+ <li class="flex items-start">
54
+ <i data-feather="check-circle" class="mr-2 mt-1 text-green-400 flex-shrink-0"></i>
55
+ <span>MIDI device connectivity</span>
56
+ </li>
57
+ </ul>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
63
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
64
+ <h2 class="text-2xl font-bold mb-4 text-gray-300 flex items-center">
65
+ <i data-feather="code" class="mr-2"></i> Technology
66
+ </h2>
67
+ <p class="text-gray-400 mb-4">
68
+ SynthWave Studio is built with modern web technologies:
69
+ </p>
70
+ <ul class="space-y-2 text-gray-400">
71
+ <li class="flex items-center">
72
+ <i data-feather="cpu" class="mr-2 text-blue-400"></i>
73
+ <span>Tone.js for Web Audio API implementation</span>
74
+ </li>
75
+ <li class="flex items-center">
76
+ <i data-feather="globe" class="mr-2 text-green-400"></i>
77
+ <span>Web MIDI API for device connectivity</span>
78
+ </li>
79
+ <li class="flex items-center">
80
+ <i data-feather="layers" class="mr-2 text-purple-400"></i>
81
+ <span>HTML5 Canvas for real-time visualization</span>
82
+ </li>
83
+ <li class="flex items-center">
84
+ <i data-feather="wind" class="mr-2 text-yellow-400"></i>
85
+ <span>Tailwind CSS for responsive UI</span>
86
+ </li>
87
+ </ul>
88
+ </div>
89
+
90
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
91
+ <h2 class="text-2xl font-bold mb-4 text-gray-300 flex items-center">
92
+ <i data-feather="users" class="mr-2"></i> About
93
+ </h2>
94
+ <p class="text-gray-400 mb-4">
95
+ SynthWave Studio is a passion project created for music enthusiasts and developers
96
+ interested in exploring the capabilities of web-based audio synthesis.
97
+ </p>
98
+ <p class="text-gray-400">
99
+ Built with love for the synthwave aesthetic and the power of modern web technologies.
100
+ </p>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <custom-footer></custom-footer>
106
+
107
+ <script src="components/navbar.js"></script>
108
+ <script src="components/footer.js"></script>
109
+ <script>
110
+ feather.replace();
111
+ </script>
112
+ </body>
113
+ </html>
114
+ >>>>>>> REPLACE
index.html CHANGED
@@ -10,175 +10,168 @@
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="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
23
- <!-- Oscilloscope Panel -->
24
- <div class="lg:col-span-2 bg-gray-900 border border-cyan-800 rounded-lg p-4 shadow-lg shadow-cyan-900/20">
25
- <div class="flex justify-between items-center mb-3">
26
- <h2 class="text-lg font-bold text-cyan-300 flex items-center">
27
- <i data-feather="activity" class="mr-2 text-cyan-500"></i> OSCILLOSCOPE
28
- </h2>
29
- <div class="flex space-x-2">
30
- <div class="w-3 h-3 bg-green-500 rounded-full"></div>
31
- <div class="w-3 h-3 bg-yellow-500 rounded-full"></div>
32
- <div class="w-3 h-3 bg-red-500 rounded-full"></div>
33
- </div>
34
- </div>
35
- <canvas id="visualizer" class="w-full h-64 bg-black border border-cyan-900 rounded"></canvas>
36
-
37
- <!-- ADSR Visualizer -->
38
- <div class="mt-6">
39
- <h3 class="text-md font-bold text-cyan-300 mb-3">ADSR ENVELOPE</h3>
40
- <div class="relative h-32 bg-black border border-cyan-900 rounded">
41
- <canvas id="adsr-visualizer" class="w-full h-full"></canvas>
42
- </div>
43
- </div>
44
- </div>
45
-
46
- <!-- MIDI Connection Panel -->
47
- <div class="bg-gray-900 border border-cyan-800 rounded-lg p-4 shadow-lg shadow-cyan-900/20">
48
- <h2 class="text-lg font-bold mb-4 text-cyan-300 flex items-center">
49
- <i data-feather="music" class="mr-2 text-cyan-500"></i> MIDI INTERFACE
50
- </h2>
51
-
52
- <div class="mb-4">
53
- <button id="midi-connect-btn" class="w-full bg-gray-800 hover:bg-gray-700 text-cyan-300 font-bold py-2 px-4 rounded border border-cyan-700 transition duration-200 flex items-center justify-center">
54
- <i data-feather="power" class="mr-2"></i> CONNECT DEVICE
55
- </button>
56
- </div>
57
-
58
- <div class="bg-gray-800 rounded p-3 mb-4 border border-cyan-900">
59
- <h3 class="font-semibold mb-2 text-cyan-300">STATUS</h3>
60
- <div id="midi-status" class="text-sm text-yellow-400">
61
- NOT CONNECTED
62
- </div>
63
- </div>
64
-
65
- <div class="bg-gray-800 rounded p-3 border border-cyan-900">
66
- <h3 class="font-semibold mb-2 text-cyan-300">ACTIVE NOTES</h3>
67
- <div id="active-notes" class="text-sm text-cyan-500 h-32 overflow-y-auto">
68
- NO ACTIVE NOTES
69
- </div>
70
- </div>
71
- </div>
72
- </div>
73
-
74
- <!-- Control Matrix -->
75
- <div class="bg-gray-900 border border-cyan-800 rounded-lg p-4 shadow-lg shadow-cyan-900/20 mb-8">
76
- <h2 class="text-lg font-bold mb-4 text-cyan-300 flex items-center">
77
- <i data-feather="sliders" class="mr-2 text-cyan-500"></i> CONTROL MATRIX
78
- </h2>
79
-
80
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
81
- <!-- Oscillator Section -->
82
- <div class="bg-gray-800 rounded p-4 border border-cyan-900">
83
- <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">OSCILLATOR</h3>
84
- <div class="space-y-4">
85
- <div>
86
- <label class="block text-xs text-cyan-500 mb-1">WAVEFORM</label>
87
- <select id="oscillator-type" class="w-full bg-gray-900 text-cyan-300 rounded p-2 border border-cyan-800">
88
- <option value="sawtooth">SAWTOOTH</option>
89
- <option value="sine">SINE</option>
90
- <option value="square">SQUARE</option>
91
- <option value="triangle">TRIANGLE</option>
92
- </select>
93
  </div>
94
- <div>
95
- <label class="block text-xs text-cyan-500 mb-1">DETUNE</label>
96
- <input type="range" id="oscillator-detune" min="-50" max="50" value="0" step="1"
97
- class="w-full accent-cyan-500">
98
- <span id="oscillator-detune-value" class="text-xs text-cyan-600">0 CENTS</span>
99
  </div>
100
  </div>
101
- </div>
102
-
103
- <!-- Filter Section -->
104
- <div class="bg-gray-800 rounded p-4 border border-cyan-900">
105
- <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">FILTER</h3>
106
- <div class="space-y-4">
107
- <div>
108
- <label class="block text-xs text-cyan-500 mb-1">CUTOFF</label>
109
- <input type="range" id="filter-frequency" min="50" max="5000" value="1000" step="1"
110
- class="w-full accent-cyan-500">
111
- <span id="filter-frequency-value" class="text-xs text-cyan-600">1000 HZ</span>
112
- </div>
113
- <div>
114
- <label class="block text-xs text-cyan-500 mb-1">RESONANCE</label>
115
- <input type="range" id="filter-q" min="0.1" max="20" value="1" step="0.1"
116
- class="w-full accent-cyan-500">
117
- <span id="filter-q-value" class="text-xs text-cyan-600">1.0</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  </div>
119
- <div>
120
- <label class="block text-xs text-cyan-500 mb-1">TYPE</label>
121
- <select id="filter-type" class="w-full bg-gray-900 text-cyan-300 rounded p-2 border border-cyan-800">
122
- <option value="lowpass">LOWPASS</option>
123
- <option value="highpass">HIGHPASS</option>
124
- <option value="bandpass">BANDPASS</option>
125
- </select>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  </div>
127
  </div>
128
  </div>
129
 
130
- <!-- Envelope Section -->
131
- <div class="bg-gray-800 rounded p-4 border border-cyan-900">
132
- <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">ENVELOPE</h3>
133
- <div class="space-y-4">
134
- <div>
135
- <label class="block text-xs text-cyan-500 mb-1">ATTACK</label>
136
- <input type="range" id="envelope-attack" min="0.01" max="5" value="0.1" step="0.01"
137
- class="w-full accent-cyan-500">
138
- <span id="envelope-attack-value" class="text-xs text-cyan-600">0.1 S</span>
139
- </div>
140
- <div>
141
- <label class="block text-xs text-cyan-500 mb-1">DECAY</label>
142
- <input type="range" id="envelope-decay" min="0.01" max="5" value="0.3" step="0.01"
143
- class="w-full accent-cyan-500">
144
- <span id="envelope-decay-value" class="text-xs text-cyan-600">0.3 S</span>
145
- </div>
146
  <div>
147
- <label class="block text-xs text-cyan-500 mb-1">SUSTAIN</label>
148
- <input type="range" id="envelope-sustain" min="0" max="1" value="0.5" step="0.01"
149
- class="w-full accent-cyan-500">
150
- <span id="envelope-sustain-value" class="text-xs text-cyan-600">50%</span>
 
 
 
 
 
 
 
 
151
  </div>
 
 
152
  <div>
153
- <label class="block text-xs text-cyan-500 mb-1">RELEASE</label>
154
- <input type="range" id="envelope-release" min="0.1" max="10" value="1.0" step="0.1"
155
- class="w-full accent-cyan-500">
156
- <span id="envelope-release-value" class="text-xs text-cyan-600">1.0 S</span>
 
 
 
 
 
 
 
 
 
157
  </div>
158
- </div>
159
- </div>
160
-
161
- <!-- Effects Section -->
162
- <div class="bg-gray-800 rounded p-4 border border-cyan-900">
163
- <h3 class="font-bold mb-3 text-cyan-300 border-b border-cyan-900 pb-2">EFFECTS</h3>
164
- <div class="space-y-4">
165
  <div>
166
- <label class="block text-xs text-cyan-500 mb-1">REVERB</label>
167
- <input type="range" id="reverb-wet" min="0" max="1" value="0.3" step="0.01"
168
- class="w-full accent-cyan-500">
169
- <span id="reverb-wet-value" class="text-xs text-cyan-600">30%</span>
 
 
 
 
 
 
 
 
 
170
  </div>
171
- <div>
172
- <label class="block text-xs text-cyan-500 mb-1">DISTORTION</label>
173
- <input type="range" id="distortion-wet" min="0" max="1" value="0.2" step="0.01"
174
- class="w-full accent-cyan-500">
175
- <span id="distortion-wet-value" class="text-xs text-cyan-600">20%</span>
176
- </div>
177
- <div>
178
- <label class="block text-xs text-cyan-500 mb-1">DELAY</label>
179
- <input type="range" id="delay-wet" min="0" max="1" value="0.25" step="0.01"
180
- class="w-full accent-cyan-500">
181
- <span id="delay-wet-value" class="text-xs text-cyan-600">25%</span>
182
  </div>
183
  </div>
184
  </div>
@@ -186,10 +179,15 @@
186
  </div>
187
  </div>
188
 
 
 
 
 
189
  <script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
190
  <script src="script.js"></script>
191
  <script>
192
  feather.replace();
193
  </script>
 
194
  </body>
195
  </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-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
  </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>
script.js CHANGED
@@ -1,12 +1,16 @@
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
 
11
  // Initialize audio context on first interaction
12
  let audioContextStarted = false;
@@ -32,18 +36,21 @@ function setupSynth() {
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 main synth
49
  synth = new Tone.PolySynth(Tone.Synth, {
@@ -54,12 +61,12 @@ function setupSynth() {
54
  attack: 0.1,
55
  decay: 0.3,
56
  sustain: 0.5,
57
- release: 1.0
58
  }
59
  }).connect(filter);
60
 
61
  // Create analyzer for visualizer
62
- analyser = new Tone.Analyser("fft", 64);
63
  synth.connect(analyser);
64
 
65
  console.log('Synth initialized');
@@ -71,8 +78,8 @@ let midiInputs = [];
71
 
72
  function onMIDISuccess(midi) {
73
  midiAccess = midi;
74
- updateMIDIStatus('CONNECTED');
75
- document.getElementById('midi-connect-btn').textContent = 'DEVICE CONNECTED';
76
  document.getElementById('midi-connect-btn').disabled = true;
77
 
78
  // Get inputs
@@ -87,7 +94,7 @@ function onMIDISuccess(midi) {
87
 
88
  function onMIDIFailure(msg) {
89
  console.error('Failed to get MIDI access - ' + msg);
90
- updateMIDIStatus('CONNECTION FAILED: ' + msg, 'error');
91
  }
92
 
93
  function onMIDIStateChange(event) {
@@ -106,12 +113,12 @@ function onMIDIStateChange(event) {
106
  function setupMIDIInput(input) {
107
  midiInputs.push(input);
108
  input.addEventListener('midimessage', onMIDIMessage);
109
- updateMIDIStatus(`CONNECTED TO: ${input.name}`, 'success');
110
  }
111
 
112
  function removeMIDIInput(input) {
113
  midiInputs = midiInputs.filter(i => i.id !== input.id);
114
- updateMIDIStatus(`DISCONNECTED: ${input.name}`, 'warning');
115
  }
116
 
117
  function onMIDIMessage(message) {
@@ -131,8 +138,6 @@ function onMIDIMessage(message) {
131
  noteOff(note);
132
  break;
133
  }
134
-
135
- updateActiveNotesDisplay();
136
  }
137
 
138
  function noteOn(note, velocity) {
@@ -142,7 +147,9 @@ function noteOn(note, velocity) {
142
  const velocityNorm = velocity / 127;
143
 
144
  synth.triggerAttack(noteName, undefined, velocityNorm);
145
- activeNotes.set(note, { name: noteName, velocity: velocity });
 
 
146
 
147
  console.log(`Note On: ${noteName}, Velocity: ${velocity}`);
148
  }
@@ -152,26 +159,11 @@ function noteOff(note) {
152
 
153
  const noteName = Tone.Frequency(note, "midi").toNote();
154
  synth.triggerRelease(noteName);
155
- activeNotes.delete(note);
156
-
157
- console.log(`Note Off: ${noteName}`);
158
- }
159
-
160
- function updateActiveNotesDisplay() {
161
- const container = document.getElementById('active-notes');
162
- if (activeNotes.size === 0) {
163
- container.innerHTML = '<div class="text-cyan-700">NO ACTIVE NOTES</div>';
164
- return;
165
  }
166
 
167
- let html = '';
168
- activeNotes.forEach((note, key) => {
169
- html += `<div class="py-1 px-2 rounded mb-1 bg-gray-900 border border-cyan-900 flex justify-between">
170
- <span>${note.name}</span>
171
- <span class="text-cyan-600">VEL: ${note.velocity}</span>
172
- </div>`;
173
- });
174
- container.innerHTML = html;
175
  }
176
 
177
  function updateMIDIStatus(message, type = 'info') {
@@ -190,7 +182,7 @@ function updateMIDIStatus(message, type = 'info') {
190
  statusEl.classList.add('text-red-400');
191
  break;
192
  default:
193
- statusEl.classList.add('text-cyan-400');
194
  }
195
  }
196
 
@@ -203,104 +195,105 @@ function setupUIControls() {
203
  }
204
  });
205
 
206
- // Oscillator Detune
207
- document.getElementById('oscillator-detune').addEventListener('input', (e) => {
208
- const value = parseInt(e.target.value);
209
- document.getElementById('oscillator-detune-value').textContent = `${value} CENTS`;
210
- if (synth) {
211
- synth.set({ oscillator: { detune: value } });
212
- }
213
- });
214
-
215
  // Filter Frequency
216
  document.getElementById('filter-frequency').addEventListener('input', (e) => {
217
- const value = parseInt(e.target.value);
218
- document.getElementById('filter-frequency-value').textContent = `${value} HZ`;
219
  if (filter) {
220
- filter.frequency.value = value;
221
  }
222
  });
223
 
224
  // Filter Resonance (Q)
225
  document.getElementById('filter-q').addEventListener('input', (e) => {
226
- const value = parseFloat(e.target.value);
227
- document.getElementById('filter-q-value').textContent = value.toFixed(1);
228
  if (filter) {
229
- filter.Q.value = value;
230
  }
231
  });
232
 
233
- // Filter Type
234
- document.getElementById('filter-type').addEventListener('change', (e) => {
235
- if (filter) {
236
- filter.type = e.target.value;
237
- }
238
- });
239
-
240
- // Envelope Attack
241
  document.getElementById('envelope-attack').addEventListener('input', (e) => {
242
  const value = parseFloat(e.target.value);
243
- document.getElementById('envelope-attack-value').textContent = `${value.toFixed(2)} S`;
244
  if (synth) {
245
  synth.set({ envelope: { attack: value } });
246
  }
247
- drawADSR(); // Update ADSR visualization
248
  });
249
 
250
- // Envelope Decay
251
  document.getElementById('envelope-decay').addEventListener('input', (e) => {
252
  const value = parseFloat(e.target.value);
253
- document.getElementById('envelope-decay-value').textContent = `${value.toFixed(2)} S`;
254
  if (synth) {
255
  synth.set({ envelope: { decay: value } });
256
  }
257
- drawADSR(); // Update ADSR visualization
258
  });
259
 
260
- // Envelope Sustain
261
  document.getElementById('envelope-sustain').addEventListener('input', (e) => {
262
  const value = parseFloat(e.target.value);
263
- document.getElementById('envelope-sustain-value').textContent = `${Math.round(value * 100)}%`;
264
  if (synth) {
265
  synth.set({ envelope: { sustain: value } });
266
  }
267
- drawADSR(); // Update ADSR visualization
268
  });
269
 
270
- // Envelope Release
271
  document.getElementById('envelope-release').addEventListener('input', (e) => {
272
  const value = parseFloat(e.target.value);
273
- document.getElementById('envelope-release-value').textContent = `${value.toFixed(1)} S`;
274
  if (synth) {
275
  synth.set({ envelope: { release: value } });
276
  }
277
- drawADSR(); // Update ADSR visualization
278
  });
279
 
280
- // Reverb Wet
281
- document.getElementById('reverb-wet').addEventListener('input', (e) => {
282
  const value = parseFloat(e.target.value);
283
- document.getElementById('reverb-wet-value').textContent = `${Math.round(value * 100)}%`;
284
- if (reverb) {
285
- reverb.wet.value = value;
286
  }
 
287
  });
288
 
289
- // Distortion Wet
290
- document.getElementById('distortion-wet').addEventListener('input', (e) => {
291
  const value = parseFloat(e.target.value);
292
- document.getElementById('distortion-wet-value').textContent = `${Math.round(value * 100)}%`;
293
- if (distortion) {
294
- distortion.wet.value = value;
295
  }
 
296
  });
297
 
298
- // Delay Wet
299
- document.getElementById('delay-wet').addEventListener('input', (e) => {
300
  const value = parseFloat(e.target.value);
301
- document.getElementById('delay-wet-value').textContent = `${Math.round(value * 100)}%`;
302
- if (delay) {
303
- delay.wet.value = value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  }
305
  });
306
  }
@@ -327,8 +320,12 @@ function setupVisualizer() {
327
  const values = analyser.getValue();
328
  ctx.clearRect(0, 0, canvas.width, canvas.height);
329
 
 
 
 
 
330
  // Draw grid
331
- ctx.strokeStyle = 'rgba(6, 182, 212, 0.1)';
332
  ctx.lineWidth = 1;
333
 
334
  // Vertical lines
@@ -347,10 +344,10 @@ function setupVisualizer() {
347
  ctx.stroke();
348
  }
349
 
350
- // Draw waveform
351
  ctx.beginPath();
352
  ctx.lineWidth = 2;
353
- ctx.strokeStyle = '#06b6d4'; // cyan-500
354
 
355
  const sliceWidth = canvas.width / values.length;
356
  let x = 0;
@@ -369,99 +366,183 @@ function setupVisualizer() {
369
  }
370
 
371
  ctx.stroke();
 
 
 
 
 
 
 
372
  requestAnimationFrame(draw);
373
  }
374
 
375
  draw();
376
  }
377
 
378
- // ADSR Visualizer
379
- function setupADSRVisualizer() {
380
- const canvas = document.getElementById('adsr-visualizer');
 
 
 
 
 
381
  const ctx = canvas.getContext('2d');
382
 
383
- function resizeCanvas() {
384
- canvas.width = canvas.clientWidth;
385
- canvas.height = canvas.clientHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  }
387
 
388
- window.addEventListener('resize', resizeCanvas);
389
- resizeCanvas();
 
 
 
 
 
390
 
391
- // Initial draw
392
- drawADSR();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
- function drawADSR() {
395
- if (!canvas.width || !canvas.height) return;
396
-
397
- ctx.clearRect(0, 0, canvas.width, canvas.height);
398
-
399
- // Draw grid
400
- ctx.strokeStyle = 'rgba(6, 182, 212, 0.1)';
401
- ctx.lineWidth = 1;
402
-
403
- // Vertical lines
404
- for (let x = 0; x < canvas.width; x += 20) {
405
- ctx.beginPath();
406
- ctx.moveTo(x, 0);
407
- ctx.lineTo(x, canvas.height);
408
- ctx.stroke();
409
- }
410
-
411
- // Horizontal lines
412
- for (let y = 0; y < canvas.height; y += 20) {
413
- ctx.beginPath();
414
- ctx.moveTo(0, y);
415
- ctx.lineTo(canvas.width, y);
416
- ctx.stroke();
417
- }
418
-
419
- // Draw ADSR envelope
420
- const attack = parseFloat(document.getElementById('envelope-attack').value);
421
- const decay = parseFloat(document.getElementById('envelope-decay').value);
422
- const sustain = parseFloat(document.getElementById('envelope-sustain').value);
423
- const release = parseFloat(document.getElementById('envelope-release').value);
424
-
425
- // Normalize values for visualization
426
- const totalTime = attack + decay + 2 + release; // Add some time for sustain and release visualization
427
- const attackWidth = (attack / totalTime) * canvas.width;
428
- const decayWidth = (decay / totalTime) * canvas.width;
429
- const sustainWidth = (2 / totalTime) * canvas.width;
430
- const releaseWidth = (release / totalTime) * canvas.width;
431
-
432
  ctx.beginPath();
433
- ctx.lineWidth = 3;
434
- ctx.strokeStyle = '#06b6d4'; // cyan-500
435
-
436
- // Start at 0
437
- ctx.moveTo(0, canvas.height);
438
-
439
- // Attack
440
- ctx.lineTo(attackWidth, 0);
441
-
442
- // Decay
443
- const sustainHeight = canvas.height - (sustain * canvas.height);
444
- ctx.lineTo(attackWidth + decayWidth, sustainHeight);
445
-
446
- // Sustain
447
- ctx.lineTo(attackWidth + decayWidth + sustainWidth, sustainHeight);
448
-
449
- // Release
450
- ctx.lineTo(attackWidth + decayWidth + sustainWidth + releaseWidth, canvas.height);
451
-
452
  ctx.stroke();
453
-
454
- // Draw labels
455
- ctx.fillStyle = '#06b6d4';
456
- ctx.font = '10px monospace';
457
- ctx.fillText('A', 5, canvas.height - 5);
458
- ctx.fillText('D', attackWidth - 10, canvas.height - 5);
459
- ctx.fillText('S', attackWidth + decayWidth - 10, sustainHeight - 5);
460
- ctx.fillText('R', attackWidth + decayWidth + sustainWidth + releaseWidth - 15, canvas.height - 5);
461
  }
462
 
463
- // Make drawADSR accessible globally for updates
464
- window.drawADSR = drawADSR;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  }
466
 
467
  // Initialize everything when page loads
@@ -469,9 +550,11 @@ document.addEventListener('DOMContentLoaded', () => {
469
  // Setup UI controls
470
  setupUIControls();
471
 
472
- // Setup visualizers
473
  setupVisualizer();
474
- setupADSRVisualizer();
 
 
475
 
476
  // Setup synth
477
  setupSynth();
@@ -483,7 +566,7 @@ document.addEventListener('DOMContentLoaded', () => {
483
  navigator.requestMIDIAccess()
484
  .then(onMIDISuccess, onMIDIFailure);
485
  } else {
486
- updateMIDIStatus('WEBMIDI NOT SUPPORTED', 'error');
487
  }
488
  });
489
  });
 
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;
 
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
  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');
 
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
 
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
  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
  noteOff(note);
139
  break;
140
  }
 
 
141
  }
142
 
143
  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
 
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') {
 
182
  statusEl.classList.add('text-red-400');
183
  break;
184
  default:
185
+ statusEl.classList.add('text-blue-400');
186
  }
187
  }
188
 
 
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
  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
  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
  }
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
  // 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
  navigator.requestMIDIAccess()
567
  .then(onMIDISuccess, onMIDIFailure);
568
  } else {
569
+ updateMIDIStatus('WebMIDI is not supported in this browser', 'error');
570
  }
571
  });
572
  });
style.css CHANGED
@@ -1,22 +1,21 @@
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,7 +32,7 @@ h1, h2, h3, h4, h5, h6 {
33
  }
34
 
35
  ::-webkit-scrollbar-track {
36
- background: #0f172a; /* gray-900 */
37
  }
38
 
39
  ::-webkit-scrollbar-thumb {
@@ -42,19 +41,17 @@ h1, h2, h3, h4, h5, h6 {
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,7 +59,7 @@ input[type="range"] {
62
  -webkit-appearance: none;
63
  height: 6px;
64
  border-radius: 3px;
65
- background: #0f172a; /* gray-900 */
66
  outline: none;
67
  }
68
 
@@ -73,7 +70,7 @@ input[type="range"]::-webkit-slider-thumb {
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,57 +79,48 @@ 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, #adsr-visualizer {
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
  /* Responsive adjustments */
@@ -142,26 +130,18 @@ select {
142
  }
143
 
144
  h1 {
145
- font-size: 2rem;
146
  }
147
 
148
  .grid {
149
  gap: 1rem;
150
  }
151
 
152
- .text-lg {
153
- font-size: 1rem;
154
- }
155
-
156
- .text-md {
157
- font-size: 0.875rem;
158
- }
159
-
160
- .text-sm {
161
- font-size: 0.75rem;
162
  }
163
 
164
- .text-xs {
165
- font-size: 0.625rem;
166
  }
167
  }
 
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
  }
33
 
34
  ::-webkit-scrollbar-track {
35
+ background: #1f2937; /* gray-800 */
36
  }
37
 
38
  ::-webkit-scrollbar-thumb {
 
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
  -webkit-appearance: none;
60
  height: 6px;
61
  border-radius: 3px;
62
+ background: #374151; /* gray-700 */
63
  outline: none;
64
  }
65
 
 
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
  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
  }
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
  }