metro.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. *
  3. * 修改自:
  4. * https://github.com/zya/wa-metro/blob/master/lib/wa-metro.js
  5. *
  6. */
  7. import runtime, * as RunTimeUtils from '/src/pages/detail/runtime'
  8. import state from '/src/pages/detail/state'
  9. type ICallback = (time?: number, step?: number, scheduler_interval?: number, context?: AudioContext) => void
  10. type IOptions = {
  11. steps: number
  12. tempo: number
  13. denominator: number
  14. numerator: number
  15. limit: number
  16. onStop?: () => void
  17. onTick?: (tick?: number) => void
  18. }
  19. export const ac = (window.AudioContext || (window as any).webkitAudioContext || (window as any).mozAudioContext || (window as any).msAudioContext)
  20. class Metro {
  21. audio: HTMLAudioElement = new Audio()//runtime.audiosInstance.audio.cloneNode(true)
  22. context = new ac()
  23. callback: ICallback = () => {}
  24. look_ahead = 1.0
  25. _step = 1
  26. _scheduler_interval = 20
  27. _next_event_time = 0.0
  28. _first = true
  29. _is_running = false
  30. timer: number = -1
  31. tiems: number = 0
  32. tick: number = 0
  33. options: IOptions = {
  34. steps: 4,
  35. tempo: 90,
  36. denominator: 4,
  37. numerator: 4,
  38. limit: 0,
  39. onStop: () => {},
  40. onTick: () => {}
  41. }
  42. constructor(options?: IOptions) {
  43. this.options = {
  44. ...this.options,
  45. ...options
  46. }
  47. }
  48. refreshTick = () => {
  49. RunTimeUtils.refreshTick(this.audio.currentTime)
  50. if (this.audio.currentTime >= 4.8) {
  51. this.stop()
  52. }
  53. }
  54. start = (options?: IOptions) => {
  55. // state.showTick = true
  56. this.audio.play()
  57. this.audio.addEventListener('timeupdate', this.refreshTick)
  58. // if (this._is_running) {
  59. // console.log('already started');
  60. // return;
  61. // }
  62. // this._step = 1
  63. this.options = {
  64. ...this.options,
  65. ...options
  66. }
  67. // this._is_running = true;
  68. // this.timer = setInterval(() => {
  69. // this._scheduler()
  70. // }, 25)
  71. // this._worker.postMessage('start');
  72. };
  73. pause = () => {
  74. this._is_running = false;
  75. // this._worker.postMessage('stop');
  76. this.stop()
  77. };
  78. stop = (dontCall?: boolean) => {
  79. // state.showTick = false
  80. this.audio.pause()
  81. this.audio.removeEventListener('timeupdate', this.refreshTick)
  82. if (this.audio.currentTime >= 4.8) {
  83. this.options.onStop && this.options.onStop()
  84. }
  85. runtime.osmd.cursor.hide()
  86. this.audio.currentTime = 0
  87. this.refreshTick()
  88. // this._first = true;
  89. // this._step = 1;
  90. // this._is_running = false;
  91. // clearInterval(this.timer)
  92. // this.timer = -1
  93. // this.tiems = 0
  94. // this.tick = 0
  95. // this.context = new ac()
  96. // if (dontCall !== true) {
  97. // setTimeout(() => {
  98. // this._is_running = false;
  99. // this.options.onStop && this.options.onStop()
  100. // this.context.close()
  101. // }, 500)
  102. // } else {
  103. // // this.context.close()
  104. // }
  105. // this._worker.postMessage('stop');
  106. };
  107. scheduleNote = (beatNumber: number, time: number) => {
  108. // create an oscillator
  109. var osc = this.context.createOscillator();
  110. osc.connect( this.context.destination );
  111. // if (beatNumber % 16 === 0) // beat 0 == low pitch
  112. // osc.frequency.value = 880.0;
  113. // osc.type = 'sine';
  114. // osc.detune.value = -300;
  115. if (beatNumber === 1) {
  116. osc.frequency.value = 880.0;
  117. } else {
  118. osc.frequency.value = 440.0;
  119. }
  120. osc.start( time );
  121. osc.stop( time + 0.05 );
  122. // osc.onended = () => {
  123. // console.log(time)
  124. // this.tick ++
  125. // this.options.onTick && this.options.onTick(this.tick);
  126. // // if (this.options.limit > 0 && this.tick === this.options.limit * this.options.numerator) {
  127. // // this.options.onStop && this.options.onStop()
  128. // // }
  129. // }
  130. }
  131. _scheduler = () => {
  132. var self = this;
  133. if (this._step === 1 && this._first) {
  134. this._next_event_time = this.context.currentTime;
  135. }
  136. while (this._next_event_time < this.context.currentTime + this.look_ahead && this._is_running) {
  137. if (this.options.limit > 0 && this.tiems >= this.options.limit) {
  138. this.stop()
  139. break
  140. }
  141. if (this._step === this.options.numerator) {
  142. this.tiems ++
  143. }
  144. this.options.onTick && this.options.onTick(this._step);
  145. // var event_time_from_scheduled = self._next_event_time - self.context.currentTime;
  146. this.scheduleNote(this._step, self._next_event_time)
  147. this._next();
  148. }
  149. };
  150. _next = () => {
  151. this._step++;
  152. if (this._first) {
  153. this._next_event_time = this.context.currentTime;
  154. this._first = false;
  155. }
  156. // if (this._step > this.options.numerator) {
  157. // this._step = 1;
  158. // }
  159. this._next_event_time += ((60.0 / this.options.tempo) * 4) / this.options.denominator;
  160. };
  161. }
  162. export default Metro