Skip to content

Commit 1bf656d

Browse files
authored
note player: supporting Concert Pitch, TET, Steps, Frequency & Note Names (#8)
- resolves #3 - new functions: - setTemperament (Only TET-12 for now) - setConcertPitch - getFrenquencyFromSteps - getStepsFromFrequency - getNoteNameFromSteps - getFrequencyFromNoteName - getLowestStep - getLowestFrequency - getLowestMetrics - demo - support the new functions and styling - infrastructure - add script "demo" from root
2 parents 6941bcb + ff693f0 commit 1bf656d

File tree

8 files changed

+375
-49
lines changed

8 files changed

+375
-49
lines changed

demo/src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import NavBar from './components/NavBar.vue'
77
<header>
88
<NavBar />
99
</header>
10-
<body>
10+
<body class="flex flex-col items-center text-center pt-8 gap-4">
1111
<RouterView />
1212
</body>
1313
</template>

demo/src/assets/main.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,19 @@ select:hover {
7272
color: var(--bg-color);
7373
cursor: pointer;
7474
}
75+
76+
.bordered {
77+
border: 4px solid var(--sub-color);
78+
padding: 0.5rem;
79+
border-radius: 0.5rem;
80+
gap: calc(var(--spacing) * 4);
81+
}
82+
83+
[disabled] {
84+
opacity: 0.5;
85+
}
86+
87+
hr {
88+
color: var(--sub-color);
89+
border-top-width: 2px;
90+
}

demo/src/components/PlayNote.vue

Lines changed: 181 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,140 @@
11
<template>
2-
<div class="flex flex-col items-center gap-4 pt-8 text-center">
3-
<label :for="note_frequency_text">
4-
{{ note_frequency_text }}
5-
<br />
6-
<input
7-
:id="note_frequency_text"
8-
v-model="note_frequency"
9-
type="number"
10-
:placeholder="note_frequency_text"
11-
/>
12-
<br />
13-
<input type="range" v-model="note_frequency" :id="note_frequency_text" min="20" max="2000" />
14-
</label>
15-
<span class="relative inline-flex">
16-
<button @click="playNote()">
17-
<span> {{ play_button_text }}</span>
18-
<span v-if="is_playing" class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
19-
<span
20-
class="absolute h-full w-full animate-ping rounded-full bg-[var(--caret-color)] opacity-75"
21-
></span>
22-
<span class="relative size-3 rounded-full bg-[var(--main-color)]"></span>
23-
</span>
24-
</button>
25-
</span>
26-
27-
<label :for="volume_text">
28-
{{ volume_text }}
29-
<input :id="volume_text" type="range" v-model="volume" min="0" max="100" />
30-
{{ volume }}%
31-
</label>
32-
<label :for="oscillator_type_text" class="align-middle">
33-
{{ oscillator_type_text }}
34-
<span class="align-middle mr-1" :class="oscillator_icon"></span>
35-
<select v-model="oscillator_type" :id="oscillator_type_text">
36-
<option :key="index" v-for="(type, index) in oscillator_types" :value="type">
37-
{{ type }}
38-
</option>
39-
</select>
40-
</label>
2+
<div class="flex flex-col gap-4 w-[40vw]">
3+
<div class="flex flex-col bordered">
4+
<label :for="toggle_temperament_text">
5+
{{ toggle_temperament_text }}
6+
<div>
7+
<button
8+
@click="toggle_temperament = true"
9+
:class="{ 'text-[var(--bg-color)] !bg-[var(--text-color)]': toggle_temperament }"
10+
>
11+
ON
12+
</button>
13+
<button
14+
@click="toggle_temperament = false"
15+
:class="{ 'text-[var(--bg-color)] !bg-[var(--text-color)]': !toggle_temperament }"
16+
>
17+
OFF
18+
</button>
19+
</div>
20+
</label>
21+
22+
<hr />
23+
<div class="flex flex-col gap-4" :disabled="!toggle_temperament || null">
24+
<label :for="concert_pitch_text">
25+
{{ concert_pitch_text }}
26+
<i class="icon-[ph--bell-simple]"></i>
27+
<br />
28+
<input
29+
:id="concert_pitch_text"
30+
v-model="concert_pitch"
31+
type="number"
32+
:placeholder="concert_pitch_text"
33+
:disabled="!toggle_temperament"
34+
/>
35+
</label>
36+
37+
<label :for="temperament_text" class="align-middle">
38+
{{ temperament_text }}
39+
<i class="icon-[ph--divide] mr-2"></i>
40+
<select v-model="temperament" :id="temperament_text" :disabled="!toggle_temperament">
41+
<option :key="index" v-for="(type, index) in temperaments" :value="type">
42+
{{ type }}
43+
</option>
44+
</select>
45+
</label>
46+
47+
<label :for="note_name_text">
48+
{{ note_name_text }}
49+
<i class="icon-[ph--music-note-simple]"></i>
50+
<br />
51+
<input
52+
:id="note_name_text"
53+
v-model="note_name"
54+
type="text"
55+
:placeholder="note_name_text"
56+
:disabled="!toggle_temperament"
57+
/>
58+
</label>
59+
60+
<label :for="steps_text">
61+
{{ steps_text }}
62+
<i class="icon-[ph--ladder-simple]"></i>
63+
<br />
64+
<input
65+
:id="steps_text"
66+
v-model="steps"
67+
type="number"
68+
:placeholder="steps_text"
69+
:disabled="!toggle_temperament"
70+
:min="MIN_STEPS"
71+
:max="MAX_STEPS"
72+
/>
73+
<br />
74+
<input
75+
type="range"
76+
v-model.number="steps"
77+
:id="steps_text"
78+
:min="MIN_STEPS"
79+
:max="MAX_STEPS"
80+
:disabled="!toggle_temperament"
81+
/>
82+
</label>
83+
</div>
84+
</div>
85+
86+
<div class="flex flex-col bordered">
87+
<label :for="note_frequency_text">
88+
{{ note_frequency_text }}
89+
<i class="icon-[ph--bell-simple-ringing]"></i>
90+
<br />
91+
<input
92+
:id="note_frequency_text"
93+
v-model="note_frequency"
94+
type="number"
95+
:placeholder="note_frequency_text"
96+
:min="MIN_FREQEUNCY"
97+
:max="MAX_FREQEUNCY"
98+
/>
99+
<br />
100+
<input
101+
type="range"
102+
v-model="note_frequency"
103+
:id="note_frequency_text"
104+
:min="MIN_FREQEUNCY"
105+
:max="MAX_FREQEUNCY"
106+
/>
107+
</label>
108+
<span class="relative inline-flex mx-auto">
109+
<button @click="playNote()">
110+
<i v-if="is_playing" class="icon-[ph--megaphone] mr-2"></i>
111+
<span>{{ play_button_text }}</span>
112+
113+
<span v-if="is_playing" class="absolute top-0 right-0 -mt-1 -mr-1 flex size-3">
114+
<span
115+
class="absolute h-full w-full animate-ping rounded-full bg-[var(--caret-color)] opacity-75"
116+
></span>
117+
<span class="relative size-3 rounded-full bg-[var(--main-color)]"></span>
118+
</span>
119+
</button>
120+
</span>
121+
122+
<label :for="volume_text">
123+
{{ volume_text }}
124+
<i class="icon-[ph--speaker-high]"></i>
125+
<input :id="volume_text" type="range" v-model="volume" min="0" max="100" />
126+
{{ volume }}%
127+
</label>
128+
<label :for="oscillator_type_text" class="align-middle">
129+
{{ oscillator_type_text }}
130+
<span class="align-middle mr-2" :class="oscillator_icon"></span>
131+
<select v-model="oscillator_type" :id="oscillator_type_text">
132+
<option :key="index" v-for="(type, index) in oscillator_types" :value="type">
133+
{{ type }}
134+
</option>
135+
</select>
136+
</label>
137+
</div>
41138
</div>
42139
</template>
43140

@@ -51,7 +148,7 @@ const note_frequency_text = 'Frequency'
51148
const np = new notePlayer()
52149
53150
const is_playing = ref(false)
54-
const play_button_text = computed(() => (!is_playing.value ? 'Play note' : '🔊 Playing note...'))
151+
const play_button_text = computed(() => (!is_playing.value ? 'Play note' : 'Playing note...'))
55152
56153
function playNote() {
57154
if (!is_playing.value) {
@@ -65,6 +162,7 @@ function playNote() {
65162
66163
watch(note_frequency, () => {
67164
np.setFrequency(note_frequency.value)
165+
if (toggle_temperament.value) steps.value = np.getStepsFromFrequency(note_frequency.value)
68166
})
69167
70168
const volume = ref(50)
@@ -73,13 +171,54 @@ watch(volume, () => {
73171
np.setGain(volume.value / 100)
74172
})
75173
76-
const oscillator_types = ref<OscillatorType[]>(['sawtooth', 'sine', 'square', 'triangle'])
77-
const oscillator_type = ref<OscillatorType>('sine')
78174
const oscillator_type_text = 'Oscillator type'
175+
const oscillator_types = ref<OscillatorType[]>(['sine', 'square', 'triangle', 'sawtooth'])
176+
const oscillator_type = ref<OscillatorType>(oscillator_types.value[0])
79177
const oscillator_icon = computed(
80178
() => `icon-[ph--wave-${oscillator_type.value}${is_playing.value ? '-duotone' : ''}]`,
81179
)
82180
watch(oscillator_type, () => {
83181
np.setOscillatorType(oscillator_type.value)
84182
})
183+
184+
const toggle_temperament = ref(true)
185+
const toggle_temperament_text = 'Toggle Tone Equal Temperament'
186+
187+
const concert_pitch_text = 'A4 Frequency (Concert pitch)'
188+
const concert_pitch = ref(440)
189+
watch(concert_pitch, () => {
190+
np.setConcertPitch(concert_pitch.value)
191+
updateLowestMetrics()
192+
})
193+
194+
const lowest_metrics = ref(np.getLowestMetrics())
195+
function updateLowestMetrics() {
196+
lowest_metrics.value = np.getLowestMetrics()
197+
}
198+
const MIN_FREQEUNCY = computed(() => lowest_metrics.value.frequency)
199+
const MIN_STEPS = computed(() => lowest_metrics.value.step)
200+
const MAX_FREQEUNCY = 20000
201+
const MAX_STEPS = np.getStepsFromFrequency(MAX_FREQEUNCY)
202+
203+
type Temperament = 12
204+
const temperament_text = ref('Temperament')
205+
const temperaments = ref<Temperament[]>([12])
206+
const temperament = ref<Temperament>(temperaments.value[0])
207+
watch(temperament, () => {
208+
np.setTemperament(temperament.value)
209+
updateLowestMetrics()
210+
})
211+
212+
const note_name_text = 'Note name'
213+
const note_name = ref('A4')
214+
watch(note_name, () => {
215+
note_frequency.value = np.getFrequencyFromNoteName(note_name.value)
216+
})
217+
218+
const steps_text = 'Steps'
219+
const steps = ref<number>(0)
220+
watch(steps, () => {
221+
note_frequency.value = np.getFrenquencyFromSteps(steps.value)
222+
note_name.value = np.getNoteNameFromSteps(steps.value)
223+
})
85224
</script>

demo/src/views/HomeView.vue

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@ import PlayNote from '../components/PlayNote.vue'
33
</script>
44

55
<template>
6-
<main>
7-
<PlayNote />
8-
</main>
6+
<PlayNote />
97
</template>

dist/index.d.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@ declare class notePlayer {
44
private oscillator;
55
private DEFAULT_FREQUENCY;
66
private DEFAULT_OSCILLATOR_TYPE;
7+
private concert_pitch;
8+
private CONCERT_PITCH_OCTAVE;
9+
private temperament;
10+
private noteNames;
11+
private noteNameRegex;
712
constructor();
8-
private setOscillatorDefaultSettings;
13+
setOscillatorDefaultSettings(): void;
914
setOscillatorType(type: OscillatorType): void;
1015
setFrequency(frequency: number): void;
1116
setGain(gain: number): void;
1217
play(frequency?: number): void;
1318
stop(): void;
19+
setTemperament(temperament: number): void;
20+
setConcertPitch(concert_pitch: number): void;
21+
getFrenquencyFromSteps(steps: number): number;
22+
getStepsFromFrequency(frequency: number): number;
23+
getNoteNameFromSteps(steps: number): string;
24+
getLowestStep(): number;
25+
getLowestFrequency(): number;
26+
getLowestMetrics(): {
27+
step: number;
28+
frequency: number;
29+
};
30+
getFrequencyFromNoteName(noteFullName: string): number;
1431
}
1532

1633
export { notePlayer as default };

0 commit comments

Comments
 (0)