erl-j commited on
Commit
a0a9280
·
1 Parent(s): 1c45a6c

css fixes, keyboard settings

Browse files
Files changed (3) hide show
  1. app.py +3 -4
  2. custom.css +175 -116
  3. custom.js +124 -79
app.py CHANGED
@@ -69,6 +69,7 @@ def generate_and_export_soundfont(text, steps=20, instrument_name=None):
69
  "F#7",
70
  "C8",
71
  ]
 
72
  wav_files = []
73
  for i in range(audio.shape[0]):
74
  wav_path = f"{output_dir}/{pitches[i]}.wav"
@@ -150,12 +151,10 @@ with demo:
150
  )
151
 
152
  html = """
153
- <div id="custom-player"
154
- style="width: 100%; height: 600px; border: 1px solid #f8f9fa; border-radius: 5px; margin-top: 10px;"
155
- ></div>
156
  """
157
 
158
- gr.HTML(html, min_height=1000, max_height=1000)
159
 
160
  gr.Markdown("## Download Soundfont Package here:")
161
  with gr.Row():
 
69
  "F#7",
70
  "C8",
71
  ]
72
+
73
  wav_files = []
74
  for i in range(audio.shape[0]):
75
  wav_path = f"{output_dir}/{pitches[i]}.wav"
 
151
  )
152
 
153
  html = """
154
+ <div id="keyboard-container"></div>
 
 
155
  """
156
 
157
+ gr.HTML(html)
158
 
159
  gr.Markdown("## Download Soundfont Package here:")
160
  with gr.Row():
custom.css CHANGED
@@ -1,54 +1,58 @@
1
- /* CSS Variables */
2
  :root {
3
  --keyboard-bg: #fafafa;
4
  --inactive-key-bg: #e0e0e0;
5
  --border-color: #e5e5e5;
6
  --slider-track: #393a39;
7
  --slider-thumb: #000000;
 
8
  }
9
 
10
- /* Base styles */
11
- html, body, #root, .wrapper, main, .main-container {
12
- min-height: 100vh;
13
- margin: 0;
14
- padding: 0;
15
  width: 100%;
16
- max-width: 100%;
 
 
 
 
 
 
 
 
17
  }
18
 
19
- body {
20
- font-family: 'Roboto', sans-serif;
21
- font-size: 1rem;
22
- line-height: 1.5;
23
- overflow-x: hidden;
24
- transition: background-color 0.2s ease;
 
 
25
  }
26
 
27
- /* Keyboard layout */
28
- .keyboard-container {
29
  width: 100%;
30
- padding: 1.5rem;
31
- background: var(--keyboard-bg);
32
- border: 1px solid var(--border-color);
33
- border-radius: 4px;
34
- user-select: none;
35
- color: #333;
36
  }
37
 
38
  .keyboard-row {
39
  display: flex;
40
- gap: 0.25rem;
41
- margin-bottom: 0.25rem;
42
  width: 100%;
 
 
43
  }
44
 
