Source

vendors/AlphaTab/listeners.js

  1. // File imports
  2. import atVars from "./variables";
  3. import { startPlayingMusic } from "./actions";
  4. import { store } from "../../store/reduxSetup";
  5. import Drawer from "../P5/Drawer";
  6. import * as sketchBehaviors from "../P5/sketchBehaviors";
  7. import p5 from "p5";
  8. import feedbackSketch from "../P5/sketchFeedback";
  9. import performanceSketch from "../P5/sketchPerformance";
  10. import { stopPitchDetection } from "../ML5/PitchDetection/actions";
  11. import * as playerStates from "./playerStates";
  12. /**
  13. * Functions called when an AlphaTab listener is triggered
  14. * @module alphaTabListeners
  15. * @category AlphaTab
  16. * @author Daniel Griessler <dgriessler20@gmail.com>
  17. * @author Dan Levy <danlevy124@gmail.com>
  18. */
  19. /**
  20. * Run when AlphaTab is rendered on the screen
  21. * @function
  22. */
  23. export const alphaTabPostRenderFinished = () => {
  24. // Retrieves staff lines using IDs attacked to elements generated by AlphaTab
  25. // Note: This required editing AlphaTab.js directly
  26. let topLine = document.getElementById("rect_0");
  27. let nextLine = document.getElementById("rect_1");
  28. if (!atVars.isFirstRender) {
  29. onSubsequentRender(topLine, nextLine);
  30. } else {
  31. onFirstRender(topLine, nextLine);
  32. }
  33. };
  34. /**
  35. * Handles changes to AlphaTab's player state
  36. * @function
  37. */
  38. export const alphaTabPlayerStateChanged = () => {
  39. // Due to our page turns, the AlphaTex is re rendered and the player state is automatically "stopped" as part of the re rendering
  40. // We want the sheet music to keep playing though so the checks are as follows
  41. if (
  42. atVars.api.playerState !== playerStates.PLAYING &&
  43. atVars.playerState === playerStates.PLAYING
  44. ) {
  45. // Real stop -> stop playing the music
  46. stopPlayingMusic();
  47. resetSheetMusic();
  48. } else if (
  49. atVars.api.playerState === playerStates.PLAYING &&
  50. atVars.playerState === playerStates.STOPPED
  51. ) {
  52. // Real play -> play the music
  53. startPlayingMusic();
  54. } else if (atVars.playerState === playerStates.PENDING_STOP) {
  55. // Request was made to destroy the api -> so stop playing the music
  56. stopPlayingMusic();
  57. }
  58. };
  59. /**
  60. * Resets the time back to the beginning of the song and our tracker points at the beginning of the piece again
  61. * @function
  62. */
  63. export const alphaTabPlayerFinished = () => {
  64. atVars.noteStreamIndex = 0;
  65. atVars.cumulativeTime = 0;
  66. };
  67. /**
  68. * During page turns, we only need to update a subset of variables
  69. * @function
  70. */
  71. const onSubsequentRender = (topLine, nextLine) => {
  72. // On a re-render, update the top line height and distance between lines which might have changed
  73. const { topLineHeight, distanceBetweenLines } = getSheetMusicLedgerHeights(
  74. topLine,
  75. nextLine
  76. );
  77. atVars.drawer.setTopLineAndDistanceBetween(
  78. topLineHeight,
  79. distanceBetweenLines,
  80. atVars.texLoaded.getStartOctave()
  81. );
  82. atVars.texLoaded.setFirstMeasurePosition();
  83. if (atVars.sketchBehavior === atVars.p5Obj.type) {
  84. // On a re render, the alpha tab surface might have changed size, so resize the p5 drawing canvas to overlay it
  85. let aTS = document.getElementById("aTS");
  86. atVars.p5Obj.resizeCanvas(aTS.clientWidth, aTS.clientHeight);
  87. atVars.p5Obj.clear();
  88. } else {
  89. atVars.p5Obj.remove();
  90. atVars.p5Obj = null;
  91. if (atVars.sketchBehavior === sketchBehaviors.REAL_TIME_FEEDBACK) {
  92. // Creates a new p5 instance which we will use for real time feedback during performance
  93. atVars.p5Obj = new p5(feedbackSketch);
  94. } else if (
  95. atVars.sketchBehavior === sketchBehaviors.PERFORMANCE_HIGHLIGHTING
  96. ) {
  97. // Sets up drawing for performance highlighting
  98. // Creates a new p5 instance which we will use for highlighting during performance overview
  99. atVars.p5Obj = new p5(performanceSketch);
  100. }
  101. // setup is called immediately upon creating a new p5 sketch but we need to call it explictly to give it a handle
  102. // to the drawer that we created. This also signals to actually create an appropriately sized canvas since Alpha Tab
  103. // is now actually rendered to the dom
  104. atVars.p5Obj.setup(atVars.drawer);
  105. }
  106. };
  107. /**
  108. * On the first render, isFirstRender will be false and this will signal an initial setup of the variables
  109. * @function
  110. */
  111. const onFirstRender = async (topLine, nextLine) => {
  112. atVars.isFirstRender = false;
  113. // We were getting an error where rect_0 or rect_1 were null even though AlphaTab said they were rendered
  114. // This sets up an interval to keep waiting for them to not be null before moving on with the render process
  115. const lineReadyID = setInterval(() => {
  116. if (topLine !== null && nextLine !== null) {
  117. // stop interval from running
  118. clearInterval(lineReadyID);
  119. atVars.texLoaded.setFirstMeasurePosition();
  120. // Sets up drawing
  121. initializeFeedbackDrawer(topLine, nextLine);
  122. if (atVars.sketchBehavior === sketchBehaviors.REAL_TIME_FEEDBACK) {
  123. // Creates a new p5 instance which we will use for real time feedback during performance
  124. atVars.p5Obj = new p5(feedbackSketch);
  125. // setup is called immediately upon creating a new p5 sketch but we need to call it explictly to give it a handle
  126. // to the drawer that we created. This also signals to actually create an appropriately sized canvas since Alpha Tab
  127. // is now actually rendered to the dom
  128. atVars.p5Obj.setup(atVars.drawer);
  129. } else {
  130. // Sets up drawing for performance highlighting
  131. // Creates a new p5 instance which we will use for highlighting during performance overview
  132. atVars.p5Obj = new p5(performanceSketch);
  133. // setup is called immediately upon creating a new p5 sketch but we need to call it explictly to give it a handle
  134. // to the drawer that we created. This also signals to actually create an appropriately sized canvas since Alpha Tab
  135. // is now actually rendered to the dom
  136. atVars.p5Obj.setup(atVars.drawer);
  137. }
  138. } else {
  139. // keeps trying to retrieve the top line and next line of the Alpha Tab music until they are loaded in the dom
  140. topLine = document.getElementById("rect_0");
  141. nextLine = document.getElementById("rect_1");
  142. }
  143. }, 500);
  144. };
  145. /**
  146. * Creates an instance of the of the the drawer
  147. * @function
  148. * @param {object} topLine - The top line DOM element
  149. * @param {object} nextLine - The next line DOM element
  150. */
  151. const initializeFeedbackDrawer = (topLine, nextLine) => {
  152. const { topLineHeight, distanceBetweenLines } = getSheetMusicLedgerHeights(
  153. topLine,
  154. nextLine
  155. );
  156. // Creates a new drawer using the top line height, distance between lines, and start octave of the music to decide
  157. // how high to draw the notes for feedback
  158. atVars.drawer = new Drawer(
  159. topLineHeight + 1,
  160. distanceBetweenLines,
  161. atVars.texLoaded.getStartOctave()
  162. );
  163. };
  164. /**
  165. * Stops playing the music.
  166. * Shuts off pitch detection (this does not turn the microphone off).
  167. * Resets the sheet music (this is due to pagination).
  168. * @function
  169. */
  170. const stopPlayingMusic = () => {
  171. // Stops the pitch detection
  172. stopPitchDetection(store.getState().practice.selectedSheetMusicId);
  173. // Changes player state to stopped
  174. atVars.playerState = playerStates.STOPPED;
  175. // Resets the sheet music back to the beginning
  176. resetSheetMusic();
  177. };
  178. /**
  179. * @typedef LedgerHeightsPackage
  180. * @property {number} topLineHeight The y height of the top ledger line
  181. * @property {number} distanceBetweenLines The y distance between ledger lines
  182. */
  183. /**
  184. * Gets the top line height and distance between ledger lines based on the sheet music
  185. * @function
  186. * @param {object} topLine
  187. * @param {object} nextLine
  188. * @returns {module:alphaTabListeners~LedgerHeightsPackage} An object containing the top line height and distance between lines
  189. */
  190. const getSheetMusicLedgerHeights = (topLine, nextLine) => {
  191. // Retrieves the height of the staff lines based on a relative offset to their wrapping contanier
  192. // Used to setup the p5Obj canvas so the canvas needs to be directly on top of the alphaTab container where these are stored
  193. const topLineHeight = topLine.y.animVal.value;
  194. return {
  195. topLineHeight: topLine.y.animVal.value,
  196. distanceBetweenLines: nextLine.y.animVal.value - topLineHeight,
  197. };
  198. };
  199. /**
  200. * Clears the p5 drawing for real-time feedback.
  201. * Resets the sheet music back to the beginning.
  202. * @function
  203. */
  204. const resetSheetMusic = () => {
  205. // Clears the p5 drawing
  206. atVars.p5Obj.clear();
  207. // Resets the sheet music
  208. atVars.api.settings.display.startBar = 1;
  209. atVars.api.settings.display.barCount = atVars.barCount;
  210. atVars.api.updateSettings();
  211. atVars.api.render();
  212. // Resets the progress through expected performance
  213. atVars.noteStreamIndex = 0;
  214. atVars.cumulativeTime = 0;
  215. };