123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- // 定义音高对照表的类型,每个八度(number)映射到一个音符(string)和其频率(number)的对象
- type FrequencyTable = {
- [octave: number]: {
- [note: string]: number;
- };
- };
- const noteNames = ["c", "♯c", "d", "♯d", "e", "f", "♯f", "g", "♯g", "a", "♯a", "b"];
-
- /**
- * 根据给定音名、八度和基准 A4 频率计算音符频率
- * @param note - 音名(例如 "C", "C#", "D", ..., "B")
- * @param octave - 八度,数字(例如 4 表示第 4 八度)
- * @param baseA4 - A4 的基准频率(Hz),默认为 442
- * @returns 计算得到的频率(Hz)
- */
- function getFrequency(note: string, octave: number, baseA4: number = 442): number {
- const noteIndex = noteNames.indexOf(note);
- if (noteIndex === -1) {
- throw new Error("Invalid note: " + note);
- }
- // A4 的序号:4*12 + 9 = 57 (因为 A 的索引为 9)
- const A4Index = 4 * 12 + 9;
- const currentIndex = octave * 12 + noteIndex;
- const semitoneOffset = currentIndex - A4Index;
- return baseA4 * Math.pow(2, semitoneOffset / 12);
- }
- /**
- * 生成一个从 C0 到 B9 的完整音名与频率对照表
- * @param baseA4 - A4 的基准频率(Hz),例如 442 或 440
- * @returns 一个 FrequencyTable 对象,结构如下:
- * {
- * 0: { C: 16.352, "C#": 17.324, ..., B: 30.868 },
- * 1: { C: 32.703, "C#": 34.648, ..., B: 61.735 },
- * ...
- * 9: { ... }
- * }
- */
- export function buildFreqTable(baseA4: number = 442): FrequencyTable {
- const freqTable: FrequencyTable = {};
- // 遍历八度范围 0 ~ 9
- for (let octave = 0; octave <= 9; octave++) {
- freqTable[octave] = {};
- noteNames.forEach(note => {
- const freq = getFrequency(note, octave, baseA4);
- // 保留3位小数
- freqTable[octave][note] = parseFloat(freq.toFixed(3));
- });
- }
- return freqTable;
- }
-
- /**
- * 根据给定频率值和基准 A4 频率,计算该频率对应的音名和八度
- * @param freq 待计算的频率(Hz)
- * @param baseA4 A4 的基准频率(Hz),默认值为 442
- * @returns 返回一个对象,包含音名(note)和八度(octave)
- */
- export function frequencyToNote(freq: number, baseA4: number = 442): { note: string; octave: number } {
- // A4 对应的绝对半音索引:A 在 noteNames 中索引为 9,加上 4 个八度(每个八度 12 个半音),所以 A4Index = 4 * 12 + 9 = 57
- const A4Index = 57;
- // 计算待测频率相对于基准 A4 的半音偏移值(可能是小数)
- const semitoneOffset = 12 * Math.log2(freq / baseA4);
- // 四舍五入到最近的半音
- const nearestSemitone = Math.round(semitoneOffset);
- // 计算绝对音高索引:以 C0 为起点
- const absoluteIndex = nearestSemitone + A4Index;
- // 计算所属八度:整数除以 12
- const octave = Math.floor(absoluteIndex / 12);
- // 计算音符在该八度内的索引(0~11)
- const noteIndex = absoluteIndex % 12;
- const note = noteNames[noteIndex];
- return { note, octave };
- }
-
- /**
- * 根据给定频率、基准 A4 频率计算该频率对应的绝对半音索引
- * @param freq - 待计算的频率(Hz)
- * @param baseA4 - A4 的基准频率(Hz),默认为 442Hz
- * @returns 绝对半音索引(C0 对应 0, B0 对应 11, C1 对应 12,依此类推)
- */
- function frequencyToAbsoluteIndex(freq: number, baseA4: number = 442): number {
- const A4Index = 57; // A4 在十二平均律中的绝对索引 (4 * 12 + 9)
- const semitoneOffset = 12 * Math.log2(freq / baseA4);
- const nearestSemitone = Math.round(semitoneOffset);
- return A4Index + nearestSemitone;
- }
- /**
- * 根据传入的最低频率和最高频率,计算出对应的 bottomNoteIndex 和 topNoteIndex
- * @param lowFreq - 最低频率(Hz)
- * @param highFreq - 最高频率(Hz)
- * @param baseA4 - A4 的基准频率(Hz),默认为 442Hz
- * @returns 对象 { bottomNoteIndex, topNoteIndex }
- * - frequencyToAbsoluteIndex 会将频率转换为一个绝对半音索引。
- 例如:如果 lowFreq 对应的音符是 C3,那么绝对索引大约为 3 * 12 + 0 = 36;
- 同理,highFreq 对应的音符可能为 B4,即 4 * 12 + 11 = 59;
- - 实际数值会因基准频率不同而略有变化。
- */
- export function calculateNoteRange(lowFreq: number, highFreq: number, baseA4: number = 442): { bottomNoteIndex: number; topNoteIndex: number } {
- const bottomNoteIndex = frequencyToAbsoluteIndex(lowFreq, baseA4);
- const topNoteIndex = frequencyToAbsoluteIndex(highFreq, baseA4);
- return { bottomNoteIndex, topNoteIndex };
- }
- // 计算音符索引
- export function getAbsoluteIndex(note: string, octave: number): number {
- // const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
- const noteIndex = noteNames.indexOf(note);
- return octave * 12 + noteIndex;
- }
-
|