diff --git a/BrowserNoEditor.htm b/BrowserNoEditor.htm
index 8733fcf..9722fc7 100644
--- a/BrowserNoEditor.htm
+++ b/BrowserNoEditor.htm
@@ -8,6 +8,7 @@
-
+
SE Volume:
@@ -41,6 +42,8 @@
+
+
\ No newline at end of file
diff --git a/main.js b/main.js
index b1e910e..390a427 100644
--- a/main.js
+++ b/main.js
@@ -40,7 +40,9 @@ let noteList = [];
* 10: sfl 1
*/
let controlList = [];
-let noteListForPlayback = [];
+let noteListForPlayback = []
+let bpmList = []
+let metList = []
let sflTsList = []
let laneToggleList = []
let idOffsetMap = []
@@ -52,6 +54,9 @@ let seTrigger = []
let pendingSeTrigger = []
let seRTrigger = []
let pendingSeRTrigger = []
+let seClockTrigger = []
+let pendingSeClockTrigger = []
+let comboInfo = [0,0]
const arrowCanvas = {
in: document.createElement('canvas'),
@@ -88,8 +93,6 @@ function createArrows() {
}
}
-const enableBga = false
-
const TICK_PER_GAME_SECTION = 1920;
const TICK_PER_BEAT = TICK_PER_GAME_SECTION / 3.8;
let RENDER_DISTANCE = 750
@@ -100,8 +103,10 @@ let RENDER_DISTANCE = 750
//parseNotesFromFile('MusicData/S02-218/S02-218_02.mer'); setBgm('music001282.wav')
//parseNotesFromFile('MusicData/S00-004/S00-004_02.mer'); setBgm('4.wav')
//parseNotesFromFile('MusicData/S01-055/S01-055_02 (2).mer')
-//const playbackId = 'S01-093'
-//parseNotesFromFile(`MusicData/${playbackId}/${playbackId}_02.mer`); setBgm('Sound/Bgm/output/MER_BGM_'+playbackId.replace('-', '_')+'.m4a')
+setTimeout(function() {
+const playbackId = 'S03-062'
+//parseNotesFromFile(`MusicData/${playbackId}/${playbackId}_03.mer`); setBgm('Sound/Bgm/output/MER_BGM_'+playbackId.replace('-', '_')+'.m4a')
+})
let musicTable
function cutText(s) {
const charRenderSize = s.split('').map(c=> c.match(/[a-zA-Z0-9 ]/)?1:2)
@@ -228,6 +233,8 @@ function parseNotesFromText(text) {noteList = [];
noteList = []
controlList = []
noteListForPlayback = []
+ bpmList = []
+ metList = []
sflTsList = []
idOffsetMap = []
prevIdMap = {}
@@ -236,6 +243,8 @@ function parseNotesFromText(text) {noteList = [];
holdList = []
seTrigger = []
seRTrigger = []
+ seClockTrigger = []
+ comboInfo = [0,0]
const controlDupFix = {}
const lines = text.trim().replace(/ +/g, '\t').split('\n');
@@ -320,8 +329,8 @@ function parseNotesFromText(text) {noteList = [];
noteList = noteList.sort((a, b) => a.tickTotal - b.tickTotal)
let noteNo = 0
let timeStampOffset = 0;
- const bpmList = controlList.filter(i => i.cmdType === '2')
- const metList = controlList.filter(i => i.cmdType === '3')
+ bpmList = controlList.filter(i => i.cmdType === '2')
+ metList = controlList.filter(i => i.cmdType === '3')
let metOffset = 0
let TICK_PER_BEAT = TICK_PER_GAME_SECTION / 4
{
@@ -499,6 +508,21 @@ function parseNotesFromText(text) {noteList = [];
seRTrigger = Object.keys(noteListForPlayback.filter(i=>['20','21','22','23','24','25','26'].indexOf(i.noteType) !== -1).map(i => i.timestamp).reduce((v,i) => (v[Math.round(i)]=1,v), {})).map(i => parseInt(i)).sort((a,b)=>(a-b))
seTrigger = Object.keys(noteListForPlayback.filter(i=>['10','12','13','14','sectionSep'].indexOf(i.noteType) === -1).map(i => i.timestamp).reduce((v,i) => (v[Math.round(i)]=1,v), {})).map(i => parseInt(i)).sort((a,b)=>(a-b))
+ comboInfo = [
+ noteListForPlayback.filter(i=>['9','10','12','13','14','25','sectionSep'].indexOf(i.noteType) === -1).length,
+ noteListForPlayback.filter(i=>['20','21','22','23','24','25','26'].indexOf(i.noteType) !== -1).length
+ ]
+
+ {
+ sections = noteListForPlayback.filter(i=>i.noteType === 'sectionSep')
+ const start = sections[0].timestamp
+ const beats = metList[0].value1
+ const duration = sections[1].timestamp - start
+ for (let i=0; i {
document.body.classList[toggle_ui.checked ? 'add' : 'remove']('hide-control')
})
@@ -516,7 +539,8 @@ toggle_long_audio.addEventListener('input', () => {
document.body.classList[toggle_long_audio.checked ? 'add' : 'remove']('long-audio')
})
setInterval(() => {
- stats.textContent = [
+ if (stats.childNodes.length == 0) stats.appendChild(document.createTextNode(''))
+ stats.childNodes[0].nodeValue = [
`frame draw: ${drawCount.frame}`,
`frame actual draw: ${drawCount.actualFrame}`,
].join('\n')
@@ -528,7 +552,6 @@ let startTs = 0
let startNextFrame = false
let currentTs = 0
let currentDistance = 0;
-let currentSection = 0;
let playing = false;
let sfl = 1
let sflOffset = 0
@@ -539,10 +562,19 @@ let chartLength = 0
let displayRatio = 1
const laneEffectMul = 1
const laneOnState = new Uint8Array(60 * laneEffectMul)
+function mainClock(now) {
+ try {
+ render(now)
+ bgmCtr.timerFunc(now)
+ } catch(e) {console.error(e)}
+ requestAnimationFrame(mainClock)
+}
+requestAnimationFrame(mainClock)
function render(now) {
- requestAnimationFrame(render)
drawCount.frame++
+ if (!sflTsList.length) return
+
if (!playing) {
if (!drawForNextFrame) {
return
@@ -588,6 +620,7 @@ function render(now) {
let drawDistance = RENDER_DISTANCE
let previousTs = currentTs
+ let reversing = null
if (playing) {
if (startNextFrame) {
startNextFrame = false
@@ -612,6 +645,7 @@ function render(now) {
let calcBaseTs = Math.max(currentTs, 0)
for (let i=0; i reverseSection[i][0] && calcBaseTs < reverseSection[i][1]) {
+ reversing = reverseSection[i]
calcBaseTs = reverseSection[i][1] + (reverseSection[i][1] - calcBaseTs) * (reverseSection[i][2] - reverseSection[i][1]) / (reverseSection[i][1] - reverseSection[i][0])
drawDistance = Math.min(drawDistance, reverseSection[i][2] - calcBaseTs)
break
@@ -620,11 +654,6 @@ function render(now) {
currentDistance = (calcBaseTs - sflTsList[sflOffset].timestamp) * sfl + sflTsList[sflOffset].distance
}
- if (noteListForPlayback.length) {
- currentSection = (noteListForPlayback.filter(i => i.noteType === 'sectionSep' && i.timestamp <= currentTs).pop()||{section:0}).section
- }
- currentSectionDiv.textContent = `current section: ${currentSection}`
-
updateLaneOnState(previousTs, currentTs)
// black out "off" lanes
const laneBgGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, maxR)
@@ -645,7 +674,7 @@ function render(now) {
ctx.lineTo(centerX, centerY)
}
ctx.fill()
- const notesToRenderArr = getNotesForDraw(currentDistance, drawDistance).filter(i => i.timestamp >= currentTs)
+ const notesToRenderArr = getNotesForDraw(currentDistance - drawDistance * 0.1, drawDistance * 1.1)
notesToRender = {
sectionSep: [],
touch: [],
@@ -661,7 +690,7 @@ function render(now) {
laneEffect: [],
sameTime: [],
unknown: []
- }
+ };
notesToRenderArr.forEach(i => {
switch (i.noteType) {
case 'sectionSep': {notesToRender.sectionSep.push(i); break;}
@@ -972,10 +1001,10 @@ function render(now) {
//ctx.clearRect(0, 0, canvas.width, canvas.height)
const scale = i.r / maxR
ctx.fillStyle = i.c
+ ctx.translate(centerX, centerY)
+ ctx.scale(scale, scale)
i.n.forEach(i => {
ctx.lineWidth = i.w / 0.8 * 4 * displayRatio
- ctx.translate(centerX, centerY)
- ctx.scale(scale, scale)
ctx.beginPath()
i.n.forEach(i => {
ctx.arc(0, 0, maxR * (0.875 + 0.075 * i[0]), i[1], i[1])
@@ -983,8 +1012,8 @@ function render(now) {
ctx.closePath()
ctx.fill()
if (i.w > 0) ctx.stroke()
- ctx.setTransform(1, 0, 0, 1, 0, 0)
})
+ ctx.setTransform(1, 0, 0, 1, 0, 0)
//const r = i.r
//ctx.drawImage(arrowCanvas.flick, 0, 0)
@@ -1008,6 +1037,57 @@ function render(now) {
ctx.stroke()
}
+ {
+ ctx.globalCompositeOperation = 'destination-in'
+ ctx.beginPath();
+ ctx.arc(centerX, centerY, maxR, 0, Math.PI * 2)
+ ctx.fillStyle = 'white'
+ ctx.fill()
+ ctx.globalCompositeOperation = 'source-over'
+ }
+
+ if (bpmList.length && metList.length) {
+ const chkTs = Math.round(Math.max(0, currentTs))
+ const bpmItem = (bpmList.filter(v => v.timestamp <= chkTs).slice(-1)[0])
+ const bpm = bpmItem.value1
+ const met = (metList.filter(v => v.timestamp <= chkTs).slice(-1)[0])
+ const refBase = bpmItem.timestamp > met.timestamp ? bpmItem : met
+ const beatDuration = 60000 / bpm * 4 / (met.value2 || 4)
+ const playedNotesCount = [
+ 0,
+ 0
+ ]
+ for (let i=0; i chkTs) break
+ playedNotesCount[0] += ['9','10','12','13','14','25','sectionSep'].indexOf(noteListForPlayback[i].noteType) === -1
+ playedNotesCount[1] += ['20','21','22','23','24','25','26'].indexOf(noteListForPlayback[i].noteType) !== -1
+ }
+ const playInfoStat = {
+ min: ''+Math.floor(chkTs / 60000),
+ sec: ('0'+(Math.floor(chkTs / 1000) % 60)).slice(-2),
+ milisec: ('000'+(chkTs % 1000)).slice(-3),
+ section: ('000'+Math.floor((chkTs - refBase.timestamp) / beatDuration / met.value1 + refBase.section)).slice(-3),
+ beat: Math.floor((chkTs - refBase.timestamp) / beatDuration + refBase.tick / (TICK_PER_GAME_SECTION / met.value1)) % met.value1,
+ bpm,
+ sfl
+ }
+ const playInfoText = [
+ `${playedNotesCount[0]} (${playedNotesCount[1]}) / ${comboInfo[0]} (${comboInfo[1]})`,
+ `${playInfoStat.min}:${playInfoStat.sec}.${playInfoStat.milisec} (${playInfoStat.section}/${playInfoStat.beat})`,
+ `BPM: ${playInfoStat.bpm}`,
+ `SFL: ${playInfoStat.sfl}`
+ ]
+ if (reversing) {
+ playInfoText.push('Reversing')
+ }
+ const fontSize = 16 * devicePixelRatio
+ ctx.font = `${fontSize}px Arial`
+ ctx.fillStyle = 'white'
+ for (let i=0; i -25) {
+ let bufSrc = seContext.createBufferSource()
+ bufSrc.buffer = seBuffer
+ bufSrc.connect(gain)
+ bufSrc.start(seContext.currentTime + Math.max(0, pendingSeClockTrigger[0] - currentTs) / 1000)
+ }
+ pendingSeClockTrigger.shift()
+ }
}
}
let globalPlaybackOffset = 0
@@ -1057,6 +1147,7 @@ window.play = function () {
playing = true
pendingSeTrigger = seTrigger.filter(i => i > currentTs)
pendingSeRTrigger = seRTrigger.filter(i => i > currentTs)
+ pendingSeClockTrigger = seClockTrigger.filter(i => i > currentTs)
seContext.resume()
gain.gain.value = se_volume.value / 100
}
@@ -1195,11 +1286,11 @@ function resize() {
maxR = Math.round(Math.min(w, h) * 0.45)
drawForNextFrame = true
displayRatio = Math.max(w, h) / 1920
+ const wView = window.innerWidth, hView = window.innerHeight
+ const centerX = wView / 2, centerY = hView / 2
+ const rView = Math.round(Math.min(wView, hView) * 0.45)
if (enableBga) {
- const wView = window.innerWidth, hView = window.innerHeight
- const centerX = wView / 2, centerY = hView / 2
- const rView = Math.round(Math.min(wView, hView) * 0.45)
bga.style.left = (centerX - rView) + 'px'
bga.style.top = (centerY - rView) + 'px'
bga.style.width = (rView * 2) + 'px'
@@ -1208,6 +1299,9 @@ function resize() {
} else {
bga.style.display = 'none'
}
+
+ play_info.style.left = (centerX - rView) + 'px'
+ play_info.style.top = (centerY - rView) + 'px'
createArrows()
}
@@ -1271,8 +1365,8 @@ class BgmController {
e.innerHTML = '0:00 / 0:00
'
this._playBtn = e.getElementsByClassName('btn')[0]
this._progressInput = e.getElementsByClassName('progress')[0].children[0]
- this._playedText = e.getElementsByClassName('played')[0]
- this._durationText = e.getElementsByClassName('total')[0]
+ this._playedText = e.getElementsByClassName('played')[0].childNodes[0]
+ this._durationText = e.getElementsByClassName('total')[0].childNodes[0]
this._volumeBtn = e.getElementsByClassName('volume')[0]
this._volumeInput = e.getElementsByClassName('volume_slider')[0].children[0]
@@ -1354,7 +1448,6 @@ class BgmController {
timerFunc(now) {
const elapsed = now - this._lastNow
this._lastNow = now
- requestAnimationFrame(this._timerFuncBound)
if (!this._playing) return
if (this._duration <= 0) return
this._currentTime += elapsed / 1000
@@ -1376,9 +1469,7 @@ class BgmController {
this._volumeInput.addEventListener('input', this.volumeInput.bind(this))
this._volumeInput.addEventListener('change', this.volumeChange.bind(this))
- this._timerFuncBound = this.timerFunc.bind(this)
this._lastNow = performance.now()
- requestAnimationFrame(this._timerFuncBound)
}
playBtnClick(e) {
if (this._playing) { // pause
@@ -1430,13 +1521,13 @@ class BgmController {
return m+':'+s
}
updateCurrentTime(sec) {
- this._playedText.textContent = this.formatTime(sec)
+ this._playedText.nodeValue = this.formatTime(sec)
if (this._duration > 0) {
this._progressInput.value = sec / this._duration
}
}
updateTotalTime(dur) {
- this._durationText.textContent = this.formatTime(dur)
+ this._durationText.nodeValue = this.formatTime(dur)
}
eventListeners = {