@@ -17,7 +17,7 @@ const defaultSettings = {
1717 fontDirectory : '/font/bravura/'
1818 } ,
1919 player : {
20- enablePlayer : true ,
20+ playerMode : alphaTab . PlayerMode . EnabledAutomatic ,
2121 scrollOffsetX : - 10 ,
2222 soundFont : '/font/sonivox/sonivox.sf2'
2323 }
@@ -96,6 +96,125 @@ function createTrackItem(track, trackSelection) {
9696 return trackItem ;
9797}
9898
99+ let backingTrackScore = null ;
100+ let backingTrackAudioElement = null ;
101+ let waveForm = null ;
102+ let waveFormCursor = null ;
103+ window . onload = ( ) => {
104+ waveForm = document . querySelector ( '.at-waveform' ) ;
105+ waveFormCursor = waveForm . querySelector ( '.at-waveform-cursor' ) ;
106+ waveForm . onclick = ( e ) => {
107+ const percent = e . offsetX / waveForm . offsetWidth ;
108+ if ( backingTrackAudioElement ) {
109+ backingTrackAudioElement . currentTime = backingTrackAudioElement . duration * percent ;
110+ }
111+ } ;
112+ }
113+
114+ function updateWaveFormCursor ( ) {
115+ if ( waveFormCursor ) {
116+ waveFormCursor . style . left = ( ( backingTrackAudioElement . currentTime / backingTrackAudioElement . duration ) * 100 ) + '%' ;
117+ }
118+ } ;
119+
120+ function hideBackingTrack ( at ) {
121+ if ( backingTrackAudioElement ) {
122+ backingTrackAudioElement . removeEventListener ( 'timeupdate' , updateWaveFormCursor ) ;
123+ backingTrackAudioElement . removeEventListener ( 'durationchange' , updateWaveFormCursor ) ;
124+ backingTrackAudioElement . removeEventListener ( 'seeked' , updateWaveFormCursor ) ;
125+ }
126+ const waveForm = document . querySelector ( '.at-waveform' ) ;
127+ waveForm . classList . add ( 'd-none' ) ;
128+ }
129+
130+
131+ async function showBackingTrack ( at ) {
132+
133+ const audioElement = at . player . output . audioElement ;
134+ if ( audioElement !== backingTrackAudioElement ) {
135+ backingTrackAudioElement = audioElement ;
136+ audioElement . addEventListener ( 'timeupdate' , updateWaveFormCursor ) ;
137+ audioElement . addEventListener ( 'durationchange' , updateWaveFormCursor ) ;
138+ audioElement . addEventListener ( 'seeked' , updateWaveFormCursor ) ;
139+ updateWaveFormCursor ( ) ;
140+ }
141+
142+ const score = at . score ;
143+ if ( score === backingTrackScore ) {
144+ return ;
145+ }
146+ backingTrackScore = at . score ;
147+
148+ const audioContext = new AudioContext ( ) ;
149+ const rawData = await audioContext . decodeAudioData (
150+ structuredClone ( at . score . backingTrack . rawAudioFile . buffer )
151+ ) ;
152+
153+ const topChannel = rawData . getChannelData ( 0 ) ;
154+ const bottomChannel = rawData . numberOfChannels > 1 ? rawData . getChannelData ( 1 ) : topChannel ;
155+ const length = topChannel . length
156+
157+ waveForm . classList . remove ( 'd-none' ) ;
158+
159+ const canvas = document . querySelector ( '.at-waveform canvas' ) ?? document . createElement ( 'canvas' ) ;
160+ const width = waveForm . offsetWidth ;
161+ const height = 80 ;
162+ canvas . width = width ;
163+ canvas . height = height ;
164+ waveForm . appendChild ( canvas ) ;
165+
166+ const ctx = canvas . getContext ( '2d' ) ;
167+
168+ const pixelRatio = window . devicePixelRatio ;
169+ const halfHeight = height / 2 ;
170+
171+ const barWidth = 2 * pixelRatio ;
172+ const barGap = 1 * pixelRatio ;
173+ const barIndexScale = width / ( barWidth + barGap ) / length
174+
175+ ctx . beginPath ( ) ;
176+
177+ let prevX = 0
178+ let maxTop = 0
179+ let maxBottom = 0
180+ for ( let i = 0 ; i <= length ; i ++ ) {
181+ const x = Math . round ( i * barIndexScale )
182+
183+ if ( x > prevX ) {
184+ const topBarHeight = Math . round ( maxTop * halfHeight )
185+ const bottomBarHeight = Math . round ( maxBottom * halfHeight )
186+ const barHeight = topBarHeight + bottomBarHeight || 1
187+
188+ ctx . roundRect ( prevX * ( barWidth + barGap ) , halfHeight - topBarHeight , barWidth , barHeight , 2 )
189+
190+ prevX = x
191+ maxTop = 0
192+ maxBottom = 0
193+ }
194+
195+ const magnitudeTop = Math . abs ( topChannel [ i ] || 0 )
196+ const magnitudeBottom = Math . abs ( bottomChannel [ i ] || 0 )
197+ if ( magnitudeTop > maxTop ) maxTop = magnitudeTop
198+ if ( magnitudeBottom > maxBottom ) maxBottom = magnitudeBottom
199+ }
200+
201+ ctx . fillStyle = '#436d9d'
202+ ctx . fill ( )
203+ }
204+
205+ function updateBackingTrack ( at ) {
206+ switch ( at . actualPlayerMode ) {
207+ case alphaTab . PlayerMode . Disabled :
208+ case alphaTab . PlayerMode . EnabledSynthesizer :
209+ case alphaTab . PlayerMode . EnabledExternalMedia :
210+ hideBackingTrack ( at ) ;
211+ break ;
212+ case alphaTab . PlayerMode . EnabledBackingTrack :
213+ showBackingTrack ( at ) ;
214+ break ;
215+ }
216+ }
217+
99218export function setupControl ( selector , customSettings ) {
100219 const el = document . querySelector ( selector ) ;
101220 const control = el . closest ( '.at-wrap' ) ;
@@ -184,11 +303,25 @@ export function setupControl(selector, customSettings) {
184303 trackItems . push ( trackItem ) ;
185304 trackList . appendChild ( trackItem ) ;
186305 } ) ;
306+
307+ updateBackingTrack ( at ) ;
187308 } ) ;
188309
189310 const timePositionLabel = control . querySelector ( '.at-time-position' ) ;
190311 const timeSliderValue = control . querySelector ( '.at-time-slider-value' ) ;
191312
313+ const timeSlider = control . querySelector ( '.at-time-slider' ) ;
314+ let songTimeInfo = null ;
315+ timeSlider . onclick = ( e ) => {
316+ const percent = e . offsetX / timeSlider . offsetWidth ;
317+ if ( songTimeInfo ) {
318+ at . timePosition = Math . floor ( songTimeInfo . endTime * percent ) ;
319+ }
320+ } ;
321+ at . midiLoaded . on ( e => { songTimeInfo = e ; } ) ;
322+
323+
324+
192325 function formatDuration ( milliseconds ) {
193326 let seconds = milliseconds / 1000 ;
194327 const minutes = ( seconds / 60 ) | 0 ;
@@ -214,6 +347,8 @@ export function setupControl(selector, customSettings) {
214347 control . querySelectorAll ( '.at-player .disabled' ) . forEach ( function ( c ) {
215348 c . classList . remove ( 'disabled' ) ;
216349 } ) ;
350+
351+ updateBackingTrack ( at ) ;
217352 } ) ;
218353
219354 at . playerStateChanged . on ( function ( args ) {
0 commit comments