76 lines
2.5 KiB
TypeScript
76 lines
2.5 KiB
TypeScript
export const SPECTROGRAM_FULL_MIN_MIDI_PITCH = 0;
|
|
export const SPECTROGRAM_FULL_MAX_MIDI_PITCH = 127;
|
|
export const SPECTROGRAM_MIN_MIDI_PITCH = 12; // C0
|
|
export const SPECTROGRAM_MAX_MIDI_PITCH = 107; // B7
|
|
export const SPECTROGRAM_FULL_SEMITONES =
|
|
SPECTROGRAM_FULL_MAX_MIDI_PITCH - SPECTROGRAM_FULL_MIN_MIDI_PITCH + 1;
|
|
export const SPECTROGRAM_VISIBLE_SEMITONES =
|
|
SPECTROGRAM_MAX_MIDI_PITCH - SPECTROGRAM_MIN_MIDI_PITCH + 1;
|
|
export const SPECTROGRAM_ANALYSIS_RESOLUTION_MULTIPLIER = 2;
|
|
|
|
export type SpectrogramHeightResolution = 1 | 3 | 5;
|
|
|
|
export const SPECTROGRAM_HEIGHT_RESOLUTION_OPTIONS: SpectrogramHeightResolution[] = [1, 3, 5];
|
|
|
|
export function normalizeSpectrogramHeightResolution(value: unknown): SpectrogramHeightResolution {
|
|
if (value === 1 || value === 3 || value === 5) {
|
|
return value;
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
export function getSpectrogramPitchBinCount(
|
|
resolution: number,
|
|
): number {
|
|
return SPECTROGRAM_FULL_SEMITONES * resolution;
|
|
}
|
|
|
|
export function getSpectrogramVisibleBinRange(
|
|
resolution: number,
|
|
): { start: number; end: number } {
|
|
return {
|
|
start: (SPECTROGRAM_MIN_MIDI_PITCH - SPECTROGRAM_FULL_MIN_MIDI_PITCH) * resolution,
|
|
end: (SPECTROGRAM_MAX_MIDI_PITCH - SPECTROGRAM_FULL_MIN_MIDI_PITCH + 1) * resolution,
|
|
};
|
|
}
|
|
|
|
export function getSpectrogramAnalysisResolution(
|
|
resolution: SpectrogramHeightResolution,
|
|
): number {
|
|
return resolution * SPECTROGRAM_ANALYSIS_RESOLUTION_MULTIPLIER;
|
|
}
|
|
|
|
export function mapMidiPitchToSpectrogramPosition(
|
|
midiPitch: number,
|
|
resolution: number,
|
|
): number | null {
|
|
const pitchOffset = midiPitch - SPECTROGRAM_FULL_MIN_MIDI_PITCH;
|
|
if (pitchOffset < 0 || pitchOffset > SPECTROGRAM_FULL_MAX_MIDI_PITCH) {
|
|
return null;
|
|
}
|
|
|
|
// Anchor each MIDI pitch to the center of its semitone band so the
|
|
// rendered ridge lines up with the piano-roll key row rather than a boundary.
|
|
const scaled = pitchOffset * resolution + (resolution - 1) / 2;
|
|
const maxBin = getSpectrogramPitchBinCount(resolution) - 1;
|
|
return Math.max(0, Math.min(maxBin, scaled));
|
|
}
|
|
|
|
export function frequencyToMidiPitch(frequency: number): number | null {
|
|
if (!Number.isFinite(frequency) || frequency <= 0) {
|
|
return null;
|
|
}
|
|
return 69 + 12 * Math.log2(frequency / 440);
|
|
}
|
|
|
|
export function mapFrequencyToSpectrogramPosition(
|
|
frequency: number,
|
|
resolution: number,
|
|
): number | null {
|
|
const midiPitch = frequencyToMidiPitch(frequency);
|
|
if (midiPitch === null) {
|
|
return null;
|
|
}
|
|
return mapMidiPitchToSpectrogramPosition(midiPitch, resolution);
|
|
}
|