Source

vendors/P5/sketchFeedback.js

  1. // File imports
  2. import atVars from "../AlphaTab/variables";
  3. import * as sketchBehaviors from "./sketchBehaviors";
  4. /**
  5. * P5 sketch wrapper for real time feedback
  6. * @module sketchFeedback
  7. * @category P5
  8. * @author Daniel Griessler <dgriessler20@gmail.com>
  9. * @author Dan Levy <danlevy124@gmail.com>
  10. */
  11. /**
  12. * Wrapper for local p5 setup and draw functions
  13. * @function
  14. * @param {sketch} p Sketch object that will include all of the functions that will be called by p5
  15. */
  16. const p5FeedbackSketch = (p) => {
  17. p.type = sketchBehaviors.REAL_TIME_FEEDBACK;
  18. // how much space to add around note for drawing lines, obtained by guess and check
  19. const EXTRA_BAR_VARIANCE = 7;
  20. // document elements retrieved from the document
  21. let barCursor;
  22. let alphaTabSurface;
  23. let sideNavElementWidth;
  24. // wraps together the Alpha tab container and p5's canvas
  25. let wrapper;
  26. // provided by reference which is updated in other functions
  27. let drawer;
  28. // reference to p5's canvas when created. It should overlay the AlphaTab canvas
  29. let canvas;
  30. // how large to draw the notehead
  31. // TODO: Dynamically change based on scale of music
  32. let circleSize = 10;
  33. // stores last two XY coordinates of notes drawn
  34. let previousPos = [-1, -1, -1, -1];
  35. // stores the last recorded midi value and its time
  36. let lastPitchAndTime = [-1, -1];
  37. /**
  38. * This function is called twice. Once, upon initialization p5 calls it which we use to tell p5 to stop looping
  39. * Then, AlphaTab will call setup when its done being rendered. Then, the canvas can be setup for drawing since
  40. * the canvas overlays the AlphaTab container
  41. * @param {Drawer} drawerGiven - p5 will not provide this but atVars provides a reference to the Drawer being used
  42. */
  43. p.setup = function (drawerGiven) {
  44. if (drawerGiven === undefined) {
  45. p.noLoop();
  46. return;
  47. }
  48. drawer = drawerGiven;
  49. // Retrieved by attaching unique IDs to elements generated by AlphaTab. Required editing AlphaTab.js directly
  50. barCursor = document.getElementById("bC");
  51. alphaTabSurface = document.getElementById("aTS");
  52. const sideNavElement = document.querySelector("#side-nav");
  53. sideNavElementWidth =
  54. sideNavElement !== null ? sideNavElement.clientWidth : 0;
  55. wrapper = document.getElementById("alpha-tab-wrapper");
  56. // creates a canvas that overlaps the alphaTabSurface. Position is absolute for the canvas by default
  57. canvas = p.createCanvas(
  58. alphaTabSurface.clientWidth,
  59. alphaTabSurface.clientHeight
  60. );
  61. const x = 0;
  62. const y = 0;
  63. canvas.position(x, y);
  64. canvas.parent("sketch-holder");
  65. };
  66. /**
  67. * Updates the drawer line heights from the page if the drawer is active
  68. * @function
  69. */
  70. const updateDrawerLines = () => {
  71. let topLine = document.getElementById("rect_0");
  72. let nextLine = document.getElementById("rect_1");
  73. if (
  74. drawer &&
  75. topLine &&
  76. topLine.y &&
  77. topLine.y.animVal &&
  78. topLine.y.animVal.value &&
  79. nextLine &&
  80. nextLine.y &&
  81. nextLine.y.animVal &&
  82. nextLine.y.animVal.value
  83. ) {
  84. const topLineHeight = topLine.y.animVal.value;
  85. const distanceBetweenLines =
  86. nextLine.y.animVal.value - topLineHeight;
  87. drawer.setTopLineAndDistanceBetween(
  88. topLineHeight,
  89. distanceBetweenLines,
  90. drawer.baseOctave
  91. );
  92. }
  93. };
  94. /**
  95. * Draws the canvas on the screen. Requires that the canvas is not undefined ie setup has run
  96. * TODO: Handle sheet music scale
  97. */
  98. p.draw = function () {
  99. if (!atVars.getsFeedback) {
  100. return;
  101. }
  102. if (atVars && atVars.shouldResetDrawPositions) {
  103. previousPos[0] = -1;
  104. previousPos[1] = -1;
  105. previousPos[2] = -1;
  106. previousPos[3] = -1;
  107. atVars.shouldResetDrawPositions = false;
  108. barCursor = document.getElementById("bC");
  109. alphaTabSurface = document.getElementById("aTS");
  110. }
  111. // handles clearing ahead and drawing line behind the note head
  112. if (previousPos[0] !== -1 && previousPos[1] !== -1) {
  113. // fills with white
  114. p.fill("#F8F8F8");
  115. // draws clearing rectangle with total height of alpha tab from previous X position to the end
  116. p.rect(
  117. previousPos[0],
  118. 0,
  119. alphaTabSurface.clientWidth - previousPos[0],
  120. alphaTabSurface.clientHeight
  121. );
  122. if (previousPos[2] !== -1) {
  123. // If there is any confusion in the player then this will help keep the drawing at the right height
  124. if (previousPos[0] < previousPos[2]) {
  125. updateDrawerLines();
  126. }
  127. }
  128. if (previousPos[2] !== -1) {
  129. // If there is any confusion in the player then this will help keep the drawing at the right height
  130. if (previousPos[0] < previousPos[2]) {
  131. updateDrawerLines();
  132. }
  133. }
  134. // don't draw silence which has special value -1 or if we don't have a previous point
  135. if (
  136. drawer &&
  137. drawer.note.midiVal >= 0 &&
  138. previousPos[2] !== -1 &&
  139. previousPos[3] !== -1
  140. ) {
  141. if (atVars.noteStream[atVars.noteStreamIndex] === -1) {
  142. // singing should be silent
  143. p.stroke(255, 0, 0);
  144. } else {
  145. let diff = Math.abs(
  146. lastPitchAndTime[0] -
  147. atVars.noteStream[atVars.noteStreamIndex]
  148. );
  149. // fill with green if really close
  150. if (diff < 1) {
  151. p.stroke(0, 255, 0);
  152. } else if (diff < 2) {
  153. // yellow if farther away
  154. p.stroke("#CCCC00");
  155. } else {
  156. // and red if too far or singing when should be silent
  157. p.stroke(255, 0, 0);
  158. }
  159. }
  160. p.strokeWeight(3);
  161. p.line(
  162. previousPos[0],
  163. previousPos[1],
  164. previousPos[2],
  165. previousPos[3]
  166. );
  167. p.noStroke();
  168. }
  169. previousPos[2] = previousPos[0];
  170. previousPos[3] = previousPos[1];
  171. }
  172. // dont draw the outline of the shape, note: you need to turn stroke on to draw lines as we do below.
  173. p.noStroke();
  174. if (drawer) {
  175. lastPitchAndTime[0] = drawer.note.midiVal;
  176. lastPitchAndTime[1] = atVars.api.timePosition / 1000;
  177. while (
  178. lastPitchAndTime[1] >
  179. atVars.cumulativeTime +
  180. atVars.noteStream[atVars.noteStreamIndex + 1]
  181. ) {
  182. atVars.cumulativeTime +=
  183. atVars.noteStream[atVars.noteStreamIndex + 1];
  184. atVars.noteStreamIndex += 2;
  185. }
  186. let currentHeight = drawer.noteHeight;
  187. previousPos[1] = currentHeight;
  188. // fills with pink
  189. p.fill(255, 0, 255);
  190. // Binds x position to the bar cursor
  191. let posX =
  192. barCursor.getClientRects()[0].left.valueOf() -
  193. sideNavElementWidth +
  194. wrapper.scrollLeft -
  195. 27;
  196. // TODO: Handle resizing scale
  197. // places sharp if present beside the note. These magic values were calculated via trial and error
  198. let sharpPos = [posX - 14, currentHeight + 3.5];
  199. previousPos[0] = sharpPos[0] - EXTRA_BAR_VARIANCE;
  200. // silence has Sentinel value -1 so only draw when not silent
  201. if (drawer.note.midiVal >= 0) {
  202. // actually draws note circle at the given position
  203. p.ellipse(posX, currentHeight, circleSize, circleSize);
  204. // TODO Handle resizing scale
  205. // Adds sharp symbol if needed
  206. if (drawer.note.isSharp) {
  207. p.text("#", sharpPos[0], sharpPos[1]);
  208. }
  209. // Adds ledger lines above or below the staff
  210. if (drawer.belowOrAbove !== 0) {
  211. let isIncreasing = drawer.belowOrAbove > 0;
  212. p.stroke(0);
  213. p.strokeWeight(1);
  214. let height = isIncreasing
  215. ? drawer.topLine
  216. : drawer.firstLine - drawer.distanceBetweenLines;
  217. for (let i = 0; i < Math.abs(drawer.belowOrAbove); i++) {
  218. if (isIncreasing) {
  219. height -= drawer.distanceBetweenLines;
  220. } else {
  221. height += drawer.distanceBetweenLines;
  222. }
  223. p.line(
  224. posX - EXTRA_BAR_VARIANCE,
  225. height,
  226. posX + EXTRA_BAR_VARIANCE,
  227. height
  228. );
  229. }
  230. p.noStroke();
  231. }
  232. }
  233. }
  234. };
  235. };
  236. export default p5FeedbackSketch;