45
- /* Key styles */
46
  .key {
47
- width: calc((100% - 2.75rem) / 12);
48
- aspect-ratio: 1;
49
- min-width: 40px;
50
- flex: none;
51
- padding: 0.5rem;
52
  border: 2px solid var(--border-color);
53
  border-radius: 4px;
54
  cursor: pointer;
@@ -57,143 +61,198 @@ body {
57
  flex-direction: column;
58
  align-items: center;
59
  justify-content: center;
60
- }
61
-
62
- .key:hover {
63
- transform: translateY(-1px);
64
- }
65
-
66
- .key:active {
67
- transform: translateY(0);
68
- }
69
-
70
- .key.inactive,
71
- .key:disabled {
72
- background: var(--inactive-key-bg);
73
  }
74
 
75
  .key-label {
76
- font-size: 0.875rem;
77
  font-weight: 500;
78
  color: #333;
79
  }
80
 
81
  .note-label {
82
- font-size: 0.75rem;
83
- margin-top: 0.25rem;
84
  color: #666;
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  /* Controls section */
88
  .controls-section {
89
- margin: 1em 0;
90
- padding: 1em;
 
91
  }
92
 
93
- .controls-row {
94
- display: flex;
95
- gap: 1em;
96
- margin: 1em 0;
 
97
  }
98
 
99
- .control-group {
100
- margin: 1em 0;
 
101
  }
102
 
103
- .control-group.half-width {
104
- flex: 1;
105
  margin: 0;
106
  }
107
 
108
- /* Labels and values */
109
- .slider-label,
110
- h3 {
111
- display: block;
112
- margin-bottom: 0.5em;
113
- font-size: 0.9em;
114
- color: #333;
115
  }
116
 
117
- h3 {
118
- margin-top: 0;
119
- font-size: 1rem;
120
- font-weight: 500;
121
  }
122
 
123
- /* Range inputs */
124
- input[type="range"] {
 
 
 
 
125
  width: 100%;
126
- height: 24px;
 
127
  background: transparent;
128
- border-radius: 2px;
129
  appearance: none;
130
- cursor: pointer;
131
- margin: 0.5em 0;
132
- padding: 10px 0;
133
  }
134
 
135
- input[type="range"]::-webkit-slider-thumb {
136
- appearance: none;
137
- width: 16px;
138
- height: 16px;
139
  background: var(--slider-thumb);
140
  border-radius: 50%;
141
  cursor: pointer;
142
- margin-top: -6px;
143
  }
144
 
145
- input[type="range"]::-webkit-slider-runnable-track {
146
- background: var(--slider-track);
147
  height: 4px;
148
- border-radius: 2px;
149
- }
150
-
151
- input[type="range"]::-moz-range-thumb {
152
- width: 16px;
153
- height: 16px;
154
- background: var(--slider-thumb);
155
- border: none;
156
- border-radius: 50%;
157
- cursor: pointer;
158
- }
159
-
160
- input[type="range"]::-moz-range-track {
161
  background: var(--slider-track);
162
- height: 4px;
163
  border-radius: 2px;
164
  }
165
 
166
- /* Mobile styles */
167
- @media (max-width: 768px) {
168
- .control-group {
169
- min-width: 100%;
170
- }
171
-
172
  .key {
173
- min-width: 35px;
 
174
  }
175
 
176
  .key-label {
177
- font-size: 0.75rem;
178
  }
179
 
180
- input[type="range"] {
181
- height: 32px;
182
- padding: 14px 0;
183
  }
184
 
185
- input[type="range"]::-webkit-slider-thumb {
186
- width: 28px;
187
- height: 28px;
188
  }
189
  }
190
 
 
 
 
 
 
 
191
 
192
- .release-value,
193
- .reverb-mix-value,
194
- .master-value,
195
- .root-value,
196
- .column-value,
197
- .row-value {
198
- color: black;
199
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  :root {
2
  --keyboard-bg: #fafafa;
3
  --inactive-key-bg: #e0e0e0;
4
  --border-color: #e5e5e5;
5
  --slider-track: #393a39;
6
  --slider-thumb: #000000;
7
+ --key-ratio: 1;
8
  }
9
 
10
+ #keyboard-container {
 
 
 
 
11
  width: 100%;
12
+ max-width: 1200px;
13
+ margin: 0 auto;
14
+ padding: 0.25rem;
15
+ background: var(--keyboard-bg);
16
+ border: 2px solid var(--border-color);
17
+ border-radius: 4px;
18
+ user-select: none;
19
+ box-sizing: border-box;
20
+ color: #000000; /* Add this - sets a default dark color for all text */
21
  }
22
 
23
+ /* Make all text elements inside use the inherited color */
24
+ #keyboard-container .key-label,
25
+ #keyboard-container .note-label,
26
+ #keyboard-container .slider-label,
27
+ #keyboard-container span,
28
+ #keyboard-container h3,
29
+ #keyboard-container summary {
30
+ color: black;
31
  }
32
 
33
+ .keyboard {
 
34
  width: 100%;
35
+ padding: 0.25rem 0;
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
+ box-sizing: border-box;
 
40
  }
41
 
42
  .keyboard-row {
43
  display: flex;
44
+ gap: 0.125rem;
45
+ margin-bottom: 0.125rem;
46
  width: 100%;
47
+ justify-content: center;
48
+ box-sizing: border-box;
49
  }
50
 
 
51
  .key {
52
+ /* Smaller base size with viewport-based scaling */
53
+ width: clamp(1.5rem, min(4vw + 0.75rem, 3rem), 3rem);
54
+ height: clamp(1.5rem, min(4vw + 0.75rem, 3rem), 3rem);
55
+ padding: 0.125rem;
 
56
  border: 2px solid var(--border-color);
57
  border-radius: 4px;
58
  cursor: pointer;
 
61
  flex-direction: column;
62
  align-items: center;
63
  justify-content: center;
64
+ touch-action: none;
65
+ background: white;
66
+ flex-shrink: 0;
67
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
  .key-label {
71
+ font-size: clamp(0.5rem, min(1.5vw + 0.4rem, 0.875rem), 0.875rem);
72
  font-weight: 500;
73
  color: #333;
74
  }
75
 
76
  .note-label {
77
+ font-size: clamp(0.4rem, min(1.25vw + 0.3rem, 0.75rem), 0.75rem);
 
78
  color: #666;
79
  }
80
 
81
+ /* Additional reduction for very small screens */
82
+ @media (max-width: 360px) {
83
+ .keyboard-row {
84
+ gap: 0.1rem;
85
+ }
86
+
87
+ .key {
88
+ width: clamp(1.25rem, 3.5vw + 0.5rem, 1.75rem);
89
+ height: clamp(1.25rem, 3.5vw + 0.5rem, 1.75rem);
90
+ padding: 0.1rem;
91
+ }
92
+
93
+ .key-label {
94
+ font-size: clamp(0.4rem, 1.25vw + 0.35rem, 0.5rem);
95
+ }
96
+
97
+ .note-label {
98
+ font-size: clamp(0.35rem, 1vw + 0.3rem, 0.45rem);
99
+ }
100
+ }
101
+
102
+ /* Larger screens */
103
+ @media (min-width: 768px) {
104
+ .keyboard-row {
105
+ gap: 0.25rem;
106
+ margin-bottom: 0.25rem;
107
+ }
108
+
109
+ .key {
110
+ padding: 0.25rem;
111
+ }
112
+ }
113
+
114
  /* Controls section */
115
  .controls-section {
116
+ margin: 0.5rem 0;
117
+ width: 100%;
118
+ box-sizing: border-box;
119
  }
120
 
121
+ .controls-section details {
122
+ background: white;
123
+ border: 1px solid var(--border-color);
124
+ border-radius: 4px;
125
+ padding: 0.5rem;
126
  }
127
 
128
+ .controls-section summary {
129
+ cursor: pointer;
130
+ padding: 0.5rem;
131
  }
132
 
133
+ .controls-section summary h3 {
134
+ display: inline;
135
  margin: 0;
136
  }
137
 
138
+ .controls-row {
139
+ display: flex;
140
+ flex-direction: column;
141
+ gap: 1rem;
 
 
 
142
  }
143
 
144
+ .control-group {
145
+ margin: 0.5rem 0;
 
 
146
  }
147
 
148
+ .control-group.half-width {
149
+ flex: 1;
150
+ }
151
+
152
+ /* Slider styles */
153
+ .control-slider {
154
  width: 100%;
155
+ height: 44px;
156
+ padding: 14px 0;
157
  background: transparent;
158
+ -webkit-appearance: none;
159
  appearance: none;
 
 
 
160
  }
161
 
162
+ .control-slider::-webkit-slider-thumb {
163
+ -webkit-appearance: none;
164
+ width: 28px;
165
+ height: 28px;
166
  background: var(--slider-thumb);
167
  border-radius: 50%;
168
  cursor: pointer;
169
+ margin-top: -14px;
170
  }
171
 
172
+ .control-slider::-webkit-slider-runnable-track {
173
+ width: 100%;
174
  height: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  background: var(--slider-track);
 
176
  border-radius: 2px;
177
  }
178
 
179
+
180
+ /* Media Queries */
181
+ @media (min-width: 768px) {
 
 
 
182
  .key {
183
+ width: 3.5rem;
184
+ height: 3.5rem;
185
  }
186
 
187
  .key-label {
188
+ font-size: 1rem;
189
  }
190
 
191
+ .note-label {
192
+ font-size: 0.75rem;
 
193
  }
194
 
195
+ .controls-row {
196
+ flex-direction: row;
 
197
  }
198
  }
199
 
200
+ @media (min-width: 1024px) {
201
+ .key {
202
+ width: 4rem;
203
+ height: 4rem;
204
+ }
205
+ }
206
 
207
+ /* Ensure proper box-sizing everywhere */
208
+ *, *::before, *::after {
209
+ box-sizing: border-box;
210
+ }
211
+
212
+ .loader {
213
+ position: fixed;
214
+ bottom: 20px;
215
+ left: 50%;
216
+ transform: translateX(-50%);
217
+ padding: 10px 20px;
218
+ border-radius: 20px;
219
+ font-size: 14px;
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 8px;
223
+ transition: all 0.3s ease;
224
+ z-index: 1000;
225
+ }
226
+
227
+ .loader.active {
228
+ background-color: #03ae5e;
229
+ border: 1px solid #00ff22;
230
+ color: #00ffae;
231
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
232
+ }
233
+
234
+ .loader.inactive {
235
+ background-color: #e8f5e9;
236
+ border: 1px solid #81c784;
237
+ color: #2e7d32;
238
+ }
239
+
240
+ .loader-emoji {
241
+ font-size: 16px;
242
+ animation: bounce 1s infinite;
243
+ }
244
+
245
+ .loader.active .loader-emoji {
246
+ animation: spin 1s infinite linear;
247
+ }
248
+
249
+ @keyframes bounce {
250
+ 0%, 100% { transform: translateY(0); }
251
+ 50% { transform: translateY(-3px); }
252
+ }
253
+
254
+
255
+ @keyframes spin {
256
+ from { transform: rotate(0deg); }
257
+ to { transform: rotate(360deg); }
258
+ }
custom.js CHANGED
@@ -6,8 +6,6 @@ function previewPlayer() {
6
  this.loadToneJS().then(() => this.init());
7
  this.setupWavFileObserver();
8
 
9
-
10
- // Add click handlers for activation/deactivation
11
  this.container.addEventListener('click', (e) => {
12
  e.stopPropagation();
13
  if (!this.keyboardEnabled) {
@@ -21,13 +19,30 @@ function previewPlayer() {
21
  }
22
  });
23
 
24
- // disable keyboard
25
  this.disableKeyboard();
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  enableKeyboard() {
 
29
  this.keyboardEnabled = true;
30
  this.container.style.opacity = '1';
 
31
  }
32
 
33
  disableKeyboard() {
@@ -35,48 +50,18 @@ function previewPlayer() {
35
  this.container.style.opacity = '0.5';
36
  }
37
 
 
 
 
 
38
 
39
-
40
- setupWavFileObserver() {
41
- const observer = new MutationObserver((mutations) => {
42
- const hasDownloadLinkChanges = mutations.some(mutation =>
43
- mutation.type === 'childList' &&
44
- mutation.target.classList.contains('download-link')
45
- );
46
-
47
- if (hasDownloadLinkChanges) {
48
- this.initializeSampler();
49
- this.enableKeyboard();
50
- // scroll so middle of keyboard is in centre of viewport
51
- const keyboardTop = this.container.querySelector('.keyboard').getBoundingClientRect().top;
52
- window.scrollTo(0, keyboardTop - window.innerHeight / 2, { behavior: 'smooth' });
53
- }
54
- });
55
-
56
- const wavFilesContainer = document.getElementById('individual-wav-files');
57
- if (wavFilesContainer) {
58
- observer.observe(wavFilesContainer, {
59
- childList: true,
60
- subtree: true
61
- });
62
  }
63
  }
64
 
65
- initializeProperties() {
66
- this.sampler = null;
67
- this.keyboardEnabled = true;
68
- this.layout = null;
69
- this.rootPitch = 60;
70
- this.columnOffset = 2;
71
- this.rowOffset = 4;
72
- this.activeNotes = new Map();
73
- this.reverb = null;
74
- this.masterGain = null;
75
- this.releaseTime = 0.1;
76
- this.noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
77
- this.majorScale = [0, 2, 4, 5, 7, 9, 11];
78
- }
79
-
80
  async loadToneJS() {
81
  if (window.Tone) return;
82
  const script = document.createElement('script');
@@ -98,11 +83,11 @@ function previewPlayer() {
98
 
99
  createUI() {
100
  this.container.innerHTML = `
101
- <div class="keyboard-container">
102
- <div class="controls-section">
103
- <h3>Master & Effects</h3>
104
  <div class="control-group">
105
- <label class="slider-label">Release: <span class="release-value">0.1s</span></label>
106
  <input type="range" class="control-slider release-slider" min="0" max="3" step="0.1" value="0.1">
107
  </div>
108
  <div class="controls-row">
@@ -115,25 +100,30 @@ function previewPlayer() {
115
  <input type="range" class="control-slider master-slider" min="0" max="200" value="100">
116
  </div>
117
  </div>
118
- </div>
119
- <div class="keyboard"></div>
120
- <div class="controls-section">
121
- <h3>Keyboard Mapping</h3>
 
 
122
  <div class="control-group">
123
  <label class="slider-label">Root Pitch: <span class="root-value">C4</span></label>
124
  <input type="range" class="control-slider root-slider" min="24" max="84" value="60">
125
  </div>
126
  <div class="controls-row">
127
  <div class="control-group half-width">
128
- <label class="slider-label">Column Offset: <span class="column-value">2</span> keys</label>
129
- <input type="range" class="control-slider column-slider" min="0" max="6" value="2">
130
  </div>
131
  <div class="control-group half-width">
132
  <label class="slider-label">Row Offset: <span class="row-value">4</span> degrees</label>
133
  <input type="range" class="control-slider row-slider" min="1" max="20" value="4">
134
  </div>
135
  </div>
136
- </div>
 
 
 
137
  </div>
138
  `;
139
  this.cacheElements();
@@ -160,7 +150,7 @@ function previewPlayer() {
160
  [key, this.container.querySelector(selector)]
161
  )
162
  );
163
- };
164
 
165
  setupEventListeners() {
166
  const handlers = {
@@ -203,41 +193,96 @@ function previewPlayer() {
203
  document.addEventListener('keyup', e => this.handleKeyEvent(e, false));
204
  }
205
 
206
- initializeEffects() {
207
- this.masterGain = new Tone.Gain(1).toDestination();
208
- this.reverb = new Tone.Reverb({
209
- decay: 1.5,
210
- wet: 0.5,
211
- preDelay: 0.01
212
- }).connect(this.masterGain);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
 
214
  async initializeSampler() {
215
- const availableNotes = ['C1', 'F#1', 'C2', 'F#2', 'C3', 'F#3', 'C4', 'F#4', 'C5', 'F#5'];
216
- const urls = Object.fromEntries(
217
- availableNotes
218
- .map(note => [note, document.querySelector(`a[href*="${note}.wav"]`)?.href])
219
- .filter(([, url]) => url)
220
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
- if (!Object.keys(urls).length) {
223
  this.handleSamplerError();
224
  return;
225
  }
226
 
227
- this.sampler = new Tone.Sampler({
228
- urls,
229
- onload: () => this.handleSamplerLoad(),
230
- }).connect(this.reverb);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
 
233
  handleSamplerError() {
234
  console.log('No WAV files found');
235
-
 
236
  }
237
 
238
- handleSamplerLoad() {
239
- console.log('Sampler loaded');
240
- this.container.querySelectorAll('.key').forEach(key => key.style.opacity = '1');
 
 
 
 
241
  }
242
 
243
  detectKeyboardLayout() {
@@ -364,11 +409,11 @@ function previewPlayer() {
364
  }
365
  }
366
 
367
- let container = document.getElementById('custom-player');
368
  if (!container) {
369
  container = document.createElement('div');
370
- container.id = 'custom-player';
371
  document.body.appendChild(container);
372
  }
373
- new KeyboardPlayer('custom-player');
374
- }
 
6
  this.loadToneJS().then(() => this.init());
7
  this.setupWavFileObserver();
8
 
 
 
9
  this.container.addEventListener('click', (e) => {
10
  e.stopPropagation();
11
  if (!this.keyboardEnabled) {
 
19
  }
20
  });
21
 
 
22
  this.disableKeyboard();
23
  }
24
 
25
+ initializeProperties() {
26
+ this.sampler = null;
27
+ this.keyboardEnabled = true;
28
+ this.layout = null;
29
+ this.rootPitch = 36;
30
+ this.columnOffset = -12;
31
+ this.rowOffset = 4;
32
+ this.activeNotes = new Map();
33
+ this.reverb = null;
34
+ this.masterGain = null;
35
+ this.releaseTime = 1;
36
+ this.noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
37
+ this.majorScale = [0, 2, 4, 5, 7, 9, 11];
38
+ this.isLoading = false;
39
+ }
40
+
41
  enableKeyboard() {
42
+ if (this.isLoading) return;
43
  this.keyboardEnabled = true;
44
  this.container.style.opacity = '1';
45
+ this.setLoaderState('inactive');
46
  }
47
 
48
  disableKeyboard() {
 
50
  this.container.style.opacity = '0.5';
51
  }
52
 
53
+ setLoaderState(state) {
54
+ const loader = this.container.querySelector('.loader');
55
+ loader.className = `loader ${state}`;
56
+ loader.style.display = state === 'active' ? 'flex' : 'none';
57
 
58
+ if (state === 'active') {
59
+ loader.innerHTML = 'Loading samples... <span class="loader-emoji">🎹</span>';
60
+ } else {
61
+ loader.innerHTML = 'Ready <span class="loader-emoji">✨</span>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  }
63
  }
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  async loadToneJS() {
66
  if (window.Tone) return;
67
  const script = document.createElement('script');
 
83
 
84
  createUI() {
85
  this.container.innerHTML = `
86
+ <div class="controls-section">
87
+ <details>
88
+ <summary><h3>Master & Effects</h3></summary>
89
  <div class="control-group">
90
+ <label class="slider-label">Release: <span class="release-value">1s</span></label>
91
  <input type="range" class="control-slider release-slider" min="0" max="3" step="0.1" value="0.1">
92
  </div>
93
  <div class="controls-row">
 
100
  <input type="range" class="control-slider master-slider" min="0" max="200" value="100">
101
  </div>
102
  </div>
103
+ </details>
104
+ </div>
105
+ <div class="keyboard"></div>
106
+ <div class="controls-section">
107
+ <details>
108
+ <summary><h3>Keyboard Mapping</h3></summary>
109
  <div class="control-group">
110
  <label class="slider-label">Root Pitch: <span class="root-value">C4</span></label>
111
  <input type="range" class="control-slider root-slider" min="24" max="84" value="60">
112
  </div>
113
  <div class="controls-row">
114
  <div class="control-group half-width">
115
+ <label class="slider-label">Column Offset: <span class="column-value">-12</span> keys</label>
116
+ <input type="range" class="control-slider column-slider" min="-20" max="20" value="2">
117
  </div>
118
  <div class="control-group half-width">
119
  <label class="slider-label">Row Offset: <span class="row-value">4</span> degrees</label>
120
  <input type="range" class="control-slider row-slider" min="1" max="20" value="4">
121
  </div>
122
  </div>
123
+ </details>
124
+ </div>
125
+ <div class="loader inactive">
126
+ Ready <span class="loader-emoji">🦦</span>
127
  </div>
128
  `;
129
  this.cacheElements();
 
150
  [key, this.container.querySelector(selector)]
151
  )
152
  );
153
+ }
154
 
155
  setupEventListeners() {
156
  const handlers = {
 
193
  document.addEventListener('keyup', e => this.handleKeyEvent(e, false));
194
  }
195
 
196
+ setupWavFileObserver() {
197
+ const observer = new MutationObserver((mutations) => {
198
+ const hasDownloadLinkChanges = mutations.some(mutation =>
199
+ mutation.type === 'childList' &&
200
+ mutation.target.classList.contains('download-link')
201
+ );
202
+
203
+ if (hasDownloadLinkChanges) {
204
+ this.initializeSampler();
205
+ this.enableKeyboard();
206
+ const keyboardTop = this.container.querySelector('.keyboard').getBoundingClientRect().top;
207
+ window.scrollTo(0, keyboardTop - window.innerHeight / 2, { behavior: 'smooth' });
208
+ }
209
+ });
210
+
211
+ const wavFilesContainer = document.getElementById('individual-wav-files');
212
+ if (wavFilesContainer) {
213
+ observer.observe(wavFilesContainer, {
214
+ childList: true,
215
+ subtree: true
216
+ });
217
+ }
218
  }
219
+
220
  async initializeSampler() {
221
+ this.isLoading = true;
222
+ this.setLoaderState('active');
223
+ this.container.querySelectorAll('.key').forEach(key => key.style.opacity = '0.5');
224
+
225
+ const requiredNotes = ['C1', 'F#1', 'C2', 'F#2', 'C3', 'F#3', 'C4', 'F#4', 'C5', 'F#5'];
226
+ const urls = {};
227
+ let allNotesFound = true;
228
+
229
+ for (const note of requiredNotes) {
230
+ // Look for download links with the note name in them
231
+ const downloadLink = document.querySelector(`.download-link[href*="${note}.wav"], .download-link[href*="${note.replace('#', '')}.wav"]`);
232
+ if (downloadLink?.href) {
233
+ urls[note] = downloadLink.href;
234
+ } else {
235
+ console.log(`Could not find sample for note: ${note}`);
236
+ allNotesFound = false;
237
+ break;
238
+ }
239
+ }
240
 
241
+ if (!allNotesFound) {
242
  this.handleSamplerError();
243
  return;
244
  }
245
 
246
+ if (this.sampler) {
247
+ this.sampler.dispose();
248
+ }
249
+
250
+ try {
251
+ this.sampler = new Tone.Sampler({
252
+ urls,
253
+ onload: () => this.handleSamplerLoad(),
254
+ onerror: (error) => {
255
+ console.error('Sampler loading error:', error);
256
+ this.handleSamplerError();
257
+ }
258
+ }).connect(this.reverb);
259
+ } catch (error) {
260
+ console.error('Sampler initialization error:', error);
261
+ this.handleSamplerError();
262
+ }
263
+ }
264
+
265
+ handleSamplerLoad() {
266
+ console.log('All samples loaded successfully');
267
+ this.isLoading = false;
268
+ this.container.querySelectorAll('.key').forEach(key => key.style.opacity = '1');
269
+ this.setLoaderState('inactive');
270
+ this.enableKeyboard();
271
  }
272
 
273
  handleSamplerError() {
274
  console.log('No WAV files found');
275
+ this.isLoading = false;
276
+ this.setLoaderState('inactive');
277
  }
278
 
279
+ initializeEffects() {
280
+ this.masterGain = new Tone.Gain(1).toDestination();
281
+ this.reverb = new Tone.Reverb({
282
+ decay: 1.5,
283
+ wet: 0.5,
284
+ preDelay: 0.01
285
+ }).connect(this.masterGain);
286
  }
287
 
288
  detectKeyboardLayout() {
 
409
  }
410
  }
411
 
412
+ let container = document.getElementById('keyboard-container');
413
  if (!container) {
414
  container = document.createElement('div');
415
+ container.id = 'keyboard-container';
416
  document.body.appendChild(container);
417
  }
418
+ new KeyboardPlayer('keyboard-container');
419
+ }