Pitch.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. /**
  2. *
  3. * 修改 此处根据不同声部处理任务
  4. */
  5. /** 指定部分声部频率计算移调, key: 移动位置 声部id,以管乐迷声部ID为准 */
  6. const Additional: {[key: string]: number[]} = {
  7. "-2": [4, 12, 23],
  8. "-7": [13],
  9. "-9": [6],
  10. "12": [5, 116, 55]
  11. };
  12. // The value of the enum indicates the number of halftoneSteps from one note to the next
  13. export enum NoteEnum {
  14. C = 0,
  15. D = 2,
  16. E = 4,
  17. F = 5,
  18. G = 7,
  19. A = 9,
  20. B = 11
  21. }
  22. /** Describes Accidental types.
  23. * Do not use the number values of these enum members directly for calculation anymore.
  24. * To use these for pitch calculation, use pitch.AccidentalHalfTones()
  25. * or Pitch.HalfTonesFromAccidental(accidentalEnum).
  26. */
  27. export enum AccidentalEnum {
  28. SHARP,
  29. FLAT,
  30. NONE,
  31. NATURAL,
  32. DOUBLESHARP,
  33. DOUBLEFLAT,
  34. TRIPLESHARP,
  35. TRIPLEFLAT,
  36. QUARTERTONESHARP,
  37. QUARTERTONEFLAT,
  38. SLASHFLAT,
  39. THREEQUARTERSSHARP,
  40. THREEQUARTERSFLAT,
  41. SLASHQUARTERSHARP,
  42. SLASHSHARP,
  43. DOUBLESLASHFLAT,
  44. SORI,
  45. KORON
  46. }
  47. // This class represents a musical note. The middle A (440 Hz) lies in the octave with the value 1.
  48. export class Pitch {
  49. public static pitchEnumValues: NoteEnum[] = [
  50. NoteEnum.C, NoteEnum.D, NoteEnum.E, NoteEnum.F, NoteEnum.G, NoteEnum.A, NoteEnum.B,
  51. ];
  52. private static halftoneFactor: number = 12 / (Math.LN2 / Math.LN10);
  53. private static octXmlDiff: number = 3;
  54. // private _sourceOctave: number;
  55. // private _sourceFundamentalNote: NoteEnum;
  56. // private _sourceAccidental: AccidentalEnum = AccidentalEnum.NONE;
  57. private octave: number;
  58. private fundamentalNote: NoteEnum;
  59. private accidental: AccidentalEnum = AccidentalEnum.NONE;
  60. private accidentalXml: string;
  61. private frequency: number;
  62. private halfTone: number;
  63. public nextFrequency: number;
  64. public prevFrequency: number;
  65. public static getNoteEnumString(note: NoteEnum): string {
  66. switch (note) {
  67. case NoteEnum.C:
  68. return "C";
  69. case NoteEnum.D:
  70. return "D";
  71. case NoteEnum.E:
  72. return "E";
  73. case NoteEnum.F:
  74. return "F";
  75. case NoteEnum.G:
  76. return "G";
  77. case NoteEnum.A:
  78. return "A";
  79. case NoteEnum.B:
  80. return "B";
  81. default:
  82. return "";
  83. }
  84. }
  85. /** Changes a note x lines/steps up (+) or down (-) from a NoteEnum on a staffline/keyboard (white keys).
  86. * E.g. Two lines down (-2) from a D is a B.
  87. * Two lines up from an A is a C.
  88. * (e.g. in the treble/violin clef, going one line up: E -> F (semitone), F -> G (2 semitones)).
  89. * Returns new NoteEnum and the octave shift (e.g. -1 = new octave is one octave down). */
  90. public static lineShiftFromNoteEnum(noteEnum: NoteEnum, lines: number): [NoteEnum, number] {
  91. if (lines === 0) {
  92. return [noteEnum, 0];
  93. }
  94. const enums: NoteEnum[] = Pitch.pitchEnumValues;
  95. const originalIndex: number = enums.indexOf(noteEnum);
  96. let octaveShift: number = 0;
  97. let newIndex: number = (originalIndex + lines) % enums.length; // modulo only handles positive overflow
  98. if (originalIndex + lines > enums.length - 1) {
  99. octaveShift = 1;
  100. }
  101. if (newIndex < 0) {
  102. newIndex = enums.length + newIndex; // handle underflow, e.g. - 1: enums.length + (-1) = last element
  103. octaveShift = -1;
  104. }
  105. return [enums[newIndex], octaveShift];
  106. }
  107. /**
  108. * @param the input pitch
  109. * @param the number of halftones to transpose with
  110. * @returns ret[0] = the transposed fundamental.
  111. * ret[1] = the octave shift (not the new octave!)
  112. * @constructor
  113. */
  114. public static CalculateTransposedHalfTone(pitch: Pitch, transpose: number): { halftone: number, overflow: number } {
  115. const newHalfTone: number = <number>pitch.fundamentalNote + pitch.AccidentalHalfTones + transpose;
  116. return Pitch.WrapAroundCheck(newHalfTone, 12);
  117. }
  118. public static WrapAroundCheck(value: number, limit: number): { halftone: number, overflow: number } {
  119. let overflow: number = 0;
  120. while (value < 0) {
  121. value += limit;
  122. overflow--; // the octave change
  123. }
  124. while (value >= limit) {
  125. value -= limit;
  126. overflow++; // the octave change
  127. }
  128. return {overflow: overflow, halftone: value};
  129. }
  130. //public static calcFrequency(pitch: Pitch): number;
  131. //public static calcFrequency(fractionalKey: number): number;
  132. public static calcFrequency(obj: Pitch|number, type?: string): number {
  133. let octaveSteps: number = 0;
  134. let halfToneSteps: number;
  135. if (obj instanceof Pitch) {
  136. // obj is a pitch
  137. const pitch: Pitch = obj;
  138. octaveSteps = pitch.octave - 1;
  139. halfToneSteps = <number>pitch.fundamentalNote - <number>NoteEnum.A + pitch.AccidentalHalfTones;
  140. } else if (typeof obj === "number") {
  141. // obj is a fractional key
  142. const fractionalKey: number = obj;
  143. halfToneSteps = fractionalKey - 57.0;
  144. }
  145. const DYSubjectId = (window as any).DYSubjectId
  146. // console.log(DYSubjectId)
  147. // Return frequency:
  148. // 修改 频率标准
  149. let fixnum: number = 0
  150. /**
  151. * 此处 比较独立无rules,通过全局变量传递环境
  152. */
  153. if ((window as any).DYEnvironment === 'COLEXIU') {
  154. Additional['12'] = [120];
  155. }
  156. for (const partName in Additional) {
  157. if (Object.prototype.hasOwnProperty.call(Additional, partName)) {
  158. const partValue = Additional[partName];
  159. if (partValue.includes(DYSubjectId)) {
  160. fixnum = parseFloat(partName)
  161. break
  162. }
  163. }
  164. }
  165. if ((DYSubjectId === 5 || DYSubjectId === 55) && !(window as any).needPitchVoice) {
  166. fixnum = 0
  167. }
  168. // console.log('修改音符频率',fixnum,(window as any).needPitchVoice)
  169. // console.log('halfToneSteps', octaveSteps, halfToneSteps)
  170. let step = halfToneSteps + fixnum
  171. if (type === "next") {
  172. step++;
  173. }
  174. if (type === "prev") {
  175. step--;
  176. }
  177. return 442.0 * Math.pow(2, octaveSteps) * Math.pow(2, step / 12.0);
  178. }
  179. public static calcFractionalKey(frequency: number): number {
  180. // Return half-tone frequency:
  181. return Math.log(frequency / 440.0) / Math.LN10 * Pitch.halftoneFactor + 57.0;
  182. }
  183. public static fromFrequency(frequency: number): Pitch {
  184. const key: number = Pitch.calcFractionalKey(frequency) + 0.5;
  185. const octave: number = Math.floor(key / 12) - Pitch.octXmlDiff;
  186. const halftone: number = Math.floor(key) % 12;
  187. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  188. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  189. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  190. fundamentalNote = <NoteEnum>(halftone - 1);
  191. accidental = AccidentalEnum.SHARP;
  192. }
  193. return new Pitch(fundamentalNote, octave, accidental);
  194. }
  195. public static fromHalftone(halftone: number): Pitch {
  196. const octave: number = Math.floor(halftone / 12) - Pitch.octXmlDiff;
  197. const halftoneInOctave: number = halftone % 12;
  198. let fundamentalNote: NoteEnum = <NoteEnum>halftoneInOctave;
  199. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  200. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  201. fundamentalNote = <NoteEnum>(halftoneInOctave - 1);
  202. accidental = AccidentalEnum.SHARP;
  203. }
  204. return new Pitch(fundamentalNote, octave, accidental);
  205. }
  206. public static ceiling(halftone: number): NoteEnum {
  207. halftone = (halftone) % 12;
  208. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  209. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  210. fundamentalNote = <NoteEnum>(halftone + 1);
  211. }
  212. return fundamentalNote;
  213. }
  214. public static floor(halftone: number): NoteEnum {
  215. halftone = halftone % 12;
  216. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  217. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  218. fundamentalNote = <NoteEnum>(halftone - 1);
  219. }
  220. return fundamentalNote;
  221. }
  222. constructor(fundamentalNote: NoteEnum, octave: number, accidental: AccidentalEnum, accidentalXml: string = undefined) {
  223. this.fundamentalNote = fundamentalNote;
  224. this.octave = octave;
  225. this.accidental = accidental;
  226. this.accidentalXml = accidentalXml;
  227. this.halfTone = <number>(fundamentalNote) + (octave + Pitch.octXmlDiff) * 12 +
  228. Pitch.HalfTonesFromAccidental(accidental);
  229. this.frequency = Pitch.calcFrequency(this);
  230. this.nextFrequency = Pitch.calcFrequency(this, "next");
  231. this.prevFrequency = Pitch.calcFrequency(this, "prev");
  232. }
  233. /** Turns an AccidentalEnum into half tone steps for pitch calculation.
  234. *
  235. */
  236. public static HalfTonesFromAccidental(accidental: AccidentalEnum): number {
  237. // about equal performance to hashmap/dictionary. could be turned into hashmap for convenience
  238. // switch is very slightly faster, but both are negligibly short anyways.
  239. switch (accidental) {
  240. // ordered from most to least common to improve average runtime
  241. case AccidentalEnum.NONE:
  242. return 0;
  243. case AccidentalEnum.SHARP:
  244. return 1;
  245. case AccidentalEnum.FLAT:
  246. return -1;
  247. case AccidentalEnum.NATURAL:
  248. return 0;
  249. case AccidentalEnum.DOUBLESHARP:
  250. return 2;
  251. case AccidentalEnum.DOUBLEFLAT:
  252. return -2;
  253. case AccidentalEnum.TRIPLESHARP: // very rare, in some classical pieces
  254. return 3;
  255. case AccidentalEnum.TRIPLEFLAT:
  256. return -3;
  257. case AccidentalEnum.QUARTERTONESHARP:
  258. return 0.5;
  259. case AccidentalEnum.QUARTERTONEFLAT:
  260. return -0.5;
  261. case AccidentalEnum.SLASHFLAT:
  262. return -0.51; // TODO currently necessary for quarter tone flat rendering after slash flat
  263. case AccidentalEnum.THREEQUARTERSSHARP:
  264. return 1.5;
  265. case AccidentalEnum.THREEQUARTERSFLAT:
  266. return -1.5;
  267. case AccidentalEnum.SLASHQUARTERSHARP:
  268. return 0.0013; // tmp for identification
  269. case AccidentalEnum.SLASHSHARP:
  270. return 0.0014; // tmp for identification
  271. case AccidentalEnum.DOUBLESLASHFLAT:
  272. return -0.0015; // tmp for identification
  273. case AccidentalEnum.SORI:
  274. return 0.0016; // tmp for identification
  275. case AccidentalEnum.KORON:
  276. return 0.0017; // tmp for identification
  277. default:
  278. throw new Error("Unhandled AccidentalEnum value");
  279. // return 0;
  280. }
  281. }
  282. public static AccidentalFromHalfTones(halfTones: number): AccidentalEnum {
  283. switch (halfTones) {
  284. case 0:
  285. // for enharmonic change, we won't get a Natural accidental. Maybe there are edge cases though?
  286. return AccidentalEnum.NONE;
  287. case 1:
  288. return AccidentalEnum.SHARP;
  289. case -1:
  290. return AccidentalEnum.FLAT;
  291. case 2:
  292. return AccidentalEnum.DOUBLESHARP;
  293. case -2:
  294. return AccidentalEnum.DOUBLEFLAT;
  295. case 3:
  296. return AccidentalEnum.TRIPLESHARP;
  297. case -3:
  298. return AccidentalEnum.TRIPLEFLAT;
  299. case 0.5:
  300. return AccidentalEnum.QUARTERTONESHARP;
  301. case -0.5:
  302. return AccidentalEnum.QUARTERTONEFLAT;
  303. case 1.5:
  304. return AccidentalEnum.THREEQUARTERSSHARP;
  305. case -1.5:
  306. return AccidentalEnum.THREEQUARTERSFLAT;
  307. default:
  308. if (halfTones > 0 && halfTones < 1) {
  309. return AccidentalEnum.QUARTERTONESHARP;
  310. } else if (halfTones < 0 && halfTones > -1) {
  311. return AccidentalEnum.QUARTERTONEFLAT;
  312. }
  313. // potentially unhandled or broken accidental halfTone value
  314. return AccidentalEnum.QUARTERTONESHARP; // to signal unhandled value
  315. }
  316. }
  317. /**
  318. * Converts AccidentalEnum to a string which represents an accidental in VexFlow
  319. * Can also be useful in other cases, but has to match Vexflow accidental codes.
  320. * @param accidental
  321. * @returns {string} Vexflow Accidental code
  322. */
  323. public static accidentalVexflow(accidental: AccidentalEnum): string {
  324. let acc: string;
  325. switch (accidental) {
  326. case AccidentalEnum.NATURAL:
  327. acc = "n";
  328. break;
  329. case AccidentalEnum.FLAT:
  330. acc = "b";
  331. break;
  332. case AccidentalEnum.SHARP:
  333. acc = "#";
  334. break;
  335. case AccidentalEnum.DOUBLESHARP:
  336. acc = "##";
  337. break;
  338. case AccidentalEnum.TRIPLESHARP:
  339. acc = "###";
  340. break;
  341. case AccidentalEnum.DOUBLEFLAT:
  342. acc = "bb";
  343. break;
  344. case AccidentalEnum.TRIPLEFLAT:
  345. acc = "bbs"; // there is no "bbb" in VexFlow yet, unfortunately.
  346. break;
  347. case AccidentalEnum.QUARTERTONESHARP:
  348. acc = "+";
  349. break;
  350. case AccidentalEnum.QUARTERTONEFLAT:
  351. acc = "d";
  352. break;
  353. case AccidentalEnum.SLASHFLAT:
  354. acc = "bs";
  355. break;
  356. case AccidentalEnum.THREEQUARTERSSHARP:
  357. acc = "++";
  358. break;
  359. case AccidentalEnum.THREEQUARTERSFLAT:
  360. acc = "db";
  361. break;
  362. case AccidentalEnum.SLASHQUARTERSHARP:
  363. acc = "+-";
  364. break;
  365. case AccidentalEnum.SLASHSHARP:
  366. acc = "++-";
  367. break;
  368. case AccidentalEnum.DOUBLESLASHFLAT:
  369. acc = "bss";
  370. break;
  371. case AccidentalEnum.SORI:
  372. acc = "o";
  373. break;
  374. case AccidentalEnum.KORON:
  375. acc = "k";
  376. break;
  377. default:
  378. }
  379. return acc;
  380. }
  381. public get AccidentalHalfTones(): number {
  382. return Pitch.HalfTonesFromAccidental(this.accidental);
  383. }
  384. public get Octave(): number {
  385. return this.octave;
  386. }
  387. public get FundamentalNote(): NoteEnum {
  388. return this.fundamentalNote;
  389. }
  390. public get Accidental(): AccidentalEnum {
  391. return this.accidental;
  392. }
  393. public get AccidentalXml(): string {
  394. return this.accidentalXml;
  395. }
  396. public get Frequency(): number {
  397. return this.frequency;
  398. }
  399. public static get OctaveXmlDifference(): number {
  400. return Pitch.octXmlDiff;
  401. }
  402. public getHalfTone(): number {
  403. return this.halfTone;
  404. }
  405. // This method returns a new Pitch transposed by the given factor
  406. public getTransposedPitch(factor: number): Pitch {
  407. if (factor > 12) {
  408. throw new Error("rewrite this method to handle bigger octave changes or don't use is with bigger octave changes!");
  409. }
  410. if (factor > 0) {
  411. return this.getHigherPitchByTransposeFactor(factor);
  412. }
  413. if (factor < 0) {
  414. return this.getLowerPitchByTransposeFactor(-factor);
  415. }
  416. return this;
  417. }
  418. public DoEnharmonicChange(): void {
  419. switch (this.accidental) {
  420. case AccidentalEnum.FLAT:
  421. case AccidentalEnum.DOUBLEFLAT:
  422. this.fundamentalNote = this.getPreviousFundamentalNote(this.fundamentalNote);
  423. this.accidental = Pitch.AccidentalFromHalfTones(this.halfTone - (<number>(this.fundamentalNote) +
  424. (this.octave + Pitch.octXmlDiff) * 12));
  425. break;
  426. case AccidentalEnum.SHARP:
  427. case AccidentalEnum.DOUBLESHARP:
  428. this.fundamentalNote = this.getNextFundamentalNote(this.fundamentalNote);
  429. this.accidental = Pitch.AccidentalFromHalfTones(this.halfTone - (<number>(this.fundamentalNote) +
  430. (this.octave + Pitch.octXmlDiff) * 12));
  431. break;
  432. default:
  433. return;
  434. }
  435. }
  436. public ToString(): string {
  437. let accidentalString: string = Pitch.accidentalVexflow(this.accidental);
  438. if (!accidentalString) {
  439. accidentalString = "";
  440. }
  441. return "Key: " + Pitch.getNoteEnumString(this.fundamentalNote) + accidentalString +
  442. ", Note: " + this.fundamentalNote + ", octave: " + this.octave.toString();
  443. }
  444. public OperatorEquals(p2: Pitch): boolean {
  445. const p1: Pitch = this;
  446. // if (ReferenceEquals(p1, p2)) {
  447. // return true;
  448. // }
  449. if (!p1 || !p2) {
  450. return false;
  451. }
  452. return (p1.FundamentalNote === p2.FundamentalNote && p1.Octave === p2.Octave && p1.Accidental === p2.Accidental);
  453. }
  454. public OperatorNotEqual(p2: Pitch): boolean {
  455. const p1: Pitch = this;
  456. return !(p1 === p2);
  457. }
  458. //These don't take into account accidentals! which isn't needed for our current purpose
  459. public OperatorFundamentalGreaterThan(p2: Pitch): boolean {
  460. const p1: Pitch = this;
  461. if (p1.Octave === p2.Octave) {
  462. return p1.FundamentalNote > p2.FundamentalNote;
  463. } else {
  464. return p1.Octave > p2.Octave;
  465. }
  466. }
  467. public OperatorFundamentalLessThan(p2: Pitch): boolean {
  468. const p1: Pitch = this;
  469. if (p1.Octave === p2.Octave) {
  470. return p1.FundamentalNote < p2.FundamentalNote;
  471. } else {
  472. return p1.Octave < p2.Octave;
  473. }
  474. }
  475. // This method returns a new Pitch factor-Halftones higher than the current Pitch
  476. private getHigherPitchByTransposeFactor(factor: number): Pitch {
  477. const noteEnumIndex: number = Pitch.pitchEnumValues.indexOf(this.fundamentalNote);
  478. let newOctave: number = this.octave;
  479. let newNoteEnum: NoteEnum;
  480. if (noteEnumIndex + factor > Pitch.pitchEnumValues.length - 1) {
  481. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex + factor - Pitch.pitchEnumValues.length];
  482. newOctave++;
  483. } else {
  484. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex + factor];
  485. }
  486. return new Pitch(newNoteEnum, newOctave, AccidentalEnum.NONE);
  487. }
  488. private getLowerPitchByTransposeFactor(factor: number): Pitch {
  489. const noteEnumIndex: number = Pitch.pitchEnumValues.indexOf(this.fundamentalNote);
  490. let newOctave: number = this.octave;
  491. let newNoteEnum: NoteEnum;
  492. if (noteEnumIndex - factor < 0) {
  493. newNoteEnum = Pitch.pitchEnumValues[Pitch.pitchEnumValues.length + noteEnumIndex - factor];
  494. newOctave--;
  495. } else {
  496. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex - factor];
  497. }
  498. return new Pitch(newNoteEnum, newOctave, AccidentalEnum.NONE);
  499. }
  500. private getNextFundamentalNote(fundamental: NoteEnum): NoteEnum {
  501. let i: number = Pitch.pitchEnumValues.indexOf(fundamental);
  502. i = (i + 1) % Pitch.pitchEnumValues.length;
  503. return Pitch.pitchEnumValues[i];
  504. }
  505. private getPreviousFundamentalNote(fundamental: NoteEnum): NoteEnum {
  506. const i: number = Pitch.pitchEnumValues.indexOf(fundamental);
  507. if (i > 0) {
  508. return Pitch.pitchEnumValues[i - 1];
  509. } else {
  510. return Pitch.pitchEnumValues[Pitch.pitchEnumValues.length - 1];
  511. }
  512. }
  513. }