|
1 | 1 | <script setup>
|
2 |
| -definePageMeta({ layout: "empty" }); |
3 |
| -
|
4 |
| -const params = reactive({ |
5 |
| - frequency: 440, |
6 |
| - frequency_min: 20, |
7 |
| - frequency_max: 1000, |
8 |
| - frequency_steps: 20, |
9 |
| - bpm: 90, |
10 |
| -}); |
11 |
| -
|
12 |
| -let audio_context; |
13 |
| -let oscillator; |
14 |
| -let gain_node; |
15 |
| -let metronome_interval_id; |
16 |
| -
|
17 |
| -const bpm_to_ms = (bpm) => { |
18 |
| - if (bpm <= 0) { |
19 |
| - return 0; |
20 |
| - } |
21 |
| - return (60 / bpm) * 1000; |
22 |
| -}; |
23 |
| -
|
24 |
| -const start = () => { |
25 |
| - clearInterval(metronome_interval_id); |
26 |
| - metronome_interval_id = setInterval(() => { |
27 |
| - oscillator.frequency.value = params.frequency; |
28 |
| -
|
29 |
| - // fade-in and fade-out to prevent undesired click |
30 |
| - gain_node.gain.setValueAtTime(0, audio_context.currentTime); |
31 |
| - gain_node.gain.linearRampToValueAtTime(1, audio_context.currentTime + 0.05); |
32 |
| - gain_node.gain.linearRampToValueAtTime(0, audio_context.currentTime + 0.1); |
33 |
| - }, bpm_to_ms(params.bpm)); |
34 |
| -}; |
35 |
| -
|
36 |
| -const adjust_bpm = (value) => { |
37 |
| - params.bpm += value; |
38 |
| - start(); // re-start interval w/ new bpm value |
39 |
| -}; |
40 |
| -
|
41 |
| -onMounted(() => { |
42 |
| - audio_context = new (window.AudioContext || window.webkitAudioContext)(); |
43 |
| - oscillator = audio_context.createOscillator(); |
44 |
| - gain_node = audio_context.createGain(); |
45 |
| - gain_node.gain.value = 0; |
46 |
| - gain_node.connect(audio_context.destination); |
47 |
| - oscillator.connect(gain_node); |
48 |
| - oscillator.type = "sine"; |
49 |
| - oscillator.frequency.value = params.frequency; |
50 |
| - oscillator.start(); |
51 |
| -
|
52 |
| - start(); |
53 |
| -}); |
| 2 | +definePageMeta({ layout: "light" }); |
54 | 3 | </script>
|
55 | 4 |
|
56 | 5 | <template>
|
57 |
| - <main class="flex h-screen justify-center bg-white"> |
58 |
| - <div class="space-y-2 p-2"> |
59 |
| - <div class="flex justify-center border-2 border-gray-400 p-2"> |
60 |
| - <div class="flex space-x-2"> |
61 |
| - <div class="flex items-center"> |
62 |
| - <button |
63 |
| - class="button rounded-full bg-gradient-to-r from-red-500 to-blue-500 px-2 text-white" |
64 |
| - @click="adjust_bpm(-10)" |
65 |
| - > |
66 |
| - - |
67 |
| - </button> |
68 |
| - </div> |
69 |
| - <div class="text-[72px] font-bold"> |
70 |
| - {{ params.bpm.toString().padStart(3, " ") |
71 |
| - }}<span class="text-[16px]">bpm</span> |
72 |
| - </div> |
73 |
| - |
74 |
| - <div class="flex items-center"> |
75 |
| - <button |
76 |
| - class="button rounded-full bg-gradient-to-r from-red-500 to-blue-500 px-2 text-white" |
77 |
| - @click="adjust_bpm(10)" |
78 |
| - > |
79 |
| - + |
80 |
| - </button> |
81 |
| - </div> |
82 |
| - </div> |
83 |
| - </div> |
84 |
| - |
85 |
| - <!-- Frequency --> |
86 |
| - <div class="border-2 border-gray-400 p-2"> |
87 |
| - <div class="flex"> |
88 |
| - <div class="flex-1 font-bold"> |
89 |
| - Frequency |
90 |
| - <span class="bg-yellow-200 px-2"> {{ params.frequency }} </span> |
91 |
| - </div> |
92 |
| - </div> |
93 |
| - <div class="flex space-x-2"> |
94 |
| - <div>{{ params.frequency_min }}Hz</div> |
95 |
| - <input |
96 |
| - type="range" |
97 |
| - v-model="params.frequency" |
98 |
| - :min="params.frequency_min" |
99 |
| - :max="params.frequency_max" |
100 |
| - :step="params.frequency_steps" |
101 |
| - class="accent-red-500" |
102 |
| - /> |
103 |
| - <div>{{ params.frequency_max }}Hz</div> |
104 |
| - </div> |
105 |
| - </div> |
106 |
| - </div> |
| 6 | + <main class="flex h-[768px] justify-center border-2 border-gray-500"> |
| 7 | + <iframe src="https://simple-tempo.com" width="100%" height="100%"></iframe> |
107 | 8 | </main>
|
108 | 9 | </template>
|
0 commit comments