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

get rid of all the extra pages, lets just get rid of the sequencer and give the program a more 'synth-like' layout. also, add more capability for the envelope, I want a visual adsr editor and multi-stage lowpass filtering. give it a more shaped 'oscilloscope/matrix style aesthetic (similar to the photo attached)

Browse files
Files changed (3) hide show
  1. index.html +150 -111
  2. script.js +184 -29
  3. style.css +78 -32
index.html CHANGED
@@ -1,3 +1,4 @@
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
@@ -9,148 +10,186 @@
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-6xl font-bold mb-4 bg-gradient-to-r from-gray-400 to-white bg-clip-text text-transparent">
18
- SynthWave Studio
19
  </h1>
20
- <p class="text-gray-400 text-lg max-w-2xl mx-auto">
21
- Polyphonic MIDI synthesizer with velocity-sensitive controls, filters, and effects
22
- </p>
23
  </header>
24
 
25
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
26
- <!-- Synthesizer Controls -->
27
- <div class="lg:col-span-2 bg-gray-800 rounded-xl p-6 shadow-2xl">
28
- <h2 class="text-2xl font-bold mb-6 text-gray-300 flex items-center">
29
- <i data-feather="sliders" class="mr-2"></i> Synthesizer Controls
30
- </h2>
31
-
32
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
33
- <!-- Oscillator Section -->
34
- <div class="bg-gray-700 rounded-lg p-4">
35
- <h3 class="font-semibold mb-3 text-gray-300">Oscillator</h3>
36
- <div class="space-y-4">
37
- <div>
38
- <label class="block text-sm text-gray-400 mb-1">Type</label>
39
- <select id="oscillator-type" class="w-full bg-gray-600 text-white rounded p-2">
40
- <option value="sawtooth">Sawtooth</option>
41
- <option value="sine">Sine</option>
42
- <option value="square">Square</option>
43
- <option value="triangle">Triangle</option>
44
- </select>
45
- </div>
46
- </div>
47
  </div>
48
-
49
- <!-- Filter Section -->
50
- <div class="bg-gray-700 rounded-lg p-4">
51
- <h3 class="font-semibold mb-3 text-gray-300">Filter</h3>
52
- <div class="space-y-4">
53
- <div>
54
- <label class="block text-sm text-gray-400 mb-1">Cutoff Frequency</label>
55
- <input type="range" id="filter-frequency" min="50" max="5000" value="1000" step="1"
56
- class="w-full accent-gray-500">
57
- <span id="filter-frequency-value" class="text-xs text-gray-400">1000 Hz</span>
58
- </div>
59
- <div>
60
- <label class="block text-sm text-gray-400 mb-1">Resonance</label>
61
- <input type="range" id="filter-q" min="0.1" max="10" value="1" step="0.1"
62
- class="w-full accent-gray-500">
63
- <span id="filter-q-value" class="text-xs text-gray-400">1.0</span>
64
- </div>
65
- </div>
66
- </div>
67
-
68
- <!-- Envelope Section -->
69
- <div class="bg-gray-700 rounded-lg p-4">
70
- <h3 class="font-semibold mb-3 text-gray-300">Envelope</h3>
71
- <div class="space-y-4">
72
- <div>
73
- <label class="block text-sm text-gray-400 mb-1">Attack</label>
74
- <input type="range" id="envelope-attack" min="0.01" max="2" value="0.1" step="0.01"
75
- class="w-full accent-gray-500">
76
- <span id="envelope-attack-value" class="text-xs text-gray-400">0.1 s</span>
77
- </div>
78
- <div>
79
- <label class="block text-sm text-gray-400 mb-1">Release</label>
80
- <input type="range" id="envelope-release" min="0.1" max="5" value="0.5" step="0.1"
81
- class="w-full accent-gray-500">
82
- <span id="envelope-release-value" class="text-xs text-gray-400">0.5 s</span>
83
- </div>
84
- </div>
85
- </div>
86
-
87
- <!-- Effects Section -->
88
- <div class="bg-gray-700 rounded-lg p-4">
89
- <h3 class="font-semibold mb-3 text-gray-300">Effects</h3>
90
- <div class="space-y-4">
91
- <div>
92
- <label class="block text-sm text-gray-400 mb-1">Reverb</label>
93
- <input type="range" id="reverb-wet" min="0" max="1" value="0.3" step="0.01"
94
- class="w-full accent-gray-500">
95
- <span id="reverb-wet-value" class="text-xs text-gray-400">30%</span>
96
- </div>
97
- <div>
98
- <label class="block text-sm text-gray-400 mb-1">Distortion</label>
99
- <input type="range" id="distortion-wet" min="0" max="1" value="0.2" step="0.01"
100
- class="w-full accent-gray-500">
101
- <span id="distortion-wet-value" class="text-xs text-gray-400">20%</span>
102
- </div>
103
- </div>
104
  </div>
