not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1492 lines
53 KiB

  1. // Copyright (c) 2016-17 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // Scalable sinewave graphic
  12. const SYNTHSVG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" y="0px" xml:space="preserve" x="0px" width="SVGWIDTHpx" viewBox="0 0 SVGWIDTH 55" version="1.1" height="55px" enable-background="new 0 0 SVGWIDTH 55"><g transform="scale(XSCALE,1)"><path d="m 1.5,27.5 c 0,0 2.2,-17.5 6.875,-17.5 4.7,0.0 6.25,11.75 6.875,17.5 0.75,6.67 2.3,17.5 6.875,17.5 4.1,0.0 6.25,-13.6 6.875,-17.5 C 29.875,22.65 31.1,10 35.875,10 c 4.1,0.0 5.97,13.0 6.875,17.5 1.15,5.7 1.75,17.5 6.875,17.5 4.65,0.0 6.875,-17.5 6.875,-17.5" style="stroke:#90c100;fill-opacity:1;fill:none;stroke-width:STROKEWIDTHpx;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg>';
  13. // Notes graphics
  14. const WHOLENOTE = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg6468" viewBox="0 0 5.1680003 12.432" height="12.432" width="5.1680002"> <g transform="translate(-375.23523,-454.37592)"> <g transform="translate(7.9606,5.6125499)" style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 369.80263,457.99537 q 1.104,0 1.872,0.432 0.768,0.416 0.768,1.2 0,0.752 -0.752,1.168 -0.752,0.4 -1.808,0.4 -1.104,0 -1.856,-0.416 -0.752,-0.416 -0.752,-1.232 0,-0.576 0.464,-0.944 0.48,-0.368 1.008,-0.48 0.528,-0.128 1.056,-0.128 z m -0.864,1.136 q 0,0.672 0.304,1.184 0.304,0.512 0.784,0.512 0.736,0 0.736,-0.8 0,-0.64 -0.304,-1.136 -0.288,-0.512 -0.8,-0.512 -0.72,0 -0.72,0.752 z" /> </g> </g> </svg>';
  15. const HALFNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3.84 12.432" height="3.5085866mm" width="1.0837333mm"> <g transform="translate(-375.23523,-454.37592)"> <g style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 375.23523,465.70392 q 0,-0.832 0.816,-1.472 0.816,-0.656 1.728,-0.656 0.528,0 0.944,0.272 l 0,-9.472 0.352,0 0,10.352 q 0,0.896 -0.784,1.488 -0.784,0.592 -1.728,0.592 -0.528,0 -0.928,-0.304 -0.4,-0.32 -0.4,-0.8 z m 0.736,0.48 q 0.848,0 1.712,-0.72 0.88,-0.72 0.88,-1.072 0,-0.224 -0.192,-0.224 -0.592,0 -1.632,0.688 -1.024,0.672 -1.024,1.12 0,0.208 0.256,0.208 z" /> </g> </g> </svg>';
  16. const QUARTERNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.0859801 11.74224" height="3.313921mm" width="1.1531544mm"> <g transform="translate(-226.1339,-457.841)"> <g style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 229.60268,457.841 0.5625,0 0.0547,0.0625 0,10.02344 q 0,1.27344 -1.53125,1.625 l -0.375,0.0313 -0.27343,0 q -1.65625,0 -1.875,-1.03906 l -0.0313,-0.24219 q 0,-1.01562 1.64843,-1.20312 l 0.25782,-0.0391 q 0.77343,0 1.47656,0.5 l 0.0313,0 0,-9.65625 0.0547,-0.0625 z" /> </g> </g> </svg>';
  17. const EIGHTHNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7.5234898 11.7422" height="3.3139098mm" width="2.123296mm"> <g transform="translate(-244.80575,-403.5553)"> <g style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 248.14955,403.5553 0.67969,0 0.0625,0.0547 0,0.30468 q 0.21094,0.42188 1.5625,0.91407 1.875,0.54687 1.875,1.625 0,1.14062 -0.95313,1.89062 l -0.0313,0 -0.23437,-0.25 q 0.47656,-0.38281 0.47656,-1.03906 0,-0.54688 -1.78125,-1.10156 -0.71875,-0.32813 -0.91406,-0.53125 l 0,8.32812 q 0,1.19531 -1.75,1.54688 l -0.44531,0 q -1.89063,0 -1.89063,-1.3125 0,-1.02344 1.65625,-1.20313 l 0.17969,0 q 0.75,0 1.44531,0.5 l 0,-9.67187 0.0625,-0.0547 z" /> </g> </g> </svg>';
  18. const SIXTEENTHNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7.0080001 12.432" height="3.5085866mm" width="1.9778134mm"> <g transform="translate(-182.21292,-431.51877)"> <g style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 182.21292,442.84677 q 0,-0.832 0.816,-1.472 0.816,-0.656 1.728,-0.656 0.528,0 0.944,0.272 l 0,-9.472 0.336,0 q 0.064,0.56 0.4,1.088 0.352,0.512 0.8,0.944 0.448,0.416 0.88,0.864 0.448,0.432 0.752,1.024 0.304,0.576 0.304,1.232 0,0.544 -0.256,1.104 0.304,0.448 0.304,1.184 0,1.232 -0.608,2.24 l -0.384,0 q 0.56,-1.12 0.56,-2.032 0,-0.512 -0.256,-0.96 -0.24,-0.448 -0.752,-0.816 -0.496,-0.368 -0.832,-0.56 -0.32,-0.192 -0.896,-0.48 l 0,5.52 q 0,0.896 -0.784,1.488 -0.784,0.592 -1.728,0.592 -0.528,0 -0.928,-0.304 -0.4,-0.32 -0.4,-0.8 z m 6.464,-5.904 q 0,-1.648 -2.624,-3.072 0,0.464 0.192,0.88 0.192,0.416 0.512,0.752 0.32,0.32 0.656,0.592 0.336,0.272 0.688,0.608 0.352,0.32 0.544,0.608 0.032,-0.256 0.032,-0.368 z" /> </g> </g> </svg>';
  19. const THIRTYSECONDNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7.0080001 14.496001" height="4.0910935mm" width="1.9778134mm"> <g transform="translate(-630.78433,-240.88335)"> <g style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 630.78433,254.27535 q 0,-0.832 0.816,-1.472 0.816,-0.656 1.728,-0.656 0.528,0 0.944,0.272 l 0,-11.536 0.352,0 q 0.048,0.56 0.384,1.072 0.336,0.496 0.768,0.912 0.432,0.4 0.864,0.848 0.432,0.448 0.72,1.104 0.304,0.656 0.304,1.456 0,0.48 -0.16,1.056 0.224,0.416 0.224,0.912 0,0.512 -0.24,0.976 0.304,0.448 0.304,1.168 0,1.232 -0.608,2.24 l -0.384,0 q 0.56,-1.12 0.56,-2.032 0,-0.512 -0.256,-0.96 -0.24,-0.448 -0.752,-0.816 -0.496,-0.368 -0.832,-0.56 -0.32,-0.192 -0.896,-0.48 l 0,5.52 q 0,0.896 -0.784,1.488 -0.784,0.592 -1.728,0.592 -0.528,0 -0.928,-0.304 -0.4,-0.32 -0.4,-0.8 z m 6.448,-7.872 q 0,-0.496 -0.208,-0.928 -0.192,-0.432 -0.64,-0.832 -0.432,-0.416 -0.784,-0.672 -0.352,-0.256 -0.976,-0.656 0.032,0.448 0.352,0.896 0.32,0.432 0.704,0.752 0.4,0.32 0.848,0.8 0.464,0.464 0.704,0.912 l 0,-0.272 z m 0,2.096 q 0,-0.4 -0.16,-0.768 -0.144,-0.368 -0.32,-0.608 -0.16,-0.256 -0.592,-0.608 -0.416,-0.352 -0.672,-0.528 -0.256,-0.176 -0.848,-0.576 0.064,0.48 0.4,0.976 0.336,0.48 0.72,0.816 0.4,0.336 0.832,0.784 0.448,0.432 0.64,0.784 l 0,-0.272 z" /> </g> </g> </svg>';
  20. const SIXTYFOURTHNOTE = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7.0080001 14.528" height="4.1001244mm" width="1.9778134mm"> <g transform="translate(-345.3223,-325.39492)"> <g transform="translate(3.1093785,1.6864426)" style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> <path d="m 342.21292,337.13248 q 0,-0.832 0.816,-1.472 0.816,-0.656 1.728,-0.656 0.528,0 0.944,0.272 l 0,-11.568 0.336,0 q 0.064,0.64 0.384,1.104 0.336,0.464 0.752,0.768 0.416,0.304 0.832,0.656 0.416,0.336 0.688,0.928 0.288,0.592 0.288,1.44 0,0.24 -0.144,0.768 0.256,0.608 0.256,1.376 0,0.32 -0.16,0.896 0.224,0.416 0.224,0.912 0,0.496 -0.24,0.96 0.304,0.448 0.304,1.024 0,0.384 -0.08,0.688 -0.08,0.304 -0.16,0.448 -0.08,0.144 -0.368,0.608 l -0.384,0 q 0.08,-0.16 0.192,-0.368 0.112,-0.224 0.16,-0.32 0.064,-0.096 0.112,-0.24 0.064,-0.144 0.08,-0.288 0.016,-0.144 0.016,-0.32 0,-0.272 -0.096,-0.512 -0.08,-0.256 -0.176,-0.432 -0.096,-0.192 -0.32,-0.4 -0.224,-0.208 -0.368,-0.32 -0.144,-0.128 -0.464,-0.304 -0.304,-0.192 -0.432,-0.256 -0.128,-0.064 -0.48,-0.224 -0.336,-0.176 -0.4,-0.208 l 0,4.064 q 0,0.896 -0.784,1.488 -0.784,0.592 -1.728,0.592 -0.528,0 -0.928,-0.304 -0.4,-0.32 -0.4,-0.8 z m 6.352,-8.384 q 0,-0.352 -0.144,-0.688 -0.128,-0.352 -0.288,-0.576 -0.16,-0.224 -0.48,-0.496 -0.32,-0.272 -0.512,-0.4 -0.192,-0.144 -0.592,-0.384 -0.384,-0.24 -0.496,-0.32 0.032,0.432 0.352,0.832 0.32,0.384 0.704,0.656 0.4,0.272 0.816,0.72 0.432,0.432 0.624,0.912 0.016,-0.176 0.016,-0.256 z m 0.016,2.128 q 0,-0.208 -0.048,-0.4 -0.032,-0.192 -0.08,-0.336 -0.048,-0.16 -0.176,-0.336 -0.128,-0.176 -0.208,-0.288 -0.08,-0.112 -0.272,-0.272 -0.192,-0.176 -0.288,-0.256 -0.096,-0.08 -0.352,-0.256 -0.24,-0.176 -0.336,-0.224 -0.096,-0.064 -0.384,-0.24 -0.288,-0.192 -0.384,-0.256 0.032,0.464 0.368,0.88 0.336,0.416 0.736,0.704 0.4,0.272 0.816,0.688 0.416,0.416 0.576,0.864 0.032,-0.192 0.032,-0.272 z m -0.016,1.936 q 0,-0.848 -0.624,-1.504 -0.608,-0.672 -1.872,-1.392 0.064,0.464 0.384,0.896 0.336,0.416 0.72,0.688 0.4,0.272 0.8,0.704 0.4,0.416 0.576,0.88 0.016,-0.064 0.016,-0.272 z" /> </g> </g> </svg>';
  21. const SHARP = '♯';
  22. const FLAT = '♭';
  23. const BTOFLAT = {'Eb': 'E♭', 'Gb': 'G♭', 'Ab': 'A♭', 'Bb': 'B♭', 'Db': 'D♭', 'Cb': 'B', 'Fb': 'E', 'eb': 'E♭', 'gb': 'G♭', 'ab': 'A♭', 'bb': 'B♭', 'db': 'D♭', 'cb': 'B', 'fb': 'E'};
  24. const STOSHARP = {'E#': 'F', 'G#': 'G♯', 'A#': 'A♯', 'B#': 'C', 'D#': 'D♯', 'C#': 'C♯', 'F#': 'F♯', 'e#': 'F', 'g#': 'G♯', 'a#': 'A♯', 'b#': 'C', 'd#': 'D♯', 'c#': 'C♯', 'f#': 'F♯'};
  25. const NOTESSHARP = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'];
  26. const NOTESFLAT = ['C', 'D♭', 'D', 'E♭', 'E', 'F', 'G♭', 'G', 'A♭', 'A', 'B♭', 'B'];
  27. const NOTESFLAT2 = ['c', 'd♭', 'd', 'e♭', 'e', 'f', 'g♭', 'g', 'a♭', 'a', 'b♭', 'b'];
  28. const EQUIVALENTNOTES = {'C♯': 'D♭', 'D♯': 'E♭', 'F♯': 'G♭', 'G♯': 'A♭', 'A♯': 'B♭', 'D♭': 'C♯', 'E♭': 'D♯', 'G♭': 'F♯', 'A♭': 'G♯', 'B♭': 'A♯'};
  29. const EXTRATRANSPOSITIONS = {'E♯': ['F', 0], 'B♯': ['C', 1], 'C♭': ['B', -1], 'F♭': ['E', 0], 'e♯': ['F', 0], 'b♯': ['C', 1], 'c♭': ['B', -1], 'f♭': ['E', 0]};
  30. const SOLFEGENAMES = ['do', 're', 'mi', 'fa', 'sol', 'la', 'ti'];
  31. const SOLFEGECONVERSIONTABLE = {'C': 'do', 'C♯': 'do' + '♯', 'D': 're', 'D♯': 're' + '♯', 'E': 'mi', 'F': 'fa', 'F♯': 'fa' + '♯', 'G': 'sol', 'G♯': 'sol' + '♯', 'A': 'la', 'A♯': 'la' + '♯', 'B': 'ti', 'D♭': 're' + '♭', 'E♭': 'mi' + '♭', 'G♭': 'sol' + '♭', 'A♭': 'la' + '♭', 'B♭': 'ti' + '♭', 'R': _('rest')};
  32. const WESTERN2EISOLFEGENAMES = {'do': 'sa', 're': 're', 'mi': 'ga', 'fa': 'ma', 'sol': 'pa', 'la': 'dha', 'ti': 'ni'};
  33. const PITCHES = ['C', 'D♭', 'D', 'E♭', 'E', 'F', 'G♭', 'G', 'A♭', 'A', 'B♭', 'B'];
  34. const PITCHES1 = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
  35. const PITCHES2 = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'];
  36. const PITCHES3 = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
  37. const NOTESTABLE = {1: "do", 2: "do♯", 3: "re", 4: "re♯", 5: "mi", 6: "fa", 7: "fa♯", 8: "sol", 9: "sol♯", 10: "la", 11: "la♯", 0: "ti"};
  38. const NOTESTEP = {'C': 1, 'D': 3, 'E': 5, 'F': 6, 'G': 8, 'A': 10, 'B': 12};
  39. // Halfsteps used in calculating absolute intervals
  40. const AUGMENTED = {1: 1, 2: 2, 3: 5, 4: 6, 5: 8, 6: 9, 7: 11, 8: 13};
  41. const PERFECT = {1: 0, 4: 5, 5: 7, 8: 12};
  42. const DIMINISHED = {1: -1, 2: 0, 3: 2, 4: 4, 5: 6, 6: 7, 7: 9, 8: 11};
  43. const MAJOR = {2: 2, 3: 4, 6: 9, 7: 11};
  44. const MINOR = {2: 1, 3: 3, 6: 8, 7: 10};
  45. // SOLFNOTES is the internal representation used in selectors
  46. const SOLFNOTES = ['ti', 'la', 'sol', 'fa', 'mi', 're', 'do'];
  47. const EASTINDIANSOLFNOTES = ['ni', 'dha', 'pa', 'ma', 'ga', 're', 'sa']
  48. const SOLFATTRS = ['♯♯', '♯', '♮', '♭', '♭♭'];
  49. function mod12(a) {
  50. while (a < 0) {
  51. a += 12;
  52. }
  53. return a % 12;
  54. }
  55. function calcAugmented(obj) {
  56. var interval = obj[0];
  57. var deltaOctave = obj[1];
  58. if (interval < 0) {
  59. return -AUGMENTED[-interval] + (12 * deltaOctave);
  60. } else {
  61. return AUGMENTED[interval] + (12 * deltaOctave);
  62. }
  63. }
  64. function calcPerfect(obj) {
  65. var interval = obj[0];
  66. var deltaOctave = obj[1];
  67. if (interval < 0) {
  68. return -PERFECT[-interval] + (12 * deltaOctave);
  69. } else {
  70. return PERFECT[interval] + (12 * deltaOctave);
  71. }
  72. }
  73. function calcDiminished(obj) {
  74. var interval = obj[0];
  75. var deltaOctave = obj[1];
  76. if (interval < 0) {
  77. return -DIMINISHED[-interval] + (12 * deltaOctave);
  78. } else {
  79. return DIMINISHED[interval] + (12 * deltaOctave);
  80. }
  81. }
  82. function calcMajor(obj) {
  83. var interval = obj[0];
  84. var deltaOctave = obj[1];
  85. if (interval < 0) {
  86. return -MAJOR[-interval] + (12 * deltaOctave);
  87. } else {
  88. return MAJOR[interval] + (12 * deltaOctave);
  89. }
  90. }
  91. function calcMinor(obj) {
  92. var interval = obj[0];
  93. var deltaOctave = obj[1];
  94. if (interval < 0) {
  95. return -MINOR[-interval] + (12 * deltaOctave);
  96. } else {
  97. return MINOR[interval] + (12 * deltaOctave);
  98. }
  99. }
  100. const SEMITONES = 12;
  101. const POWER2 = [1, 2, 4, 8, 16, 32, 64, 128];
  102. const TWELTHROOT2 = 1.0594630943592953;
  103. const TWELVEHUNDRETHROOT2 = 1.0005777895065549;
  104. const A0 = 27.5;
  105. const C8 = 4186.01;
  106. const RHYTHMRULERHEIGHT = 100;
  107. const SLIDERHEIGHT = 200;
  108. const SLIDERWIDTH = 50;
  109. const MATRIXBUTTONCOLOR = '#c374e9';
  110. const MATRIXLABELCOLOR = '#90c100';
  111. const MATRIXNOTECELLCOLOR = '#b1db00';
  112. const MATRIXTUPLETCELLCOLOR = '#57e751';
  113. const MATRIXRHYTHMCELLCOLOR = '#c8c8c8';
  114. const MATRIXBUTTONCOLORHOVER = '#c894e0';
  115. const MATRIXNOTECELLCOLORHOVER = '#c2e820';
  116. const MATRIXSOLFEWIDTH = 52;
  117. const EIGHTHNOTEWIDTH = 24;
  118. const MATRIXBUTTONHEIGHT = 40;
  119. const MATRIXBUTTONHEIGHT2 = 66;
  120. const MATRIXSOLFEHEIGHT = 30;
  121. const wholeNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(WHOLENOTE)));
  122. const halfNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(HALFNOTE)));
  123. const quarterNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(QUARTERNOTE)));
  124. const eighthNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(EIGHTHNOTE)));
  125. const sixteenthNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(SIXTEENTHNOTE)));
  126. const thirtysecondNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(THIRTYSECONDNOTE)));
  127. const sixtyfourthNoteImg = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(SIXTYFOURTHNOTE)));
  128. const NOTESYMBOLS = {1: wholeNoteImg, 2: halfNoteImg, 4: quarterNoteImg, 8: eighthNoteImg, 16: sixteenthNoteImg, 32: thirtysecondNoteImg, 64: sixtyfourthNoteImg};
  129. // The table contains the intervals that define the modes.
  130. // All of these modes assume 12 semitones per octave.
  131. // See http://www.pianoscales.org
  132. const MUSICALMODES = {
  133. // 12 notes in an octave
  134. 'chromatic': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  135. // 8 notes in an octave
  136. 'algerian': [2, 1, 2, 1, 1, 1, 3, 1],
  137. 'diminished': [2, 1, 2, 1, 2, 1, 2, 1],
  138. 'spanish': [1, 2, 1, 1, 1, 2, 2, 2],
  139. 'ocatonic': [1, 2, 1, 2, 1, 2, 1, 2],
  140. // 7 notes in an octave
  141. 'major': [2, 2, 1, 2, 2, 2, 1],
  142. 'ionian': [2, 2, 1, 2, 2, 2, 1],
  143. 'dorian': [2, 1, 2, 2, 2, 1, 2],
  144. 'phrygian': [1, 2, 2, 2, 1, 2, 2],
  145. 'lydian': [2, 2, 2, 1, 2, 2, 1],
  146. 'mixolydian': [2, 2, 1, 2, 2, 1, 2],
  147. 'minor': [2, 1, 2, 2, 1, 2, 2],
  148. 'aeolian': [2, 1, 2, 2, 1, 2, 2],
  149. 'locrian': [1, 2, 2, 1, 2, 2, 2],
  150. 'jazz minor': [2, 1, 2, 2, 2, 2, 1],
  151. 'bebop': [1, 1, 1, 2, 2, 1, 2],
  152. 'arabic': [2, 2, 1, 1, 2, 2, 2],
  153. 'byzantine': [1, 3, 1, 2, 1, 3, 1],
  154. 'enigmatic': [1, 3, 2, 2, 2, 1, 1],
  155. 'ethiopian': [2, 1, 2, 2, 1, 2, 2],
  156. 'geez': [2, 1, 2, 2, 1, 2, 2],
  157. 'hindu': [2, 2, 1, 2, 1, 2, 2],
  158. 'hungarian': [2, 1, 3, 1, 1, 3, 1],
  159. 'maqam': [1, 3, 1, 2, 1, 3, 1],
  160. 'romanian minor': [2, 1, 3, 1, 2, 1, 2],
  161. 'spanish gypsy': [1, 3, 1, 2, 1, 2, 2],
  162. // 6 notes in an octave
  163. 'blues': [3, 2, 1, 1, 3, 2],
  164. 'major blues': [2, 1, 1, 3, 2, 2],
  165. 'whole tone': [2, 2, 2, 2, 2, 2],
  166. // 5 notes in an octave
  167. 'pentatonic': [3, 2, 2, 3, 2],
  168. 'chinese': [4, 2, 1, 4, 1],
  169. 'egyptian': [2, 3, 2, 3, 2],
  170. 'hirajoshi': [1, 4, 1, 4, 2],
  171. 'japanese': [1, 4, 2, 3, 2],
  172. 'fibonacci': [1, 1, 2, 3, 5],
  173. // User definition overrides this constant
  174. 'custom': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
  175. };
  176. const MAQAMTABLE = {
  177. 'hijaz kar': 'C maqam',
  178. 'hijaz kar maqam': 'C maqam',
  179. 'shahnaz': 'D maqam',
  180. 'maqam mustar': 'Eb maqam',
  181. 'maqam jiharkah': 'F maqam',
  182. 'shadd araban': 'G maqam',
  183. 'suzidil': 'A maqam',
  184. 'ajam': 'Bb maqam',
  185. 'ajam maqam': 'Bb maqam',
  186. };
  187. var MODENAMES = [
  188. //.TRANS: twelve semi-tone scale for music
  189. [_('chromatic'), 'chromatic'],
  190. [_('algerian'), 'algerian'],
  191. //.TRANS: modal scale for music
  192. [_('diminished'), 'diminished'],
  193. [_('spanish'), 'spanish'],
  194. //.TRANS: modal scale for music
  195. [_('octatonic'), 'octatonic'],
  196. //.TRANS: major scales in music
  197. [_('major'), 'major'],
  198. //.TRANS: modal scale for music
  199. [_('ionian'), 'ionian'],
  200. //.TRANS: modal scale for music
  201. [_('dorian'), 'dorian'],
  202. //.TRANS: modal scale for music
  203. [_('phrygian'), 'phrygian'],
  204. //.TRANS: modal scale for music
  205. [_('lydian'), 'lydian'],
  206. //.TRANS: modal scale for music
  207. [_('mixolydian'), 'mixolydian'],
  208. //.TRANS: natural minor scales in music
  209. [_('minor'), 'minor'],
  210. //.TRANS: modal scale for music
  211. [_('aeolian'), 'aeolian'],
  212. //.TRANS: modal scale for music
  213. [_('locrian'), 'locrian'],
  214. //.TRANS: minor jazz scale for music
  215. [_('jazz minor'), 'jazz minor'],
  216. //.TRANS: bebop scale for music
  217. [_('bebop'), 'bebop'],
  218. [_('arabic'), 'arabic'],
  219. [_('byzantine'), 'byzantine'],
  220. //.TRANS: musical scale for music by Verdi
  221. [_('enigmatic'), 'enigmatic'],
  222. [_('ethiopian'), 'ethiopian'],
  223. //.TRANS: Ethiopic scale for music
  224. [_('geez'), 'geez'],
  225. [_('hindu'), 'hindu'],
  226. [_('hungarian'), 'hungarian'],
  227. //.TRANS: minor Romanian scale for music
  228. [_('romanian minor'), 'romanian minor'],
  229. [_('spanish gypsy'), 'spanish gypsy'],
  230. //.TRANS: musical scale for Mid-Eastern music
  231. [_('maqam'), 'maqam'],
  232. //.TRANS: minor blues scale for music
  233. [_('blues'), 'blues'],
  234. //.TRANS: major blues scale for music
  235. [_('major blues'), 'major blues'],
  236. [_('whole tone'), 'whole tone'],
  237. //.TRANS: pentatonic scale in music
  238. [_('pentatonic'), 'pentatonic'],
  239. [_('chinese'), 'chinese'],
  240. [_('egyptian'), 'egyptian'],
  241. //.TRANS: Japanese pentatonic scale for music
  242. [_('hirajoshi'), 'hirajoshi'],
  243. [_('japanese'), 'japanese'],
  244. //.TRANS: Italian mathematician
  245. [_('fibonacci'), 'fibonacci'],
  246. [_('custom'), 'custom'],
  247. ];
  248. var VOICENAMES = [
  249. //.TRANS: musical instrument
  250. [_('violin'), 'violin', 'images/voices.svg'],
  251. //.TRANS: musical instrument
  252. [_('cello'), 'cello', 'images/voices.svg'],
  253. //.TRANS: musical instrument
  254. // [_('basse'), 'basse', 'images/voices.svg'],
  255. //.TRANS: polytone synthesizer
  256. [_('poly'), 'poly', 'images/synth.svg'],
  257. //.TRANS: sine wave
  258. [_('sine'), 'sine', 'images/synth.svg'],
  259. //.TRANS: square wave
  260. [_('square'), 'square', 'images/synth.svg'],
  261. //.TRANS: sawtooth wave
  262. [_('sawtooth'), 'sawtooth', 'images/synth.svg'],
  263. //.TRANS: triangle wave
  264. [_('triangle'), 'triangle', 'images/synth.svg'],
  265. ];
  266. var DRUMNAMES = [
  267. //.TRANS: musical instrument
  268. [_('snare drum'), 'snare drum', 'images/snaredrum.svg'],
  269. //.TRANS: musical instrument
  270. [_('kick drum'), 'kick drum', 'images/kick.svg'],
  271. //.TRANS: musical instrument
  272. [_('tom tom'), 'tom tom', 'images/tom.svg'],
  273. //.TRANS: musical instrument
  274. [_('floor tom tom'), 'floor tom tom', 'images/floortom.svg'],
  275. //.TRANS: a drum made from an inverted cup
  276. [_('cup drum'), 'cup drum', 'images/cup.svg'],
  277. //.TRANS: musical instrument
  278. [_('darbuka drum'), 'darbuka drum', 'images/darbuka.svg'],
  279. //.TRANS: musical instrument
  280. [_('hi hat'), 'hi hat', 'images/hihat.svg'],
  281. //.TRANS: a small metal bell
  282. [_('ride bell'), 'ride bell', 'images/ridebell.svg'],
  283. //.TRANS: musical instrument
  284. [_('cow bell'), 'cow bell', 'images/cowbell.svg'],
  285. //.TRANS: musical instrument
  286. [_('triangle bell'), 'trianglebell', 'images/trianglebell.svg'],
  287. //.TRANS: musical instrument
  288. [_('finger cymbals'), 'finger cymbals', 'images/fingercymbals.svg'],
  289. //.TRANS: a musically tuned set of bells
  290. [_('chine'), 'chine', 'images/chine.svg'],
  291. //.TRANS: sound effect
  292. [_('clang'), 'clang', 'images/clang.svg'],
  293. //.TRANS: sound effect
  294. [_('crash'), 'crash', 'images/crash.svg'],
  295. //.TRANS: sound effect
  296. [_('bottle'), 'bottle', 'images/bottle.svg'],
  297. //.TRANS: sound effect
  298. [_('clap'), 'clap', 'images/clap.svg'],
  299. //.TRANS: sound effect
  300. [_('slap'), 'slap', 'images/slap.svg'],
  301. //.TRANS: sound effect
  302. [_('splash'), 'splash', 'images/splash.svg'],
  303. //.TRANS: sound effect
  304. [_('bubbles'), 'bubbles', 'images/bubbles.svg'],
  305. //.TRANS: animal sound effect
  306. [_('cat'), 'cat', 'images/cat.svg'],
  307. //.TRANS: animal sound effect
  308. [_('cricket'), 'cricket', 'images/cricket.svg'],
  309. //.TRANS: animal sound effect
  310. [_('dog'), 'dog', 'images/dog.svg'],
  311. //.TRANS: animal sound effect
  312. [_('duck'), 'duck', 'images/duck.svg'],
  313. ];
  314. const DEFAULTVOICE = 'sine';
  315. const DEFAULTDRUM = 'kick drum';
  316. const DEFAULTMODE = 'major';
  317. var customMode = MUSICALMODES['custom'];
  318. // The sample has a pitch which is subsequently transposed. This
  319. // number is that starting pitch.
  320. const SAMPLECENTERNO = {'violin': 63, 'cello': 39, 'basse': 15};
  321. function getModeName(name) {
  322. for (var mode in MODENAMES) {
  323. if (MODENAMES[mode][0] === name || MODENAMES[mode][1].toLowerCase() === name.toLowerCase()) {
  324. if (MODENAMES[mode][0] != '') {
  325. return MODENAMES[mode][0];
  326. } else {
  327. console.log('I18n for mode name is misbehaving.');
  328. console.log(name + ' ' + name.toLowerCase() + ' ' + MODENAMES[mode][0].toLowerCase() + ' ' + MODENAMES[mode][1].toLowerCase());
  329. return MODENAMES[mode][1];
  330. }
  331. }
  332. }
  333. console.log(name + ' not found in MODENAMES');
  334. return name;
  335. };
  336. function initModeI18N() {
  337. for (var i = 0; i < MODENAMES.length; i++) {
  338. if (MODENAMES[i][0] == null) {
  339. MODENAMES[i][0] = _(MODENAMES[i][1]);
  340. }
  341. if (MODENAMES[i][0] == null) {
  342. MODENAMES[i][0] = MODENAMES[i][1];
  343. }
  344. }
  345. };
  346. function initVoiceI18N() {
  347. for (var i = 0; i < VOICENAMES.length; i++) {
  348. if (VOICENAMES[i][0] == null) {
  349. VOICENAMES[i][0] = _(VOICENAMES[i][1]);
  350. }
  351. if (VOICENAMES[i][0] == null) {
  352. VOICENAMES[i][0] = VOICENAMES[i][1];
  353. }
  354. }
  355. };
  356. function initDrumI18N() {
  357. for (var i = 0; i < DRUMNAMES.length; i++) {
  358. if (DRUMNAMES[i][0] == null || DRUMNAMES[i][0] === '') {
  359. DRUMNAMES[i][0] = _(DRUMNAMES[i][1]);
  360. }
  361. if (DRUMNAMES[i][0] == null) {
  362. DRUMNAMES[i][0] = DRUMNAMES[i][1];
  363. }
  364. }
  365. };
  366. function getDrumName(name) {
  367. if (name === '') {
  368. console.log('getDrumName passed blank name. Returning ' + DEFAULTDRUM);
  369. name = DEFAULTDRUM;
  370. } else if (name.slice(0, 4) == 'http') {
  371. return null;
  372. }
  373. for (var drum = 0; drum < DRUMNAMES.length; drum++) {
  374. if (DRUMNAMES[drum][0].toLowerCase() === name.toLowerCase() || DRUMNAMES[drum][1].toLowerCase() === name.toLowerCase()) {
  375. if (DRUMNAMES[drum][0] != '') {
  376. return DRUMNAMES[drum][0];
  377. } else {
  378. console.log('I18n is misbehaving when parsing drum name: ' + name);
  379. return DRUMNAMES[drum][1];
  380. }
  381. }
  382. }
  383. return null;
  384. };
  385. function getDrumIcon(name) {
  386. if (name === '') {
  387. console.log('getDrumIcon passed blank name. Returning ' + DEFAULTDRUM);
  388. name = DEFAULTDRUM;
  389. } else if (name.slice(0, 4) == 'http') {
  390. return 'images/drum.svg';
  391. }
  392. for (var i = 0; i < DRUMNAMES.length; i++) {
  393. // if (DRUMNAMES[i].indexOf(name) !== -1) {
  394. if (DRUMNAMES[i][0] === name || DRUMNAMES[i][1].toLowerCase() === name.toLowerCase()) {
  395. return DRUMNAMES[i][2];
  396. }
  397. }
  398. return 'images/drum.svg';
  399. };
  400. function getDrumSynthName(name) {
  401. if (name == null || name == undefined) {
  402. console.log('getDrumSynthName passed null name. Returning null');
  403. return null;
  404. } else if (name === '') {
  405. console.log('getDrumSynthName passed blank name. Returning ' + DEFAULTDRUM);
  406. name = DEFAULTDRUM;
  407. } else if (name.slice(0, 4) == 'http') {
  408. return name;
  409. }
  410. for (var i = 0; i < DRUMNAMES.length; i++) {
  411. // if (DRUMNAMES[i].indexOf(name) !== -1) {
  412. if (DRUMNAMES[i][0] === name || DRUMNAMES[i][1].toLowerCase() === name.toLowerCase()) {
  413. return DRUMNAMES[i][1];
  414. }
  415. }
  416. return null;
  417. };
  418. function getVoiceName(name) {
  419. if (name === '') {
  420. console.log('getVoiceName passed blank name. Returning ' + DEFAULTVOICE);
  421. name = DEFAULTVOICE;
  422. } else if (name.slice(0, 4) == 'http') {
  423. return null;
  424. }
  425. for (var i = 0; i < VOICENAMES.length; i++) {
  426. if (VOICENAMES[i][0] === name || VOICENAMES[i][1] === name) {
  427. if (VOICENAMES[i][0] != '') {
  428. return VOICENAMES[i][0];
  429. } else {
  430. console.log('I18n is misbehaving when parsing voice name: ' + name);
  431. return VOICENAMES[i][1];
  432. }
  433. }
  434. }
  435. return null;
  436. };
  437. function getVoiceIcon(name) {
  438. if (name === '') {
  439. console.log('getVoiceIcon passed blank name. Returning ' + DEFAULTVOICE);
  440. name = DEFAULTVOICE;
  441. } else if (name.slice(0, 4) == 'http') {
  442. return 'images/voices.svg';
  443. }
  444. for (var i = 0; i < VOICENAMES.length; i++) {
  445. if (VOICENAMES[i][0] === name || VOICENAMES[i][1] === name) {
  446. return VOICENAMES[i][2];
  447. }
  448. }
  449. return 'images/voices.svg';
  450. };
  451. function getVoiceSynthName(name) {
  452. if (name == null || name == undefined) {
  453. console.log('getVoiceSynthName passed null name. Returning null');
  454. return null;
  455. } else if (name === '') {
  456. console.log('getVoiceSynthName passed blank name. Returning ' + DEFAULTVOICE);
  457. name = DEFAULTVOICE;
  458. } else if (name.slice(0, 4) == 'http') {
  459. return name;
  460. }
  461. for (var i = 0; i < VOICENAMES.length; i++) {
  462. if (VOICENAMES[i][0] === name || VOICENAMES[i][1] === name) {
  463. return VOICENAMES[i][1];
  464. }
  465. }
  466. return null;
  467. };
  468. function keySignatureToMode(keySignature) {
  469. // Convert from "A Minor" to "A" and "MINOR"
  470. if (keySignature === '') {
  471. console.log('No key signature provided; reverting to C major.');
  472. return ['C', 'major'];
  473. }
  474. // Maqams have special names for certain keys.
  475. if (keySignature.toLowerCase() in MAQAMTABLE) {
  476. keySignature = MAQAMTABLE[keySignature.toLowerCase()];
  477. }
  478. var parts = keySignature.split(' ');
  479. // A special case to test: m used for minor.
  480. var minorMode = false;
  481. if (parts.length === 1 && parts[0][parts[0].length - 1] === 'm') {
  482. minorMode = true;
  483. parts[0] = parts[0].slice(0, parts[0].length - 1);
  484. }
  485. if (parts[0] in BTOFLAT) {
  486. var key = BTOFLAT[parts[0]];
  487. } else if (parts[0] in STOSHARP) {
  488. var key = STOSHARP[parts[0]];
  489. } else {
  490. var key = parts[0];
  491. }
  492. if (NOTESSHARP.indexOf(key) === -1 && NOTESFLAT.indexOf(key) === -1) {
  493. console.log('Invalid key or missing name; reverting to C.');
  494. // Is is possible that the key was left out?
  495. var keySignature = 'C ' + keySignature;
  496. var parts = keySignature.split(' ');
  497. key = 'C';
  498. }
  499. if (minorMode) {
  500. return [key, 'minor'];
  501. }
  502. // Reassemble remaining parts to get mode name
  503. var mode = '';
  504. for (var i = 1; i < parts.length; i++) {
  505. if (parts[i] !== '') {
  506. if (mode === '') {
  507. mode = parts[i];
  508. } else {
  509. mode += ' ' + parts[i];
  510. }
  511. }
  512. }
  513. if (mode === '') {
  514. mode = 'major';
  515. } else {
  516. mode = mode.toLowerCase();
  517. }
  518. mode = getModeName(mode);
  519. for (var i = 0; i < MODENAMES.length; i++) {
  520. if (MODENAMES[i][0] === mode) {
  521. mode = MODENAMES[i][1];
  522. break;
  523. }
  524. }
  525. if (mode in MUSICALMODES) {
  526. return [key, mode];
  527. } else {
  528. console.log('Invalid mode name: ' + mode + ' reverting to major.');
  529. return [key, 'major'];
  530. }
  531. };
  532. function getStepSizeUp(keySignature, pitch) {
  533. return _getStepSize(keySignature, pitch, 'up');
  534. };
  535. function getStepSizeDown(keySignature, pitch) {
  536. return _getStepSize(keySignature, pitch, 'down');
  537. };
  538. function _getStepSize(keySignature, pitch, direction) {
  539. // Returns how many half-steps to the next note in this key.
  540. var obj = _buildScale(keySignature);
  541. var scale = obj[0];
  542. var halfSteps = obj[1];
  543. if (pitch in BTOFLAT) {
  544. pitch = BTOFLAT[pitch];
  545. } else if (pitch in STOSHARP) {
  546. pitch = STOSHARP[pitch];
  547. }
  548. ii = scale.indexOf(pitch);
  549. if (ii !== -1) {
  550. if (direction === 'up') {
  551. return halfSteps[ii];
  552. } else {
  553. if (ii > 0) {
  554. return -halfSteps[ii - 1];
  555. } else {
  556. return -last(halfSteps);
  557. }
  558. }
  559. }
  560. if (pitch in EQUIVALENTNOTES) {
  561. pitch = EQUIVALENTNOTES[pitch];
  562. }
  563. ii = scale.indexOf(pitch);
  564. if (ii !== -1) {
  565. if (direction === 'up') {
  566. return halfSteps[ii];
  567. } else {
  568. if (ii > 0) {
  569. return -halfSteps[ii - 1];
  570. } else {
  571. return -last(halfSteps);
  572. }
  573. }
  574. }
  575. // current Note not in the consonant scale if this key.
  576. console.log(pitch + ' not found in key of ' + keySignature);
  577. return 1;
  578. };
  579. function _buildScale(keySignature) {
  580. var obj = keySignatureToMode(keySignature);
  581. var myKeySignature = obj[0];
  582. if (obj[1] === 'CUSTOM') {
  583. var halfSteps = customMode;
  584. } else {
  585. var halfSteps = MUSICALMODES[obj[1]];
  586. }
  587. if (NOTESFLAT.indexOf(myKeySignature) !== -1) {
  588. var thisScale = NOTESFLAT;
  589. } else {
  590. var thisScale = NOTESSHARP;
  591. }
  592. var idx = thisScale.indexOf(myKeySignature);
  593. if (idx === -1) {
  594. idx = 0;
  595. }
  596. var scale = [myKeySignature];
  597. var ii = idx;
  598. for (var i = 0; i < halfSteps.length; i++) {
  599. ii += halfSteps[i];
  600. scale.push(thisScale[ii % SEMITONES]);
  601. }
  602. return [scale, halfSteps];
  603. }
  604. function scaleDegreeToPitch(keySignature, scaleDegree) {
  605. // Returns note corresponding to scale degree in current key
  606. // signature. Used for moveable solfege.
  607. var obj = _buildScale(keySignature);
  608. var scale = obj[0];
  609. // Scale degree is specified as do == 1, re == 2, etc., so we need
  610. // to subtract 1 to make it zero-based.
  611. scaleDegree -= 1;
  612. // We mod to ensure we don't run out of notes.
  613. // FixMe: bump octave if we wrap.
  614. scaleDegree %= (scale.length - 1);
  615. return (scale[scaleDegree]);
  616. };
  617. function getScaleAndHalfSteps(keySignature) {
  618. // Determine scale and half-step pattern from key signature
  619. var obj = keySignatureToMode(keySignature);
  620. var myKeySignature = obj[0];
  621. if (obj[1] === 'CUSTOM') {
  622. var halfSteps = customMode;
  623. } else {
  624. var halfSteps = MUSICALMODES[obj[1]];
  625. }
  626. var solfege = [];
  627. for (var i = 0; i < halfSteps.length; i++) {
  628. solfege.push(SOLFEGENAMES[i]);
  629. for (var j = 1; j < halfSteps[i]; j++) {
  630. solfege.push('');
  631. }
  632. }
  633. if (NOTESFLAT.indexOf(myKeySignature) !== -1) {
  634. var thisScale = NOTESFLAT;
  635. } else {
  636. var thisScale = NOTESSHARP;
  637. }
  638. if (myKeySignature in EXTRATRANSPOSITIONS) {
  639. myKeySignature = EXTRATRANSPOSITIONS[myKeySignature][0];
  640. }
  641. return [thisScale, solfege, myKeySignature, obj[1]];
  642. };
  643. // Relative interval (used by the Interval Block) is based on the
  644. // steps within the current key and mode.
  645. function getInterval (interval, keySignature, pitch) {
  646. // Step size interval based on the position (pitch) in the scale
  647. var obj = _buildScale(keySignature);
  648. var scale = obj[0];
  649. var halfSteps = obj[1];
  650. if (pitch in BTOFLAT) {
  651. pitch = BTOFLAT[pitch];
  652. ii = scale.indexOf(pitch);
  653. } else if (pitch in STOSHARP) {
  654. pitch = STOSHARP[pitch];
  655. ii = scale.indexOf(pitch);
  656. } else if (scale.indexOf(pitch) !== -1) {
  657. ii = scale.indexOf(pitch);
  658. } else if (pitch in EQUIVALENTNOTES) {
  659. pitch = EQUIVALENTNOTES[pitch];
  660. if (scale.indexOf(pitch) !== -1) {
  661. ii = scale.indexOf(pitch);
  662. } else {
  663. console.log('Note ' + pitch + ' not in scale ' + keySignature);
  664. ii = 0;
  665. }
  666. } else {
  667. // In case pitch is solfege, convert it.
  668. var ii = SOLFEGENAMES.indexOf(pitch);
  669. }
  670. if (interval > 0) {
  671. var myOctave = Math.floor(interval / SEMITONES);
  672. var myInterval = Math.floor(interval) % SEMITONES;
  673. var j = 0;
  674. for (var i = 0; i < (myInterval - 1); i++) {
  675. j += halfSteps[(ii + i) % halfSteps.length];
  676. }
  677. return j + myOctave * SEMITONES;
  678. } else {
  679. var myOctave = Math.ceil(interval / SEMITONES);
  680. var myInterval = Math.ceil(interval) % SEMITONES;
  681. var j = 0;
  682. for (var i = 0; i > myInterval + 1; i--) {
  683. var z = (ii + i - 1) % halfSteps.length;
  684. while (z < 0) {
  685. z += halfSteps.length;
  686. }
  687. j -= halfSteps[z];
  688. }
  689. return j + myOctave * SEMITONES;
  690. }
  691. };
  692. function calcNoteValueToDisplay(a, b) {
  693. var noteValue = a / b;
  694. var noteValueToDisplay = null;
  695. if (NOTESYMBOLS != undefined && noteValue in NOTESYMBOLS) {
  696. noteValueToDisplay = '1<br>&mdash;<br>' + noteValue.toString() + '<br>' + '<img src="' + NOTESYMBOLS[noteValue] + '" height=' + (MATRIXBUTTONHEIGHT / 2) * this.cellScale + '>';
  697. } else {
  698. noteValueToDisplay = reducedFraction(b, a);
  699. }
  700. if (parseInt(noteValue) < noteValue) {
  701. noteValueToDisplay = parseInt((noteValue * 1.5))
  702. if (NOTESYMBOLS != undefined && noteValueToDisplay in NOTESYMBOLS) {
  703. noteValueToDisplay = '1.5<br>&mdash;<br>' + noteValueToDisplay.toString() + '<br>' + '<img src="' + NOTESYMBOLS[noteValueToDisplay] + '" height=' + (MATRIXBUTTONHEIGHT / 2) * this.cellScale + '> .';
  704. } else {
  705. noteValueToDisplay = parseInt((noteValue * 1.75))
  706. if (NOTESYMBOLS != undefined && noteValueToDisplay in NOTESYMBOLS) {
  707. noteValueToDisplay = '1.75<br>&mdash;<br>' + noteValueToDisplay.toString() + '<br>' + '<img src="' + NOTESYMBOLS[noteValueToDisplay] + '" height=' + (MATRIXBUTTONHEIGHT / 2) * this.cellScale + '> ..';
  708. } else {
  709. noteValueToDisplay = reducedFraction(b, a);
  710. }
  711. }
  712. }
  713. return noteValueToDisplay;
  714. };
  715. function durationToNoteValue(duration) {
  716. // returns [note value, no. of dots, tuplet factor]
  717. // Try to find a match or a dotted match.
  718. for (var dotCount = 0; dotCount < 3; dotCount++) {
  719. var currentDotFactor = 2 - (1 / Math.pow(2, dotCount));
  720. var d = duration * currentDotFactor;
  721. if (POWER2.indexOf(d) !== -1) {
  722. return [d, dotCount, null];
  723. }
  724. }
  725. // First, round down.
  726. var roundDown = duration;
  727. for (var i = 1; i < POWER2.length; i++) {
  728. // Rounding down
  729. if (roundDown < POWER2[i]) {
  730. roundDown = POWER2[i - 1];
  731. break;
  732. }
  733. }
  734. if (POWER2.indexOf(roundDown) === -1) {
  735. roundDown = 128;
  736. }
  737. // Next, see if the note has a factor of 2.
  738. var factorOfTwo = 1;
  739. var tupletValue = duration;
  740. while (Math.floor(tupletValue / 2) * 2 === tupletValue) {
  741. factorOfTwo *= 2;
  742. tupletValue /= 2;
  743. }
  744. if (factorOfTwo > 1) {
  745. // We have a tuplet of sorts
  746. return [duration, 0, tupletValue, roundDown];
  747. }
  748. // Next, generate a fauve tuplet for a singleton.
  749. return [1, 0, duration, roundDown];
  750. };
  751. function toFraction(d) {
  752. // Convert float to its approximate fractional representation.
  753. if (d > 1) {
  754. var flip = true;
  755. d = 1 / d;
  756. } else {
  757. var flip = false;
  758. }
  759. var df = 1.0;
  760. var top = 1;
  761. var bot = 1;
  762. while (Math.abs(df - d) > 0.00000001) {
  763. if (df < d) {
  764. top += 1
  765. } else {
  766. bot += 1
  767. top = parseInt(d * bot);
  768. }
  769. df = top / bot;
  770. }
  771. if (flip) {
  772. var tmp = top;
  773. top = bot;
  774. bot = tmp;
  775. }
  776. return([top, bot]);
  777. };
  778. function frequencyToPitch(hz) {
  779. // Calculate the pitch and octave based on frequency, rounding to
  780. // the nearest cent.
  781. if (hz < A0) {
  782. return ['A', 0];
  783. } else if (hz > C8) {
  784. // FIXME: set upper bound of C10
  785. return ['C', 8];
  786. }
  787. // Calculate cents to keep track of drift
  788. var cents = 0;
  789. for (var i = 0; i < 8800; i++) {
  790. var f = A0 * Math.pow(TWELVEHUNDRETHROOT2, i);
  791. if (hz < f * 1.0003 && hz > f * 0.9997) {
  792. var cents = i % 100;
  793. var j = Math.floor(i / 100);
  794. return [PITCHES[(j + PITCHES.indexOf('A')) % 12], Math.floor((j + PITCHES.indexOf('A')) / 12), cents];
  795. }
  796. }
  797. console.log('Could not find note/octave/cents for ' + hz);
  798. return ['?', -1, 0];
  799. };
  800. function numberToPitch(i) {
  801. // Calculate the pitch and octave based on index.
  802. // We start at A0.
  803. if (i < 0) {
  804. var n = 0;
  805. while (i < 0) {
  806. i += 12;
  807. n += 1; // Count octave bump ups.
  808. }
  809. return [PITCHES[(i + PITCHES.indexOf('A')) % 12], Math.floor((i + PITCHES.indexOf('A')) / 12) - n];
  810. } else {
  811. return [PITCHES[(i + PITCHES.indexOf('A')) % 12], Math.floor((i + PITCHES.indexOf('A')) / 12)];
  812. }
  813. };
  814. function noteToPitchOctave(note) {
  815. var len = note.length;
  816. var octave = last(note);
  817. var pitch = note.substring(0, len - 1);
  818. return [pitch, Number(octave)];
  819. };
  820. function noteToFrequency(note, keySignature) {
  821. var obj = noteToPitchOctave(note);
  822. return pitchToFrequency(obj[0], obj[1], 0, keySignature);
  823. };
  824. function pitchToFrequency(pitch, octave, cents, keySignature) {
  825. // Calculate the frequency based on pitch and octave.
  826. var pitchNumber = pitchToNumber(pitch, octave, keySignature);
  827. if (cents === 0) {
  828. return A0 * Math.pow(TWELTHROOT2, pitchNumber);
  829. } else {
  830. return A0 * Math.pow(TWELVEHUNDRETHROOT2, pitchNumber * 100 + cents);
  831. }
  832. };
  833. function pitchToNumber(pitch, octave, keySignature) {
  834. // Calculate the pitch index based on pitch and octave.
  835. if (pitch.toUpperCase() === 'R') {
  836. return 0;
  837. }
  838. // Check for flat, sharp, double flat, or double sharp.
  839. var transposition = 0;
  840. var len = pitch.length;
  841. if (len > 1) {
  842. if (len > 2) {
  843. var lastTwo = pitch.slice(len - 2);
  844. if (lastTwo === 'bb' || lastTwo === '♭♭') {
  845. pitch = pitch.slice(0, len - 2);
  846. transposition -= 2;
  847. } else if (lastTwo === '##' || lastTwo === '♯♯') {
  848. pitch = pitch.slice(0, len - 2);
  849. transposition += 2;
  850. } else if (lastTwo === '#b' || lastTwo === '♯♭' || lastTwo === 'b#' || lastTwo === '♭♯') {
  851. // Not sure this could occur... but just in case.
  852. pitch = pitch.slice(0, len - 2);
  853. }
  854. }
  855. if (pitch.length > 1) {
  856. var lastOne = pitch.slice(len - 1);
  857. if (lastOne === 'b' || lastOne === '♭') {
  858. pitch = pitch.slice(0, len - 1);
  859. transposition -= 1;
  860. } else if (lastOne === '#' || lastOne === '♯') {
  861. pitch = pitch.slice(0, len - 1);
  862. transposition += 1;
  863. }
  864. }
  865. }
  866. var pitchNumber = 0;
  867. if (PITCHES.indexOf(pitch) !== -1) {
  868. pitchNumber = PITCHES.indexOf(pitch.toUpperCase());
  869. } else {
  870. // obj[1] is the solfege mapping for the current key/mode
  871. var obj = getScaleAndHalfSteps(keySignature)
  872. if (obj[1].indexOf(pitch.toLowerCase()) !== -1) {
  873. pitchNumber = obj[1].indexOf(pitch.toLowerCase());
  874. } else {
  875. console.log('pitch ' + pitch + ' not found.');
  876. pitchNumber = 0;
  877. }
  878. }
  879. // We start at A0.
  880. return octave * 12 + pitchNumber - PITCHES.indexOf('A') + transposition;
  881. };
  882. function noteIsSolfege(note) {
  883. if (SOLFEGECONVERSIONTABLE[note] == undefined) {
  884. return true;
  885. } else {
  886. return false;
  887. }
  888. };
  889. function getSolfege(note) {
  890. // FIXME: Use mode-specific conversion.
  891. if (noteIsSolfege(note)) {
  892. return note;
  893. } else {
  894. return SOLFEGECONVERSIONTABLE[note];
  895. }
  896. };
  897. function i18nSolfege(note) {
  898. // solfnotes_ is used in the interface for i18n
  899. //.TRANS: the note names must be separated by single spaces
  900. var solfnotes_ = _('ti la sol fa mi re do').split(' ');
  901. var obj = splitSolfege(note);
  902. var i = SOLFNOTES.indexOf(obj[0]);
  903. if (i !== -1) {
  904. return solfnotes_[i] + obj[1];
  905. } else {
  906. console.log(note + ' not found.');
  907. return note;
  908. }
  909. };
  910. function splitSolfege(value) {
  911. // Separate the pitch from any attributes, e.g., # or b
  912. if (value != null) {
  913. if (SOLFNOTES.indexOf(value) !== -1) {
  914. var note = value;
  915. var attr = '';
  916. } else if (value.slice(0, 3) === 'sol') {
  917. var note = 'sol';
  918. if (value.length === 4) {
  919. var attr = value[3];
  920. } else {
  921. var attr = value[3] + value[3];
  922. }
  923. } else {
  924. var note = value.slice(0, 2);
  925. if (value.length === 3) {
  926. var attr = value[2];
  927. } else {
  928. var attr = value[2] + value[2];
  929. }
  930. }
  931. } else {
  932. var note = 'sol';
  933. var attr = ''
  934. }
  935. return [note, attr];
  936. };
  937. function getNumber(notename, octave) {
  938. // Converts a note, e.g., C, and octave to a number
  939. if (octave < 0) {
  940. var num = 0;
  941. } else if (octave > 10) {
  942. var num = 9 * 12;
  943. } else {
  944. var num = 12 * (octave - 1);
  945. }
  946. notename = String(notename);
  947. if (notename.substring(0, 1) in NOTESTEP) {
  948. num += NOTESTEP[notename.substring(0, 1)];
  949. if (notename.length >= 1) {
  950. var delta = notename.substring(1);
  951. if (delta === 'bb' || delta === '♭♭') {
  952. num -= 2;
  953. } else if (delta === '##' || delta === '♯♯') {
  954. num += 2;
  955. } else if (delta === 'b' || delta === '♭') {
  956. num -= 1;
  957. } else if (delta === '#' || delta === '♯') {
  958. num += 1;
  959. }
  960. }
  961. }
  962. return num;
  963. };
  964. function getNumNote(value, delta) {
  965. // Converts from number to note
  966. var num = value + delta;
  967. /*
  968. if (num < 0) {
  969. num = 1;
  970. var octave = 1;
  971. } else if (num > 10 * 12) {
  972. num = 12;
  973. var octave = 10;
  974. } else {
  975. var octave = Math.floor(num / 12);
  976. num = num % 12;
  977. }
  978. */
  979. var octave = Math.floor(num / 12);
  980. num = num % 12;
  981. var note = NOTESTABLE[num];
  982. if (note[num] === "ti") {
  983. octave -= 1;
  984. }
  985. return [note, octave + 1];
  986. };
  987. calcOctave = function (current, arg) {
  988. switch(arg) {
  989. case _('next'):
  990. case 'next':
  991. return Math.min(current + 1, 10);
  992. case _('previous'):
  993. case 'previous':
  994. return Math.max(current - 1, 1);
  995. case _('current'):
  996. case 'current':
  997. return current;
  998. default:
  999. return Math.floor(arg);
  1000. }
  1001. };
  1002. calcOctaveInterval = function (arg) {
  1003. // Used by intervals to determine octave to use in an interval.
  1004. var value = 0;
  1005. switch(arg) {
  1006. case 1:
  1007. case _('next'):
  1008. case 'next':
  1009. value = 1;
  1010. break;
  1011. case -1:
  1012. case _('previous'):
  1013. case 'previous':
  1014. value = -1;
  1015. break;
  1016. case _('current'):
  1017. case 'current':
  1018. case 0:
  1019. value = 0;
  1020. break;
  1021. case 2:
  1022. value = 2;
  1023. break;
  1024. case -2:
  1025. value = -2;
  1026. break;
  1027. default:
  1028. console.log('Interval octave must be between -2 and 2.');
  1029. value = 0;
  1030. break;
  1031. }
  1032. return value;
  1033. };
  1034. function isInt(value) {
  1035. return !isNaN(value) &&
  1036. parseInt(Number(value)) == value &&
  1037. !isNaN(parseInt(value, 10));
  1038. };
  1039. function reducedFraction(a, b) {
  1040. greatestCommonMultiple = function (a, b) {
  1041. return b === 0 ? a : greatestCommonMultiple(b, a % b);
  1042. }
  1043. var gcm = greatestCommonMultiple(a, b);
  1044. if (NOTESYMBOLS != undefined && [1, 2, 4, 8, 16, 32, 64].indexOf(b/gcm) !== -1) {
  1045. return (a / gcm) + '<br>&mdash;<br>' + (b / gcm) + '<br><img src=' + NOTESYMBOLS[b / gcm] + '>';
  1046. } else {
  1047. return (a / gcm) + '<br>&mdash;<br>' + (b / gcm) + '<br><br>';
  1048. }
  1049. };
  1050. function Synth () {
  1051. // Isolate synth functions here.
  1052. if (_THIS_IS_MUSIC_BLOCKS_) {
  1053. // Using Tone.js
  1054. this.tone = new Tone();
  1055. }
  1056. this.synthset = {
  1057. // builtin synths
  1058. 'poly': [null, null],
  1059. 'sine': [null, null],
  1060. 'triangle': [null, null],
  1061. 'sawtooth': [null, null],
  1062. 'square': [null, null],
  1063. 'pluck': [null, null],
  1064. // voiced samples
  1065. 'violin': [VIOLINSOUNDSAMPLE, null],
  1066. 'cello': [CELLOSOUNDSAMPLE, null],
  1067. 'basse': [BASSESOUNDSAMPLE, null],
  1068. // drum samples
  1069. 'bottle': [BOTTLESOUNDSAMPLE, null],
  1070. 'clap': [CLAPSOUNDSAMPLE, null],
  1071. 'darbuka drum': [DARBUKASOUNDSAMPLE, null],
  1072. 'hi hat': [HIHATSOUNDSAMPLE, null],
  1073. 'splash': [SPLASHSOUNDSAMPLE, null],
  1074. 'bubbles': [BUBBLESSOUNDSAMPLE, null],
  1075. 'cow bell': [COWBELLSOUNDSAMPLE, null],
  1076. 'dog': [DOGSOUNDSAMPLE, null],
  1077. 'kick drum': [KICKSOUNDSAMPLE, null],
  1078. 'tom tom': [TOMSOUNDSAMPLE, null],
  1079. 'cat': [CATSOUNDSAMPLE, null],
  1080. 'crash': [CRASHSOUNDSAMPLE, null],
  1081. 'duck': [DUCKSOUNDSAMPLE, null],
  1082. 'ride bell': [RIDEBELLSOUNDSAMPLE, null],
  1083. 'triangle bell': [TRIANGLESOUNDSAMPLE, null],
  1084. 'chine': [CHINESOUNDSAMPLE, null],
  1085. 'cricket': [CRICKETSOUNDSAMPLE, null],
  1086. 'finger cymbals': [FINGERCYMBALSSOUNDSAMPLE, null],
  1087. 'slap': [SLAPSOUNDSAMPLE, null],
  1088. 'clang': [CLANGSOUNDSAMPLE, null],
  1089. 'cup drum': [CUPSOUNDSAMPLE, null],
  1090. 'floor tom tom': [FLOORTOMSOUNDSAMPLE, null],
  1091. 'snare drum': [SNARESOUNDSAMPLE, null],
  1092. };
  1093. if (_THIS_IS_MUSIC_BLOCKS_) {
  1094. Tone.Buffer.onload = function (){
  1095. console.log('drum loaded');
  1096. };
  1097. }
  1098. this.getSynthByName = function (name) {
  1099. if (name == null || name == undefined) {
  1100. return this.synthset['poly'][1];
  1101. }
  1102. switch (name) {
  1103. case 'pluck':
  1104. case 'triangle':
  1105. case 'square':
  1106. case 'sawtooth':
  1107. case 'sine':
  1108. return this.synthset[name][1];
  1109. break;
  1110. case 'violin':
  1111. case 'cello':
  1112. case 'basse':
  1113. return this.synthset[name][1];
  1114. break;
  1115. case 'default':
  1116. case 'poly':
  1117. return this.synthset['poly'][1];
  1118. break;
  1119. default:
  1120. var drumName = getDrumSynthName(name);
  1121. if (name.slice(0, 4) == 'http') {
  1122. if (name in this.synthset) {
  1123. return this.synthset[name][1];
  1124. } else {
  1125. console.log('no synth by that name');
  1126. return null;
  1127. }
  1128. } else if (drumName != null) {
  1129. return this.synthset[drumName][1];
  1130. } else if (name === 'drum') {
  1131. return this.synthset[DEFAULTDRUM][1];
  1132. }
  1133. break;
  1134. }
  1135. // Use polysynth if all else fails.
  1136. return this.synthset['poly'][1];
  1137. };
  1138. this.loadSynth = function (name) {
  1139. var thisSynth = this.getSynthByName(name);
  1140. if (thisSynth == null) {
  1141. console.log('loading synth for ' + name);
  1142. switch (name) {
  1143. case 'pluck':
  1144. this.synthset['pluck'][1] = new Tone.PluckSynth();
  1145. break;
  1146. case 'triangle':
  1147. case 'square':
  1148. case 'sawtooth':
  1149. case 'sine':
  1150. var synthOptions = {
  1151. oscillator: {
  1152. type: name
  1153. },
  1154. envelope: {
  1155. attack: 0.03,
  1156. decay: 0,
  1157. sustain: 1,
  1158. release: 0.03
  1159. },
  1160. };
  1161. this.synthset[name][1] = new Tone.Synth(synthOptions);
  1162. break;
  1163. case 'poly':
  1164. case 'default':
  1165. this.synthset['poly'][1] = new Tone.PolySynth(6, Tone.AMSynth);
  1166. break;
  1167. case 'violin':
  1168. case 'cello':
  1169. case 'basse':
  1170. this.synthset[name][1] = new Tone.Sampler(this.synthset[name][0]);
  1171. break;
  1172. default:
  1173. if (name.slice(0, 4) == 'http') {
  1174. this.synthset[name] = [name, new Tone.Sampler(name)];
  1175. } else if (name.slice(0, 4) == 'file') {
  1176. this.synthset[name] = [name, new Tone.Sampler(name)];
  1177. } else {
  1178. this.synthset[name][1] = new Tone.Sampler(this.synthset[name][0]);
  1179. }
  1180. break;
  1181. }
  1182. }
  1183. this.getSynthByName(name).toMaster();
  1184. };
  1185. this.performNotes = function (synth, notes, beatValue, doVibrato, vibratoIntensity, vibratoFrequency) {
  1186. if (doVibrato) {
  1187. var vibrato = new Tone.Vibrato(1 / vibratoFrequency, vibratoIntensity);
  1188. synth.chain(vibrato, Tone.Master);
  1189. synth.triggerAttackRelease(notes, beatValue);
  1190. setTimeout(function () {
  1191. vibrato.dispose();
  1192. }, beatValue * 1000); //disable vibrato effect when beat is over
  1193. } else {
  1194. synth.triggerAttackRelease(notes, beatValue);
  1195. }
  1196. }
  1197. this.trigger = function (notes, beatValue, name, vibratoArgs) {
  1198. var doVibrato = false;
  1199. var vibratoIntensity = 0;
  1200. var vibratoFrequency = 0;
  1201. if (vibratoArgs.length == 2 && vibratoArgs[0] != 0) {
  1202. doVibrato = true;
  1203. vibratoIntensity = vibratoArgs[0];
  1204. vibratoFrequency = vibratoArgs[1];
  1205. }
  1206. switch (name) {
  1207. case 'pluck':
  1208. case 'triangle':
  1209. case 'square':
  1210. case 'sawtooth':
  1211. case 'sine':
  1212. if (typeof(notes) === 'object') {
  1213. var noteToPlay = notes[0];
  1214. } else {
  1215. var noteToPlay = notes;
  1216. }
  1217. this.performNotes(this.synthset[name][1], noteToPlay, beatValue, doVibrato, vibratoIntensity, vibratoFrequency);
  1218. break;
  1219. case 'violin':
  1220. case 'cello':
  1221. case 'basse':
  1222. // The violin sample is tuned to C6
  1223. // The cello sample is tuned to C4???
  1224. // The basse sample is tuned to C2???
  1225. var centerNo = SAMPLECENTERNO[name];
  1226. var obj = noteToPitchOctave(notes);
  1227. var noteNo = pitchToNumber(obj[0], obj[1], 'C Major');
  1228. this.performNotes(this.synthset[name][1], noteNo - centerNo, beatValue, doVibrato, vibratoIntensity, vibratoFrequency);
  1229. break;
  1230. case 'default':
  1231. case 'poly':
  1232. this.performNotes(this.synthset['poly'][1], notes, beatValue, doVibrato, vibratoIntensity, vibratoFrequency);
  1233. break;
  1234. default:
  1235. var drumName = getDrumSynthName(name);
  1236. if (drumName != null) {
  1237. // Work around i8n bug in Firefox.
  1238. if (drumName === '' && name in this.synthset) {
  1239. this.synthset[name][1].triggerAttack(0, beatValue);
  1240. } else if (drumName in this.synthset) {
  1241. if (this.synthset[drumName][1] == null) {
  1242. console.log('Something has gone terribly wrong: ' + name + ', ' + drumName);
  1243. } else {
  1244. this.synthset[drumName][1].triggerAttack(0);
  1245. }
  1246. } else if (name.slice(0, 4) == 'http') {
  1247. this.synthset[name][1].triggerAttack(0, beatValue);
  1248. } else if (name.slice(0, 4) == 'file') {
  1249. this.synthset[name][1].triggerAttack(0, beatValue);
  1250. } else {
  1251. console.log('Something has gone terribly wrong: ' + name + ', ' + drumName);
  1252. }
  1253. } else if (name === 'drum') {
  1254. this.synthset[DEFAULTDRUM][1].triggerAttack(0, beatValue, 1);
  1255. } else if (name.slice(0, 4) == 'http') {
  1256. this.synthset[name][1].triggerAttack(0, beatValue, 1);
  1257. } else if (name.slice(0, 4) == 'file') {
  1258. this.synthset[name][1].triggerAttack(0, beatValue, 1);
  1259. } else {
  1260. this.performNotes(this.synthset['poly'][1], notes, beatValue, doVibrato, vibratoIntensity, vibratoFrequency);
  1261. }
  1262. break;
  1263. }
  1264. };
  1265. this.stopSound = function (name) {
  1266. this.getSynthByName(name).triggerRelease();
  1267. };
  1268. this.start = function () {
  1269. Tone.Transport.start();
  1270. };
  1271. this.stop = function () {
  1272. Tone.Transport.stop();
  1273. };
  1274. this.setVolume = function (vol) {
  1275. var db = this.tone.gainToDb(vol / 100);
  1276. Tone.Master.volume.rampTo(db, 0.01);
  1277. };
  1278. this.getOscillator = function (oscillatorName, frequency) {
  1279. return new Tone.Oscillator(oscillatorName, frequency).toMaster();
  1280. };
  1281. };