Hitekno commited on
Commit
7f5bdfe
·
verified ·
1 Parent(s): 00054f5

== Updated 08/08/2025 == Enable the playback/editing and other functions for generated chord charts located in "My Library". Make the BPM number directly enterable after double-clicking on it. If the user enters a title in the progression description, update the display of the [Progression Title] field. Add (and enable the function after implementation) a [Play] button in a transport bar for the chord chart in the [Generated Progression] field. Strictly respect the number of bars corresponding to the user's request or recording. Play the corresponding chord (with a piano sound) when the user clicks on it. In the [My Library] menu, display the metronome set to the indicated values of the loaded progression. Also display a drop-down submenu to choose the output instrument from a list of General Midi instruments. In the comments in the [Analysis Notes] panel, take into account the actual chords entered by the user where applicable. When playing the generated progression, ALWAYS use chord inversions like a pianist! When referring to user-entered chords in the description, the term "Time Signature" refers to the unit of time per measure! For example, "Duration of each fret in the chord grid: 4 beats" is preferably written as "Time Signature: 4/4". In the description, chords are separated by commas, while chord grid squares are separated by slashes. In the [Tonal Analysis] panel, chords should sound when the corresponding bubbles are clicked! Also display the [Instrument Selection] submenu in the [Generated Progression] panel. When the [Edit/Lock] menu of the [Generated Progression] is enabled for chord modification, also make the time signature (4 beats, etc.) editable. Make the time signature adjustable, and modify the duration of each chord accordingly to adapt to the new time signature entered by the user. Start AND synchronize the metronome with the playback trigger of the loaded progression grid. Ask the AI what the chord inversion is, and apply this technique to the chord generation of the current progression! Add and enable all necessary MIDI functions for connecting and playing progressions via any connected or available MIDI interface, and ensure that the choice of MIDI interfaces is available at all times. - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +692 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Kordflow
3
- emoji: 🏢
4
- colorFrom: gray
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: kordflow
3
+ emoji: 🐳
4
+ colorFrom: green
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,692 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>KordFlow - Music Chord Progression Generator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .progress-bar {
11
+ height: 6px;
12
+ transition: width 0.1s linear;
13
+ }
14
+ .chord-cell {
15
+ min-height: 60px;
16
+ }
17
+ .metronome-input {
18
+ width: 50px;
19
+ text-align: center;
20
+ }
21
+ .menu-slide {
22
+ transition: transform 0.5s ease-in-out;
23
+ }
24
+ .beat-emphasis {
25
+ animation: pulse 0.3s;
26
+ }
27
+ @keyframes pulse {
28
+ 0% { transform: scale(1); }
29
+ 50% { transform: scale(1.2); }
30
+ 100% { transform: scale(1); }
31
+ }
32
+ </style>
33
+ </head>
34
+ <body class="bg-gray-900 text-white font-sans">
35
+ <div class="container mx-auto px-4 py-6 max-w-6xl">
36
+ <!-- Header -->
37
+ <header class="flex justify-between items-center mb-8">
38
+ <h1 class="text-3xl font-bold text-purple-400">KordFlow</h1>
39
+ <div class="flex items-center space-x-4">
40
+ <button id="language-btn" class="p-2 rounded-full bg-gray-800 hover:bg-gray-700 transition">
41
+ <i class="fas fa-globe text-xl"></i>
42
+ </button>
43
+ <div id="language-menu" class="hidden absolute mt-10 right-4 bg-gray-800 rounded shadow-lg z-10">
44
+ <button class="block w-full text-left px-4 py-2 hover:bg-gray-700">English</button>
45
+ <button class="block w-full text-left px-4 py-2 hover:bg-gray-700">Français</button>
46
+ <button class="block w-full text-left px-4 py-2 hover:bg-gray-700">Español</button>
47
+ </div>
48
+ <button class="px-4 py-2 bg-purple-600 rounded hover:bg-purple-500 transition">Help</button>
49
+ </div>
50
+ </header>
51
+
52
+ <!-- Main Content -->
53
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
54
+ <!-- Left Panel - Controls -->
55
+ <div class="lg:col-span-1 bg-gray-800 rounded-lg p-6">
56
+ <div class="flex justify-between mb-6">
57
+ <button id="load-btn" class="px-4 py-2 bg-blue-600 rounded hover:bg-blue-500 transition w-1/2 mr-2">
58
+ <i class="fas fa-folder-open mr-2"></i>Load
59
+ </button>
60
+ <button id="save-btn" class="px-4 py-2 bg-green-600 rounded hover:bg-green-500 transition w-1/2 ml-2">
61
+ <i class="fas fa-save mr-2"></i>Save
62
+ </button>
63
+ </div>
64
+
65
+ <div class="mb-6">
66
+ <h2 class="text-xl font-semibold mb-3">Audio Input</h2>
67
+ <div class="flex space-x-2 mb-3">
68
+ <button id="record-btn" class="flex-1 px-4 py-2 bg-red-600 rounded hover:bg-red-500 transition">
69
+ <i class="fas fa-microphone mr-2"></i>Record
70
+ </button>
71
+ <button id="import-btn" class="flex-1 px-4 py-2 bg-yellow-600 rounded hover:bg-yellow-500 transition">
72
+ <i class="fas fa-file-audio mr-2"></i>Import
73
+ </button>
74
+ </div>
75
+ <div class="progress-container bg-gray-700 rounded-full h-2 mb-2">
76
+ <div id="progress-bar" class="progress-bar bg-purple-500 rounded-full w-0"></div>
77
+ </div>
78
+ <div class="flex justify-between text-sm text-gray-400">
79
+ <span id="time-elapsed">0:00</span>
80
+ <span id="time-total">0:00</span>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="mb-6">
85
+ <h2 class="text-xl font-semibold mb-3">Metronome</h2>
86
+ <div class="flex items-center mb-3">
87
+ <button id="metronome-toggle" class="px-4 py-2 bg-gray-700 rounded-l hover:bg-gray-600 transition">
88
+ <i class="fas fa-play"></i>
89
+ </button>
90
+ <div class="flex-1 bg-gray-700 px-4 py-2 text-center">
91
+ <span id="bpm-display" class="font-mono">120</span>
92
+ <input id="bpm-input" type="number" min="40" max="300" value="120"
93
+ class="hidden metronome-input bg-gray-800 text-white border border-gray-600 rounded">
94
+ <span> BPM</span>
95
+ </div>
96
+ <button id="bpm-decrease" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 transition">
97
+ <i class="fas fa-minus"></i>
98
+ </button>
99
+ <button id="bpm-increase" class="px-4 py-2 bg-gray-700 rounded-r hover:bg-gray-600 transition">
100
+ <i class="fas fa-plus"></i>
101
+ </button>
102
+ </div>
103
+ <div class="flex items-center">
104
+ <span class="mr-2">Volume:</span>
105
+ <input id="metronome-volume" type="range" min="0" max="100" value="50" class="flex-1">
106
+ </div>
107
+ <div class="mt-2 flex justify-center space-x-2">
108
+ <div id="beat-1" class="w-4 h-4 bg-purple-500 rounded-full"></div>
109
+ <div id="beat-2" class="w-4 h-4 bg-gray-600 rounded-full"></div>
110
+ <div id="beat-3" class="w-4 h-4 bg-gray-600 rounded-full"></div>
111
+ <div id="beat-4" class="w-4 h-4 bg-gray-600 rounded-full"></div>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="mb-6">
116
+ <h2 class="text-xl font-semibold mb-3">MIDI Settings</h2>
117
+ <select id="midi-input" class="w-full bg-gray-700 rounded px-4 py-2 mb-2">
118
+ <option value="">No MIDI Input</option>
119
+ </select>
120
+ <select id="midi-output" class="w-full bg-gray-700 rounded px-4 py-2">
121
+ <option value="">No MIDI Output</option>
122
+ </select>
123
+ </div>
124
+ <div>
125
+ <h2 class="text-xl font-semibold mb-3">Practice Mode</h2>
126
+ <button id="practice-mode-btn" class="w-full px-4 py-2 bg-gray-700 rounded hover:bg-gray-600 transition">
127
+ <i class="fas fa-guitar mr-2"></i>Enable Practice Mode
128
+ </button>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Center Panel - Chord Grid -->
133
+ <div class="lg:col-span-2 bg-gray-800 rounded-lg p-6">
134
+ <div class="flex justify-between items-center mb-6">
135
+ <div>
136
+ <h2 class="text-xl font-semibold">Chord Progression</h2>
137
+ <input id="progression-title" type="text" placeholder="Progression Title"
138
+ class="bg-transparent border-b border-gray-600 focus:border-purple-500 outline-none">
139
+ </div>
140
+ <div class="flex space-x-2">
141
+ <button id="analyze-btn" class="px-4 py-2 bg-blue-600 rounded hover:bg-blue-500 transition">
142
+ <i class="fas fa-search mr-2"></i>Analyze
143
+ </button>
144
+ <div class="flex items-center bg-gray-700 rounded">
145
+ <button id="play-btn" class="px-4 py-2 bg-green-600 rounded-l hover:bg-green-500 transition">
146
+ <i class="fas fa-play mr-2"></i>Play
147
+ </button>
148
+ <button id="stop-btn" class="px-4 py-2 hover:bg-gray-600 transition">
149
+ <i class="fas fa-stop"></i>
150
+ </button>
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ <div class="grid grid-cols-4 gap-2 mb-4">
156
+ <div class="text-center font-semibold">Measure 1</div>
157
+ <div class="text-center font-semibold">Measure 2</div>
158
+ <div class="text-center font-semibold">Measure 3</div>
159
+ <div class="text-center font-semibold">Measure 4</div>
160
+ </div>
161
+
162
+ <div class="grid grid-cols-4 gap-2">
163
+ <div class="chord-cell bg-gray-700 rounded p-3 flex items-center justify-center cursor-pointer hover:bg-gray-600 transition">
164
+ <span class="text-xl">C</span>
165
+ </div>
166
+ <div class="chord-cell bg-gray-700 rounded p-3 flex items-center justify-center cursor-pointer hover:bg-gray-600 transition">
167
+ <span class="text-xl">G</span>
168
+ </div>
169
+ <div class="chord-cell bg-gray-700 rounded p-3 flex items-center justify-center cursor-pointer hover:bg-gray-600 transition">
170
+ <span class="text-xl">Am</span>
171
+ </div>
172
+ <div class="chord-cell bg-gray-700 rounded p-3 flex items-center justify-center cursor-pointer hover:bg-gray-600 transition">
173
+ <span class="text-xl">F</span>
174
+ </div>
175
+ </div>
176
+
177
+ <div class="mt-6">
178
+ <div class="flex items-center mb-2">
179
+ <h3 class="text-lg font-semibold mr-4">Detected Key: <span class="text-purple-400">C Major</span></h3>
180
+ <select id="instrument-select" class="bg-gray-700 rounded px-3 py-1">
181
+ <option value="0">Acoustic Piano</option>
182
+ <option value="24">Acoustic Guitar</option>
183
+ <option value="33">Electric Bass</option>
184
+ <option value="48">String Ensemble</option>
185
+ </select>
186
+ </div>
187
+ <div class="flex items-center">
188
+ <h3 class="text-lg font-semibold mr-4">Detected Tempo: <span class="text-purple-400">120 BPM</span></h3>
189
+ <div class="flex items-center">
190
+ <span class="mr-2">Time Signature:</span>
191
+ <input id="time-signature" type="text" value="4/4"
192
+ class="w-16 bg-gray-700 rounded px-2 py-1 text-center">
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+
199
+ <!-- Menu Slides -->
200
+ <div class="mt-8 bg-gray-800 rounded-lg overflow-hidden">
201
+ <div id="menu-slides-container" class="relative h-64 overflow-hidden">
202
+ <div id="menu-slide-1" class="menu-slide absolute inset-0 p-6">
203
+ <h2 class="text-2xl font-bold mb-4 text-purple-400">Sing Your Song</h2>
204
+ <p class="mb-4">With KordFlow, you can easily start creating your music. Simply sing your favorite song into the microphone or import an audio file. The app analyzes your voice or audio track to identify the chords that match your melody.</p>
205
+ <p>This intuitive feature allows you to quickly and efficiently transform your musical ideas into a chord progression. Whether you're a beginner or an experienced musician, this easy step opens the door to limitless creativity. Unleash your musical passion and start singing!</p>
206
+ <div class="absolute bottom-4 right-4 text-gray-400">1 / 4</div>
207
+ </div>
208
+ <div id="menu-slide-2" class="menu-slide absolute inset-0 p-6 transform translate-x-full">
209
+ <h2 class="text-2xl font-bold mb-4 text-purple-400">Audio Track Analysis</h2>
210
+ <p class="mb-4">Once you've recorded your voice or imported your audio file, KordFlow uses advanced analysis technology to detect the key and tempo of your song. Using this analysis, the app automatically generates a chord progression that perfectly matches your performance.</p>
211
+ <p>This allows you to focus on creating without worrying about technical details. Enjoy a user-friendly interface that guides you through every step of the process. With KordFlow, transform your inspiration into a complete musical composition in no time!</p>
212
+ <div class="absolute bottom-4 right-4 text-gray-400">2 / 4</div>
213
+ </div>
214
+ <div id="menu-slide-3" class="menu-slide absolute inset-0 p-6 transform translate-x-full">
215
+ <h2 class="text-2xl font-bold mb-4 text-purple-400">Synchronized Chord Progression</h2>
216
+ <p class="mb-4">Once KordFlow has generated your chord progression, you can listen to it in sync with your performance. This feature allows you to play your instrument while following the melody you sang.</p>
217
+ <p>Synchronized playback helps you feel the rhythm and adjust your performance in real time. You can also modify the chords if you want to explore different options. This flexibility gives you complete control over your musical composition.</p>
218
+ <div class="absolute bottom-4 right-4 text-gray-400">3 / 4</div>
219
+ </div>
220
+ <div id="menu-slide-4" class="menu-slide absolute inset-0 p-6 transform translate-x-full">
221
+ <h2 class="text-2xl font-bold mb-4 text-purple-400">Saving and Sharing</h2>
222
+ <p class="mb-4">Once you're satisfied with your chord progression, KordFlow allows you to save your work in WAV format. You can also save your projects to return to later or share your creations with other musicians.</p>
223
+ <p>The saving feature simplifies the process of managing your compositions, allowing you to keep track of your musical development. Whether you're creating an album or simply experimenting with new ideas, KordFlow gives you the tools you need.</p>
224
+ <div class="absolute bottom-4 right-4 text-gray-400">4 / 4</div>
225
+ </div>
226
+ </div>
227
+ <div class="flex justify-center p-4 bg-gray-700">
228
+ <button id="prev-slide" class="px-4 py-2 mx-1 rounded hover:bg-gray-600 transition">
229
+ <i class="fas fa-chevron-left"></i>
230
+ </button>
231
+ <button class="slide-dot px-2 py-1 mx-1 rounded-full bg-purple-500 w-3 h-3" data-slide="0"></button>
232
+ <button class="slide-dot px-2 py-1 mx-1 rounded-full bg-gray-500 w-3 h-3" data-slide="1"></button>
233
+ <button class="slide-dot px-2 py-1 mx-1 rounded-full bg-gray-500 w-3 h-3" data-slide="2"></button>
234
+ <button class="slide-dot px-2 py-1 mx-1 rounded-full bg-gray-500 w-3 h-3" data-slide="3"></button>
235
+ <button id="next-slide" class="px-4 py-2 mx-1 rounded hover:bg-gray-600 transition">
236
+ <i class="fas fa-chevron-right"></i>
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ <script>
243
+ // Language selector
244
+ const languageBtn = document.getElementById('language-btn');
245
+ const languageMenu = document.getElementById('language-menu');
246
+
247
+ languageBtn.addEventListener('click', () => {
248
+ languageMenu.classList.toggle('hidden');
249
+ });
250
+
251
+ // Close language menu when clicking elsewhere
252
+ document.addEventListener('click', (e) => {
253
+ if (!languageBtn.contains(e.target) && !languageMenu.contains(e.target)) {
254
+ languageMenu.classList.add('hidden');
255
+ }
256
+ });
257
+
258
+ // Menu slides navigation
259
+ const slides = document.querySelectorAll('.menu-slide');
260
+ const dots = document.querySelectorAll('.slide-dot');
261
+ const prevBtn = document.getElementById('prev-slide');
262
+ const nextBtn = document.getElementById('next-slide');
263
+ let currentSlide = 0;
264
+
265
+ function showSlide(index) {
266
+ slides.forEach((slide, i) => {
267
+ slide.style.transform = `translateX(${100 * (i - index)}%)`;
268
+ });
269
+
270
+ dots.forEach((dot, i) => {
271
+ dot.classList.toggle('bg-purple-500', i === index);
272
+ dot.classList.toggle('bg-gray-500', i !== index);
273
+ });
274
+
275
+ currentSlide = index;
276
+ }
277
+
278
+ dots.forEach(dot => {
279
+ dot.addEventListener('click', () => {
280
+ showSlide(parseInt(dot.dataset.slide));
281
+ });
282
+ });
283
+
284
+ prevBtn.addEventListener('click', () => {
285
+ showSlide((currentSlide - 1 + slides.length) % slides.length);
286
+ });
287
+
288
+ nextBtn.addEventListener('click', () => {
289
+ showSlide((currentSlide + 1) % slides.length);
290
+ });
291
+
292
+ // Initialize first slide
293
+ showSlide(0);
294
+
295
+ // Metronome functionality
296
+ const metronomeToggle = document.getElementById('metronome-toggle');
297
+ const bpmDisplay = document.getElementById('bpm-display');
298
+ const bpmInput = document.getElementById('bpm-input');
299
+ const bpmIncrease = document.getElementById('bpm-increase');
300
+ const bpmDecrease = document.getElementById('bpm-decrease');
301
+ const metronomeVolume = document.getElementById('metronome-volume');
302
+ const beatElements = [
303
+ document.getElementById('beat-1'),
304
+ document.getElementById('beat-2'),
305
+ document.getElementById('beat-3'),
306
+ document.getElementById('beat-4')
307
+ ];
308
+
309
+ let bpm = 120;
310
+ let metronomeInterval;
311
+ let currentBeat = 0;
312
+ let isMetronomeRunning = false;
313
+ let audioContext;
314
+ let gainNode;
315
+ let oscillator;
316
+
317
+ // Initialize audio context on user interaction
318
+ function initAudioContext() {
319
+ if (!audioContext) {
320
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
321
+ gainNode = audioContext.createGain();
322
+ gainNode.connect(audioContext.destination);
323
+ gainNode.gain.value = metronomeVolume.value / 100;
324
+ }
325
+ }
326
+
327
+ function playMetronomeBeat(beatIndex) {
328
+ if (!audioContext) initAudioContext();
329
+
330
+ // Stop any existing sound
331
+ if (oscillator) {
332
+ oscillator.stop();
333
+ }
334
+
335
+ oscillator = audioContext.createOscillator();
336
+ oscillator.connect(gainNode);
337
+
338
+ // Different frequencies for different beats
339
+ if (beatIndex === 0) {
340
+ oscillator.frequency.value = 880; // First beat (higher pitch)
341
+ } else {
342
+ oscillator.frequency.value = 440; // Other beats
343
+ }
344
+
345
+ oscillator.start();
346
+ oscillator.stop(audioContext.currentTime + 0.05);
347
+
348
+ // Visual feedback
349
+ beatElements.forEach((el, i) => {
350
+ if (i === beatIndex) {
351
+ el.classList.add('beat-emphasis');
352
+ setTimeout(() => el.classList.remove('beat-emphasis'), 300);
353
+ }
354
+ });
355
+ }
356
+
357
+ function startMetronome() {
358
+ if (metronomeInterval) clearInterval(metronomeInterval);
359
+
360
+ const interval = 60000 / bpm; // Convert BPM to milliseconds
361
+ metronomeInterval = setInterval(() => {
362
+ playMetronomeBeat(currentBeat);
363
+ currentBeat = (currentBeat + 1) % 4;
364
+ }, interval);
365
+
366
+ isMetronomeRunning = true;
367
+ metronomeToggle.innerHTML = '<i class="fas fa-stop"></i>';
368
+ }
369
+
370
+ function stopMetronome() {
371
+ clearInterval(metronomeInterval);
372
+ metronomeInterval = null;
373
+ isMetronomeRunning = false;
374
+ metronomeToggle.innerHTML = '<i class="fas fa-play"></i>';
375
+ }
376
+
377
+ metronomeToggle.addEventListener('click', () => {
378
+ initAudioContext();
379
+ if (isMetronomeRunning) {
380
+ stopMetronome();
381
+ } else {
382
+ startMetronome();
383
+ }
384
+ });
385
+
386
+ bpmDisplay.addEventListener('dblclick', () => {
387
+ bpmDisplay.classList.add('hidden');
388
+ bpmInput.classList.remove('hidden');
389
+ bpmInput.focus();
390
+ bpmInput.select();
391
+ });
392
+
393
+ bpmInput.addEventListener('blur', () => {
394
+ updateBpm(parseInt(bpmInput.value));
395
+ bpmInput.classList.add('hidden');
396
+ bpmDisplay.classList.remove('hidden');
397
+ });
398
+
399
+ bpmInput.addEventListener('keypress', (e) => {
400
+ if (e.key === 'Enter') {
401
+ updateBpm(parseInt(bpmInput.value));
402
+ bpmInput.classList.add('hidden');
403
+ bpmDisplay.classList.remove('hidden');
404
+ }
405
+ });
406
+
407
+ bpmIncrease.addEventListener('click', () => {
408
+ updateBpm(bpm + 1);
409
+ });
410
+
411
+ bpmDecrease.addEventListener('click', () => {
412
+ updateBpm(bpm - 1);
413
+ });
414
+
415
+ function updateBpm(newBpm) {
416
+ if (newBpm < 40) newBpm = 40;
417
+ if (newBpm > 300) newBpm = 300;
418
+
419
+ bpm = newBpm;
420
+ bpmDisplay.textContent = bpm;
421
+ bpmInput.value = bpm;
422
+
423
+ if (isMetronomeRunning) {
424
+ stopMetronome();
425
+ startMetronome();
426
+ }
427
+ }
428
+
429
+ metronomeVolume.addEventListener('input', () => {
430
+ if (gainNode) {
431
+ gainNode.gain.value = metronomeVolume.value / 100;
432
+ }
433
+ });
434
+
435
+ // Chord cell editing
436
+ const chordCells = document.querySelectorAll('.chord-cell');
437
+ const commonChords = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B',
438
+ 'Cm', 'C#m', 'Dm', 'D#m', 'Em', 'Fm', 'F#m', 'Gm', 'G#m', 'Am', 'A#m', 'Bm'];
439
+
440
+ chordCells.forEach(cell => {
441
+ cell.addEventListener('click', () => {
442
+ const currentChord = cell.textContent.trim();
443
+ const input = document.createElement('input');
444
+ input.type = 'text';
445
+ input.value = currentChord;
446
+ input.className = 'w-16 text-center bg-gray-800 text-white border border-purple-500 rounded px-2 py-1';
447
+ input.list = 'chord-suggestions';
448
+
449
+ // Create datalist for suggestions
450
+ const datalist = document.createElement('datalist');
451
+ datalist.id = 'chord-suggestions';
452
+ commonChords.forEach(chord => {
453
+ const option = document.createElement('option');
454
+ option.value = chord;
455
+ datalist.appendChild(option);
456
+ });
457
+
458
+ cell.innerHTML = '';
459
+ cell.appendChild(input);
460
+ cell.appendChild(datalist);
461
+ input.focus();
462
+
463
+ input.addEventListener('blur', () => {
464
+ const newChord = input.value.trim();
465
+ cell.innerHTML = `<span class="text-xl">${newChord || currentChord}</span>`;
466
+ });
467
+
468
+ input.addEventListener('keypress', (e) => {
469
+ if (e.key === 'Enter') {
470
+ const newChord = input.value.trim();
471
+ cell.innerHTML = `<span class="text-xl">${newChord || currentChord}</span>`;
472
+ }
473
+ });
474
+ });
475
+ });
476
+
477
+ // MIDI setup
478
+ let midiAccess;
479
+ let midiInput;
480
+ let midiOutput;
481
+
482
+ async function setupMIDI() {
483
+ try {
484
+ midiAccess = await navigator.requestMIDIAccess();
485
+ updateMIDIDevices();
486
+
487
+ midiAccess.onstatechange = updateMIDIDevices;
488
+ } catch (e) {
489
+ console.error("MIDI not supported", e);
490
+ }
491
+ }
492
+
493
+ function updateMIDIDevices() {
494
+ const inputSelect = document.getElementById('midi-input');
495
+ const outputSelect = document.getElementById('midi-output');
496
+
497
+ // Clear existing options
498
+ inputSelect.innerHTML = '<option value="">No MIDI Input</option>';
499
+ outputSelect.innerHTML = '<option value="">No MIDI Output</option>';
500
+
501
+ // Add available inputs
502
+ midiAccess.inputs.forEach(input => {
503
+ const option = document.createElement('option');
504
+ option.value = input.id;
505
+ option.textContent = input.name;
506
+ inputSelect.appendChild(option);
507
+ });
508
+
509
+ // Add available outputs
510
+ midiAccess.outputs.forEach(output => {
511
+ const option = document.createElement('option');
512
+ option.value = output.id;
513
+ option.textContent = output.name;
514
+ outputSelect.appendChild(option);
515
+ });
516
+ }
517
+
518
+ document.getElementById('midi-input').addEventListener('change', (e) => {
519
+ midiInput = midiAccess.inputs.get(e.target.value);
520
+ if (midiInput) {
521
+ midiInput.onmidimessage = handleMIDIMessage;
522
+ }
523
+ });
524
+
525
+ document.getElementById('midi-output').addEventListener('change', (e) => {
526
+ midiOutput = midiAccess.outputs.get(e.target.value);
527
+ });
528
+
529
+ function handleMIDIMessage(message) {
530
+ // Handle incoming MIDI messages
531
+ console.log('MIDI message:', message.data);
532
+ }
533
+
534
+ // Initialize MIDI
535
+ if (navigator.requestMIDIAccess) {
536
+ setupMIDI();
537
+ }
538
+
539
+ // Progression title handling
540
+ document.getElementById('progression-title').addEventListener('input', (e) => {
541
+ // Save title to progression data
542
+ console.log('Progression title updated:', e.target.value);
543
+ });
544
+
545
+ // Time signature handling
546
+ document.getElementById('time-signature').addEventListener('change', (e) => {
547
+ const [beats, noteValue] = e.target.value.split('/').map(Number);
548
+ if (beats && noteValue) {
549
+ // Update progression timing
550
+ console.log('Time signature changed to:', beats, noteValue);
551
+ } else {
552
+ e.target.value = '4/4'; // Reset if invalid
553
+ }
554
+ });
555
+
556
+ // Instrument selection
557
+ document.getElementById('instrument-select').addEventListener('change', (e) => {
558
+ const program = parseInt(e.target.value);
559
+ if (midiOutput) {
560
+ // Send program change to MIDI
561
+ midiOutput.send([0xC0, program]);
562
+ }
563
+ console.log('Instrument changed to:', program);
564
+ });
565
+
566
+ // Chord playback with inversions
567
+ function playChordWithInversion(chord, duration = 1) {
568
+ if (!audioContext) initAudioContext();
569
+
570
+ // This is a simplified version - in practice you'd want to:
571
+ // 1. Parse the chord (e.g. "C" or "Am7")
572
+ // 2. Determine the notes in the chord
573
+ // 3. Apply inversion (rotate the notes)
574
+ // 4. Play the notes
575
+
576
+ // For now just play a simple triad
577
+ const notes = getChordNotes(chord);
578
+ playNotes(notes, duration);
579
+ }
580
+
581
+ function getChordNotes(chord) {
582
+ // Simplified chord mapping - in practice use a proper chord library
583
+ const rootNotes = {
584
+ 'C': 60, 'C#': 61, 'D': 62, 'D#': 63,
585
+ 'E': 64, 'F': 65, 'F#': 66, 'G': 67,
586
+ 'G#': 68, 'A': 69, 'A#': 70, 'B': 71
587
+ };
588
+
589
+ const root = chord.replace(/m|M|7|sus|dim|aug/g, '');
590
+ const third = chord.includes('m') ? 3 : 4;
591
+
592
+ return [
593
+ rootNotes[root] || 60,
594
+ (rootNotes[root] || 60) + third,
595
+ (rootNotes[root] || 60) + 7
596
+ ];
597
+ }
598
+
599
+ function playNotes(notes, duration) {
600
+ const now = audioContext.currentTime;
601
+
602
+ notes.forEach((note, i) => {
603
+ const osc = audioContext.createOscillator();
604
+ const gain = audioContext.createGain();
605
+
606
+ osc.connect(gain);
607
+ gain.connect(audioContext.destination);
608
+
609
+ osc.frequency.value = 440 * Math.pow(2, (note - 69) / 12);
610
+ gain.gain.value = 0.3;
611
+
612
+ osc.start(now);
613
+ osc.stop(now + duration);
614
+
615
+ // Add slight envelope
616
+ gain.gain.setValueAtTime(0.3, now);
617
+ gain.gain.exponentialRampToValueAtTime(0.001, now + duration);
618
+ });
619
+ }
620
+
621
+ // Update chord cell click to play chord
622
+ chordCells.forEach(cell => {
623
+ cell.addEventListener('click', (e) => {
624
+ if (e.target.tagName !== 'INPUT') { // Don't play when editing
625
+ const chord = cell.textContent.trim();
626
+ if (chord) playChordWithInversion(chord, 0.5);
627
+ }
628
+ });
629
+ });
630
+
631
+ // Simulate recording progress
632
+ const recordBtn = document.getElementById('record-btn');
633
+ const progressBar = document.getElementById('progress-bar');
634
+ const timeElapsed = document.getElementById('time-elapsed');
635
+ const timeTotal = document.getElementById('time-total');
636
+
637
+ recordBtn.addEventListener('click', () => {
638
+ if (recordBtn.textContent.includes('Record')) {
639
+ recordBtn.innerHTML = '<i class="fas fa-stop mr-2"></i>Stop';
640
+ recordBtn.classList.remove('bg-red-600');
641
+ recordBtn.classList.add('bg-red-700');
642
+
643
+ // Simulate recording progress
644
+ let progress = 0;
645
+ const duration = 30; // 30 seconds
646
+ timeTotal.textContent = '0:30';
647
+
648
+ const interval = setInterval(() => {
649
+ progress += 1;
650
+ const percentage = (progress / duration) * 100;
651
+ progressBar.style.width = `${percentage}%`;
652
+
653
+ // Update time display
654
+ const seconds = progress % 60;
655
+ const minutes = Math.floor(progress / 60);
656
+ timeElapsed.textContent = `${minutes}:${seconds < 10 ? '0' + seconds : seconds}`;
657
+
658
+ if (progress >= duration) {
659
+ clearInterval(interval);
660
+ recordBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i>Record';
661
+ recordBtn.classList.add('bg-red-600');
662
+ recordBtn.classList.remove('bg-red-700');
663
+ }
664
+ }, 1000);
665
+
666
+ // Stop recording if button clicked again
667
+ recordBtn.onclick = function() {
668
+ clearInterval(interval);
669
+ recordBtn.innerHTML = '<i class="fas fa-microphone mr-2"></i>Record';
670
+ recordBtn.classList.add('bg-red-600');
671
+ recordBtn.classList.remove('bg-red-700');
672
+ recordBtn.onclick = arguments.callee;
673
+ };
674
+ }
675
+ });
676
+
677
+ // Practice mode toggle
678
+ const practiceModeBtn = document.getElementById('practice-mode-btn');
679
+ practiceModeBtn.addEventListener('click', () => {
680
+ if (practiceModeBtn.textContent.includes('Enable')) {
681
+ practiceModeBtn.innerHTML = '<i class="fas fa-guitar mr-2"></i>Disable Practice Mode';
682
+ practiceModeBtn.classList.remove('bg-gray-700');
683
+ practiceModeBtn.classList.add('bg-purple-600');
684
+ } else {
685
+ practiceModeBtn.innerHTML = '<i class="fas fa-guitar mr-2"></i>Enable Practice Mode';
686
+ practiceModeBtn.classList.add('bg-gray-700');
687
+ practiceModeBtn.classList.remove('bg-purple-600');
688
+ }
689
+ });
690
+ </script>
691
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Hitekno/kordflow" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
692
+ </html>