105
  </div>
106
  </div>
107
 
108
  <!-- MIDI Connection Panel -->
109
- <div class="bg-gray-800 rounded-xl p-6 shadow-2xl">
110
- <h2 class="text-2xl font-bold mb-6 text-gray-300 flex items-center">
111
- <i data-feather="music" class="mr-2"></i> MIDI Connection
112
  </h2>
113
 
114
- <div class="mb-6">
115
- <button id="midi-connect-btn" class="w-full bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center">
116
- <i data-feather="power" class="mr-2"></i> Connect MIDI Device
117
  </button>
118
  </div>
119
 
120
- <div class="bg-gray-700 rounded-lg p-4 mb-6">
121
- <h3 class="font-semibold mb-3 text-gray-300">MIDI Status</h3>
122
  <div id="midi-status" class="text-sm text-yellow-400">
123
- Not connected
124
  </div>
125
  </div>
126
 
127
- <div class="bg-gray-700 rounded-lg p-4">
128
- <h3 class="font-semibold mb-3 text-gray-300">Active Notes</h3>
129
- <div id="active-notes" class="text-sm text-gray-400 h-32 overflow-y-auto">
130
- No active notes
131
  </div>
132
  </div>
133
  </div>
134
  </div>
135
 
136
- <!-- Visualizer -->
137
- <div class="bg-gray-800 rounded-xl p-6 shadow-2xl mb-12">
138
- <h2 class="text-2xl font-bold mb-6 text-gray-300 flex items-center">
139
- <i data-feather="activity" class="mr-2"></i> Audio Visualizer
140
  </h2>
141
- <canvas id="visualizer" class="w-full h-48 bg-gray-900 rounded-lg"></canvas>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  </div>
143
  </div>
144
 
145
- <custom-footer></custom-footer>
146
-
147
- <script src="components/navbar.js"></script>
148
- <script src="components/footer.js"></script>
149
  <script src="https://unpkg.com/tone@14.7.77/build/Tone.js"></script>
150
  <script src="script.js"></script>
151
  <script>
152
  feather.replace();
153
  </script>
154
- <script src="https://deepsite.hf.co/deepsite-badge.js"></script>
155
  </body>
156
- </html>
 
1
+
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
 
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>
185
+ </div>
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>
script.js CHANGED
@@ -1,11 +1,10 @@
1
- // Shared JavaScript across all pages
2
- console.log('SynthWave Studio loaded');
3
 
4
  // Tone.js setup
5
  let synth;
6
  let filter;
7
  let reverb;
8
  let distortion;
 
9
  let analyser;
10
  let activeNotes = new Map();
11
 
@@ -33,12 +32,18 @@ function setupSynth() {
33
  wet: 0.2
34
  }).connect(reverb);
35
 
 
 
 
 
 
 
36
  // Create filter
37
  filter = new Tone.Filter({
38
  type: "lowpass",
39
  frequency: 1000,
40
  Q: 1
41
- }).connect(distortion);
42
 
43
  // Create main synth
44
  synth = new Tone.PolySynth(Tone.Synth, {
@@ -47,14 +52,14 @@ function setupSynth() {
47
  },
48
  envelope: {
49
  attack: 0.1,
50
- decay: 0.2,
51
- sustain: 0.3,
52
- release: 0.5
53
  }
54
  }).connect(filter);
55
 
56
  // Create analyzer for visualizer
57
- analyser = new Tone.Analyser("fft", 32);
58
  synth.connect(analyser);
59
 
60
  console.log('Synth initialized');
@@ -66,8 +71,8 @@ let midiInputs = [];
66
 
