note-listener 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/package.json +1 -1
- package/src/index.js +10 -3
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# note-listener
|
|
2
|
+
|
|
3
|
+
A lightweight JavaScript/TypeScript library for detecting pitch from any audio input. Works in the browser and supports selecting specific microphones or sound cards. Perfect for guitar practice, music apps, or live pitch detection.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* Detect musical notes (C0–B8) in real-time.
|
|
10
|
+
* Smooths out note jitter with a configurable buffer.
|
|
11
|
+
* Supports multiple audio inputs and allows choosing a specific device.
|
|
12
|
+
* Pure browser-based — no backend required.
|
|
13
|
+
* Minimal dependencies (`pitchy`).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install note-listener
|
|
21
|
+
# or
|
|
22
|
+
yarn add note-listener
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Plain JavaScript
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { listAudioInputs, createPitchListener } from "note-listener";
|
|
31
|
+
|
|
32
|
+
// List all available audio inputs
|
|
33
|
+
const inputs = await listAudioInputs();
|
|
34
|
+
console.log(inputs); // [{ deviceId, label }, ...]
|
|
35
|
+
|
|
36
|
+
const listener = await createPitchListener({
|
|
37
|
+
deviceId: inputs[0].deviceId, // select a device
|
|
38
|
+
minEnergy: 0.03,
|
|
39
|
+
onNote: (note, freq, clarity) => {
|
|
40
|
+
console.log(`Detected note: ${note} (${freq.toFixed(2)} Hz), clarity: ${clarity.toFixed(2)}`);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
listener.start();
|
|
45
|
+
|
|
46
|
+
// Stop when needed
|
|
47
|
+
// listener.stop();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Vue 3 Example
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<template>
|
|
54
|
+
<div>
|
|
55
|
+
<h2>Select audio input</h2>
|
|
56
|
+
<select v-model="selectedId">
|
|
57
|
+
<option disabled value="">-- choose input --</option>
|
|
58
|
+
<option v-for="d in inputs" :key="d.deviceId" :value="d.deviceId">
|
|
59
|
+
{{ d.label }}
|
|
60
|
+
</option>
|
|
61
|
+
</select>
|
|
62
|
+
|
|
63
|
+
<button @click="start" :disabled="!selectedId">Start listening</button>
|
|
64
|
+
<p>Last note: {{ lastNote || "—" }}</p>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup>
|
|
69
|
+
import { ref, onMounted, onBeforeUnmount } from "vue"
|
|
70
|
+
import { listAudioInputs, createPitchListener } from "note-listener"
|
|
71
|
+
|
|
72
|
+
const inputs = ref([])
|
|
73
|
+
const selectedId = ref("")
|
|
74
|
+
const lastNote = ref(null)
|
|
75
|
+
let listener = null
|
|
76
|
+
|
|
77
|
+
onMounted(async () => {
|
|
78
|
+
await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
79
|
+
inputs.value = await listAudioInputs()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
async function start() {
|
|
83
|
+
if (listener) listener.stop()
|
|
84
|
+
|
|
85
|
+
listener = await createPitchListener({
|
|
86
|
+
deviceId: selectedId.value,
|
|
87
|
+
minEnergy: 0.03,
|
|
88
|
+
onNote(note) {
|
|
89
|
+
lastNote.value = note
|
|
90
|
+
console.log("🎵", note)
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
listener.start()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onBeforeUnmount(() => {
|
|
98
|
+
if (listener) listener.stop()
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Options
|
|
104
|
+
|
|
105
|
+
| Option | Type | Default | Description |
|
|
106
|
+
| ---------------- | -------- | --------- | ------------------------------------------------------------------- |
|
|
107
|
+
| deviceId | string | undefined | Audio input device ID to use. |
|
|
108
|
+
| minEnergy | number | 0.03 | Minimum RMS energy to consider a signal as valid. |
|
|
109
|
+
| clarityThreshold | number | 0.88 | Minimum clarity from pitchy to register a note. |
|
|
110
|
+
| minFreq | number | 82 | Minimum frequency to detect (Hz). |
|
|
111
|
+
| maxFreq | number | 1319 | Maximum frequency to detect (Hz). |
|
|
112
|
+
| onNote | function | required | Callback when a new note is detected: `(note, freq, clarity) => {}` |
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT © Donald Edwin
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { PitchDetector } from "pitchy";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export async function listAudioInputs() {
|
|
4
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
5
|
+
return devices.filter((d) => d.kind === "audioinput");
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
|
|
9
|
+
/* ====== Utilities ====== */
|
|
5
10
|
function freqToNote(freq) {
|
|
6
11
|
const A4 = 440;
|
|
7
12
|
const notes = [
|
|
@@ -34,9 +39,10 @@ function rms(buffer) {
|
|
|
34
39
|
return Math.sqrt(sum / buffer.length);
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
function getGuitar() {
|
|
42
|
+
function getGuitar(deviceId) {
|
|
38
43
|
return navigator.mediaDevices.getUserMedia({
|
|
39
44
|
audio: {
|
|
45
|
+
...(deviceId ? { deviceId: { exact: deviceId } } : {}),
|
|
40
46
|
echoCancellation: false,
|
|
41
47
|
autoGainControl: false,
|
|
42
48
|
noiseSuppression: false,
|
|
@@ -49,6 +55,7 @@ function getGuitar() {
|
|
|
49
55
|
|
|
50
56
|
export async function createPitchListener({
|
|
51
57
|
onNote,
|
|
58
|
+
deviceId,
|
|
52
59
|
minEnergy = 0.03,
|
|
53
60
|
clarityThreshold = 0.88,
|
|
54
61
|
minFreq = 82,
|
|
@@ -64,7 +71,7 @@ export async function createPitchListener({
|
|
|
64
71
|
const context = new AudioContext();
|
|
65
72
|
let guitar;
|
|
66
73
|
try {
|
|
67
|
-
guitar = await getGuitar();
|
|
74
|
+
guitar = await getGuitar(deviceId);
|
|
68
75
|
} catch (e) {
|
|
69
76
|
console.warn("Microphone access not granted:", e.message);
|
|
70
77
|
return {
|