feedBack/tests/plugins/tuner/js/tuning-utils.test.js
2026-06-16 18:47:13 +02:00

199 lines
7.0 KiB
JavaScript

'use strict';
const { test } = require('node:test');
const assert = require('node:assert/strict');
// tuning-utils.js targets browsers (uses window global). Provide a stub so
// the IIFE's final line `window._tunerUtils = {...}` writes into this object.
const window = {};
global.window = window;
require('../../../../plugins/tuner/utils/tuning-utils.js');
const { freqToMidi, midiToFreq, midiToNote, offsetsToFreqs, getTuningName, preferFlats } = window._tunerUtils;
// ── preferFlats ──────────────────────────────────────────────────────────────
test('preferFlats: returns true for "Eb Standard"', () => {
assert.equal(preferFlats('Eb Standard'), true);
});
test('preferFlats: returns true for "Bb Standard"', () => {
assert.equal(preferFlats('Bb Standard'), true);
});
test('preferFlats: returns true for "Ab Standard"', () => {
assert.equal(preferFlats('Ab Standard'), true);
});
test('preferFlats: returns true for "Db Standard"', () => {
assert.equal(preferFlats('Db Standard'), true);
});
test('preferFlats: returns false for "Standard"', () => {
assert.equal(preferFlats('Standard'), false);
});
test('preferFlats: returns false for "Drop D"', () => {
assert.equal(preferFlats('Drop D'), false);
});
test('preferFlats: returns false for "D Standard"', () => {
assert.equal(preferFlats('D Standard'), false);
});
test('preferFlats: returns false for "Drop C#"', () => {
assert.equal(preferFlats('Drop C#'), false);
});
test('preferFlats: returns false for null', () => {
assert.equal(preferFlats(null), false);
});
test('preferFlats: returns false for undefined', () => {
assert.equal(preferFlats(undefined), false);
});
test('preferFlats: returns false for empty string', () => {
assert.equal(preferFlats(''), false);
});
// ── midiToNote (sharp names) ──────────────────────────────────────────────────
test('midiToNote: A4 (midi 69) is "A" regardless of useFlats', () => {
assert.equal(midiToNote(69, false), 'A');
assert.equal(midiToNote(69, true), 'A');
});
test('midiToNote: C4 (midi 60) is "C" regardless of useFlats', () => {
assert.equal(midiToNote(60, false), 'C');
assert.equal(midiToNote(60, true), 'C');
});
test('midiToNote: midi 61 is "C#" with sharps', () => {
assert.equal(midiToNote(61, false), 'C#');
});
test('midiToNote: midi 61 is "Db" with flats', () => {
assert.equal(midiToNote(61, true), 'Db');
});
test('midiToNote: midi 63 is "D#" with sharps', () => {
assert.equal(midiToNote(63, false), 'D#');
});
test('midiToNote: midi 63 is "Eb" with flats', () => {
assert.equal(midiToNote(63, true), 'Eb');
});
test('midiToNote: midi 66 is "F#" with sharps', () => {
assert.equal(midiToNote(66, false), 'F#');
});
test('midiToNote: midi 66 is "Gb" with flats', () => {
assert.equal(midiToNote(66, true), 'Gb');
});
test('midiToNote: midi 68 is "G#" with sharps', () => {
assert.equal(midiToNote(68, false), 'G#');
});
test('midiToNote: midi 68 is "Ab" with flats', () => {
assert.equal(midiToNote(68, true), 'Ab');
});
test('midiToNote: midi 70 is "A#" with sharps', () => {
assert.equal(midiToNote(70, false), 'A#');
});
test('midiToNote: midi 70 is "Bb" with flats', () => {
assert.equal(midiToNote(70, true), 'Bb');
});
test('midiToNote: natural notes are identical regardless of useFlats (E)', () => {
// E2 = midi 40
assert.equal(midiToNote(40, false), midiToNote(40, true));
});
test('midiToNote: natural notes are identical regardless of useFlats (B)', () => {
// B3 = midi 59
assert.equal(midiToNote(59, false), midiToNote(59, true));
});
test('midiToNote: works with fractional midi (rounds to nearest semitone)', () => {
// 60.4 rounds to 60 → C
assert.equal(midiToNote(60.4, false), 'C');
// 60.6 rounds to 61 → C#
assert.equal(midiToNote(60.6, false), 'C#');
});
test('midiToNote: handles midi values below C4 (wraps correctly)', () => {
// midi 48 = C3
assert.equal(midiToNote(48, false), 'C');
// midi 47 = B2
assert.equal(midiToNote(47, false), 'B');
});
// ── freqToMidi / midiToFreq round-trip ───────────────────────────────────────
test('freqToMidi(440) equals 69', () => {
assert.ok(Math.abs(freqToMidi(440) - 69) < 0.001);
});
test('midiToFreq(69) equals 440 Hz', () => {
assert.ok(Math.abs(midiToFreq(69) - 440) < 0.01);
});
test('freqToMidi / midiToFreq round-trip within 0.01 Hz', () => {
for (const freq of [82.41, 110, 146.83, 196, 246.94, 329.63]) {
const back = midiToFreq(freqToMidi(freq));
assert.ok(Math.abs(back - freq) < 0.01, `round-trip failed for ${freq} Hz`);
}
});
// ── offsetsToFreqs ────────────────────────────────────────────────────────────
test('offsetsToFreqs: all-zero 6-string is E Standard open strings', () => {
const freqs = offsetsToFreqs([0, 0, 0, 0, 0, 0], false);
// E2 A2 D3 G3 B3 E4 = 82.41 110 146.83 196 246.94 329.63
const expected = [82.41, 110.00, 146.83, 196.00, 246.94, 329.63];
freqs.forEach((f, i) => assert.ok(Math.abs(f - expected[i]) < 0.5, `string ${i}: ${f} vs ${expected[i]}`));
});
test('offsetsToFreqs: -1 offset on all strings gives Eb Standard', () => {
const freqs = offsetsToFreqs([-1, -1, -1, -1, -1, -1], false);
const eStandard = offsetsToFreqs([0, 0, 0, 0, 0, 0], false);
// Each string should be one semitone below E Standard
freqs.forEach((f, i) => {
const ratio = eStandard[i] / f;
assert.ok(Math.abs(ratio - Math.pow(2, 1/12)) < 0.01, `string ${i} ratio off`);
});
});
test('offsetsToFreqs: all-zero 4-string bass is E Standard bass', () => {
const freqs = offsetsToFreqs([0, 0, 0, 0], true);
// E1 A1 D2 G2 = 41.20 55.00 73.42 98.00
const expected = [41.20, 55.00, 73.42, 98.00];
freqs.forEach((f, i) => assert.ok(Math.abs(f - expected[i]) < 0.5, `string ${i}: ${f} vs ${expected[i]}`));
});
// ── getTuningName ─────────────────────────────────────────────────────────────
test('getTuningName: all zeros → "E Standard"', () => {
assert.equal(getTuningName([0, 0, 0, 0, 0, 0]), 'E Standard');
});
test('getTuningName: all -1 → "Eb Standard"', () => {
assert.equal(getTuningName([-1, -1, -1, -1, -1, -1]), 'Eb Standard');
});
test('getTuningName: Drop D pattern → "Drop D"', () => {
assert.equal(getTuningName([-2, 0, 0, 0, 0, 0]), 'Drop D');
});
test('getTuningName: empty → "Unknown"', () => {
assert.equal(getTuningName([]), 'Unknown');
});
test('getTuningName: null → "Unknown"', () => {
assert.equal(getTuningName(null), 'Unknown');
});