67
  function onMIDISuccess(midi) {
68
  midiAccess = midi;
69
- updateMIDIStatus('Connected');
70
- document.getElementById('midi-connect-btn').textContent = 'MIDI Connected';
71
  document.getElementById('midi-connect-btn').disabled = true;
72
 
73
  // Get inputs
@@ -82,7 +87,7 @@ function onMIDISuccess(midi) {
82
 
83
  function onMIDIFailure(msg) {
84
  console.error('Failed to get MIDI access - ' + msg);
85
- updateMIDIStatus('Connection failed: ' + msg, 'error');
86
  }
87
 
88
  function onMIDIStateChange(event) {
@@ -101,12 +106,12 @@ function onMIDIStateChange(event) {
101
  function setupMIDIInput(input) {
102
  midiInputs.push(input);
103
  input.addEventListener('midimessage', onMIDIMessage);
104
- updateMIDIStatus(`Connected to: ${input.name}`, 'success');
105
  }
106
 
107
  function removeMIDIInput(input) {
108
  midiInputs = midiInputs.filter(i => i.id !== input.id);
109
- updateMIDIStatus(`Disconnected: ${input.name}`, 'warning');
110
  }
111
 
112
  function onMIDIMessage(message) {
@@ -155,15 +160,15 @@ function noteOff(note) {
155
  function updateActiveNotesDisplay() {
156
  const container = document.getElementById('active-notes');
157
  if (activeNotes.size === 0) {
158
- container.innerHTML = '<div class="text-gray-500">No active notes</div>';
159
  return;
160
  }
161
 
162
  let html = '';
163
  activeNotes.forEach((note, key) => {
164
- html += `<div class="py-1 px-2 rounded mb-1 bg-gray-600 flex justify-between">
165
  <span>${note.name}</span>
166
- <span class="text-gray-400">Velocity: ${note.velocity}</span>
167
  </div>`;
168
  });
169
  container.innerHTML = html;
@@ -185,7 +190,7 @@ function updateMIDIStatus(message, type = 'info') {
185
  statusEl.classList.add('text-red-400');
186
  break;
187
  default:
188
- statusEl.classList.add('text-blue-400');
189
  }
190
  }
191
 
@@ -198,10 +203,19 @@ function setupUIControls() {
198
  }
199
  });
200
 
 
 
 
 
 
 
 
 
 
201
  // Filter Frequency
202
  document.getElementById('filter-frequency').addEventListener('input', (e) => {
203
  const value = parseInt(e.target.value);
204
- document.getElementById('filter-frequency-value').textContent = `${value} Hz`;
205
  if (filter) {
206
  filter.frequency.value = value;
207
  }
@@ -216,22 +230,51 @@ function setupUIControls() {
216
  }
217
  });
218
 
 
 
 
 
 
 
 
219
  // Envelope Attack
220
  document.getElementById('envelope-attack').addEventListener('input', (e) => {
221
  const value = parseFloat(e.target.value);
222
- document.getElementById('envelope-attack-value').textContent = `${value.toFixed(2)} s`;
223
  if (synth) {
224
  synth.set({ envelope: { attack: value } });
225
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  });
227
 
228
  // Envelope Release
229
  document.getElementById('envelope-release').addEventListener('input', (e) => {
230
  const value = parseFloat(e.target.value);
231
- document.getElementById('envelope-release-value').textContent = `${value.toFixed(1)} s`;
232
  if (synth) {
233
  synth.set({ envelope: { release: value } });
234
  }
 
235
  });
236
 
237
  // Reverb Wet
@@ -251,6 +294,15 @@ function setupUIControls() {
251
  distortion.wet.value = value;
252
  }
253
  });
 
 
 
 
 
 
 
 
 
254
  }
255
 
256
  // Visualizer
@@ -275,17 +327,30 @@ function setupVisualizer() {
275
  const values = analyser.getValue();
276
  ctx.clearRect(0, 0, canvas.width, canvas.height);
277
 
278
- // Draw gradient background
279
- const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
280
- gradient.addColorStop(0, 'rgba(107, 114, 128, 0.2)');
281
- gradient.addColorStop(1, 'rgba(107, 114, 128, 0.05)');
282
- ctx.fillStyle = gradient;
283
- ctx.fillRect(0, 0, canvas.width, canvas.height);
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
  // Draw waveform
286
  ctx.beginPath();
287
  ctx.lineWidth = 2;
288
- ctx.strokeStyle = '#6b7280'; // gray-500
289
 
290
  const sliceWidth = canvas.width / values.length;
291
  let x = 0;
@@ -310,13 +375,103 @@ function setupVisualizer() {
310
  draw();
311
  }
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  // Initialize everything when page loads
314
  document.addEventListener('DOMContentLoaded', () => {
315
  // Setup UI controls
316
  setupUIControls();
317
 
318
- // Setup visualizer
319
  setupVisualizer();
 
320
 
321
  // Setup synth
322
  setupSynth();
@@ -328,7 +483,7 @@ document.addEventListener('DOMContentLoaded', () => {
328
  navigator.requestMIDIAccess()
329
  .then(onMIDISuccess, onMIDIFailure);
330
  } else {
331
- updateMIDIStatus('WebMIDI is not supported in this browser', 'error');
332
  }
333
  });
334
- });
 
 
 
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
 
 
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, {
 
52
  },
53
  envelope: {
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
 
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
 
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
  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) {
 
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;
 
190
  statusEl.classList.add('text-red-400');
191
  break;
192
  default:
193
+ statusEl.classList.add('text-cyan-400');
194
  }
195
  }
196
 
 
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
  }
 
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
 
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
  }
