kimhyunwoo commited on
Commit
407a467
ยท
verified ยท
1 Parent(s): 7b4c04c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +274 -19
index.html CHANGED
@@ -1,19 +1,274 @@
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="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ํ”ผ์น˜ ๊ฐ์ง€ ๋ฐ ํ”ผ์•„๋…ธ ํ•ฉ์„ฑ</title>
7
+ <style>
8
+ body {
9
+ font-family: sans-serif;
10
+ display: flex;
11
+ flex-direction: column;
12
+ align-items: center;
13
+ justify-content: center;
14
+ height: 100vh;
15
+ margin: 0;
16
+ background-color: #f4f4f4;
17
+ }
18
+ #controls {
19
+ margin-bottom: 20px;
20
+ }
21
+ button {
22
+ padding: 10px 20px;
23
+ font-size: 16px;
24
+ margin: 0 5px;
25
+ cursor: pointer;
26
+ }
27
+ #status {
28
+ font-size: 1.1em;
29
+ color: #333;
30
+ }
31
+ #pitch-display {
32
+ margin-top: 10px;
33
+ font-size: 1.2em;
34
+ font-weight: bold;
35
+ color: #007bff;
36
+ }
37
+ </style>
38
+ </head>
39
+ <body>
40
+
41
+ <h1>๋ง ๋˜๋Š” ๋…ธ๋ž˜ ํ”ผ์น˜ ๊ฐ์ง€ ๋ฐ ํ•ฉ์„ฑ</h1>
42
+ <p>๋งˆ์ดํฌ์— ๋Œ€๊ณ  ์†Œ๋ฆฌ๋ฅผ ๋‚ด์„ธ์š”. ๊ฐ์ง€๋œ ํ”ผ์น˜๋ฅผ ํ”ผ์•„๋…ธ ์†Œ๋ฆฌ๋กœ ์žฌ์ƒํ•ฉ๋‹ˆ๋‹ค.</p>
43
+
44
+ <div id="controls">
45
+ <button id="startButton" disabled>Start</button>
46
+ <button id="stopButton" disabled>Stop</button>
47
+ </div>
48
+
49
+ <div id="status">Loading model...</div>
50
+ <div id="pitch-display"></div>
51
+
52
+ <!-- TensorFlow.js ๋ฐ ml5.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋“œ -->
53
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"></script>
54
+ <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
55
+
56
+ <script>
57
+ let audioContext;
58
+ let micStream;
59
+ let pitchDetector;
60
+ let processingNode;
61
+ let currentOscillator = null; // ํ˜„์žฌ ์žฌ์ƒ ์ค‘์ธ ์˜ค์‹ค๋ ˆ์ดํ„ฐ
62
+ let gainNode; // ๋ณผ๋ฅจ ์กฐ์ ˆ ๋ฐ ๋ถ€๋“œ๋Ÿฌ์šด ์‹œ์ž‘/์ •์ง€์šฉ
63
+
64
+ const startButton = document.getElementById('startButton');
65
+ const stopButton = document.getElementById('stopButton');
66
+ const statusDisplay = document.getElementById('status');
67
+ const pitchDisplay = document.getElementById('pitch-display');
68
+
69
+ const CREPE_MODEL_URL = 'https://cdn.jsdelivr.net/gh/ml5js/ml5-data@master/models/pitch/crepe/';
70
+ const PITCH_CONFIDENCE_THRESHOLD = 0.9; // ํ”ผ์น˜๋กœ ์ธ์ •ํ•  ์ตœ์†Œ ์‹ ๋ขฐ๋„ (CREPE ๋ชจ๋ธ์€ null๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Œ)
71
+ const MIDI_NOTE_A4 = 69; // A4๋Š” MIDI ๋…ธํŠธ 69
72
+ const FREQUENCY_A4 = 440; // A4๋Š” 440Hz
73
+
74
+ // Hz๋ฅผ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด MIDI ๋…ธํŠธ ๋ฒˆํ˜ธ๋กœ ๋ณ€ํ™˜
75
+ function hzToMidi(hz) {
76
+ if (hz === 0) return null;
77
+ return Math.round(MIDI_NOTE_A4 + 12 * Math.log2(hz / FREQUENCY_A4));
78
+ }
79
+
80
+ // MIDI ๋…ธํŠธ ๋ฒˆํ˜ธ๋ฅผ ํ•ด๋‹น ํ‘œ์ค€ ์ฃผํŒŒ์ˆ˜(Hz)๋กœ ๋ณ€ํ™˜
81
+ function midiToHz(midi) {
82
+ if (midi === null) return 0;
83
+ // MIDI ๋…ธํŠธ ๋ฒ”์œ„ ์ œํ•œ (๋„ˆ๋ฌด ๋†’๊ฑฐ๋‚˜ ๋‚ฎ์€ ์†Œ๋ฆฌ ๋ฐฉ์ง€)
84
+ midi = Math.max(24, Math.min(108, midi)); // C1 (24) to C8 (108) roughly
85
+ return FREQUENCY_A4 * Math.pow(2, (midi - MIDI_NOTE_A4) / 12);
86
+ }
87
+
88
+ // ํ”ผ์น˜ ๊ฐ์ง€ ๋ชจ๋ธ ๋กœ๋“œ
89
+ async function loadModel() {
90
+ try {
91
+ statusDisplay.textContent = 'Loading model...';
92
+ pitchDetector = await ml5.pitch(CREPE_MODEL_URL, audioContext, micStream.getAudioTracks()[0], () => {
93
+ statusDisplay.textContent = 'Model loaded. Click Start.';
94
+ startButton.disabled = false;
95
+ });
96
+ } catch (error) {
97
+ statusDisplay.textContent = `Error loading model: ${error.message}`;
98
+ console.error("Error loading model:", error);
99
+ }
100
+ }
101
+
102
+ // ์˜ค์‹ค๋ ˆ์ดํ„ฐ (์†Œ๋ฆฌ) ์‹œ์ž‘/์—…๋ฐ์ดํŠธ/์ •์ง€
103
+ function startOrUpdateOscillator(hz) {
104
+ const currentTime = audioContext.currentTime;
105
+
106
+ // ํ”ผ์น˜๊ฐ€ ์œ ํšจํ•˜๊ณ , ํ˜„์žฌ ์žฌ์ƒ ์ค‘์ธ ์˜ค์‹ค๋ ˆ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ฃผํŒŒ์ˆ˜๊ฐ€ ํฌ๊ฒŒ ๋ฐ”๋€Œ๋ฉด ์ƒˆ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์‹œ์ž‘
107
+ if (hz > 0) {
108
+ // MIDI ๋…ธํŠธ ๋ฒˆํ˜ธ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์–‘์žํ™”
109
+ const midiNote = hzToMidi(hz);
110
+ if (midiNote !== null) {
111
+ // ํ•ด๋‹น MIDI ๋…ธํŠธ์˜ ํ‘œ์ค€ ์ฃผํŒŒ์ˆ˜ ๊ณ„์‚ฐ
112
+ const targetHz = midiToHz(midiNote);
113
+
114
+ // ํ˜„์žฌ ์˜ค์‹ค๋ ˆ์ดํ„ฐ๊ฐ€ ์—†๊ฑฐ๋‚˜, ๏ฟฝ๏ฟฝ๏ฟฝํ‘œ ์ฃผํŒŒ์ˆ˜๊ฐ€ ํ˜„์žฌ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ฃผํŒŒ์ˆ˜์™€ ๋‹ค๋ฅด๋ฉด
115
+ // (์•ฝ๊ฐ„์˜ ์˜ค์ฐจ ํ—ˆ์šฉ)
116
+ if (!currentOscillator || Math.abs(currentOscillator.frequency.value - targetHz) > 0.5) {
117
+
118
+ // ์ด์ „ ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ •์ง€ (๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
119
+ if (currentOscillator) {
120
+ gainNode.gain.cancelScheduledValues(currentTime);
121
+ gainNode.gain.linearRampToValueAtTime(0, currentTime + 0.05); // ์งง์€ ๊ฐ์‡ 
122
+ currentOscillator.stop(currentTime + 0.06); // ๊ฐ์‡  ํ›„ ์ •์ง€
123
+ currentOscillator.disconnect();
124
+ }
125
+
126
+ // ์ƒˆ๋กœ์šด ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ƒ์„ฑ ๋ฐ ์‹œ์ž‘
127
+ currentOscillator = audioContext.createOscillator();
128
+ gainNode = audioContext.createGain(); // ์ƒˆ ๊ฒŒ์ธ ๋…ธ๋“œ (์„ ํƒ์‚ฌํ•ญ, ๊ธฐ์กด ๊ฒƒ ์‚ฌ์šฉ๋„ ๊ฐ€๋Šฅ)
129
+
130
+ currentOscillator.type = 'sine'; // 'sine', 'square', 'sawtooth', 'triangle'
131
+ currentOscillator.frequency.setValueAtTime(targetHz, currentTime); // ์ฃผํŒŒ์ˆ˜ ์„ค์ •
132
+
133
+ currentOscillator.connect(gainNode);
134
+ gainNode.connect(audioContext.destination);
135
+
136
+ gainNode.gain.setValueAtTime(0, currentTime);
137
+ gainNode.linearRampToValueAtTime(0.5, currentTime + 0.01); // ์งง์€ ๊ณต๊ฒฉ (์ตœ๋Œ€ ๋ณผ๋ฅจ 0.5)
138
+
139
+ currentOscillator.start(currentTime);
140
+
141
+ // ํ”ผ์น˜/๋…ธํŠธ ์ •๋ณด ํ‘œ์‹œ
142
+ const noteName = music21NoteName(midiNote); // MIDI ๋…ธํŠธ ๋ฒˆํ˜ธ๋ฅผ C4, D#5 ๋“ฑ์œผ๋กœ ๋ณ€ํ™˜
143
+ pitchDisplay.textContent = `Pitch: ${hz.toFixed(2)} Hz (${noteName})`;
144
+
145
+ // ์˜ค์‹ค๋ ˆ์ดํ„ฐ๊ฐ€ ๋๋‚˜๋ฉด ์ •๋ฆฌ
146
+ currentOscillator.onended = () => {
147
+ // console.log("Oscillator ended");
148
+ if (currentOscillator && currentOscillator.frequency.value === targetHz) {
149
+ // ์˜๋„์ ์œผ๋กœ ๋ฉˆ์ถ˜ ๊ฒฝ์šฐ currentOscillator๊ฐ€ null์ด ์•„๋‹ ์ˆ˜ ์žˆ์Œ
150
+ // ํ•˜์ง€๋งŒ ํ•ด๋‹น ์ฃผํŒŒ์ˆ˜์˜ ์˜ค์‹ค๋ ˆ์ดํ„ฐ๊ฐ€ ๋๋‚ฌ๋‹ค๋ฉด null๋กœ ์„ค์ •
151
+ currentOscillator = null;
152
+ }
153
+ };
154
+
155
+
156
+ } else {
157
+ // ์ด๋ฏธ ๊ฐ™์€ ๋…ธํŠธ๊ฐ€ ์žฌ์ƒ ์ค‘์ด๋ฉด ์ฃผํŒŒ์ˆ˜๋งŒ ๋ฏธ์„ธ ์—…๋ฐ์ดํŠธ (์„ ํƒ ์‚ฌํ•ญ, ์ƒ๋žต ๊ฐ€๋Šฅ)
158
+ currentOscillator.frequency.setValueAtTime(targetHz, currentTime);
159
+ }
160
+ } else {
161
+ // MIDI ๋…ธํŠธ ๋ณ€ํ™˜ ์‹คํŒจ (์˜ˆ: ๋„ˆ๋ฌด ๋‚ฎ๊ฑฐ๋‚˜ ๋†’์€ ์ฃผํŒŒ์ˆ˜)
162
+ stopOscillator(); // ์†Œ๋ฆฌ ๋ฉˆ์ถค
163
+ pitchDisplay.textContent = `Pitch: ${hz.toFixed(2)} Hz (๊ฐ์ง€ ์˜ค๋ฅ˜)`;
164
+ }
165
+
166
+ } else {
167
+ // ํ”ผ์น˜๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Œ (0 ๋˜๋Š” null) -> ์†Œ๋ฆฌ ๋ฉˆ์ถค
168
+ stopOscillator();
169
+ pitchDisplay.textContent = `Pitch: Detecting...`;
170
+ }
171
+ }
172
+
173
+
174
+ // MIDI ๋…ธํŠธ ๋ฒˆํ˜ธ๋ฅผ ์Œ์•…์  ๋…ธํŠธ ์ด๋ฆ„์œผ๋กœ ๋ณ€ํ™˜ (์˜ˆ: 60 -> C4)
175
+ function music21NoteName(midiNote) {
176
+ const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
177
+ if (midiNote === null || midiNote < 0 || midiNote > 127) {
178
+ return "Invalid Note";
179
+ }
180
+ const octave = Math.floor(midiNote / 12) - 1; // MIDI 0-11์€ ์˜ฅํƒ€๋ธŒ -1, MIDI 12-23์€ ์˜ฅํƒ€๋ธŒ 0 ๋“ฑ
181
+ const noteIndex = midiNote % 12;
182
+ return noteNames[noteIndex] + octave;
183
+ }
184
+
185
+
186
+ // ์˜ค์‹ค๋ ˆ์ดํ„ฐ ์ •์ง€
187
+ function stopOscillator() {
188
+ if (currentOscillator) {
189
+ const currentTime = audioContext.currentTime;
190
+ gainNode.gain.cancelScheduledValues(currentTime);
191
+ gainNode.gain.linearRampToValueAtTime(0, currentTime + 0.1); // ์งง์€ ๊ฐ์‡ 
192
+ currentOscillator.stop(currentTime + 0.11); // ๊ฐ์‡  ํ›„ ์ •์ง€
193
+ currentOscillator.disconnect(); // ์—ฐ๊ฒฐ ํ•ด์ œ
194
+ currentOscillator = null; // ์ฐธ์กฐ ์ œ๊ฑฐ
195
+ pitchDisplay.textContent = `Pitch: Listening...`;
196
+ }
197
+ }
198
+
199
+
200
+ // ๋งˆ์ดํฌ์—์„œ ํ”ผ์น˜ ๊ฐ์ง€ ์‹œ์ž‘
201
+ async function startPitchDetection() {
202
+ statusDisplay.textContent = 'Listening...';
203
+ startButton.disabled = true;
204
+ stopButton.disabled = false;
205
+
206
+ // ml5 pitch.start() ํ•จ์ˆ˜๋Š” ๋ฒ„ํผ๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์—ฌ ์ฝœ๋ฐฑ์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
207
+ pitchDetector.start((error, pitch) => {
208
+ if (error) {
209
+ console.error("Pitch detection error:", error);
210
+ statusDisplay.textContent = `Pitch detection error: ${error.message}`;
211
+ stopPitchDetection(); // ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์ •์ง€
212
+ return;
213
+ }
214
+
215
+ if (pitch) {
216
+ // pitch ๊ฐ์ฒด์— Hz ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. CREPE ๋ชจ๋ธ์€ ์‹ ๋ขฐ๋„๊ฐ€ ๋‚ฎ์œผ๋ฉด pitch=null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
217
+ // ๋ณ„๋„์˜ ์‹ ๋ขฐ๋„ ์ž„๊ณ„๊ฐ’ ๊ฒ€์‚ฌ๋ณด๋‹ค๋Š” null ์ฒดํฌ๊ฐ€ ๋” ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.
218
+ startOrUpdateOscillator(pitch.hz);
219
+ } else {
220
+ // ํ”ผ์น˜ ๊ฐ์ง€ ์‹คํŒจ (๋ถˆํ™•์‹คํ•˜๊ฑฐ๋‚˜ ์†Œ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)
221
+ stopOscillator();
222
+ pitchDisplay.textContent = `Pitch: Detecting...`;
223
+ }
224
+ });
225
+ }
226
+
227
+ // ํ”ผ์น˜ ๊ฐ์ง€ ์ •์ง€
228
+ function stopPitchDetection() {
229
+ if (pitchDetector) {
230
+ pitchDetector.stop();
231
+ }
232
+ stopOscillator(); // ์žฌ์ƒ ์ค‘์ธ ์†Œ๋ฆฌ ์ •์ง€
233
+ statusDisplay.textContent = 'Model loaded. Click Start.';
234
+ startButton.disabled = false;
235
+ stopButton.disabled = true;
236
+ pitchDisplay.textContent = '';
237
+ }
238
+
239
+ // ์ดˆ๊ธฐ ์„ค์ •
240
+ async function init() {
241
+ try {
242
+ // ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ (ํฌ๋กฌ ์ •์ฑ…์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜ ํ•„์š”)
243
+ // ์‚ฌ์šฉ์ž๊ฐ€ Start ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ ์ƒ์„ฑํ•˜๋„๋ก ๋ณ€๊ฒฝ
244
+ // audioContext = new (window.AudioContext || window.webkitAudioContext)();
245
+
246
+ // ๋งˆ์ดํฌ ์ŠคํŠธ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
247
+ statusDisplay.textContent = 'Requesting microphone access...';
248
+ micStream = await navigator.mediaDevices.getUserMedia({ audio: true });
249
+
250
+ // ์˜ค๋””์˜ค ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ (getUserMedia ์„ฑ๊ณต ํ›„)
251
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
252
+
253
+ // ml5 pitch ๋ชจ๋ธ ๋กœ๋“œ (์ŠคํŠธ๋ฆผ ์ •๋ณด ํ•„์š”)
254
+ loadModel(); // loadModel ํ•จ์ˆ˜ ์•ˆ์—์„œ pitchDetector.start()๋Š” ๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ ํ›„์— ํ˜ธ์ถœ๋จ
255
+
256
+ } catch (error) {
257
+ statusDisplay.textContent = `Error accessing microphone: ${error.message}`;
258
+ console.error("Error accessing microphone:", error);
259
+ startButton.disabled = true; // ๋งˆ์ดํฌ ์ ‘๊ทผ ์‹คํŒจ ์‹œ ์‹œ์ž‘ ๋ถˆ๊ฐ€
260
+ stopButton.disabled = true;
261
+ }
262
+ }
263
+
264
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
265
+ startButton.addEventListener('click', startPitchDetection);
266
+ stopButton.addEventListener('click', stopPitchDetection);
267
+
268
+ // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์ดˆ๊ธฐํ™” ์‹œ์ž‘
269
+ window.onload = init;
270
+
271
+ </script>
272
+
273
+ </body>
274
+ </html>