pitch.ts 9.6 KB

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