307
 
308
  // Visualizer
 
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
335
+ for (let x = 0; x < canvas.width; x += 20) {
336
+ ctx.beginPath();
337
+ ctx.moveTo(x, 0);
338
+ ctx.lineTo(x, canvas.height);
339
+ ctx.stroke();
340
+ }
341
+
342
+ // Horizontal lines
343
+ for (let y = 0; y < canvas.height; y += 20) {
344
+ ctx.beginPath();
345
+ ctx.moveTo(0, y);
346
+ ctx.lineTo(canvas.width, y);
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;
 
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
468
  document.addEventListener('DOMContentLoaded', () => {
469
  // Setup UI controls
470
  setupUIControls();
471
 
472
+ // Setup visualizers
473
  setupVisualizer();
474
+ setupADSRVisualizer();
475
 
476
  // Setup synth
477
  setupSynth();
 
483
  navigator.requestMIDIAccess()
484
  .then(onMIDISuccess, onMIDIFailure);
485
  } else {
486
+ updateMIDIStatus('WEBMIDI NOT SUPPORTED', 'error');
487
  }
488
  });
489
+ });
style.css CHANGED
@@ -1,20 +1,22 @@
1
- /* Shared styles across all pages */
2
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&family=Roboto:wght@300;400;500;700&display=swap');
 
3
 
4
  :root {
5
- --primary-color: #6b7280; /* gray-500 */
6
- --secondary-color: #ffffff; /* white */
 
 
 
7
  }
8
 
9
  body {
10
- font-family: 'Roboto', sans-serif;
11
- background-color: #111827; /* gray-900 */
12
- color: var(--secondary-color);
13
  margin: 0;
14
  padding: 0;
15
  min-height: 100vh;
16
- display: flex;
17
- flex-direction: column;
18
  }
19
 
20
  h1, h2, h3, h4, h5, h6 {
@@ -31,7 +33,7 @@ h1, h2, h3, h4, h5, h6 {
31
  }
32
 
33
  ::-webkit-scrollbar-track {
34
- background: #1f2937; /* gray-800 */
35
  }
36
 
37
  ::-webkit-scrollbar-thumb {
@@ -40,69 +42,97 @@ h1, h2, h3, h4, h5, h6 {
40
  }
41
 
42
  ::-webkit-scrollbar-thumb:hover {
43
- background: #4b5563; /* gray-600 */
44
  }
45
 
46
  /* Button styles */
47
  button {
48
  transition: all 0.2s ease;
 
 
 
49
  }
50
 
51
  button:hover {
52
- transform: translateY(-2px);
53
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
54
  }
55
 
56
  /* Input range styling */
57
  input[type="range"] {
58
  -webkit-appearance: none;
59
- height: 8px;
60
- border-radius: 4px;
61
- background: #374151; /* gray-700 */
62
  outline: none;
63
  }
64
 
65
  input[type="range"]::-webkit-slider-thumb {
66
  -webkit-appearance: none;
67
- width: 20px;
68
- height: 20px;
69
  border-radius: 50%;
70
  background: var(--primary-color);
71
  cursor: pointer;
72
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
73
  }
74
 
75
  input[type="range"]::-moz-range-thumb {
76
- width: 20px;
77
- height: 20px;
78
  border-radius: 50%;
79
  background: var(--primary-color);
80
  cursor: pointer;
81
- box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
82
  border: none;
83
  }
84
 
85
  /* Select styling */
86
  select {
87
  appearance: none;
88
- 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");
89
  background-repeat: no-repeat;
90
  background-position: right 0.5rem center;
91
  background-size: 1em;
 
 
 
92
  }
93
 
94
  /* Canvas styling */
95
- #visualizer {
96
  width: 100%;
97
- height: 192px;
98
- background: #111827; /* gray-900 */
99
- border-radius: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  }
101
 
102
- /* Active note styling */
103
- .note-active {
104
- background-color: #374151 !important; /* gray-700 */
105
- border-left: 3px solid var(--primary-color);
 
 
106
  }
107
 
108
  /* Responsive adjustments */
@@ -118,4 +148,20 @@ select {
118
  .grid {
119
  gap: 1rem;
120
  }
121
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 */
61
  input[type="range"] {
62
  -webkit-appearance: none;
63
+ height: 6px;
64
+ border-radius: 3px;
65
+ background: #0f172a; /* gray-900 */
66
  outline: none;
67
  }
68
 
69
  input[type="range"]::-webkit-slider-thumb {
70
  -webkit-appearance: none;
71
+ width: 16px;
72
+ height: 16px;
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 {
80
+ width: 16px;
81
+ height: 16px;
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 */
 
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
+ }