xribene commited on
Commit
b8caa91
·
1 Parent(s): a9fcdaa

alphatab + drawAndListen

Browse files
frontend/public/Fantasia_no.2_importedFromGpByMusescore3.musicxml ADDED
The diff for this file is too large to render. See raw diff
 
frontend/src/App.vue CHANGED
@@ -31,17 +31,15 @@ const state = ref({
31
  Analyzer
32
  </RouterLink>
33
  </li>
 
 
 
 
 
34
  </ul>
35
  </nav>
36
 
37
  <div class="content">
38
- <!-- <a href="https://vitejs.dev" target="_blank">
39
- <img src="/guitarDiffLogo2.svg" class="logo" alt="Vite logo" />
40
- </a> -->
41
-
42
- <!-- <keep-alive>
43
- <router-view />
44
- </keep-alive> -->
45
  <RouterView v-slot="{ Component }">
46
  <keep-alive>
47
  <component :is="Component" :key="$route.fullPath" />
@@ -60,9 +58,9 @@ const state = ref({
60
  }
61
 
62
  .navbar {
63
- background-color: #c5bdbd;
64
- margin: 0px;
65
- padding: 0px;
66
  /* padding: 1rem; */
67
  /* width: 100%; */
68
  /* margin-left: 10px; */
@@ -83,7 +81,11 @@ const state = ref({
83
 
84
  .nav-list li a {
85
  text-decoration: none;
86
- color: rgb(78, 74, 74);
 
 
 
 
87
  }
88
 
89
  .content {
 
31
  Analyzer
32
  </RouterLink>
33
  </li>
34
+ <li>
35
+ <RouterLink :to="{ name: 'DrawAndListen' }">
36
+ Draw&Listen
37
+ </RouterLink>
38
+ </li>
39
  </ul>
40
  </nav>
41
 
42
  <div class="content">
 
 
 
 
 
 
 
43
  <RouterView v-slot="{ Component }">
44
  <keep-alive>
45
  <component :is="Component" :key="$route.fullPath" />
 
58
  }
59
 
60
  .navbar {
61
+ background-color: var(--background-light2);
62
+ /* margin: 10px; */
63
+ padding: 5px;
64
  /* padding: 1rem; */
65
  /* width: 100%; */
66
  /* margin-left: 10px; */
 
81
 
82
  .nav-list li a {
83
  text-decoration: none;
84
+ color: var(--text-color-dark);
85
+ background-color: var(--accent-color2);
86
+ padding: 0.8rem;
87
+ font-size: 1.2rem;
88
+ font-weight: 600;
89
  }
90
 
91
  .content {
frontend/src/components/AlphaTabWidget.vue CHANGED
@@ -1,5 +1,5 @@
1
  <script setup>
2
- import { computed, onMounted, onUpdated, ref, watch } from 'vue'
3
  import { Pane } from 'tweakpane'
4
 
5
  const props = defineProps({
@@ -22,9 +22,9 @@ const props = defineProps({
22
 
23
  const emit = defineEmits(['updateScoreCode'])
24
 
25
- const openSheetMusicDisplay = null
26
- const timeoutID = null
27
- let pane = null
28
  let api = null
29
  let main = null
30
  let wrapper = null
@@ -37,48 +37,61 @@ const parsingError = ref('')
37
  // const count = ref(0)
38
  const settingsStatus = ref(false)
39
  const isActive = ref(false)
40
-
41
- const SETTINGS_PARAMS = {
42
- scale: 1.0,
43
- // width: 800,
44
- // factor: 123,
45
- // title: 'hello',
46
- // color: '#ff0055',
47
- // percentage: 50,
48
- // theme: 'dark',
49
- // prop: 'Put your\nmultiline\ntext here!'
50
- }
 
 
51
 
52
  const divId = computed(() => {
53
  return `xmlScoreContainer_${props.id}`
54
  })
55
 
56
- function initializePane() {
57
- pane = new Pane({
58
- container: document.getElementById('paneContainerXml'),
59
- title: 'MusicXML Settings',
60
- },
61
- )
62
- pane.addBinding(SETTINGS_PARAMS, 'scale', {
63
- min: 0.5,
64
- max: 2.0,
65
- step: 0.1,
66
- }).on('change', (ev) => {
67
- console.log(ev.value)
68
- openSheetMusicDisplay.zoom = ev.value
69
- openSheetMusicDisplay.render()
70
- })
71
- }
 
 
 
 
 
 
 
 
 
72
 
73
- onMounted(async () => {
74
  // console.log($(`#${divId.value}`)[0])
75
- initializePane()
76
- wrapper = document.querySelector('.at-wrap')
77
- main = wrapper.querySelector('.at-main')
 
 
78
  // initialize alphatab
79
  settings = {
80
  // file: 'https://www.alphatab.net/files/canon.gp',
81
- // file: '/Fantasia_no.2.musicxml.gpx',
82
  player: {
83
  enablePlayer: true,
84
  soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
@@ -89,12 +102,12 @@ onMounted(async () => {
89
  })
90
 
91
  function initializeOSMD() {
 
92
  api = new alphaTab.AlphaTabApi(main, settings)
93
- api.tex(`
94
- \\title 'Test' . 3.3.4
95
- :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
96
-
97
- `)
98
 
99
  // overlay logic
100
  const overlay = wrapper.querySelector('.at-overlay')
@@ -271,77 +284,7 @@ function initializeOSMD() {
271
  songPosition.innerText
272
  = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
273
  })
274
-
275
- // await fetch('/guitar-diff-example.musicxml')
276
- // .then((response) => {
277
- // return response.text()
278
- // })
279
- // .then((data) => {
280
- // console.log('in data')
281
- // openSheetMusicDisplay.load(data)
282
- // .then(
283
- // () => {
284
- // window.osmd = openSheetMusicDisplay
285
- // openSheetMusicDisplay.render()
286
- // // openSheetMusicDisplay.cursor.show();
287
- // console.log('in load')
288
- // showParsingError.value = false
289
- // currentScoreCode.value = data
290
- // },
291
- // )
292
- // .catch((e) => {
293
- // showParsingError.value = true
294
- // console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
295
- // parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
296
- // })
297
- // })
298
- }
299
-
300
- // function showScore() {
301
- // openSheetMusicDisplay.clear()
302
- // console.log(currentScoreCode.value)
303
- // openSheetMusicDisplay.load(currentScoreCode.value)
304
- // .then(
305
- // () => {
306
- // window.osmd = openSheetMusicDisplay
307
- // openSheetMusicDisplay.render()
308
- // // openSheetMusicDisplay.cursor.show();
309
- // console.log('in load')
310
- // showParsingError.value = false
311
- // },
312
- // )
313
- // .catch((e) => {
314
- // showParsingError.value = true
315
- // console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
316
- // parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
317
- // })
318
- // }
319
-
320
- // function handleKeyUp(_event) {
321
- // if (timeoutID)
322
- // clearTimeout(timeoutID)
323
- // timeoutID = setTimeout(showScore, 500)
324
- // }
325
-
326
- // function handleDrop(event) {
327
- // event.preventDefault()
328
- // const textData = event.dataTransfer.getData('text')
329
- // const file = event.dataTransfer.files[0]
330
- // console.log(textData)
331
- // console.log(file)
332
- // if (file) {
333
- // const reader = new FileReader()
334
- // reader.onload = function (e) {
335
- // currentScoreCode.value = e.target.result
336
- // showScore()
337
- // }
338
- // reader.readAsText(file)
339
- // }
340
- // else if (textData) {
341
- // currentScoreCode.value = textData
342
- // showScore()
343
- // }
344
- // }
345
 
346
  function showHideSettings() {
347
  console.log('showHideSettings')
@@ -391,7 +334,7 @@ watch(currentScoreCode, () => {
391
  -->
392
  <div>
393
  <div>
394
- <div class="at-wrap">
395
  <div class="at-overlay">
396
  <div class="at-overlay-content">
397
  Music sheet is loading
@@ -404,7 +347,7 @@ watch(currentScoreCode, () => {
404
  </div>
405
  </div>
406
  <div class="at-viewport">
407
- <div class="at-main" />
408
  </div>
409
  </div>
410
  <div class="at-controls">
@@ -485,7 +428,7 @@ watch(currentScoreCode, () => {
485
  <button class="left-align" @click="showHideSettings">
486
  Settings
487
  </button>
488
- <div :id="divId" class="scoreContainer22" />
489
 
490
  <div v-show="settingsStatus" id="tabSettingsArea22">
491
  <div id="paneContainerXml22" />
@@ -515,11 +458,6 @@ watch(currentScoreCode, () => {
515
  </template>
516
 
517
  <style scoped>
518
- body {
519
- font-family: Arial, Helvetica, sans-serif;
520
- font-size: 12px;
521
- }
522
-
523
  .at-wrap {
524
  width: 80vw;
525
  height: 80vh;
 
1
  <script setup>
2
+ import { computed, onMounted, onUnmounted, onUpdated, ref, watch } from 'vue'
3
  import { Pane } from 'tweakpane'
4
 
5
  const props = defineProps({
 
22
 
23
  const emit = defineEmits(['updateScoreCode'])
24
 
25
+ // const openSheetMusicDisplay = null
26
+ // const timeoutID = null
27
+ // let pane = null
28
  let api = null
29
  let main = null
30
  let wrapper = null
 
37
  // const count = ref(0)
38
  const settingsStatus = ref(false)
39
  const isActive = ref(false)
40
+ const wrapRef = ref(null)
41
+ const mainRef = ref(null)
42
+
43
+ // const SETTINGS_PARAMS = {
44
+ // scale: 1.0,
45
+ // // width: 800,
46
+ // // factor: 123,
47
+ // // title: 'hello',
48
+ // // color: '#ff0055',
49
+ // // percentage: 50,
50
+ // // theme: 'dark',
51
+ // // prop: 'Put your\nmultiline\ntext here!'
52
+ // }
53
 
54
  const divId = computed(() => {
55
  return `xmlScoreContainer_${props.id}`
56
  })
57
 
58
+ // function initializePane() {
59
+ // pane = new Pane({
60
+ // container: document.getElementById('paneContainerXml'),
61
+ // title: 'MusicXML Settings',
62
+ // },
63
+ // )
64
+ // pane.addBinding(SETTINGS_PARAMS, 'scale', {
65
+ // min: 0.5,
66
+ // max: 2.0,
67
+ // step: 0.1,
68
+ // }).on('change', (ev) => {
69
+ // console.log(ev.value)
70
+ // openSheetMusicDisplay.zoom = ev.value
71
+ // openSheetMusicDisplay.render()
72
+ // })
73
+ // }
74
+
75
+ onUnmounted(() => {
76
+ console.log('onUnmounted')
77
+ // pane = null
78
+ api = null
79
+ main = null
80
+ wrapper = null
81
+ settings = null
82
+ })
83
 
84
+ onMounted(() => {
85
  // console.log($(`#${divId.value}`)[0])
86
+ // initializePane()
87
+ // wrapper = document.querySelector('.at-wrap')
88
+ // main = wrapper.querySelector('.at-main')
89
+ wrapper = wrapRef.value
90
+ main = mainRef.value
91
  // initialize alphatab
92
  settings = {
93
  // file: 'https://www.alphatab.net/files/canon.gp',
94
+ file: '/Fantasia_no.2.musicxml.gpx',
95
  player: {
96
  enablePlayer: true,
97
  soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
 
102
  })
103
 
104
  function initializeOSMD() {
105
+ console.log('initializeOSMD')
106
  api = new alphaTab.AlphaTabApi(main, settings)
107
+ // api.tex(`
108
+ // \\title 'Test' . 3.3.4
109
+ // :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
110
+ // `)
 
111
 
112
  // overlay logic
113
  const overlay = wrapper.querySelector('.at-overlay')
 
284
  songPosition.innerText
285
  = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
286
  })
287
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  function showHideSettings() {
290
  console.log('showHideSettings')
 
334
  -->
335
  <div>
336
  <div>
337
+ <div ref="wrapRef" class="at-wrap">
338
  <div class="at-overlay">
339
  <div class="at-overlay-content">
340
  Music sheet is loading
 
347
  </div>
348
  </div>
349
  <div class="at-viewport">
350
+ <div ref="mainRef" class="at-main" />
351
  </div>
352
  </div>
353
  <div class="at-controls">
 
428
  <button class="left-align" @click="showHideSettings">
429
  Settings
430
  </button>
431
+ <div class="scoreContainer22" />
432
 
433
  <div v-show="settingsStatus" id="tabSettingsArea22">
434
  <div id="paneContainerXml22" />
 
458
  </template>
459
 
460
  <style scoped>
 
 
 
 
 
461
  .at-wrap {
462
  width: 80vw;
463
  height: 80vh;
frontend/src/components/AlphaTexDL.vue ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { computed, onMounted, onUnmounted, onUpdated, ref, toRaw, watch } from 'vue'
3
+ import { Pane } from 'tweakpane'
4
+ import formatDuration from '@/utils/timeUtils.js'
5
+
6
+ const props = defineProps({
7
+ alphaTex: {
8
+ type: String,
9
+ required: false,
10
+ },
11
+ })
12
+
13
+ const emit = defineEmits(['updateScoreCode'])
14
+
15
+ // const openSheetMusicDisplay = null
16
+ // const timeoutID = null
17
+ let pane = null
18
+ let api = null
19
+ let wrapper = null
20
+ let settings = null
21
+ let timeoutID = null
22
+
23
+ // const message = ref('hello')
24
+ const currentScoreCode = ref(props.alphaTex)
25
+ const showParsingError = ref(false)
26
+ const parsingError = ref('')
27
+ // const count = ref(0)
28
+ const settingsStatus = ref(false)
29
+ const isActive = ref(false)
30
+ const wrapRef = ref(null)
31
+ const mainRef = ref(null)
32
+ const viewPortRef = ref(null)
33
+ const paneContainer = ref(null)
34
+ const canvasRef = ref(null)
35
+
36
+ const tracks = ref([])
37
+ const songPosition = ref('00:00 / 00:00')
38
+ const previousTime = -1
39
+ const title = ref('')
40
+ const artist = ref('')
41
+ const isPlaying = ref(false)
42
+ const selectedLayout = ref('page')
43
+ const isZoomMenuVisible = ref(false)
44
+ const selectedZoom = ref(100)
45
+ const zoomOptions = [
46
+ { value: '25', label: '25%' },
47
+ { value: '50', label: '50%' },
48
+ { value: '75', label: '75%' },
49
+ { value: '90', label: '90%' },
50
+ { value: '100', label: '100%' },
51
+ { value: '110', label: '110%' },
52
+ { value: '125', label: '125%' },
53
+ { value: '150', label: '150%' },
54
+ { value: '200', label: '200%' },
55
+ ]
56
+ const metronomeIsActive = ref(false)
57
+ // const loopIsActive = ref(false)
58
+
59
+ const CANVAS_SIZE_X = 600
60
+ const CANVAS_SIZE_Y = 300
61
+ const CANVAS_SCALE = 1
62
+ const isMouseDown = false
63
+ let mousePressed = false
64
+ const hasIntroText = true
65
+ let lastX = 0
66
+ let lastY = 0
67
+ let isFirstCurve = true
68
+ let ctx = null
69
+
70
+ const SETTINGS_PARAMS = {
71
+ scale: 1.0,
72
+ stretchForce: 1.0,
73
+ // width: 800,
74
+ // factor: 123,
75
+ // title: 'hello',
76
+ // color: '#ff0055',
77
+ // percentage: 50,
78
+ // theme: 'dark',
79
+ // prop: 'Put your\nmultiline\ntext here!'
80
+ }
81
+
82
+ // const divId = computed(() => {
83
+ // return `xmlScoreContainer_${props.id}`
84
+ // })
85
+
86
+ function initializePane() {
87
+ pane = new Pane({
88
+ container: paneContainer.value,
89
+ title: 'Notation Settings',
90
+ },
91
+ )
92
+ pane.addBinding(SETTINGS_PARAMS, 'scale', {
93
+ min: 0.1,
94
+ max: 2.0,
95
+ step: 0.1,
96
+ }).on('change', (ev) => {
97
+ console.log(ev.value)
98
+ api.settings.display.scale = ev.value
99
+ api.updateSettings()
100
+ api.render()
101
+ })
102
+ pane.addBinding(SETTINGS_PARAMS, 'stretchForce', {
103
+ min: 0.1,
104
+ max: 2.0,
105
+ step: 0.1,
106
+ }).on('change', (ev) => {
107
+ console.log(ev.value)
108
+ api.settings.display.stretchForce = ev.value
109
+ api.updateSettings()
110
+ api.render()
111
+ })
112
+ }
113
+
114
+ onUnmounted(() => {
115
+ console.log('onUnmounted')
116
+ // pane = null
117
+ api = null
118
+ wrapper = null
119
+ settings = null
120
+ })
121
+
122
+ onMounted(() => {
123
+ // console.log($(`#${divId.value}`)[0])
124
+ initializePane()
125
+ // wrapper = document.querySelector('.at-wrap')
126
+ // main = wrapper.querySelector('.at-main')
127
+ wrapper = wrapRef.value
128
+ // initialize alphatab
129
+ settings = {
130
+ // file: 'https://www.alphatab.net/files/canon.gp',
131
+ // file: '/Fantasia_no.2.musicxml.gpx',
132
+ player: {
133
+ enablePlayer: true,
134
+ soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
135
+ scrollElement: viewPortRef.value,
136
+
137
+ },
138
+ display: {
139
+ // barsPerRow: 3,
140
+ layoutMode: alphaTab.LayoutMode.Horizontal,
141
+ padding: [0, 0, 0, 0],
142
+ stretchForce: 1,
143
+ },
144
+ notation: {
145
+ elements: {
146
+ scoreTitle: false,
147
+ trackNames: false,
148
+ guitarTuning: false,
149
+ },
150
+ },
151
+ staveProfile: alphaTab.StaveProfile.Score,
152
+ }
153
+ initAlphaTab()
154
+
155
+ ctx = canvasRef.value.getContext('2d')
156
+ ctx.lineWidth = 2
157
+ ctx.lineJoin = 'round'
158
+ ctx.lineCap = 'round'
159
+ ctx.font = '20px sans-serif'
160
+ ctx.textAlign = 'center'
161
+ ctx.textBaseline = 'middle'
162
+ ctx.fillStyle = '#212121'
163
+ ctx.fillText('Draw a curve!', 600 / 2, 300 / 2)
164
+
165
+ // Set the line color for the canvas.
166
+ ctx.strokeStyle = '#212121'
167
+
168
+ // function drawLine(fromX, fromY, toX, toY) {
169
+ // // Draws a line from (fromX, fromY) to (toX, toY).
170
+ // ctx.beginPath()
171
+ // ctx.moveTo(fromX, fromY)
172
+ // ctx.lineCap = 'round'
173
+ // ctx.lineTo(toX, toY)
174
+ // ctx.closePath()
175
+ // ctx.stroke()
176
+ })
177
+
178
+ function Draw(x, y, isDown) {
179
+ if (isFirstCurve) {
180
+ ctx.clearRect(0, 0, CANVAS_SIZE_X, CANVAS_SIZE_Y)
181
+ isFirstCurve = false
182
+ }
183
+ if (isDown) {
184
+ ctx.beginPath()
185
+ // ctx.strokeStyle = $('#selColor').val();
186
+ // ctx.lineWidth = $('#selWidth').val();
187
+ // ctx.lineJoin = "round";
188
+ ctx.moveTo(lastX, lastY)
189
+ ctx.lineTo(x, y)
190
+ ctx.closePath()
191
+ ctx.stroke()
192
+ }
193
+ lastX = x
194
+ lastY = y
195
+ }
196
+ // function canvasMouseDown(e) {
197
+ // mousePressed = true
198
+ // Draw(e.pageX - $(this).offset().left, e.pageY - $(this).offset().top, false)
199
+ // }
200
+ function canvasMouseDown(e) {
201
+ const rect = e.target.getBoundingClientRect()
202
+ const mouseX = e.clientX - rect.left
203
+ const mouseY = e.clientY - rect.top
204
+
205
+ mousePressed = true
206
+ Draw(mouseX, mouseY, false)
207
+ }
208
+ function canvasMouseMove(e) {
209
+ if (mousePressed) {
210
+ const rect = e.target.getBoundingClientRect()
211
+ const mouseX = e.clientX - rect.left
212
+ const mouseY = e.clientY - rect.top
213
+
214
+ Draw(mouseX, mouseY, true)
215
+ }
216
+ }
217
+ function canvasMouseUp(e) {
218
+ mousePressed = false
219
+ const imgData = ctx.getImageData(0, 0, CANVAS_SIZE_X, CANVAS_SIZE_Y)
220
+ console.log(imgData)
221
+ // curveList.push(imgData)
222
+ }
223
+ function canvasMouseOut(e) {
224
+ mousePressed = false
225
+ }
226
+ function updateZoom() {
227
+ // Handle zoom update logic here
228
+ // const zoomLevel = Number.parseInt(zoom.value) / 100
229
+ api.settings.display.scale = selectedZoom.value / 100
230
+ api.updateSettings()
231
+ api.render()
232
+ }
233
+
234
+ function scoreExport() {
235
+ const exporter = new alphaTab.exporter.Gp7Exporter()
236
+ const data = exporter.export(api.score, api.settings)
237
+
238
+ // trigger download
239
+ const a = document.createElement('a')
240
+ a.download = api.score.title.length > 0 ? `${api.score.title}.gp` : 'Untitled.gp'
241
+ a.href = URL.createObjectURL(new Blob([data]))
242
+ document.body.appendChild(a)
243
+ a.click()
244
+ document.body.removeChild(a)
245
+ }
246
+
247
+ function handleKeyUp(event) {
248
+ if (timeoutID)
249
+ clearTimeout(timeoutID)
250
+ timeoutID = setTimeout(showScore, 500)
251
+ }
252
+
253
+ // function handleDrop(event) {
254
+ // event.preventDefault()
255
+ // const textData = event.dataTransfer.getData('text')
256
+ // const file = event.dataTransfer.files[0]
257
+ // console.log(textData)
258
+ // console.log(file)
259
+ // if (file) {
260
+ // const reader = new FileReader()
261
+ // reader.onload = function (e) {
262
+ // currentScoreCode.value = e.target.result
263
+ // showScore()
264
+ // }
265
+ // reader.readAsText(file)
266
+ // }
267
+ // else if (textData) {
268
+ // currentScoreCode.value = textData
269
+ // showScore()
270
+ // }
271
+ // }
272
+
273
+ function showScore() {
274
+ try {
275
+ api.tex(currentScoreCode.value)
276
+ showParsingError.value = false
277
+ }
278
+ catch (e) {
279
+ showParsingError.value = true
280
+ console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
281
+ parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
282
+ }
283
+ }
284
+
285
+ function initAlphaTab() {
286
+ console.log('initAlphaTab')
287
+ api = new alphaTab.AlphaTabApi(mainRef.value, settings)
288
+ // api.tex(`
289
+ // \\title 'Test' . 3.3.4
290
+ // :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
291
+ // `)
292
+ api.tex(currentScoreCode.value)
293
+
294
+ // overlay logic
295
+ const overlay = wrapper.querySelector('.at-overlay')
296
+ api.renderStarted.on(() => {
297
+ overlay.style.display = 'flex'
298
+ })
299
+ api.renderFinished.on(() => {
300
+ overlay.style.display = 'none'
301
+ })
302
+
303
+ /** Controls */
304
+ api.scoreLoaded.on((score) => {
305
+ title.value = score.title
306
+ artist.value = score.artist
307
+ })
308
+
309
+ api.playerStateChanged.on((e) => {
310
+ isPlaying.value = e.state
311
+ })
312
+ api.playerPositionChanged.on((e) => {
313
+ // reduce number of UI updates to second changes.
314
+ const currentSeconds = (e.currentTime / 1000) | 0
315
+ if (currentSeconds > previousTime)
316
+ songPosition.value = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
317
+ })
318
+ }
319
+
320
+ function showHideSettings() {
321
+ console.log('showHideSettings')
322
+ settingsStatus.value = !settingsStatus.value
323
+ }
324
+
325
+ // invoked when there is any type of change in the component
326
+ onUpdated(() => {
327
+ if (props.isActive !== isActive.value) {
328
+ // Prop has changed, update the ref and emit an event
329
+ if (props.isActive) {
330
+ isActive.value = props.isActive
331
+ emit('updateScoreCode', currentScoreCode.value)
332
+ // console.log("emitted updateScoreCode");
333
+ // console.log(currentScoreCode.value);
334
+ }
335
+ isActive.value = props.isActive
336
+ }
337
+ })
338
+
339
+ // watch(currentScoreCode, () => {
340
+ // // Emit the event so that the parent component can update the tabText
341
+ // // prop
342
+ // emit('updateScoreCode', currentScoreCode.value)
343
+ // // console.log("emitted updateScoreCode");
344
+ // // console.log(currentScoreCode.value);
345
+ // })
346
+ watch(currentScoreCode, () => {
347
+ // Emit the event so that the parent component can update the tabText
348
+ // prop
349
+ emit('updateScoreCode', currentScoreCode.value)
350
+ console.log('emitted updateScoreCode')
351
+ // this should go to the first tab that is shown (for now vextab)
352
+ }, { immediate: true })
353
+
354
+ // const moveCursor = () => {
355
+ // openSheetMusicDisplay.cursor.next();
356
+ // };
357
+
358
+ // const fillScore = () => {
359
+ // // console.log(vt);
360
+ // // Perform any logic to fill the score
361
+ // response.value = 'Button pressed!'; // Update response if needed
362
+
363
+ // };
364
+ </script>
365
+
366
+ <template>
367
+ <div>
368
+ <div
369
+ ref="wrapRef"
370
+ class="at-wrap"
371
+ >
372
+ <div class="canvasWrapper">
373
+ <canvas
374
+ ref="canvasRef" class="canvas"
375
+ width="600" height="300"
376
+ @mousedown="canvasMouseDown"
377
+ @mousemove="canvasMouseMove"
378
+ @mouseup="canvasMouseUp"
379
+ @mouseout="canvasMouseOut"
380
+ />
381
+ </div>
382
+
383
+ <div class="at-overlay">
384
+ <div class="at-overlay-content">
385
+ Music sheet is loading
386
+ </div>
387
+ </div>
388
+ <div class="at-content">
389
+ <div ref="viewPortRef" class="at-viewport">
390
+ <div ref="mainRef" class="at-main" />
391
+ </div>
392
+ </div>
393
+ <div class="at-controls">
394
+ <div class="at-controls-left">
395
+ <button class="btn i-fad:rew" @click="api.stop()" />
396
+ <button class="btn" :class="{ 'i-fad:play': !isPlaying, 'i-fad:pause': isPlaying }" @click="api.playPause()" />
397
+
398
+ <div class="at-song-info">
399
+ {{ title }} - {{ artist }}
400
+ </div>
401
+ <div class="at-song-position">
402
+ {{ songPosition }}
403
+ </div>
404
+ </div>
405
+ <div class="at-controls-right">
406
+ <!-- <div :class="{ active: metronomeIsActive }">
407
+ <button class="btn i-fad:metronome" @click="toggleMetronome" />
408
+ </div> -->
409
+ <!-- <div>
410
+ <button class="btn i-fad:repeat" :class="{ active: loopingIsActive }" @click="toggleLooping" />
411
+ </div> -->
412
+ <div :class="{ active: settingsStatus }">
413
+ <button class="btn i-fa:edit" @click="showHideSettings" />
414
+ </div>
415
+
416
+ <div>
417
+ <button class="btn i-foundation:print" @click="api.print()" />
418
+ </div>
419
+
420
+ <div>
421
+ <button class="btn i-foundation:page-export" @click="scoreExport" />
422
+ </div>
423
+
424
+ <div class="at-zoom">
425
+ <!-- <button class="btn i-fad:zoomin" @click="toggleZoomMenu" /> -->
426
+ <select v-model="selectedZoom" @change="updateZoom">
427
+ <option v-for="zoomOption in zoomOptions" :key="zoomOption.value" :value="zoomOption.value">
428
+ {{ zoomOption.label }}
429
+ </option>
430
+ </select>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ <div v-show="settingsStatus" id="tabSettingsArea">
436
+ <div ref="paneContainer" />
437
+ <textarea
438
+ v-model="currentScoreCode"
439
+ class="editorBox"
440
+ placeholder="add your musicxml code here or drag a file"
441
+ @keyup="handleKeyUp"
442
+ @drop="handleDrop"
443
+ @dragover.prevent
444
+ />
445
+ </div>
446
+ <div v-if="showParsingError" class="parsing-error" v-html="parsingError" />
447
+ </div>
448
+
449
+ <!-- </div> -->
450
+ </template>
451
+
452
+ <style scoped>
453
+ #tabSettingsArea {
454
+ display: flex;
455
+ flex-direction: row;
456
+ align-items: center;
457
+ gap: 20px;
458
+ margin-left: 10px;
459
+ }
460
+ .at-wrap {
461
+ width: 60vw;
462
+ /* width:90%; */
463
+ /* height: 50vh; */
464
+ margin: 0 auto;
465
+ /* border: 1px solid rgba(0, 0, 0, 0.12); */
466
+ display: flex;
467
+ flex-direction: column;
468
+ overflow: hidden;
469
+ position: relative;
470
+ justify-content: center;
471
+ }
472
+ .at-content {
473
+ position: relative;
474
+ overflow: hidden;
475
+ flex: 1 1 auto;
476
+ display: flex;
477
+ flex-direction: row;
478
+ }
479
+
480
+ .canvas-wrapper {
481
+ position: relative;
482
+ overflow: hidden;
483
+ flex: 1 1 auto;
484
+ display: flex;
485
+ flex-direction: row;
486
+ background-color: green;
487
+ }
488
+ .canvas {
489
+ background-color: var(--accent-color2);
490
+ width: 600px;
491
+ height: 300px;
492
+ }
493
+ .at-viewport {
494
+ flex: 100%;
495
+ overflow-y: scroll;
496
+ /* position: absolute;
497
+ top: 0;
498
+ left: 70px;
499
+ right: 0;
500
+ bottom: 0;
501
+ padding-right: 20px; */
502
+ }
503
+
504
+ .at-sidebar {
505
+ /* position: absolute;
506
+ top: 0;
507
+ left: 0;
508
+ bottom: 0;
509
+ max-width: 70px;
510
+ width: auto; */
511
+ flex: 0%;
512
+ display: flex;
513
+ align-content: stretch;
514
+ justify-content: center;
515
+ max-width: 70px;
516
+ z-index: 1001;
517
+ overflow: hidden;
518
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
519
+ background: #862b2b;
520
+
521
+ }
522
+
523
+ .at-track-list {
524
+ display: flex;
525
+ flex-direction: column;
526
+ }
527
+
528
+ .at-controls {
529
+ /* flex: 0 0 auto; */
530
+ display: flex;
531
+ justify-content: space-between;
532
+ background: #7b8379;
533
+ color: #e2e2c3;
534
+ }
535
+
536
+ .at-controls > div {
537
+ display: flex;
538
+ justify-content: flex-start;
539
+ align-content: center;
540
+ align-items: center;
541
+ }
542
+
543
+ .at-controls > div > * {
544
+ display: flex;
545
+ text-align: center;
546
+ align-items: center;
547
+ justify-content: center;
548
+ cursor: pointer;
549
+ padding: 4px;
550
+ margin: 0 3px;
551
+ }
552
+
553
+ .at-controls .btn {
554
+ color: #e2e2c3;
555
+ /* border-radius: 0; */
556
+ height: 40px;
557
+ width: 40px;
558
+ font-size: 16px;
559
+ }
560
+
561
+ .at-controls .active {
562
+ background-color: #85b379;
563
+ /* text-decoration: none; */
564
+ }
565
+
566
+ .at-controls select {
567
+ -moz-appearance: none;
568
+ -webkit-appearance: none;
569
+ appearance: none;
570
+ border: none;
571
+ width: 100%;
572
+ height: 40px;
573
+ background: #85b379;
574
+ padding: 4px 10px;
575
+ color: #e2e2c3;
576
+ font-size: 16px;
577
+ text-align-last: center;
578
+ text-align: center;
579
+ -ms-text-align-last: center;
580
+ -moz-text-align-last: center;
581
+ cursor: pointer;
582
+ }
583
+
584
+ /* .at-song-title {
585
+ font-weight: bold;
586
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
587
+ } */
588
+
589
+ .at-cursor-bar {
590
+ /* Defines the color of the bar background when a bar is played */
591
+ background: rgba(255, 242, 0, 0.25);
592
+ }
593
+
594
+ .at-selection div {
595
+ /* Defines the color of the selection background */
596
+ background: rgba(64, 64, 255, 0.1);
597
+ }
598
+
599
+ .at-cursor-beat {
600
+ /* Defines the beat cursor */
601
+ background: rgba(64, 64, 255, 0.75);
602
+ width: 3px;
603
+ }
604
+
605
+ .at-highlight * {
606
+ /* Defines the color of the music symbols when they are being played (svg) */
607
+ fill: #0078ff;
608
+ stroke: #0078ff;
609
+ }
610
+ </style>
frontend/src/components/AlphaTexWidget.vue ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { computed, onMounted, onUnmounted, onUpdated, ref, toRaw, watch } from 'vue'
3
+ import { Pane } from 'tweakpane'
4
+ import formatDuration from '@/utils/timeUtils.js'
5
+
6
+ const props = defineProps({
7
+ // int id
8
+ id: {
9
+ type: String,
10
+ required: true,
11
+ },
12
+ alphaTex: {
13
+ type: String,
14
+ required: false,
15
+ },
16
+ isActive: {
17
+ type: Boolean,
18
+ required: false,
19
+ default: false,
20
+ },
21
+
22
+ })
23
+
24
+ const emit = defineEmits(['updateScoreCode'])
25
+
26
+ // const openSheetMusicDisplay = null
27
+ // const timeoutID = null
28
+ let pane = null
29
+ let api = null
30
+ let main = null
31
+ let wrapper = null
32
+ let settings = null
33
+ let timeoutID = null
34
+
35
+ // const message = ref('hello')
36
+ const currentScoreCode = ref(props.alphaTex)
37
+ const showParsingError = ref(false)
38
+ const parsingError = ref('')
39
+ // const count = ref(0)
40
+ const settingsStatus = ref(false)
41
+ const isActive = ref(false)
42
+ const wrapRef = ref(null)
43
+ const mainRef = ref(null)
44
+ const viewPortRef = ref(null)
45
+ const paneContainer = ref(null)
46
+
47
+ const tracks = ref([])
48
+ const songPosition = ref('00:00 / 00:00')
49
+ const previousTime = -1
50
+ const title = ref('')
51
+ const artist = ref('')
52
+ const isPlaying = ref(false)
53
+ const selectedLayout = ref('page')
54
+ const isZoomMenuVisible = ref(false)
55
+ const selectedZoom = ref(100)
56
+ const zoomOptions = [
57
+ { value: '25', label: '25%' },
58
+ { value: '50', label: '50%' },
59
+ { value: '75', label: '75%' },
60
+ { value: '90', label: '90%' },
61
+ { value: '100', label: '100%' },
62
+ { value: '110', label: '110%' },
63
+ { value: '125', label: '125%' },
64
+ { value: '150', label: '150%' },
65
+ { value: '200', label: '200%' },
66
+ ]
67
+ const metronomeIsActive = ref(false)
68
+ // const loopIsActive = ref(false)
69
+
70
+ const SETTINGS_PARAMS = {
71
+ scale: 1.0,
72
+ // width: 800,
73
+ // factor: 123,
74
+ // title: 'hello',
75
+ // color: '#ff0055',
76
+ // percentage: 50,
77
+ // theme: 'dark',
78
+ // prop: 'Put your\nmultiline\ntext here!'
79
+ }
80
+
81
+ // const divId = computed(() => {
82
+ // return `xmlScoreContainer_${props.id}`
83
+ // })
84
+
85
+ function initializePane() {
86
+ pane = new Pane({
87
+ container: paneContainer.value,
88
+ title: 'MusicXML Settings',
89
+ },
90
+ )
91
+ pane.addBinding(SETTINGS_PARAMS, 'scale', {
92
+ min: 0.1,
93
+ max: 2.0,
94
+ step: 0.1,
95
+ }).on('change', (ev) => {
96
+ console.log(ev.value)
97
+ api.settings.display.scale = ev.value
98
+ api.updateSettings()
99
+ api.render()
100
+ })
101
+ }
102
+
103
+ onUnmounted(() => {
104
+ console.log('onUnmounted')
105
+ // pane = null
106
+ api = null
107
+ main = null
108
+ wrapper = null
109
+ settings = null
110
+ })
111
+
112
+ onMounted(() => {
113
+ // console.log($(`#${divId.value}`)[0])
114
+ initializePane()
115
+ // wrapper = document.querySelector('.at-wrap')
116
+ // main = wrapper.querySelector('.at-main')
117
+ wrapper = wrapRef.value
118
+ main = mainRef.value
119
+ // initialize alphatab
120
+ settings = {
121
+ // file: 'https://www.alphatab.net/files/canon.gp',
122
+ // file: '/Fantasia_no.2.musicxml.gpx',
123
+ player: {
124
+ enablePlayer: true,
125
+ soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
126
+ scrollElement: viewPortRef.value,
127
+
128
+ },
129
+ }
130
+ initAlphaTab()
131
+ })
132
+
133
+ function renderTrack(track) {
134
+ // Assuming you have an API method to render tracks
135
+ // Example: api.renderTracks([track]);
136
+ api.renderTracks([toRaw(track)])
137
+ }
138
+
139
+ function updateLayout() {
140
+ switch (selectedLayout.value) {
141
+ case 'horizontal':
142
+ api.settings.display.layoutMode = alphaTab.LayoutMode.Horizontal
143
+ break
144
+ case 'page':
145
+ api.settings.display.layoutMode = alphaTab.LayoutMode.Page
146
+ break
147
+ }
148
+ api.updateSettings()
149
+ api.render()
150
+ }
151
+
152
+ // function toggleZoomMenu() {
153
+ // isZoomMenuVisible.value = !isZoomMenuVisible.value
154
+ // }
155
+
156
+ function updateZoom() {
157
+ // Handle zoom update logic here
158
+ // const zoomLevel = Number.parseInt(zoom.value) / 100
159
+ api.settings.display.scale = selectedZoom.value / 100
160
+ api.updateSettings()
161
+ api.render()
162
+ }
163
+
164
+ function scoreExport() {
165
+ const exporter = new alphaTab.exporter.Gp7Exporter()
166
+ const data = exporter.export(api.score, api.settings)
167
+
168
+ // trigger download
169
+ const a = document.createElement('a')
170
+ a.download = api.score.title.length > 0 ? `${api.score.title}.gp` : 'Untitled.gp'
171
+ a.href = URL.createObjectURL(new Blob([data]))
172
+ document.body.appendChild(a)
173
+ a.click()
174
+ document.body.removeChild(a)
175
+ }
176
+
177
+ function toggleMetronome() {
178
+ metronomeIsActive.value = !metronomeIsActive.value
179
+ if (metronomeIsActive.value)
180
+ api.metronomeVolume = 1
181
+
182
+ else
183
+ api.metronomeVolume = 0
184
+ }
185
+
186
+ function handleKeyUp(event) {
187
+ if (timeoutID)
188
+ clearTimeout(timeoutID)
189
+ timeoutID = setTimeout(showScore, 500)
190
+ }
191
+
192
+ // function handleDrop(event) {
193
+ // event.preventDefault()
194
+ // const textData = event.dataTransfer.getData('text')
195
+ // const file = event.dataTransfer.files[0]
196
+ // console.log(textData)
197
+ // console.log(file)
198
+ // if (file) {
199
+ // const reader = new FileReader()
200
+ // reader.onload = function (e) {
201
+ // currentScoreCode.value = e.target.result
202
+ // showScore()
203
+ // }
204
+ // reader.readAsText(file)
205
+ // }
206
+ // else if (textData) {
207
+ // currentScoreCode.value = textData
208
+ // showScore()
209
+ // }
210
+ // }
211
+
212
+ function showScore() {
213
+ try {
214
+ api.tex(currentScoreCode.value)
215
+ showParsingError.value = false
216
+ }
217
+ catch (e) {
218
+ showParsingError.value = true
219
+ console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
220
+ parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
221
+ }
222
+ }
223
+
224
+ function initAlphaTab() {
225
+ console.log('initAlphaTab')
226
+ api = new alphaTab.AlphaTabApi(main, settings)
227
+ // api.tex(`
228
+ // \\title 'Test' . 3.3.4
229
+ // :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
230
+ // `)
231
+ api.tex(currentScoreCode.value)
232
+
233
+ // overlay logic
234
+ const overlay = wrapper.querySelector('.at-overlay')
235
+ api.renderStarted.on(() => {
236
+ overlay.style.display = 'flex'
237
+ })
238
+ api.renderFinished.on(() => {
239
+ overlay.style.display = 'none'
240
+ })
241
+
242
+ // track selector
243
+ api.scoreLoaded.on((score) => {
244
+ tracks.value = score.tracks
245
+ })
246
+
247
+ api.renderStarted.on(() => {
248
+ })
249
+
250
+ /** Controls */
251
+ api.scoreLoaded.on((score) => {
252
+ title.value = score.title
253
+ artist.value = score.artist
254
+ })
255
+
256
+ api.playerStateChanged.on((e) => {
257
+ isPlaying.value = e.state
258
+ })
259
+ api.playerPositionChanged.on((e) => {
260
+ // reduce number of UI updates to second changes.
261
+ const currentSeconds = (e.currentTime / 1000) | 0
262
+ if (currentSeconds > previousTime)
263
+ songPosition.value = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
264
+ })
265
+ }
266
+
267
+ function showHideSettings() {
268
+ console.log('showHideSettings')
269
+ settingsStatus.value = !settingsStatus.value
270
+ }
271
+
272
+ // invoked when there is any type of change in the component
273
+ onUpdated(() => {
274
+ if (props.isActive !== isActive.value) {
275
+ // Prop has changed, update the ref and emit an event
276
+ if (props.isActive) {
277
+ isActive.value = props.isActive
278
+ emit('updateScoreCode', currentScoreCode.value)
279
+ // console.log("emitted updateScoreCode");
280
+ // console.log(currentScoreCode.value);
281
+ }
282
+ isActive.value = props.isActive
283
+ }
284
+ })
285
+
286
+ // watch(currentScoreCode, () => {
287
+ // // Emit the event so that the parent component can update the tabText
288
+ // // prop
289
+ // emit('updateScoreCode', currentScoreCode.value)
290
+ // // console.log("emitted updateScoreCode");
291
+ // // console.log(currentScoreCode.value);
292
+ // })
293
+ watch(currentScoreCode, () => {
294
+ // Emit the event so that the parent component can update the tabText
295
+ // prop
296
+ emit('updateScoreCode', currentScoreCode.value)
297
+ console.log('emitted updateScoreCode')
298
+ // this should go to the first tab that is shown (for now vextab)
299
+ }, { immediate: true })
300
+
301
+ // const moveCursor = () => {
302
+ // openSheetMusicDisplay.cursor.next();
303
+ // };
304
+
305
+ // const fillScore = () => {
306
+ // // console.log(vt);
307
+ // // Perform any logic to fill the score
308
+ // response.value = 'Button pressed!'; // Update response if needed
309
+
310
+ // };
311
+ </script>
312
+
313
+ <template>
314
+ <div>
315
+ <div
316
+ ref="wrapRef"
317
+ class="at-wrap"
318
+ >
319
+ <div class="at-overlay">
320
+ <div class="at-overlay-content">
321
+ Music sheet is loading
322
+ </div>
323
+ </div>
324
+ <div class="at-content">
325
+ <div class="at-sidebar">
326
+ <div class="at-track-list">
327
+ <div v-for="track in tracks" :key="track.index" class="at-track" @click="renderTrack(track)">
328
+ <div class="at-track-icon">
329
+ <i class="i-fa:music" />
330
+ </div>
331
+ <div class="at-track-details">
332
+ <div class="at-track-name">
333
+ {{ track.name }}
334
+ </div>
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </div>
339
+ <div ref="viewPortRef" class="at-viewport">
340
+ <div ref="mainRef" class="at-main" />
341
+ </div>
342
+ </div>
343
+ <div class="at-controls">
344
+ <div class="at-controls-left">
345
+ <button class="btn i-fad:rew" @click="api.stop()" />
346
+ <button class="btn" :class="{ 'i-fad:play': !isPlaying, 'i-fad:pause': isPlaying }" @click="api.playPause()" />
347
+
348
+ <div class="at-song-info">
349
+ {{ title }} - {{ artist }}
350
+ </div>
351
+ <div class="at-song-position">
352
+ {{ songPosition }}
353
+ </div>
354
+ </div>
355
+ <div class="at-controls-right">
356
+ <div :class="{ active: metronomeIsActive }">
357
+ <button class="btn i-fad:metronome" @click="toggleMetronome" />
358
+ </div>
359
+ <!-- <div>
360
+ <button class="btn i-fad:repeat" :class="{ active: loopingIsActive }" @click="toggleLooping" />
361
+ </div> -->
362
+ <div :class="{ active: settingsStatus }">
363
+ <button class="btn i-fa:edit" @click="showHideSettings" />
364
+ </div>
365
+
366
+ <div>
367
+ <button class="btn i-foundation:print" @click="api.print()" />
368
+ </div>
369
+
370
+ <div>
371
+ <button class="btn i-foundation:page-export" @click="scoreExport" />
372
+ </div>
373
+
374
+ <div class="at-zoom">
375
+ <!-- <button class="btn i-fad:zoomin" @click="toggleZoomMenu" /> -->
376
+ <select v-model="selectedZoom" @change="updateZoom">
377
+ <option v-for="zoomOption in zoomOptions" :key="zoomOption.value" :value="zoomOption.value">
378
+ {{ zoomOption.label }}
379
+ </option>
380
+ </select>
381
+ </div>
382
+ <div class="at-layout">
383
+ <select v-model="selectedLayout" @change="updateLayout">
384
+ <option value="horizontal">
385
+ Horizontal
386
+ </option>
387
+ <option value="page">
388
+ Page
389
+ </option>
390
+ </select>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+ <div v-show="settingsStatus" id="tabSettingsArea">
396
+ <div ref="paneContainer" />
397
+ <textarea
398
+ v-model="currentScoreCode"
399
+ class="editorBox"
400
+ placeholder="add your musicxml code here or drag a file"
401
+ @keyup="handleKeyUp"
402
+ @drop="handleDrop"
403
+ @dragover.prevent
404
+ />
405
+ </div>
406
+ <div v-if="showParsingError" class="parsing-error" v-html="parsingError" />
407
+ </div>
408
+
409
+ <!-- </div> -->
410
+ </template>
411
+
412
+ <style scoped>
413
+ #tabSettingsArea {
414
+ display: flex;
415
+ flex-direction: row;
416
+ align-items: center;
417
+ gap: 20px;
418
+ margin-left: 10px;
419
+ }
420
+ .at-wrap {
421
+ width: 50vw;
422
+ height: 50vh;
423
+ margin: 0 auto;
424
+ border: 1px solid rgba(0, 0, 0, 0.12);
425
+ display: flex;
426
+ flex-direction: column;
427
+ overflow: hidden;
428
+ position: relative;
429
+ }
430
+ .at-content {
431
+ position: relative;
432
+ overflow: hidden;
433
+ flex: 1 1 auto;
434
+ display: flex;
435
+ flex-direction: row;
436
+ }
437
+ .at-viewport {
438
+ flex: 100%;
439
+ overflow-y: scroll;
440
+ /* position: absolute;
441
+ top: 0;
442
+ left: 70px;
443
+ right: 0;
444
+ bottom: 0;
445
+ padding-right: 20px; */
446
+ }
447
+
448
+ .at-sidebar {
449
+ /* position: absolute;
450
+ top: 0;
451
+ left: 0;
452
+ bottom: 0;
453
+ max-width: 70px;
454
+ width: auto; */
455
+ flex: 0%;
456
+ display: flex;
457
+ align-content: stretch;
458
+ justify-content: center;
459
+ max-width: 70px;
460
+ z-index: 1001;
461
+ overflow: hidden;
462
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
463
+ background: #862b2b;
464
+
465
+ }
466
+
467
+ .at-track-list {
468
+ display: flex;
469
+ flex-direction: column;
470
+ }
471
+
472
+ .at-controls {
473
+ /* flex: 0 0 auto; */
474
+ display: flex;
475
+ justify-content: space-between;
476
+ background: #329e17;
477
+ color: #e2e2c3;
478
+ }
479
+
480
+ .at-controls > div {
481
+ display: flex;
482
+ justify-content: flex-start;
483
+ align-content: center;
484
+ align-items: center;
485
+ }
486
+
487
+ .at-controls > div > * {
488
+ display: flex;
489
+ text-align: center;
490
+ align-items: center;
491
+ justify-content: center;
492
+ cursor: pointer;
493
+ padding: 4px;
494
+ margin: 0 3px;
495
+ }
496
+
497
+ .at-controls .btn {
498
+ color: #e2e2c3;
499
+ /* border-radius: 0; */
500
+ height: 40px;
501
+ width: 40px;
502
+ font-size: 16px;
503
+ }
504
+
505
+ .at-controls .active {
506
+ background-color: #5bbb43;
507
+ /* text-decoration: none; */
508
+ }
509
+
510
+ .at-controls select {
511
+ -moz-appearance: none;
512
+ -webkit-appearance: none;
513
+ appearance: none;
514
+ border: none;
515
+ width: 100%;
516
+ height: 40px;
517
+ background: #5bbb43;
518
+ padding: 4px 10px;
519
+ color: #e2e2c3;
520
+ font-size: 16px;
521
+ text-align-last: center;
522
+ text-align: center;
523
+ -ms-text-align-last: center;
524
+ -moz-text-align-last: center;
525
+ cursor: pointer;
526
+ }
527
+
528
+ /* .at-song-title {
529
+ font-weight: bold;
530
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
531
+ } */
532
+
533
+ .at-cursor-bar {
534
+ /* Defines the color of the bar background when a bar is played */
535
+ background: rgba(255, 242, 0, 0.25);
536
+ }
537
+
538
+ .at-selection div {
539
+ /* Defines the color of the selection background */
540
+ background: rgba(64, 64, 255, 0.1);
541
+ }
542
+
543
+ .at-cursor-beat {
544
+ /* Defines the beat cursor */
545
+ background: rgba(64, 64, 255, 0.75);
546
+ width: 3px;
547
+ }
548
+
549
+ .at-highlight * {
550
+ /* Defines the color of the music symbols when they are being played (svg) */
551
+ fill: #0078ff;
552
+ stroke: #0078ff;
553
+ }
554
+ </style>
frontend/src/components/MusicXmlWidget2.vue ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { computed, onMounted, onUnmounted, onUpdated, ref, toRaw, watch } from 'vue'
3
+ import { Pane } from 'tweakpane'
4
+ import { OpenSheetMusicDisplay } from 'opensheetmusicdisplay'
5
+ import formatDuration from '@/utils/timeUtils.js'
6
+
7
+ const props = defineProps({
8
+ // int id
9
+ id: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ // xmlText: {
14
+ // type: String,
15
+ // required: false,
16
+ // },
17
+ xmlText: {
18
+ type: String,
19
+ required: false,
20
+ },
21
+ isActive: {
22
+ type: Boolean,
23
+ required: false,
24
+ default: false,
25
+ },
26
+
27
+ })
28
+
29
+ const emit = defineEmits(['updateScoreCode'])
30
+
31
+ let pane = null
32
+ let api = null
33
+ let main = null
34
+ let wrapper = null
35
+ let settings = null
36
+ let timeoutID = null
37
+ let openSheetMusicDisplay = null
38
+
39
+ // const message = ref('hello')
40
+ const currentScoreCode = ref(props.xmlText)
41
+ const showParsingError = ref(false)
42
+ const parsingError = ref('')
43
+ // const count = ref(0)
44
+ const settingsStatus = ref(false)
45
+ const isActive = ref(false)
46
+ const wrapRef = ref(null)
47
+ const mainRef = ref(null)
48
+ const viewPortRef = ref(null)
49
+ const paneContainer = ref(null)
50
+
51
+ const tracks = ref([])
52
+ const songPosition = ref('00:00 / 00:00')
53
+ const previousTime = -1
54
+ const title = ref('')
55
+ const artist = ref('')
56
+ const isPlaying = ref(false)
57
+ const selectedLayout = ref('page')
58
+ const isZoomMenuVisible = ref(false)
59
+ const selectedZoom = ref(100)
60
+ const zoomOptions = [
61
+ { value: '25', label: '25%' },
62
+ { value: '50', label: '50%' },
63
+ { value: '75', label: '75%' },
64
+ { value: '90', label: '90%' },
65
+ { value: '100', label: '100%' },
66
+ { value: '110', label: '110%' },
67
+ { value: '125', label: '125%' },
68
+ { value: '150', label: '150%' },
69
+ { value: '200', label: '200%' },
70
+ ]
71
+ const metronomeIsActive = ref(false)
72
+ // const loopIsActive = ref(false)
73
+
74
+ const SETTINGS_PARAMS = {
75
+ scale: 1.0,
76
+ // width: 800,
77
+ // factor: 123,
78
+ // title: 'hello',
79
+ // color: '#ff0055',
80
+ // percentage: 50,
81
+ // theme: 'dark',
82
+ // prop: 'Put your\nmultiline\ntext here!'
83
+ }
84
+
85
+ // const divId = computed(() => {
86
+ // return `xmlScoreContainer_${props.id}`
87
+ // })
88
+
89
+ function initializePane() {
90
+ pane = new Pane({
91
+ container: paneContainer.value,
92
+ title: 'MusicXML Settings',
93
+ },
94
+ )
95
+ pane.addBinding(SETTINGS_PARAMS, 'scale', {
96
+ min: 0.1,
97
+ max: 2.0,
98
+ step: 0.1,
99
+ }).on('change', (ev) => {
100
+ console.log(ev.value)
101
+ openSheetMusicDisplay.zoom = ev.value
102
+ openSheetMusicDisplay.render()
103
+ })
104
+ }
105
+
106
+ onUnmounted(() => {
107
+ console.log('onUnmounted')
108
+ // pane = null
109
+ api = null
110
+ main = null
111
+ wrapper = null
112
+ settings = null
113
+ })
114
+
115
+ onMounted(async () => {
116
+ // console.log($(`#${divId.value}`)[0])
117
+ initializePane()
118
+ // wrapper = document.querySelector('.at-wrap')
119
+ // main = wrapper.querySelector('.at-main')
120
+ wrapper = wrapRef.value
121
+ main = mainRef.value
122
+ // initialize alphatab
123
+ // settings = {
124
+ // // file: 'https://www.alphatab.net/files/canon.gp',
125
+ // // file: '/Fantasia_no.2.musicxml.gpx',
126
+ // player: {
127
+ // enablePlayer: true,
128
+ // soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
129
+ // scrollElement: viewPortRef.value,
130
+
131
+ // },
132
+ // }
133
+ await initOSMD()
134
+ })
135
+
136
+ function updateLayout() {
137
+ switch (selectedLayout.value) {
138
+ case 'horizontal':
139
+ openSheetMusicDisplay.renderSingleHorizontalStaffline = true
140
+ break
141
+ case 'page':
142
+ openSheetMusicDisplay.renderSingleHorizontalStaffline = false
143
+ break
144
+ }
145
+ openSheetMusicDisplay.render()
146
+ }
147
+
148
+ function updateZoom() {
149
+ // Handle zoom update logic here
150
+ // const zoomLevel = Number.parseInt(zoom.value) / 100
151
+ openSheetMusicDisplay.zoom = selectedZoom.value / 100
152
+ openSheetMusicDisplay.render()
153
+ }
154
+
155
+ function handleKeyUp(event) {
156
+ if (timeoutID)
157
+ clearTimeout(timeoutID)
158
+ timeoutID = setTimeout(showScore, 500)
159
+ }
160
+
161
+ function handleDrop(event) {
162
+ event.preventDefault()
163
+ const textData = event.dataTransfer.getData('text')
164
+ const file = event.dataTransfer.files[0]
165
+ console.log(textData)
166
+ console.log(file)
167
+ if (file) {
168
+ const reader = new FileReader()
169
+ reader.onload = function (e) {
170
+ currentScoreCode.value = e.target.result
171
+ showScore()
172
+ }
173
+ reader.readAsText(file)
174
+ }
175
+ else if (textData) {
176
+ currentScoreCode.value = textData
177
+ showScore()
178
+ }
179
+ }
180
+
181
+ function showScore() {
182
+ openSheetMusicDisplay.clear()
183
+ console.log(currentScoreCode.value)
184
+ openSheetMusicDisplay.load(currentScoreCode.value)
185
+ .then(
186
+ () => {
187
+ window.osmd = openSheetMusicDisplay
188
+ openSheetMusicDisplay.render()
189
+ // openSheetMusicDisplay.cursor.show();
190
+ console.log('in load')
191
+ showParsingError.value = false
192
+ },
193
+ )
194
+ .catch((e) => {
195
+ showParsingError.value = true
196
+ console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
197
+ parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
198
+ })
199
+ }
200
+
201
+ async function initOSMD() {
202
+ // renderer = new Flow.Renderer($('#' + divId.value)[0], Flow.Renderer.Backends.SVG);
203
+ // artist = new Artist(10, 10, 750, { scale: 0.8 });
204
+ // tab = new VexTab(artist);
205
+ openSheetMusicDisplay = new OpenSheetMusicDisplay(mainRef.value, {
206
+ autoResize: true,
207
+ backend: 'svg',
208
+ disableCursor: false,
209
+ drawingParameters: 'full', // : "default", // try compact (instead of default)
210
+ drawPartNames: false, // try false
211
+ drawTitle: false,
212
+ drawSubtitle: false,
213
+ drawComposer: false,
214
+ // darkMode: true,
215
+ // cursor
216
+ followCursor: true,
217
+ drawFingerings: true,
218
+ // fingeringPosition: "left", // Above/Below is default. try left or right. experimental: above, below.
219
+ // fingeringPositionFromXML: false, // do this if you want them always left, for example.
220
+ // fingeringInsideStafflines: "true", // default: false. true draws fingerings directly above/below notes
221
+ setWantedStemDirectionByXml: true, // try false, which was previously the default behavior
222
+ // drawUpToMeasureNumber: 20, // draws only up to measure 3, meaning it draws measure 1 to 3 of the piece.
223
+ // drawFromMeasureNumber : 1,
224
+ // drawUpToMeasureNumber : 3,
225
+ drawMeasureNumbers: true,
226
+
227
+ // drawMeasureNumbers: false, // disable drawing measure numbers
228
+ // measureNumberInterval: 4, // draw measure numbers only every 4 bars (and at the beginning of a new system)
229
+ useXMLMeasureNumbers: true, // read measure numbers from xml
230
+
231
+ // coloring options
232
+ coloringEnabled: true,
233
+ // defaultColorNotehead: "#CC0055", // try setting a default color. default is black (undefined)
234
+ // defaultColorStem: "#BB0099",
235
+
236
+ autoBeam: false, // try true, OSMD Function Test AutoBeam sample
237
+ autoBeamOptions: {
238
+ beam_rests: false,
239
+ beam_middle_rests_only: false,
240
+ // groups: [[3,4], [1,1]],
241
+ maintain_stem_directions: false,
242
+ },
243
+ // pageFormat: "A4_P",//"Endless",//pageFormat,
244
+ // pageBackgroundColor: "red",
245
+ renderSingleHorizontalStaffline: true, // true
246
+
247
+ // tupletsBracketed: true, // creates brackets for all tuplets except triplets, even when not set by xml
248
+ // tripletsBracketed: true,
249
+ // tupletsRatioed: true, // unconventional; renders ratios for tuplets (3:2 instead of 3 for triplets)
250
+ })
251
+ // window.osmd = openSheetMusicDisplay;
252
+ // artist.reset();
253
+ // tab.reset();
254
+ // tab.parse(currentTabText.value);
255
+ // artist.render(renderer);
256
+ await fetch('/Fantasia_no.2.musicxml')
257
+ .then((response) => {
258
+ return response.text()
259
+ })
260
+ .then((data) => {
261
+ console.log('in data')
262
+ openSheetMusicDisplay.load(data)
263
+ .then(
264
+ () => {
265
+ window.osmd = openSheetMusicDisplay
266
+ openSheetMusicDisplay.render()
267
+ // openSheetMusicDisplay.cursor.show();
268
+ console.log('in load')
269
+ showParsingError.value = false
270
+ currentScoreCode.value = data
271
+ },
272
+ )
273
+ .catch((e) => {
274
+ showParsingError.value = true
275
+ console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'))
276
+ parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>')
277
+ })
278
+
279
+ // .catch((e) => {
280
+ // showParsingError.value = true;
281
+ // console.log(e.message.replace(/(?:\r\n|\r|\n)/g, '<br>'));
282
+ // parsingError.value = e.message.replace(/(?:\r\n|\r|\n)/g, '<br>');
283
+ // });
284
+ })
285
+
286
+ // title.value = score.title
287
+ // artist.value = score.artist
288
+ }
289
+
290
+ function showHideSettings() {
291
+ console.log('showHideSettings')
292
+ settingsStatus.value = !settingsStatus.value
293
+ }
294
+
295
+ // invoked when there is any type of change in the component
296
+ onUpdated(() => {
297
+ if (props.isActive !== isActive.value) {
298
+ // Prop has changed, update the ref and emit an event
299
+ if (props.isActive) {
300
+ isActive.value = props.isActive
301
+ emit('updateScoreCode', currentScoreCode.value)
302
+ // console.log("emitted updateScoreCode");
303
+ // console.log(currentScoreCode.value);
304
+ }
305
+ isActive.value = props.isActive
306
+ }
307
+ })
308
+
309
+ // watch(currentScoreCode, () => {
310
+ // // Emit the event so that the parent component can update the tabText
311
+ // // prop
312
+ // emit('updateScoreCode', currentScoreCode.value)
313
+ // // console.log("emitted updateScoreCode");
314
+ // // console.log(currentScoreCode.value);
315
+ // })
316
+ watch(currentScoreCode, () => {
317
+ // Emit the event so that the parent component can update the tabText
318
+ // prop
319
+ emit('updateScoreCode', currentScoreCode.value)
320
+ console.log('emitted updateScoreCode')
321
+ // this should go to the first tab that is shown (for now vextab)
322
+ }, { immediate: true })
323
+
324
+ // const moveCursor = () => {
325
+ // openSheetMusicDisplay.cursor.next();
326
+ // };
327
+
328
+ // const fillScore = () => {
329
+ // // console.log(vt);
330
+ // // Perform any logic to fill the score
331
+ // response.value = 'Button pressed!'; // Update response if needed
332
+
333
+ // };
334
+ </script>
335
+
336
+ <template>
337
+ <div>
338
+ <div
339
+ ref="wrapRef"
340
+ class="at-wrap"
341
+ >
342
+ <!-- <div class="at-overlay">
343
+ <div class="at-overlay-content">
344
+ Music sheet is loading
345
+ </div>
346
+ </div> -->
347
+ <div class="at-content">
348
+ <!-- <div class="at-sidebar">
349
+ <div class="at-track-list">
350
+ <div v-for="track in tracks" :key="track.index" class="at-track" @click="renderTrack(track)">
351
+ <div class="at-track-icon">
352
+ <i class="i-fa:music" />
353
+ </div>
354
+ <div class="at-track-details">
355
+ <div class="at-track-name">
356
+ {{ track.name }}
357
+ </div>
358
+ </div>
359
+ </div>
360
+ </div>
361
+ </div> -->
362
+ <div ref="viewPortRef" class="at-viewport">
363
+ <div ref="mainRef" class="at-main" />
364
+ </div>
365
+ </div>
366
+ <div class="at-controls">
367
+ <div class="at-controls-left">
368
+ <div class="at-song-info">
369
+ {{ title }} - {{ artist }}
370
+ </div>
371
+ </div>
372
+ <div class="at-controls-right">
373
+ <div :class="{ active: settingsStatus }">
374
+ <button class="btn i-fa:edit" @click="showHideSettings" />
375
+ </div>
376
+
377
+ <!-- <div>
378
+ <button class="btn i-foundation:print" @click="api.print()" />
379
+ </div>
380
+
381
+ <div>
382
+ <button class="btn i-foundation:page-export" @click="scoreExport" />
383
+ </div> -->
384
+
385
+ <div class="at-zoom">
386
+ <!-- <button class="btn i-fad:zoomin" @click="toggleZoomMenu" /> -->
387
+ <select v-model="selectedZoom" @change="updateZoom">
388
+ <option v-for="zoomOption in zoomOptions" :key="zoomOption.value" :value="zoomOption.value">
389
+ {{ zoomOption.label }}
390
+ </option>
391
+ </select>
392
+ </div>
393
+ <div class="at-layout">
394
+ <select v-model="selectedLayout" @change="updateLayout">
395
+ <option value="horizontal">
396
+ Horizontal
397
+ </option>
398
+ <option value="page">
399
+ Page
400
+ </option>
401
+ </select>
402
+ </div>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ <div v-show="settingsStatus" id="tabSettingsArea">
407
+ <div ref="paneContainer" />
408
+ <textarea
409
+ v-model="currentScoreCode"
410
+ class="editorBox"
411
+ placeholder="add your musicxml code here or drag a file"
412
+ @keyup="handleKeyUp"
413
+ @drop="handleDrop"
414
+ @dragover.prevent
415
+ />
416
+ </div>
417
+ <div v-if="showParsingError" class="parsing-error" v-html="parsingError" />
418
+ </div>
419
+
420
+ <!-- </div> -->
421
+ </template>
422
+
423
+ <style scoped>
424
+ #tabSettingsArea {
425
+ display: flex;
426
+ flex-direction: row;
427
+ align-items: center;
428
+ gap: 20px;
429
+ margin-left: 10px;
430
+ }
431
+ .at-wrap {
432
+ width: 50vw;
433
+ height: 50vh;
434
+ margin: 0 auto;
435
+ border: 1px solid rgba(0, 0, 0, 0.12);
436
+ display: flex;
437
+ flex-direction: column;
438
+ overflow: hidden;
439
+ position: relative;
440
+ }
441
+ .at-content {
442
+ position: relative;
443
+ overflow: hidden;
444
+ flex: 1 1 auto;
445
+ display: flex;
446
+ flex-direction: row;
447
+ }
448
+ .at-viewport {
449
+ flex: 100%;
450
+ overflow-y: scroll;
451
+ /* position: absolute;
452
+ top: 0;
453
+ left: 70px;
454
+ right: 0;
455
+ bottom: 0;
456
+ padding-right: 20px; */
457
+ }
458
+
459
+ /* .at-sidebar {
460
+
461
+ flex: 15%;
462
+ display: flex;
463
+ align-content: stretch;
464
+ justify-content: center;
465
+ max-width: 70px;
466
+ z-index: 1001;
467
+ overflow: hidden;
468
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
469
+ background: #862b2b;
470
+ } */
471
+
472
+ .at-track-list {
473
+ display: flex;
474
+ flex-direction: column;
475
+ }
476
+
477
+ .at-controls {
478
+ /* flex: 0 0 auto; */
479
+ display: flex;
480
+ justify-content: space-between;
481
+ background: #329e17;
482
+ color: #e2e2c3;
483
+ }
484
+
485
+ .at-controls > div {
486
+ display: flex;
487
+ justify-content: flex-start;
488
+ align-content: center;
489
+ align-items: center;
490
+ }
491
+
492
+ .at-controls > div > * {
493
+ display: flex;
494
+ text-align: center;
495
+ align-items: center;
496
+ justify-content: center;
497
+ cursor: pointer;
498
+ padding: 4px;
499
+ margin: 0 3px;
500
+ }
501
+
502
+ .at-controls .btn {
503
+ color: #e2e2c3;
504
+ /* border-radius: 0; */
505
+ height: 40px;
506
+ width: 40px;
507
+ font-size: 16px;
508
+ }
509
+
510
+ .at-controls .active {
511
+ background-color: #5bbb43;
512
+ /* text-decoration: none; */
513
+ }
514
+
515
+ .at-controls select {
516
+ -moz-appearance: none;
517
+ -webkit-appearance: none;
518
+ appearance: none;
519
+ border: none;
520
+ width: 100%;
521
+ height: 40px;
522
+ background: #5bbb43;
523
+ padding: 4px 10px;
524
+ color: #e2e2c3;
525
+ font-size: 16px;
526
+ text-align-last: center;
527
+ text-align: center;
528
+ -ms-text-align-last: center;
529
+ -moz-text-align-last: center;
530
+ cursor: pointer;
531
+ }
532
+
533
+ /* .at-song-title {
534
+ font-weight: bold;
535
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
536
+ } */
537
+
538
+ .at-cursor-bar {
539
+ /* Defines the color of the bar background when a bar is played */
540
+ background: rgba(255, 242, 0, 0.25);
541
+ }
542
+
543
+ .at-selection div {
544
+ /* Defines the color of the selection background */
545
+ background: rgba(64, 64, 255, 0.1);
546
+ }
547
+
548
+ .at-cursor-beat {
549
+ /* Defines the beat cursor */
550
+ background: rgba(64, 64, 255, 0.75);
551
+ width: 3px;
552
+ }
553
+
554
+ .at-highlight * {
555
+ /* Defines the color of the music symbols when they are being played (svg) */
556
+ fill: #0078ff;
557
+ stroke: #0078ff;
558
+ }
559
+ </style>
frontend/src/components/ScoreWidgetBase.vue CHANGED
@@ -1,6 +1,7 @@
1
  <script setup>
2
- import { computed, onMounted, onUpdated, ref, watch } from 'vue'
3
  import { Pane } from 'tweakpane'
 
4
 
5
  const props = defineProps({
6
  // int id
@@ -8,10 +9,6 @@ const props = defineProps({
8
  type: String,
9
  required: true,
10
  },
11
- alphtaText: {
12
- type: String,
13
- required: false,
14
- },
15
  isActive: {
16
  type: Boolean,
17
  required: false,
@@ -22,8 +19,8 @@ const props = defineProps({
22
 
23
  const emit = defineEmits(['updateScoreCode'])
24
 
25
- const openSheetMusicDisplay = null
26
- const timeoutID = null
27
  let pane = null
28
  let api = null
29
  let main = null
@@ -37,6 +34,33 @@ const parsingError = ref('')
37
  // const count = ref(0)
38
  const settingsStatus = ref(false)
39
  const isActive = ref(false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  const SETTINGS_PARAMS = {
42
  scale: 1.0,
@@ -49,55 +73,121 @@ const SETTINGS_PARAMS = {
49
  // prop: 'Put your\nmultiline\ntext here!'
50
  }
51
 
52
- const divId = computed(() => {
53
- return `xmlScoreContainer_${props.id}`
54
- })
55
 
56
  function initializePane() {
57
  pane = new Pane({
58
- container: document.getElementById('paneContainerXml'),
59
  title: 'MusicXML Settings',
60
  },
61
  )
62
  pane.addBinding(SETTINGS_PARAMS, 'scale', {
63
- min: 0.5,
64
  max: 2.0,
65
  step: 0.1,
66
  }).on('change', (ev) => {
67
  console.log(ev.value)
68
- openSheetMusicDisplay.zoom = ev.value
69
- openSheetMusicDisplay.render()
 
70
  })
71
  }
72
 
73
- onMounted(async () => {
 
 
 
 
 
 
 
 
 
74
  // console.log($(`#${divId.value}`)[0])
75
  initializePane()
76
- wrapper = document.querySelector('.at2-wrap')
77
- main = wrapper.querySelector('.at2-main')
 
 
78
  // initialize alphatab
79
  settings = {
80
  // file: 'https://www.alphatab.net/files/canon.gp',
81
- // file: '/Fantasia_no.2.musicxml.gpx',
82
  player: {
83
  enablePlayer: true,
84
  soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
85
- scrollElement: wrapper.querySelector('.at2-viewport'),
 
86
  },
87
  }
88
- initializeOSMD()
89
  })
90
 
91
- function initializeOSMD() {
92
- api = new alphaTab.AlphaTabApi(main, settings)
93
- api.tex(`
94
- \\title 'Test' . 3.3.4
95
- :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
96
 
97
- `)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  // overlay logic
100
- const overlay = wrapper.querySelector('.at2-overlay')
101
  api.renderStarted.on(() => {
102
  overlay.style.display = 'flex'
103
  })
@@ -106,170 +196,27 @@ function initializeOSMD() {
106
  })
107
 
108
  // track selector
109
- function createTrackItem(track) {
110
- const templateElement = document.querySelector('#at2-track-template')
111
- const templateContent = templateElement.firstElementChild
112
- const clonedNode = templateContent.cloneNode(true)
113
- const trackItem = clonedNode.children[1]
114
- trackItem.querySelector('.at2-track-name').textContent = track.name
115
- trackItem.track = track
116
- trackItem.onclick = (e) => {
117
- e.stopPropagation()
118
- api.renderTracks([track])
119
- }
120
- return trackItem
121
- }
122
- const trackList = wrapper.querySelector('.at2-track-list')
123
  api.scoreLoaded.on((score) => {
124
- // clear items
125
- trackList.innerHTML = ''
126
- // generate a track item for all tracks of the score
127
- score.tracks.forEach((track) => {
128
- trackList.appendChild(createTrackItem(track))
129
- })
130
  })
 
131
  api.renderStarted.on(() => {
132
- // collect tracks being rendered
133
- const tracks = new Map()
134
- api.tracks.forEach((t) => {
135
- tracks.set(t.index, t)
136
- })
137
- // mark the item as active or not
138
- const trackItems = trackList.querySelectorAll('.at2-track')
139
- trackItems.forEach((trackItem) => {
140
- if (tracks.has(trackItem.track.index))
141
- trackItem.classList.add('active')
142
-
143
- else
144
- trackItem.classList.remove('active')
145
- })
146
  })
147
 
148
  /** Controls */
149
  api.scoreLoaded.on((score) => {
150
- wrapper.querySelector('.at2-song-title').innerText = score.title
151
- wrapper.querySelector('.at2-song-artist').innerText = score.artist
152
  })
153
 
154
- const countIn = wrapper.querySelector('.at2-controls .at2-count-in')
155
- countIn.onclick = () => {
156
- countIn.classList.toggle('active')
157
- if (countIn.classList.contains('active'))
158
- api.countInVolume = 1
159
-
160
- else
161
- api.countInVolume = 0
162
- }
163
-
164
- const metronome = wrapper.querySelector('.at2-controls .at2-metronome')
165
- metronome.onclick = () => {
166
- metronome.classList.toggle('active')
167
- if (metronome.classList.contains('active'))
168
- api.metronomeVolume = 1
169
-
170
- else
171
- api.metronomeVolume = 0
172
- }
173
-
174
- const loop = wrapper.querySelector('.at2-controls .at2-loop')
175
- loop.onclick = () => {
176
- loop.classList.toggle('active')
177
- api.isLooping = loop.classList.contains('active')
178
- }
179
-
180
- wrapper.querySelector('.at2-controls .at2-print').onclick = () => {
181
- api.print()
182
- }
183
-
184
- const zoom = wrapper.querySelector('.at2-controls .at2-zoom select')
185
- zoom.onchange = () => {
186
- const zoomLevel = Number.parseInt(zoom.value) / 100
187
- api.settings.display.scale = zoomLevel
188
- api.updateSettings()
189
- api.render()
190
- }
191
-
192
- const layout = wrapper.querySelector('.at2-controls .at2-layout select')
193
- layout.onchange = () => {
194
- switch (layout.value) {
195
- case 'horizontal':
196
- api.settings.display.layoutMode = alphaTab.LayoutMode.Horizontal
197
- break
198
- case 'page':
199
- api.settings.display.layoutMode = alphaTab.LayoutMode.Page
200
- break
201
- }
202
- api.updateSettings()
203
- api.render()
204
- }
205
-
206
- // player loading indicator
207
- const playerIndicator = wrapper.querySelector(
208
- '.at2-controls .at2-player-progress',
209
- )
210
- api.soundFontLoad.on((e) => {
211
- const percentage = Math.floor((e.loaded / e.total) * 100)
212
- playerIndicator.innerText = `${percentage}%`
213
- })
214
- api.playerReady.on(() => {
215
- playerIndicator.style.display = 'none'
216
- })
217
-
218
- // main player controls
219
- const playPause = wrapper.querySelector(
220
- '.at2-controls .at2-player-play-pause',
221
- )
222
- const stop = wrapper.querySelector('.at2-controls .at2-player-stop')
223
- playPause.onclick = (e) => {
224
- if (e.target.classList.contains('disabled'))
225
- return
226
-
227
- api.playPause()
228
- }
229
- stop.onclick = (e) => {
230
- if (e.target.classList.contains('disabled'))
231
- return
232
-
233
- api.stop()
234
- }
235
- api.playerReady.on(() => {
236
- playPause.classList.remove('disabled')
237
- stop.classList.remove('disabled')
238
- })
239
  api.playerStateChanged.on((e) => {
240
- const icon = playPause.querySelector('i.fas')
241
- if (e.state === alphaTab.synth.PlayerState.Playing) {
242
- icon.classList.remove('i-fa:play')
243
- icon.classList.add('i-fa:pause')
244
- }
245
- else {
246
- icon.classList.remove('i-fa:pause')
247
- icon.classList.add('i-fa:play')
248
- }
249
  })
250
-
251
- // song position
252
- function formatDuration(milliseconds) {
253
- let seconds = milliseconds / 1000
254
- const minutes = (seconds / 60) | 0
255
- seconds = (seconds - minutes * 60) | 0
256
- return (
257
- `${String(minutes).padStart(2, '0')
258
- }:${
259
- String(seconds).padStart(2, '0')}`
260
- )
261
- }
262
-
263
- const songPosition = wrapper.querySelector('.at2-song-position')
264
- const previousTime = -1
265
  api.playerPositionChanged.on((e) => {
266
  // reduce number of UI updates to second changes.
267
  const currentSeconds = (e.currentTime / 1000) | 0
268
- if (currentSeconds == previousTime)
269
- return
270
-
271
- songPosition.innerText
272
- = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
273
  })
274
  }
275
 
@@ -313,138 +260,243 @@ watch(currentScoreCode, () => {
313
  </script>
314
 
315
  <template>
316
- <!--
317
- Which ever of the following elements wants to use the class
318
- attributes coming from the parent component needs to use
319
- :class="$attrs.class"
320
- UNLESS I have a single root div-element
321
- -->
322
  <div>
323
- <div>
324
- <!-- <div class="at2-wrap"> -->
325
- <div class="at2-wrap">
326
- <div class="at2-overlay">
327
- <div class="at2-overlay-content">
328
- Music sheet is loading
329
- </div>
330
  </div>
331
- <div class="at2-content">
332
- <div class="at2-sidebar">
333
- <div class="at2-sidebar-content">
334
- <div class="at2-track-list" />
 
 
 
 
 
 
 
 
 
335
  </div>
336
  </div>
337
- <div class="at2-viewport">
338
- <div class="at2-main" />
339
- </div>
340
  </div>
341
- <div class="at2-controls">
342
- <div class="at2-controls-left">
343
- <a class="btn at2-player-stop disabled">
344
- <i class="fas i-fa:step-backward"> Begining </i>
345
- </a>
346
- <a class="btn at2-player-play-pause disabled">
347
- <i class="fas i-fa:play">SkAta</i>
348
- </a>
349
- <span class="at2-player-progress">0%</span>
350
- <div class="at2-song-info">
351
- <span class="at2-song-title" /> -
352
- <span class="at2-song-artist" />
353
- </div>
354
- <div class="at2-song-position">
355
- 00:00 / 00:00
356
- </div>
357
- </div>
358
- <div class="at2-controls-right">
359
- <a class="btn toggle at2-count-in">
360
- <i class="fas fa-hourglass-half" />
361
- </a>
362
- <a class="btn at2-metronome">
363
- <i class="fas i-fa:edit" />
364
- </a>
365
- <a class="btn at2-loop">
366
- <i class="fas i-fa:retweet" />
367
- </a>
368
- <a class="btn at2-print">
369
- <i class="fas i-fa:print" />
370
- </a>
371
- <div class="at2-zoom">
372
- <i class="fas i-fa:search" />
373
- <select>
374
- <option value="25">
375
- 25%
376
- </option>
377
- <option value="50">
378
- 50%
379
- </option>
380
- <option value="75">
381
- 75%
382
- </option>
383
- <option value="90">
384
- 90%
385
- </option>
386
- <option value="100" selected>
387
- 100%
388
- </option>
389
- <option value="110">
390
- 110%
391
- </option>
392
- <option value="125">
393
- 125%
394
- </option>
395
- <option value="150">
396
- 150%
397
- </option>
398
- <option value="200">
399
- 200%
400
- </option>
401
- </select>
402
- </div>
403
- <div class="at2-layout">
404
- <select>
405
- <option value="horizontal">
406
- Horizontal
407
- </option>
408
- <option value="page" selected>
409
- Page
410
- </option>
411
- </select>
412
- </div>
413
- </div>
414
  </div>
415
  </div>
416
- <button class="left-align" @click="showHideSettings">
417
- Settings
418
- </button>
419
- <div :id="divId" class="scoreContainer22" />
420
-
421
- <div v-show="settingsStatus" id="tabSettingsArea22">
422
- <div id="paneContainerXml22" />
423
- <textarea
424
- v-model="currentScoreCode"
425
- class="editorBox22"
426
- placeholder="add your musicxml code here or drag a file"
427
- @keyup="handleKeyUp"
428
- @drop="handleDrop"
429
- @dragover.prevent
430
- />
431
- </div>
432
 
433
- <div v-if="showParsingError" class="parsing-error" v-html="parsingError" />
434
- </div>
435
- <template id="at2-track-template" ref="at2-trackTemplate">
436
- <div class="at2-track">
437
- <div class="at2-track-icon">
438
- <i class="fas fa-guitar" />
439
  </div>
440
- <div class="at2-track-details">
441
- <div class="at2-track-name" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
  </div>
443
  </div>
444
- </template>
 
 
 
 
 
 
 
 
 
 
 
 
445
  </div>
 
 
446
  </template>
447
 
448
  <style scoped>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  </style>
 
1
  <script setup>
2
+ import { computed, onMounted, onUnmounted, onUpdated, ref, toRaw, watch } from 'vue'
3
  import { Pane } from 'tweakpane'
4
+ import formatDuration from '@/utils/timeUtils.js'
5
 
6
  const props = defineProps({
7
  // int id
 
9
  type: String,
10
  required: true,
11
  },
 
 
 
 
12
  isActive: {
13
  type: Boolean,
14
  required: false,
 
19
 
20
  const emit = defineEmits(['updateScoreCode'])
21
 
22
+ // const openSheetMusicDisplay = null
23
+ // const timeoutID = null
24
  let pane = null
25
  let api = null
26
  let main = null
 
34
  // const count = ref(0)
35
  const settingsStatus = ref(false)
36
  const isActive = ref(false)
37
+ const wrapRef = ref(null)
38
+ const mainRef = ref(null)
39
+ const viewPortRef = ref(null)
40
+ const paneContainer = ref(null)
41
+
42
+ const tracks = ref([])
43
+ const songPosition = ref('00:00 / 00:00')
44
+ const previousTime = -1
45
+ const title = ref('')
46
+ const artist = ref('')
47
+ const isPlaying = ref(false)
48
+ const selectedLayout = ref('page')
49
+ const isZoomMenuVisible = ref(false)
50
+ const selectedZoom = ref(100)
51
+ const zoomOptions = [
52
+ { value: '25', label: '25%' },
53
+ { value: '50', label: '50%' },
54
+ { value: '75', label: '75%' },
55
+ { value: '90', label: '90%' },
56
+ { value: '100', label: '100%' },
57
+ { value: '110', label: '110%' },
58
+ { value: '125', label: '125%' },
59
+ { value: '150', label: '150%' },
60
+ { value: '200', label: '200%' },
61
+ ]
62
+ const metronomeIsActive = ref(false)
63
+ // const loopIsActive = ref(false)
64
 
65
  const SETTINGS_PARAMS = {
66
  scale: 1.0,
 
73
  // prop: 'Put your\nmultiline\ntext here!'
74
  }
75
 
76
+ // const divId = computed(() => {
77
+ // return `xmlScoreContainer_${props.id}`
78
+ // })
79
 
80
  function initializePane() {
81
  pane = new Pane({
82
+ container: paneContainer.value,
83
  title: 'MusicXML Settings',
84
  },
85
  )
86
  pane.addBinding(SETTINGS_PARAMS, 'scale', {
87
+ min: 0.1,
88
  max: 2.0,
89
  step: 0.1,
90
  }).on('change', (ev) => {
91
  console.log(ev.value)
92
+ api.settings.display.scale = ev.value
93
+ api.updateSettings()
94
+ api.render()
95
  })
96
  }
97
 
98
+ onUnmounted(() => {
99
+ console.log('onUnmounted')
100
+ // pane = null
101
+ api = null
102
+ main = null
103
+ wrapper = null
104
+ settings = null
105
+ })
106
+
107
+ onMounted(() => {
108
  // console.log($(`#${divId.value}`)[0])
109
  initializePane()
110
+ // wrapper = document.querySelector('.at-wrap')
111
+ // main = wrapper.querySelector('.at-main')
112
+ wrapper = wrapRef.value
113
+ main = mainRef.value
114
  // initialize alphatab
115
  settings = {
116
  // file: 'https://www.alphatab.net/files/canon.gp',
117
+ file: '/Fantasia_no.2.musicxml.gpx',
118
  player: {
119
  enablePlayer: true,
120
  soundFont: 'https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2',
121
+ scrollElement: viewPortRef.value,
122
+
123
  },
124
  }
125
+ initAlphaTab()
126
  })
127
 
128
+ function renderTrack(track) {
129
+ // Assuming you have an API method to render tracks
130
+ // Example: api.renderTracks([track]);
131
+ api.renderTracks([toRaw(track)])
132
+ }
133
 
134
+ function updateLayout() {
135
+ switch (selectedLayout.value) {
136
+ case 'horizontal':
137
+ api.settings.display.layoutMode = alphaTab.LayoutMode.Horizontal
138
+ break
139
+ case 'page':
140
+ api.settings.display.layoutMode = alphaTab.LayoutMode.Page
141
+ break
142
+ }
143
+ api.updateSettings()
144
+ api.render()
145
+ }
146
+
147
+ // function toggleZoomMenu() {
148
+ // isZoomMenuVisible.value = !isZoomMenuVisible.value
149
+ // }
150
+
151
+ function updateZoom() {
152
+ // Handle zoom update logic here
153
+ // const zoomLevel = Number.parseInt(zoom.value) / 100
154
+ api.settings.display.scale = selectedZoom.value / 100
155
+ api.updateSettings()
156
+ api.render()
157
+ }
158
+
159
+ function scoreExport() {
160
+ const exporter = new alphaTab.exporter.Gp7Exporter()
161
+ const data = exporter.export(api.score, api.settings)
162
+
163
+ // trigger download
164
+ const a = document.createElement('a')
165
+ a.download = api.score.title.length > 0 ? `${api.score.title}.gp` : 'Untitled.gp'
166
+ a.href = URL.createObjectURL(new Blob([data]))
167
+ document.body.appendChild(a)
168
+ a.click()
169
+ document.body.removeChild(a)
170
+ }
171
+
172
+ function toggleMetronome() {
173
+ metronomeIsActive.value = !metronomeIsActive.value
174
+ if (metronomeIsActive.value)
175
+ api.metronomeVolume = 1
176
+
177
+ else
178
+ api.metronomeVolume = 0
179
+ }
180
+
181
+ function initAlphaTab() {
182
+ console.log('initAlphaTab')
183
+ api = new alphaTab.AlphaTabApi(main, settings)
184
+ // api.tex(`
185
+ // \\title 'Test' . 3.3.4
186
+ // :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
187
+ // `)
188
 
189
  // overlay logic
190
+ const overlay = wrapper.querySelector('.at-overlay')
191
  api.renderStarted.on(() => {
192
  overlay.style.display = 'flex'
193
  })
 
196
  })
197
 
198
  // track selector
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  api.scoreLoaded.on((score) => {
200
+ tracks.value = score.tracks
 
 
 
 
 
201
  })
202
+
203
  api.renderStarted.on(() => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  })
205
 
206
  /** Controls */
207
  api.scoreLoaded.on((score) => {
208
+ title.value = score.title
209
+ artist.value = score.artist
210
  })
211
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  api.playerStateChanged.on((e) => {
213
+ isPlaying.value = e.state
 
 
 
 
 
 
 
 
214
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  api.playerPositionChanged.on((e) => {
216
  // reduce number of UI updates to second changes.
217
  const currentSeconds = (e.currentTime / 1000) | 0
218
+ if (currentSeconds > previousTime)
219
+ songPosition.value = `${formatDuration(e.currentTime)} / ${formatDuration(e.endTime)}`
 
 
 
220
  })
221
  }
222
 
 
260
  </script>
261
 
262
  <template>
 
 
 
 
 
 
263
  <div>
264
+ <div
265
+ ref="wrapRef"
266
+ class="at-wrap"
267
+ >
268
+ <div class="at-overlay">
269
+ <div class="at-overlay-content">
270
+ Music sheet is loading
271
  </div>
272
+ </div>
273
+ <div class="at-content">
274
+ <div class="at-sidebar">
275
+ <div class="at-track-list">
276
+ <div v-for="track in tracks" :key="track.index" class="at-track" @click="renderTrack(track)">
277
+ <div class="at-track-icon">
278
+ <i class="i-fa:music" />
279
+ </div>
280
+ <div class="at-track-details">
281
+ <div class="at-track-name">
282
+ {{ track.name }}
283
+ </div>
284
+ </div>
285
  </div>
286
  </div>
 
 
 
287
  </div>
288
+ <div ref="viewPortRef" class="at-viewport">
289
+ <div ref="mainRef" class="at-main" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  </div>
291
  </div>
292
+ <div class="at-controls">
293
+ <div class="at-controls-left">
294
+ <button class="btn i-fad:rew" @click="api.stop()" />
295
+ <button class="btn" :class="{ 'i-fad:play': !isPlaying, 'i-fad:pause': isPlaying }" @click="api.playPause()" />
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
+ <div class="at-song-info">
298
+ {{ title }} - {{ artist }}
299
+ </div>
300
+ <div class="at-song-position">
301
+ {{ songPosition }}
302
+ </div>
303
  </div>
304
+ <div class="at-controls-right">
305
+ <div :class="{ active: metronomeIsActive }">
306
+ <button class="btn i-fad:metronome" @click="toggleMetronome" />
307
+ </div>
308
+ <!-- <div>
309
+ <button class="btn i-fad:repeat" :class="{ active: loopingIsActive }" @click="toggleLooping" />
310
+ </div> -->
311
+ <div :class="{ active: settingsStatus }">
312
+ <button class="btn i-fa:edit" @click="showHideSettings" />
313
+ </div>
314
+
315
+ <div>
316
+ <button class="btn i-foundation:print" @click="api.print()" />
317
+ </div>
318
+
319
+ <div>
320
+ <button class="btn i-foundation:page-export" @click="scoreExport" />
321
+ </div>
322
+
323
+ <div class="at-zoom">
324
+ <!-- <button class="btn i-fad:zoomin" @click="toggleZoomMenu" /> -->
325
+ <select v-model="selectedZoom" @change="updateZoom">
326
+ <option v-for="zoomOption in zoomOptions" :key="zoomOption.value" :value="zoomOption.value">
327
+ {{ zoomOption.label }}
328
+ </option>
329
+ </select>
330
+ </div>
331
+ <div class="at-layout">
332
+ <select v-model="selectedLayout" @change="updateLayout">
333
+ <option value="horizontal">
334
+ Horizontal
335
+ </option>
336
+ <option value="page">
337
+ Page
338
+ </option>
339
+ </select>
340
+ </div>
341
  </div>
342
  </div>
343
+ </div>
344
+ <div v-show="settingsStatus" id="tabSettingsArea">
345
+ <div ref="paneContainer" />
346
+ <textarea
347
+ v-model="currentScoreCode"
348
+ class="editorBox"
349
+ placeholder="add your musicxml code here or drag a file"
350
+ @keyup="handleKeyUp"
351
+ @drop="handleDrop"
352
+ @dragover.prevent
353
+ />
354
+ </div>
355
+ <div v-if="showParsingError" class="parsing-error" v-html="parsingError" />
356
  </div>
357
+
358
+ <!-- </div> -->
359
  </template>
360
 
361
  <style scoped>
362
+ #tabSettingsArea {
363
+ display: flex;
364
+ flex-direction: row;
365
+ align-items: center;
366
+ gap: 20px;
367
+ margin-left: 10px;
368
+ }
369
+ .at-wrap {
370
+ width: 50vw;
371
+ height: 50vh;
372
+ /* margin: 0 auto; */
373
+ border: 2px solid rgba(0, 0, 0, 0.568);
374
+ display: flex;
375
+ flex-direction: column;
376
+ overflow: hidden;
377
+ position: relative;
378
+ }
379
+ .at-content {
380
+ position: relative;
381
+ overflow: hidden;
382
+ flex: 1 1 auto;
383
+ display: flex;
384
+ flex-direction: row;
385
+ }
386
+ .at-viewport {
387
+ flex: 80%;
388
+ overflow-y: scroll;
389
+ /* position: absolute;
390
+ top: 0;
391
+ left: 70px;
392
+ right: 0;
393
+ bottom: 0;
394
+ padding-right: 20px; */
395
+ }
396
+
397
+ .at-sidebar {
398
+ /* position: absolute;
399
+ top: 0;
400
+ left: 0;
401
+ bottom: 0;
402
+ max-width: 70px;
403
+ width: auto; */
404
+ flex: 15%;
405
+ display: flex;
406
+ align-content: stretch;
407
+ justify-content: center;
408
+ max-width: 70px;
409
+ z-index: 1001;
410
+ overflow: hidden;
411
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
412
+ background: #bec0b6;
413
+ }
414
+
415
+ .at-track-list {
416
+ display: flex;
417
+ flex-direction: column;
418
+ }
419
+
420
+ .at-controls {
421
+ /* flex: 0 0 auto; */
422
+ display: flex;
423
+ justify-content: space-between;
424
+ background: #329e17;
425
+ color: #e2e2c3;
426
+ }
427
+
428
+ .at-controls > div {
429
+ display: flex;
430
+ justify-content: flex-start;
431
+ align-content: center;
432
+ align-items: center;
433
+ }
434
+
435
+ .at-controls > div > * {
436
+ display: flex;
437
+ text-align: center;
438
+ align-items: center;
439
+ justify-content: center;
440
+ cursor: pointer;
441
+ padding: 4px;
442
+ margin: 0 3px;
443
+ }
444
 
445
+ .at-controls .btn {
446
+ color: #e2e2c3;
447
+ /* border-radius: 0; */
448
+ height: 40px;
449
+ width: 40px;
450
+ font-size: 16px;
451
+ }
452
+
453
+ .at-controls .active {
454
+ background-color: #5bbb43;
455
+ /* text-decoration: none; */
456
+ }
457
+
458
+ .at-controls select {
459
+ -moz-appearance: none;
460
+ -webkit-appearance: none;
461
+ appearance: none;
462
+ border: none;
463
+ width: 100%;
464
+ height: 40px;
465
+ background: #5bbb43;
466
+ padding: 4px 10px;
467
+ color: #e2e2c3;
468
+ font-size: 16px;
469
+ text-align-last: center;
470
+ text-align: center;
471
+ -ms-text-align-last: center;
472
+ -moz-text-align-last: center;
473
+ cursor: pointer;
474
+ }
475
+
476
+ /* .at-song-title {
477
+ font-weight: bold;
478
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
479
+ } */
480
+
481
+ .at-cursor-bar {
482
+ /* Defines the color of the bar background when a bar is played */
483
+ background: rgba(255, 242, 0, 0.25);
484
+ }
485
+
486
+ .at-selection div {
487
+ /* Defines the color of the selection background */
488
+ background: rgba(64, 64, 255, 0.1);
489
+ }
490
+
491
+ .at-cursor-beat {
492
+ /* Defines the beat cursor */
493
+ background: rgba(64, 64, 255, 0.75);
494
+ width: 3px;
495
+ }
496
+
497
+ .at-highlight * {
498
+ /* Defines the color of the music symbols when they are being played (svg) */
499
+ fill: #0078ff;
500
+ stroke: #0078ff;
501
+ }
502
  </style>
frontend/src/plugins/router.js CHANGED
@@ -6,6 +6,11 @@ const routes = [
6
  name: 'Analyzer',
7
  component: () => import('@/views/Analyzer.vue'),
8
  },
 
 
 
 
 
9
  {
10
  path: '/',
11
  name: 'Home',
 
6
  name: 'Analyzer',
7
  component: () => import('@/views/Analyzer.vue'),
8
  },
9
+ {
10
+ path: '/drawlisten',
11
+ name: 'DrawAndListen',
12
+ component: () => import('@/views/DrawAndListen.vue'),
13
+ },
14
  {
15
  path: '/',
16
  name: 'Home',
frontend/src/style.css CHANGED
@@ -16,6 +16,7 @@
16
  --background-light2: #dee8f8;
17
  --background-dark: #3c3d3d;
18
  --accent-color: #b9a329;
 
19
  --text-color-dark: #333333;
20
  --text-color-light: #e0e0e0;
21
  color: var(--text-color-dark);
@@ -34,12 +35,12 @@ body {
34
  /* margin: 100px; */
35
  margin: 0px;
36
  /* display: flex; */
37
- /* place-items: top center; */
38
  /* min-width: 320px; */
39
  /* min-height: 100vh; */
40
  /* overflow: auto; */
41
- /* background-color: var(--background-dark); */
42
- background-color: rgb(17, 73, 241);
43
  /* width: 100vw; */
44
  }
45
 
 
16
  --background-light2: #dee8f8;
17
  --background-dark: #3c3d3d;
18
  --accent-color: #b9a329;
19
+ --accent-color2: #e2d16f;
20
  --text-color-dark: #333333;
21
  --text-color-light: #e0e0e0;
22
  color: var(--text-color-dark);
 
35
  /* margin: 100px; */
36
  margin: 0px;
37
  /* display: flex; */
38
+ place-items: top center;
39
  /* min-width: 320px; */
40
  /* min-height: 100vh; */
41
  /* overflow: auto; */
42
+ background-color: var(--background-dark);
43
+ /* background-color: rgb(17, 73, 241); */
44
  /* width: 100vw; */
45
  }
46
 
frontend/src/utils/timeUtils.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // song position
2
+ export default function formatDuration(milliseconds) {
3
+ let seconds = milliseconds / 1000
4
+ const minutes = (seconds / 60) | 0
5
+ seconds = (seconds - minutes * 60) | 0
6
+ return (
7
+ `${String(minutes).padStart(2, '0')
8
+ }:${
9
+ String(seconds).padStart(2, '0')}`
10
+ )
11
+ }
frontend/src/views/Analyzer.vue CHANGED
@@ -1,10 +1,10 @@
1
  <script setup>
2
  import { client } from '@gradio/client'
3
  import { onMounted, ref } from 'vue'
4
- import AlphaTabWidget from '../components/AlphaTabWidget.vue'
5
  import VextabWidget from '@/components/VextabWidget.vue'
6
- import MusicXmlWidget from '@/components/MusicXmlWidget.vue'
7
  import ScoreBaseWidget from '@/components/ScoreWidgetBase.vue'
 
8
  import '@/style.css'
9
 
10
  let app = null
@@ -14,8 +14,8 @@ const currentScoreCode = ref('')
14
  const tabs = [
15
  { name: 'VexTab', component: VextabWidget, id: 'first' },
16
  { name: 'MusicXml', component: MusicXmlWidget, id: 'second' },
17
- // { name: 'AlphaTab', component: AlphaTabWidget, id: 'third' },
18
- // { name: 'Base', component: ScoreBaseWidget, id: 'fourth' },
19
  ]
20
  const tabText_component1 = `
21
  tabstave notation=true key=A time=4/4
@@ -24,10 +24,9 @@ notes :q =|: (5/2.5/3.7/4) :8 7-5h6/3 ^3^ 5h6-7/5 ^3^ :q 7V/4 |
24
  notes :8 t12p7/4 s5s3/4 :8 3s:16:5-7/5 :q p5/4
25
  text :w, |#segno, ,|, :hd, , #tr
26
  `
27
- const tabText_component2 = `
28
- tabstave notation=true key=A time=4/4
29
-
30
- notes :q =|: (5/2.5/3.7/4) :8 7-5h6/3 ^3^ 5h6-7/5 ^3^ :q 7V/4 |
31
  `
32
 
33
  async function connectToServer() {
@@ -68,9 +67,9 @@ function updateScoreCode(scoreCode) {
68
  <h1 class="title">
69
  Guitar Score Analyzer
70
  </h1>
71
- <p class="description">
72
  You can load a guitar score either using Vextab or MusicXml
73
- </p>
74
  <br>
75
 
76
  <!-- Tab Buttons -->
@@ -104,7 +103,7 @@ function updateScoreCode(scoreCode) {
104
  </div>
105
 
106
  <!-- MusicXml Widget -->
107
- <div v-show="currentTab === 1" class="tab" :class="{ active: currentTab === 2 }">
108
  <MusicXmlWidget
109
  :id="tabs[currentTab].id"
110
  class="tab-musicXml"
@@ -113,25 +112,22 @@ function updateScoreCode(scoreCode) {
113
  />
114
  </div>
115
 
116
- <!-- <div v-show="currentTab === 2" class="tab" :class="{ active: currentTab === 1 }">
117
- <AlphaTabWidget
118
  :id="tabs[currentTab].id"
119
- class="tab-vexTab"
120
- :tab-text="tabText_component2"
121
  :is-active="currentTab === 2"
122
  @update-score-code="updateScoreCode"
123
  />
124
- </div> -->
125
 
126
- <!-- <div v-show="currentTab === 3" class="tab" :class="{ active: currentTab === 1 }">
127
  <ScoreBaseWidget
128
  :id="tabs[currentTab].id"
129
- class="tab-vexTab"
130
- :tab-text="tabText_component2"
131
  :is-active="currentTab === 3"
132
  @update-score-code="updateScoreCode"
133
  />
134
- </div> -->
135
  <!-- </div> -->
136
 
137
  <!-- Report Container -->
@@ -151,6 +147,11 @@ function updateScoreCode(scoreCode) {
151
  max-width: 1280px; /* Adjust the max width as needed */
152
  margin: auto;
153
  background-color: rgb(238, 235, 212);
 
 
 
 
 
154
  }
155
 
156
  .title {
@@ -171,10 +172,11 @@ function updateScoreCode(scoreCode) {
171
  }
172
 
173
  .tab-button {
174
- background-color: rgb(248, 240, 201);
175
  padding: 10px;
176
  margin-right: 5px;
177
  cursor: pointer;
 
178
  }
179
 
180
  .tab-button.active {
@@ -187,12 +189,13 @@ function updateScoreCode(scoreCode) {
187
 
188
  .tab.active {
189
  display: flex;
190
- /* background-color: #4caf50; */
191
  width: 800px;
192
  max-width: 1280px;
193
  min-width:200px;
194
  min-height:300px;
195
-
 
196
  }
197
 
198
  .analyze-button {
 
1
  <script setup>
2
  import { client } from '@gradio/client'
3
  import { onMounted, ref } from 'vue'
 
4
  import VextabWidget from '@/components/VextabWidget.vue'
5
+ import MusicXmlWidget from '@/components/MusicXmlWidget2.vue'
6
  import ScoreBaseWidget from '@/components/ScoreWidgetBase.vue'
7
+ import AlphaTexWidget from '@/components/AlphaTexWidget.vue'
8
  import '@/style.css'
9
 
10
  let app = null
 
14
  const tabs = [
15
  { name: 'VexTab', component: VextabWidget, id: 'first' },
16
  { name: 'MusicXml', component: MusicXmlWidget, id: 'second' },
17
+ { name: 'AlphaTex', component: AlphaTexWidget, id: 'third' },
18
+ { name: 'AlphaTab', component: ScoreBaseWidget, id: 'fourth' },
19
  ]
20
  const tabText_component1 = `
21
  tabstave notation=true key=A time=4/4
 
24
  notes :8 t12p7/4 s5s3/4 :8 3s:16:5-7/5 :q p5/4
25
  text :w, |#segno, ,|, :hd, , #tr
26
  `
27
+ const alphaTex = `
28
+ \\title 'Test' . 3.3.4
29
+ :4 2.3 3.3 :8 3.3 4.3 3.3 4.3 |
 
30
  `
31
 
32
  async function connectToServer() {
 
67
  <h1 class="title">
68
  Guitar Score Analyzer
69
  </h1>
70
+ <!-- <p class="description">
71
  You can load a guitar score either using Vextab or MusicXml
72
+ </p> -->
73
  <br>
74
 
75
  <!-- Tab Buttons -->
 
103
  </div>
104
 
105
  <!-- MusicXml Widget -->
106
+ <div v-show="currentTab === 1" class="tab" :class="{ active: currentTab === 1 }">
107
  <MusicXmlWidget
108
  :id="tabs[currentTab].id"
109
  class="tab-musicXml"
 
112
  />
113
  </div>
114
 
115
+ <div v-if="currentTab === 2" class="tab" :class="{ active: currentTab === 2 }">
116
+ <AlphaTexWidget
117
  :id="tabs[currentTab].id"
118
+ :alpha-tex="alphaTex"
 
119
  :is-active="currentTab === 2"
120
  @update-score-code="updateScoreCode"
121
  />
122
+ </div>
123
 
124
+ <div v-if="currentTab === 3" class="tab" :class="{ active: currentTab === 3 }">
125
  <ScoreBaseWidget
126
  :id="tabs[currentTab].id"
 
 
127
  :is-active="currentTab === 3"
128
  @update-score-code="updateScoreCode"
129
  />
130
+ </div>
131
  <!-- </div> -->
132
 
133
  <!-- Report Container -->
 
147
  max-width: 1280px; /* Adjust the max width as needed */
148
  margin: auto;
149
  background-color: rgb(238, 235, 212);
150
+ /* height: 100vh; */
151
+ display: flex;
152
+ flex-direction: column;
153
+ justify-content: center;
154
+ align-items: center;
155
  }
156
 
157
  .title {
 
172
  }
173
 
174
  .tab-button {
175
+ background-color: var(--accent-color2);
176
  padding: 10px;
177
  margin-right: 5px;
178
  cursor: pointer;
179
+ color: var(--text-color-dark);
180
  }
181
 
182
  .tab-button.active {
 
189
 
190
  .tab.active {
191
  display: flex;
192
+ /* background-color: #f5f5f0; */
193
  width: 800px;
194
  max-width: 1280px;
195
  min-width:200px;
196
  min-height:300px;
197
+ justify-content: center;
198
+ align-items: center;
199
  }
200
 
201
  .analyze-button {
frontend/src/views/DrawAndListen.vue ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { client } from '@gradio/client'
3
+ import { onMounted, ref } from 'vue'
4
+ import VextabWidget from '@/components/VextabWidget.vue'
5
+ import MusicXmlWidget from '@/components/MusicXmlWidget2.vue'
6
+ import ScoreBaseWidget from '@/components/ScoreWidgetBase.vue'
7
+ import AlphaTexDL from '@/components/AlphaTexDL.vue'
8
+ import '@/style.css'
9
+
10
+ let app = null
11
+ const response = ref('')
12
+ const currentTab = ref(0)
13
+ const currentScoreCode = ref('')
14
+
15
+ const alphaTex = `
16
+ 15.2.4 :16 17.2 15.2 :8 14.2 14.1 17.1.4 |
17
+ :4 r r r r |
18
+ :8 1.3 3.2 1.1 3.1 3.2 3.1 3.4 2.3 1.3 2.3
19
+ `
20
+
21
+ async function connectToServer() {
22
+ try {
23
+ // app = await client('xribene/guitar-diff')
24
+ app = await client('https://xribene-guitar-diff.hf.space/--replicas/qmsjt/')
25
+ }
26
+ catch (error) {
27
+ console.error('Error fetching data:', error)
28
+ }
29
+ }
30
+
31
+ onMounted(async () => {
32
+ // Call the async function when the component is mounted
33
+ await connectToServer()
34
+ })
35
+
36
+ async function processScore() {
37
+ try {
38
+ const result = await app.predict('/process', [currentScoreCode.value])
39
+ response.value = result.data
40
+ console.log(result.data)
41
+ }
42
+ catch (error) {
43
+ console.error('Error processing the score:', error)
44
+ }
45
+ }
46
+
47
+ function updateScoreCode(scoreCode) {
48
+ currentScoreCode.value = scoreCode
49
+ // console.log("updateScoreCode")
50
+ // console.log(currentScoreCode.value);
51
+ }
52
+ </script>
53
+
54
+ <template>
55
+ <div class="draw-and-listen">
56
+ <h1 class="title">
57
+ Draw & Listen
58
+ </h1>
59
+ <!-- <p class="description">
60
+ You can load a guitar score either using Vextab or MusicXml
61
+ </p> -->
62
+ <br>
63
+
64
+ <!-- Tab Buttons -->
65
+ <!-- <div class="tab-buttons">
66
+ <button
67
+ v-for="(tab, index) in tabs"
68
+ :key="index"
69
+ class="tab-button" :class="[{ active: currentTab === index }]"
70
+ @click="currentTab = index"
71
+ >
72
+ {{ tab.name }}
73
+ </button>
74
+ </div> -->
75
+ <br>
76
+ <br>
77
+
78
+ <div class="tab">
79
+ <AlphaTexDL
80
+ :alpha-tex="alphaTex"
81
+ @update-score-code="updateScoreCode"
82
+ />
83
+ </div>
84
+
85
+ <!-- </div> -->
86
+ <br>
87
+ <br>
88
+ <!-- Report Container -->
89
+ <div class="debug-background-color report-container">
90
+ <button class="analyze-button" @click="processScore">
91
+ Generate
92
+ </button>
93
+ <!-- <p class="response">
94
+ {{ response }}
95
+ </p> -->
96
+ </div>
97
+ </div>
98
+ </template>
99
+
100
+ <style scoped>
101
+ .draw-and-listen {
102
+ /* max-width: 1280px; Adjust the max width as needed */
103
+ width: 60vw;
104
+ height: 100vh;
105
+ margin: auto;
106
+ background-color: rgb(238, 235, 212);
107
+ /* height: 100vh; */
108
+ display: flex;
109
+ flex-direction: column;
110
+ justify-content: center;
111
+ align-items: center;
112
+ }
113
+
114
+ .title {
115
+ font-size: 2em;
116
+ /* flex: 20%; */
117
+ /* margin-bottom: 10px; */
118
+ /* background-color: aqua; */
119
+ text-align: center;
120
+ }
121
+
122
+ .tab {
123
+ flex: 80%;
124
+ /* background-color: aquamarine; */
125
+ }
126
+
127
+ .description {
128
+ font-size: 1.2em;
129
+ margin-bottom: 20px;
130
+ /* background-color: aquamarine; */
131
+ }
132
+
133
+ .analyze-button {
134
+ padding: 10px;
135
+ background-color: #4caf50;
136
+ color: #fff;
137
+ cursor: pointer;
138
+ }
139
+ </style>
frontend/uno.config.js CHANGED
@@ -10,6 +10,11 @@ import {
10
  import presetIcons from '@unocss/preset-icons/browser'
11
 
12
  export default defineConfig({
 
 
 
 
 
13
  presets: [
14
  presetUno(),
15
  presetIcons({
@@ -26,11 +31,13 @@ export default defineConfig({
26
  // mdi: () => import('@iconify/json/mid.json').then(i => i.default),
27
  // logos: () => import('@iconify/json/logos.json').then(i => i.default),
28
  // twemoji: () => import('@iconify/json/twemoji.json').then(i => i.default),
29
- mdi: () => import('@iconify/json/json/mdi.json').then(i => i.default),
30
- logos: () => import('@iconify/json/json/logos.json').then(i => i.default),
31
- twemoji: () => import('@iconify/json/json/twemoji.json').then(i => i.default),
32
- vscode: () => import('@iconify/json/json/vscode-icons.json').then(i => i.default),
33
  fa: () => import('@iconify/json/json/fa-solid.json').then(i => i.default),
 
 
34
  },
35
  }),
36
  presetWebFonts({
 
10
  import presetIcons from '@unocss/preset-icons/browser'
11
 
12
  export default defineConfig({
13
+ rules: [
14
+ [/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
15
+ [/^w-screen-(\d+)$/, ([, d]) => ({ width: `${d}vw` })],
16
+ [/^h-screen-(\d+)$/, ([, d]) => ({ height: `${d}vh` })],
17
+ ],
18
  presets: [
19
  presetUno(),
20
  presetIcons({
 
31
  // mdi: () => import('@iconify/json/mid.json').then(i => i.default),
32
  // logos: () => import('@iconify/json/logos.json').then(i => i.default),
33
  // twemoji: () => import('@iconify/json/twemoji.json').then(i => i.default),
34
+ // mdi: () => import('@iconify/json/json/mdi.json').then(i => i.default),
35
+ // logos: () => import('@iconify/json/json/logos.json').then(i => i.default),
36
+ // twemoji: () => import('@iconify/json/json/twemoji.json').then(i => i.default),
37
+ // vscode: () => import('@iconify/json/json/vscode-icons.json').then(i => i.default),
38
  fa: () => import('@iconify/json/json/fa-solid.json').then(i => i.default),
39
+ fad: () => import('@iconify/json/json/fad.json').then(i => i.default),
40
+ foundation: () => import('@iconify/json/json/foundation.json').then(i => i.default),
41
  },
42
  }),
43
  presetWebFonts({