frequency.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. // 定义音高对照表的类型,每个八度(number)映射到一个音符(string)和其频率(number)的对象
  2. type FrequencyTable = {
  3. [octave: number]: {
  4. [note: string]: number;
  5. };
  6. };
  7. const noteNames = ["c", "♯c", "d", "♯d", "e", "f", "♯f", "g", "♯g", "a", "♯a", "b"];
  8. /**
  9. * 根据给定音名、八度和基准 A4 频率计算音符频率
  10. * @param note - 音名(例如 "C", "C#", "D", ..., "B")
  11. * @param octave - 八度,数字(例如 4 表示第 4 八度)
  12. * @param baseA4 - A4 的基准频率(Hz),默认为 442
  13. * @returns 计算得到的频率(Hz)
  14. */
  15. function getFrequency(note: string, octave: number, baseA4: number = 442): number {
  16. const noteIndex = noteNames.indexOf(note);
  17. if (noteIndex === -1) {
  18. throw new Error("Invalid note: " + note);
  19. }
  20. // A4 的序号:4*12 + 9 = 57 (因为 A 的索引为 9)
  21. const A4Index = 4 * 12 + 9;
  22. const currentIndex = octave * 12 + noteIndex;
  23. const semitoneOffset = currentIndex - A4Index;
  24. return baseA4 * Math.pow(2, semitoneOffset / 12);
  25. }
  26. /**
  27. * 生成一个从 C0 到 B9 的完整音名与频率对照表
  28. * @param baseA4 - A4 的基准频率(Hz),例如 442 或 440
  29. * @returns 一个 FrequencyTable 对象,结构如下:
  30. * {
  31. * 0: { C: 16.352, "C#": 17.324, ..., B: 30.868 },
  32. * 1: { C: 32.703, "C#": 34.648, ..., B: 61.735 },
  33. * ...
  34. * 9: { ... }
  35. * }
  36. */
  37. export function buildFreqTable(baseA4: number = 442): FrequencyTable {
  38. const freqTable: FrequencyTable = {};
  39. // 遍历八度范围 0 ~ 9
  40. for (let octave = 0; octave <= 9; octave++) {
  41. freqTable[octave] = {};
  42. noteNames.forEach(note => {
  43. const freq = getFrequency(note, octave, baseA4);
  44. // 保留3位小数
  45. freqTable[octave][note] = parseFloat(freq.toFixed(3));
  46. });
  47. }
  48. return freqTable;
  49. }
  50. /**
  51. * 根据给定频率值和基准 A4 频率,计算该频率对应的音名和八度
  52. * @param freq 待计算的频率(Hz)
  53. * @param baseA4 A4 的基准频率(Hz),默认值为 442
  54. * @returns 返回一个对象,包含音名(note)和八度(octave)
  55. */
  56. export function frequencyToNote(freq: number, baseA4: number = 442): { note: string; octave: number } {
  57. // A4 对应的绝对半音索引:A 在 noteNames 中索引为 9,加上 4 个八度(每个八度 12 个半音),所以 A4Index = 4 * 12 + 9 = 57
  58. const A4Index = 57;
  59. // 计算待测频率相对于基准 A4 的半音偏移值(可能是小数)
  60. const semitoneOffset = 12 * Math.log2(freq / baseA4);
  61. // 四舍五入到最近的半音
  62. const nearestSemitone = Math.round(semitoneOffset);
  63. // 计算绝对音高索引:以 C0 为起点
  64. const absoluteIndex = nearestSemitone + A4Index;
  65. // 计算所属八度:整数除以 12
  66. const octave = Math.floor(absoluteIndex / 12);
  67. // 计算音符在该八度内的索引(0~11)
  68. const noteIndex = absoluteIndex % 12;
  69. const note = noteNames[noteIndex];
  70. return { note, octave };
  71. }
  72. /**
  73. * 根据给定频率、基准 A4 频率计算该频率对应的绝对半音索引
  74. * @param freq - 待计算的频率(Hz)
  75. * @param baseA4 - A4 的基准频率(Hz),默认为 442Hz
  76. * @returns 绝对半音索引(C0 对应 0, B0 对应 11, C1 对应 12,依此类推)
  77. */
  78. function frequencyToAbsoluteIndex(freq: number, baseA4: number = 442): number {
  79. const A4Index = 57; // A4 在十二平均律中的绝对索引 (4 * 12 + 9)
  80. const semitoneOffset = 12 * Math.log2(freq / baseA4);
  81. const nearestSemitone = Math.round(semitoneOffset);
  82. return A4Index + nearestSemitone;
  83. }
  84. /**
  85. * 根据传入的最低频率和最高频率,计算出对应的 bottomNoteIndex 和 topNoteIndex
  86. * @param lowFreq - 最低频率(Hz)
  87. * @param highFreq - 最高频率(Hz)
  88. * @param baseA4 - A4 的基准频率(Hz),默认为 442Hz
  89. * @returns 对象 { bottomNoteIndex, topNoteIndex }
  90. * - frequencyToAbsoluteIndex 会将频率转换为一个绝对半音索引。
  91. 例如:如果 lowFreq 对应的音符是 C3,那么绝对索引大约为 3 * 12 + 0 = 36;
  92. 同理,highFreq 对应的音符可能为 B4,即 4 * 12 + 11 = 59;
  93. - 实际数值会因基准频率不同而略有变化。
  94. */
  95. export function calculateNoteRange(lowFreq: number, highFreq: number, baseA4: number = 442): { bottomNoteIndex: number; topNoteIndex: number } {
  96. const bottomNoteIndex = frequencyToAbsoluteIndex(lowFreq, baseA4);
  97. const topNoteIndex = frequencyToAbsoluteIndex(highFreq, baseA4);
  98. return { bottomNoteIndex, topNoteIndex };
  99. }
  100. // 计算音符索引
  101. export function getAbsoluteIndex(note: string, octave: number): number {
  102. // const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
  103. const noteIndex = noteNames.indexOf(note);
  104. return octave * 12 + noteIndex;
  105. }