Files
KGStudio/src/util/midiUtil.test.ts
T

186 lines
6.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
beatsToBar,
pitchToNoteNameString,
pitchToNoteName,
pianoRollIndexToPitch,
noteNameToPitch
} from './midiUtil';
describe('midiUtil', () => {
describe('beatsToBar', () => {
it('should convert beats to bar position object', () => {
const result1 = beatsToBar(0, { numerator: 4, denominator: 4 });
expect(result1.bar).toBe(0);
expect(result1.beatInBar).toBe(0);
const result2 = beatsToBar(4, { numerator: 4, denominator: 4 });
expect(result2.bar).toBe(1);
expect(result2.beatInBar).toBe(0);
const result3 = beatsToBar(8, { numerator: 4, denominator: 4 });
expect(result3.bar).toBe(2);
expect(result3.beatInBar).toBe(0);
});
it('should handle different time signatures', () => {
const result1 = beatsToBar(0, { numerator: 3, denominator: 4 });
expect(result1.bar).toBe(0);
expect(result1.beatInBar).toBe(0);
const result2 = beatsToBar(3, { numerator: 3, denominator: 4 });
expect(result2.bar).toBe(1);
expect(result2.beatInBar).toBe(0);
});
it('should handle fractional beats', () => {
const result1 = beatsToBar(2.5, { numerator: 4, denominator: 4 });
expect(result1.bar).toBe(0);
expect(result1.beatInBar).toBe(2.5);
const result2 = beatsToBar(4.5, { numerator: 4, denominator: 4 });
expect(result2.bar).toBe(1);
expect(result2.beatInBar).toBe(0.5);
});
});
describe('pitchToNoteNameString', () => {
it('should convert MIDI pitch to note name with octave', () => {
expect(pitchToNoteNameString(60)).toBe('C4'); // Middle C
expect(pitchToNoteNameString(61)).toBe('C#4'); // C# above middle C
expect(pitchToNoteNameString(59)).toBe('B3'); // B below middle C
expect(pitchToNoteNameString(72)).toBe('C5'); // C one octave above middle C
expect(pitchToNoteNameString(48)).toBe('C3'); // C one octave below middle C
});
it('should handle edge cases', () => {
expect(pitchToNoteNameString(0)).toBe('C-1'); // Lowest MIDI note
expect(pitchToNoteNameString(127)).toBe('G9'); // Highest MIDI note
});
it('should handle all chromatic notes', () => {
const expectedNotes = ['C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4'];
for (let i = 0; i < 12; i++) {
expect(pitchToNoteNameString(60 + i)).toBe(expectedNotes[i]);
}
});
});
describe('pitchToNoteName', () => {
it('should convert pitch to note name object', () => {
const result60 = pitchToNoteName(60); // Middle C
expect(result60.note).toBe('C');
expect(result60.octave).toBe(4);
const result61 = pitchToNoteName(61); // C#
expect(result61.note).toBe('C#');
expect(result61.octave).toBe(4);
});
it('should wrap around for different octaves', () => {
const result60 = pitchToNoteName(60);
const result72 = pitchToNoteName(72);
const result84 = pitchToNoteName(84);
expect(result60.note).toBe('C');
expect(result72.note).toBe('C');
expect(result84.note).toBe('C');
expect(result60.octave).toBe(4);
expect(result72.octave).toBe(5);
expect(result84.octave).toBe(6);
});
});
describe('pianoRollIndexToPitch', () => {
it('should convert piano roll row index to MIDI pitch', () => {
// This function likely maps visual rows to MIDI pitches
// The exact mapping depends on your implementation
const result = pianoRollIndexToPitch(10);
expect(typeof result).toBe('number');
expect(result).toBeGreaterThanOrEqual(0);
expect(result).toBeLessThanOrEqual(127);
});
it('should return different pitches for different indices', () => {
const pitch1 = pianoRollIndexToPitch(0);
const pitch2 = pianoRollIndexToPitch(1);
expect(pitch1).not.toBe(pitch2);
});
});
describe('noteNameToPitch', () => {
it('should convert note names to MIDI pitch', () => {
expect(noteNameToPitch('C4')).toBe(60); // Middle C
expect(noteNameToPitch('C#4')).toBe(61); // C# above middle C
expect(noteNameToPitch('D4')).toBe(62); // D above middle C
});
it('should handle different octaves', () => {
expect(noteNameToPitch('C3')).toBe(48); // C below middle C
expect(noteNameToPitch('C5')).toBe(72); // C above middle C
});
it('should handle sharps', () => {
expect(noteNameToPitch('C#4')).toBe(61);
expect(noteNameToPitch('F#4')).toBe(66);
expect(noteNameToPitch('G#4')).toBe(68);
});
it('should handle invalid note names', () => {
expect(() => noteNameToPitch('Db4')).toThrow('Invalid note name: Db4'); // Flats not supported
expect(() => noteNameToPitch('H4')).toThrow('Invalid note name: H4'); // Invalid note
expect(() => noteNameToPitch('C')).toThrow('Invalid note name: C'); // Missing octave
});
});
describe('edge cases and error handling', () => {
it('should handle negative values gracefully', () => {
expect(() => pitchToNoteNameString(-1)).not.toThrow();
expect(() => beatsToBar(-1, { numerator: 4, denominator: 4 })).not.toThrow();
});
it('should handle very large values', () => {
expect(() => pitchToNoteNameString(200)).not.toThrow();
expect(() => beatsToBar(1000, { numerator: 4, denominator: 4 })).not.toThrow();
});
it('should handle zero values', () => {
expect(pitchToNoteNameString(0)).toBeDefined();
expect(beatsToBar(0, { numerator: 4, denominator: 4 })).toBeDefined();
});
});
describe('mathematical consistency', () => {
it('should maintain pitch relationships', () => {
// One octave = 12 semitones - note names should be the same
const baseNote = pitchToNoteName(60);
const octaveNote = pitchToNoteName(72);
expect(baseNote.note).toBe(octaveNote.note); // Both should be 'C'
expect(octaveNote.octave).toBe(baseNote.octave + 1); // Octave should be one higher
});
it('should maintain beat-to-bar relationships', () => {
const timeSignature = { numerator: 4, denominator: 4 };
// Should increment bar by 1 for each complete measure
for (let beat = 0; beat < 20; beat += 4) {
const expectedBar = Math.floor(beat / 4);
const result = beatsToBar(beat, timeSignature);
expect(result.bar).toBe(expectedBar);
expect(result.beatInBar).toBe(0); // Should be at start of bar
}
});
it('should maintain note name to pitch conversion consistency', () => {
// Converting pitch to note name and back should be consistent
const originalPitch = 60;
const noteObj = pitchToNoteName(originalPitch);
const noteName = `${noteObj.note}${noteObj.octave}`;
const convertedPitch = noteNameToPitch(noteName);
expect(convertedPitch).toBe(originalPitch);
});
});
});