Pitch.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // The value of the enum indicates the number of halftoneSteps from one note to the next
  2. export enum NoteEnum {
  3. C = 0,
  4. D = 2,
  5. E = 4,
  6. F = 5,
  7. G = 7,
  8. A = 9,
  9. B = 11
  10. }
  11. export enum AccidentalEnum {
  12. DOUBLEFLAT = -2,
  13. FLAT = -1,
  14. NONE = 0,
  15. SHARP = 1,
  16. DOUBLESHARP = 2
  17. }
  18. // This class represents a musical note. The middle A (440 Hz) lies in the octave with the value 1.
  19. export class Pitch {
  20. public static pitchEnumValues: NoteEnum[] = [
  21. NoteEnum.C, NoteEnum.D, NoteEnum.E, NoteEnum.F, NoteEnum.G, NoteEnum.A, NoteEnum.B,
  22. ];
  23. private static halftoneFactor: number = 12 / (Math.LN2 / Math.LN10);
  24. private static octXmlDiff: number = 3;
  25. // private _sourceOctave: number;
  26. // private _sourceFundamentalNote: NoteEnum;
  27. // private _sourceAccidental: AccidentalEnum = AccidentalEnum.NONE;
  28. private octave: number;
  29. private fundamentalNote: NoteEnum;
  30. private accidental: AccidentalEnum = AccidentalEnum.NONE;
  31. private frequency: number;
  32. private halfTone: number;
  33. public static getNoteEnumString(note: NoteEnum): string {
  34. switch (note) {
  35. case NoteEnum.C:
  36. return "C";
  37. case NoteEnum.D:
  38. return "D";
  39. case NoteEnum.E:
  40. return "E";
  41. case NoteEnum.F:
  42. return "F";
  43. case NoteEnum.G:
  44. return "G";
  45. case NoteEnum.A:
  46. return "A";
  47. case NoteEnum.B:
  48. return "B";
  49. default:
  50. return "";
  51. }
  52. }
  53. /**
  54. * @param the input pitch
  55. * @param the number of halftones to transpose with
  56. * @returns ret[0] = the transposed fundamental.
  57. * ret[1] = the octave shift (not the new octave!)
  58. * @constructor
  59. */
  60. public static CalculateTransposedHalfTone(pitch: Pitch, transpose: number): { value: number; overflow: number; } {
  61. const newHalfTone: number = <number>pitch.fundamentalNote + <number>pitch.accidental + transpose;
  62. return Pitch.WrapAroundCheck(newHalfTone, 12);
  63. }
  64. public static WrapAroundCheck(value: number, limit: number): { value: number; overflow: number; } {
  65. let overflow: number = 0;
  66. while (value < 0) {
  67. value += limit;
  68. overflow--; // the octave change
  69. }
  70. while (value >= limit) {
  71. value -= limit;
  72. overflow++; // the octave change
  73. }
  74. return {overflow: overflow, value: value};
  75. }
  76. //public static calcFrequency(pitch: Pitch): number;
  77. //public static calcFrequency(fractionalKey: number): number;
  78. public static calcFrequency(obj: Pitch|number): number {
  79. let octaveSteps: number = 0;
  80. let halftoneSteps: number;
  81. if (obj instanceof Pitch) {
  82. // obj is a pitch
  83. const pitch: Pitch = obj;
  84. octaveSteps = pitch.octave - 1;
  85. halftoneSteps = <number>pitch.fundamentalNote - <number>NoteEnum.A + <number>pitch.accidental;
  86. } else if (typeof obj === "number") {
  87. // obj is a fractional key
  88. const fractionalKey: number = obj;
  89. halftoneSteps = fractionalKey - 57.0;
  90. }
  91. // Return frequency:
  92. return 440.0 * Math.pow(2, octaveSteps) * Math.pow(2, halftoneSteps / 12.0);
  93. }
  94. public static calcFractionalKey(frequency: number): number {
  95. // Return half-tone frequency:
  96. return Math.log(frequency / 440.0) / Math.LN10 * Pitch.halftoneFactor + 57.0;
  97. }
  98. public static fromFrequency(frequency: number): Pitch {
  99. const key: number = Pitch.calcFractionalKey(frequency) + 0.5;
  100. const octave: number = Math.floor(key / 12) - Pitch.octXmlDiff;
  101. const halftone: number = Math.floor(key) % 12;
  102. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  103. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  104. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  105. fundamentalNote = <NoteEnum>(halftone - 1);
  106. accidental = AccidentalEnum.SHARP;
  107. }
  108. return new Pitch(fundamentalNote, octave, accidental);
  109. }
  110. public static fromHalftone(halftone: number): Pitch {
  111. const octave: number = <number>Math.floor(<number>halftone / 12) - Pitch.octXmlDiff;
  112. const halftoneInOctave: number = halftone % 12;
  113. let fundamentalNote: NoteEnum = <NoteEnum>halftoneInOctave;
  114. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  115. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  116. fundamentalNote = <NoteEnum>(halftoneInOctave - 1);
  117. accidental = AccidentalEnum.SHARP;
  118. }
  119. return new Pitch(fundamentalNote, <number>octave, accidental);
  120. }
  121. public static ceiling(halftone: number): NoteEnum {
  122. halftone = <number>(halftone) % 12;
  123. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  124. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  125. fundamentalNote = <NoteEnum>(halftone + 1);
  126. }
  127. return fundamentalNote;
  128. }
  129. public static floor(halftone: number): NoteEnum {
  130. halftone = halftone % 12;
  131. let fundamentalNote: NoteEnum = <NoteEnum>halftone;
  132. if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
  133. fundamentalNote = <NoteEnum>(halftone - 1);
  134. }
  135. return fundamentalNote;
  136. }
  137. constructor(fundamentalNote: NoteEnum, octave: number, accidental: AccidentalEnum) {
  138. this.fundamentalNote = fundamentalNote;
  139. this.octave = octave;
  140. this.accidental = accidental;
  141. this.halfTone = <number>(fundamentalNote) + (octave + Pitch.octXmlDiff) * 12 + <number>accidental;
  142. this.frequency = Pitch.calcFrequency(this);
  143. }
  144. public get Octave(): number {
  145. return this.octave;
  146. }
  147. public get FundamentalNote(): NoteEnum {
  148. return this.fundamentalNote;
  149. }
  150. public get Accidental(): AccidentalEnum {
  151. return this.accidental;
  152. }
  153. public get Frequency(): number {
  154. return this.frequency;
  155. }
  156. public static get OctaveXmlDifference(): number {
  157. return Pitch.octXmlDiff;
  158. }
  159. public getHalfTone(): number {
  160. return this.halfTone;
  161. }
  162. // This method returns a new Pitch transposed by the given factor
  163. public getTransposedPitch(factor: number): Pitch {
  164. if (factor > 12) {
  165. throw new Error("rewrite this method to handle bigger octave changes or don't use is with bigger octave changes!");
  166. }
  167. if (factor > 0) {
  168. return this.getHigherPitchByTransposeFactor(factor);
  169. }
  170. if (factor < 0) {
  171. return this.getLowerPitchByTransposeFactor(-factor);
  172. }
  173. return this;
  174. }
  175. public DoEnharmonicChange(): void {
  176. switch (this.accidental) {
  177. case AccidentalEnum.FLAT:
  178. case AccidentalEnum.DOUBLEFLAT:
  179. this.fundamentalNote = this.getPreviousFundamentalNote(this.fundamentalNote);
  180. this.accidental = <AccidentalEnum>(this.halfTone - (<number>(this.fundamentalNote) +
  181. (this.octave + Pitch.octXmlDiff) * 12));
  182. break;
  183. case AccidentalEnum.SHARP:
  184. case AccidentalEnum.DOUBLESHARP:
  185. this.fundamentalNote = this.getNextFundamentalNote(this.fundamentalNote);
  186. this.accidental = <AccidentalEnum>(this.halfTone - (<number>(this.fundamentalNote) +
  187. (this.octave + Pitch.octXmlDiff) * 12));
  188. break;
  189. default:
  190. return;
  191. }
  192. }
  193. public ToString(): string {
  194. return "Note: " + this.fundamentalNote + ", octave: " + this.octave.toString() + ", alter: " +
  195. this.accidental;
  196. }
  197. public OperatorEquals(p2: Pitch): boolean {
  198. const p1: Pitch = this;
  199. // if (ReferenceEquals(p1, p2)) {
  200. // return true;
  201. // }
  202. if ((<Object>p1 === undefined) || (<Object>p2 === undefined)) {
  203. return false;
  204. }
  205. return (p1.FundamentalNote === p2.FundamentalNote && p1.Octave === p2.Octave && p1.Accidental === p2.Accidental);
  206. }
  207. public OperatorNotEqual(p2: Pitch): boolean {
  208. const p1: Pitch = this;
  209. return !(p1 === p2);
  210. }
  211. // This method returns a new Pitch factor-Halftones higher than the current Pitch
  212. private getHigherPitchByTransposeFactor(factor: number): Pitch {
  213. const noteEnumIndex: number = Pitch.pitchEnumValues.indexOf(this.fundamentalNote);
  214. let newOctave: number = this.octave;
  215. let newNoteEnum: NoteEnum;
  216. if (noteEnumIndex + factor > Pitch.pitchEnumValues.length - 1) {
  217. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex + factor - Pitch.pitchEnumValues.length];
  218. newOctave++;
  219. } else {
  220. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex + factor];
  221. }
  222. return new Pitch(newNoteEnum, newOctave, AccidentalEnum.NONE);
  223. }
  224. private getLowerPitchByTransposeFactor(factor: number): Pitch {
  225. const noteEnumIndex: number = Pitch.pitchEnumValues.indexOf(this.fundamentalNote);
  226. let newOctave: number = this.octave;
  227. let newNoteEnum: NoteEnum;
  228. if (noteEnumIndex - factor < 0) {
  229. newNoteEnum = Pitch.pitchEnumValues[Pitch.pitchEnumValues.length + noteEnumIndex - factor];
  230. newOctave--;
  231. } else {
  232. newNoteEnum = Pitch.pitchEnumValues[noteEnumIndex - factor];
  233. }
  234. return new Pitch(newNoteEnum, newOctave, AccidentalEnum.NONE);
  235. }
  236. private getNextFundamentalNote(fundamental: NoteEnum): NoteEnum {
  237. let i: number = Pitch.pitchEnumValues.indexOf(fundamental);
  238. i = (i + 1) % Pitch.pitchEnumValues.length;
  239. return Pitch.pitchEnumValues[i];
  240. }
  241. private getPreviousFundamentalNote(fundamental: NoteEnum): NoteEnum {
  242. const i: number = Pitch.pitchEnumValues.indexOf(fundamental);
  243. if (i > 0) {
  244. return Pitch.pitchEnumValues[i - 1];
  245. } else {
  246. return Pitch.pitchEnumValues[Pitch.pitchEnumValues.length - 1];
  247. }
  248. }
  249. }