SoundBuffer: Set song volume

- Requires a new column in the database after preview: `volume` REAL
- The value is a volume multiplier, if the value is set to null or 1 there will be no change
- The volume can be set in debugger
- Imported TJA files are now read from disk every time the song is played, freeing some memory and making it easier to create charts
- Correctly parse TJA files with alphabet notes, added "A" and "B" notes, which appear as DON (Big) and KA (Big) respectively
This commit is contained in:
LoveEevee 2019-03-16 00:34:48 +03:00
parent eb4ddb0b1f
commit 61a5d6d496
17 changed files with 135 additions and 49 deletions

3
app.py
View File

@ -142,7 +142,8 @@ def route_api_songs():
'category': category_out, 'category': category_out,
'type': song_type, 'type': song_type,
'offset': song[13], 'offset': song[13],
'song_skin': song_skin_out 'song_skin': song_skin_out,
'volume': song[16]
}) })
return jsonify(songs_out) return jsonify(songs_out)

View File

@ -143,7 +143,7 @@
} }
} }
var issueBody = strings.issueTemplate + "\n\n\n\n" + diag var issueBody = strings.about.issueTemplate + "\n\n\n\n" + diag
this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "<br>\r\n")) this.getLink(this.linkEmail).href += "?body=" + encodeURIComponent(issueBody.replace(/\n/g, "<br>\r\n"))
return diag return diag

View File

@ -21,6 +21,7 @@ class Controller{
assets.songs.forEach(song => { assets.songs.forEach(song => {
if(song.id == this.selectedSong.folder){ if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound this.mainAsset = song.sound
this.volume = song.volume || 1
} }
}) })
@ -35,6 +36,9 @@ class Controller{
if(syncWith){ if(syncWith){
this.syncWith = syncWith this.syncWith = syncWith
} }
if(this.multiplayer !== 2){
snd.musicGain.setVolumeMul(this.volume)
}
this.game.run() this.game.run()
this.view.run() this.view.run()
if(this.multiplayer === 1){ if(this.multiplayer === 1){
@ -161,8 +165,26 @@ class Controller{
if(this.multiplayer){ if(this.multiplayer){
new LoadSong(this.selectedSong, false, true, this.touchEnabled) new LoadSong(this.selectedSong, false, true, this.touchEnabled)
}else{ }else{
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled) new Promise(resolve => {
taikoGame.run() var songObj = assets.songs.find(song => song.id === this.selectedSong.folder)
if(songObj.chart){
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n")
resolve()
})
if(this.selectedSong.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{
reader.readAsText(songObj.chart)
}
}else{
resolve()
}
}).then(() => {
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
taikoGame.run()
})
} }
} }
playSound(id, time){ playSound(id, time){
@ -232,6 +254,7 @@ class Controller{
this.stopMainLoop() this.stopMainLoop()
this.keyboard.clean() this.keyboard.clean()
this.view.clean() this.view.clean()
snd.buffer.loadSettings()
if(!this.multiplayer){ if(!this.multiplayer){
debugObj.controller = null debugObj.controller = null

View File

@ -16,6 +16,7 @@ class Debug{
this.branchSelectDiv = this.byClass("branch-select") this.branchSelectDiv = this.byClass("branch-select")
this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0] this.branchSelect = this.branchSelectDiv.getElementsByTagName("select")[0]
this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0] this.branchResetBtn = this.branchSelectDiv.getElementsByClassName("reset")[0]
this.volumeDiv = this.byClass("music-volume")
this.restartCheckbox = this.byClass("change-restart") this.restartCheckbox = this.byClass("change-restart")
this.autoplayLabel = this.byClass("autoplay-label") this.autoplayLabel = this.byClass("autoplay-label")
this.autoplayCheckbox = this.byClass("autoplay") this.autoplayCheckbox = this.byClass("autoplay")
@ -40,6 +41,10 @@ class Debug{
this.measureNumSlider.onchange(this.measureNumChange.bind(this)) this.measureNumSlider.onchange(this.measureNumChange.bind(this))
this.measureNumSlider.set(0) this.measureNumSlider.set(0)
this.volumeSlider = new InputSlider(this.volumeDiv, 0, 3, 2)
this.volumeSlider.onchange(this.volumeChange.bind(this))
this.volumeSlider.set(1)
this.moveTo(100, 100) this.moveTo(100, 100)
this.restore() this.restore()
this.updateStatus() this.updateStatus()
@ -110,10 +115,12 @@ class Debug{
if(this.songFolder === selectedSong.folder){ if(this.songFolder === selectedSong.folder){
this.offsetChange(this.offsetSlider.get(), true) this.offsetChange(this.offsetSlider.get(), true)
this.branchChange(null, true) this.branchChange(null, true)
this.volumeChange(this.volumeSlider.get(), true)
}else{ }else{
this.songFolder = selectedSong.folder this.songFolder = selectedSong.folder
this.offsetSlider.set(this.defaultOffset) this.offsetSlider.set(this.defaultOffset)
this.branchReset(null, true) this.branchReset(null, true)
this.volumeSlider.set(this.controller.volume)
} }
var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => { var measures = this.controller.parsedSongData.measures.filter((measure, i, array) => {
@ -176,6 +183,14 @@ class Debug{
this.restartSong() this.restartSong()
} }
} }
volumeChange(value, noRestart){
if(this.controller){
snd.musicGain.setVolumeMul(value)
}
if(this.restartCheckbox.checked && !noRestart){
this.restartSong()
}
}
restartSong(){ restartSong(){
if(this.controller){ if(this.controller){
this.controller.restartSong() this.controller.restartSong()
@ -234,6 +249,7 @@ class Debug{
delete this.branchSelectDiv delete this.branchSelectDiv
delete this.branchSelect delete this.branchSelect
delete this.branchResetBtn delete this.branchResetBtn
delete this.volumeDiv
delete this.restartCheckbox delete this.restartCheckbox
delete this.autoplayLabel delete this.autoplayLabel
delete this.autoplayCheckbox delete this.autoplayCheckbox

View File

@ -30,6 +30,7 @@ class Game{
this.currentTimingPoint = 0 this.currentTimingPoint = 0
this.branchNames = ["normal", "advanced", "master"] this.branchNames = ["normal", "advanced", "master"]
this.resetSection() this.resetSection()
this.gameLagSync = !this.controller.touchEnabled && !(/Firefox/.test(navigator.userAgent))
assets.songs.forEach(song => { assets.songs.forEach(song => {
if(song.id == selectedSong.folder){ if(song.id == selectedSong.folder){
@ -364,7 +365,7 @@ class Game{
var value = { var value = {
score: score, score: score,
ms: circle.ms - currentTime, ms: circle.ms - currentTime,
dai: typeDai ? keyDai ? 2 : 1 : 0 dai: typeDai ? (keyDai ? 2 : 1) : 0
} }
if((!keysDon || !typeDon) && (!keysKa || !typeKa)){ if((!keysDon || !typeDon) && (!keysKa || !typeKa)){
value.reverse = true value.reverse = true
@ -536,7 +537,7 @@ class Game{
this.sndTime = this.startDate - snd.buffer.getTime() * 1000 this.sndTime = this.startDate - snd.buffer.getTime() * 1000
}else if(ms < 0 || ms >= 0 && this.started){ }else if(ms < 0 || ms >= 0 && this.started){
var currentDate = Date.now() var currentDate = Date.now()
if(!this.controller.touchEnabled){ if(this.gameLagSync){
var sndTime = currentDate - snd.buffer.getTime() * 1000 var sndTime = currentDate - snd.buffer.getTime() * 1000
var lag = sndTime - this.sndTime var lag = sndTime - this.sndTime
if(Math.abs(lag) >= 50){ if(Math.abs(lag) >= 50){

View File

@ -183,7 +183,7 @@
var songObj = { var songObj = {
id: index + 1, id: index + 1,
type: "tja", type: "tja",
chart: data, chart: file,
stars: [], stars: [],
music: "muted" music: "muted"
} }
@ -258,7 +258,7 @@
var songObj = { var songObj = {
id: index + 1, id: index + 1,
type: "osu", type: "osu",
chart: data, chart: file,
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist, subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode, subtitle_lang: osu.metadata.Artist || osu.metadata.ArtistUnicode,
preview: osu.generalInfo.PreviewTime / 1000, preview: osu.generalInfo.PreviewTime / 1000,

View File

@ -196,18 +196,17 @@ class Keyboard{
this.checkKey(keyCode, "sound", () => { this.checkKey(keyCode, "sound", () => {
var circles = this.controller.getCircles() var circles = this.controller.getCircles()
var circle = circles[this.controller.getCurrentCircle()] var circle = circles[this.controller.getCurrentCircle()]
if( var currentTime = this.keyTime[keyCode]
sound === "don" this.keyTime[sound] = currentTime
&& circle if(circle && !circle.isPlayed){
&& !circle.isPlayed if(circle.type === "balloon"){
&& circle.type === "balloon" if(sound === "don" && circle.requiredHits - circle.timesHit <= 1){
&& circle.requiredHits - circle.timesHit <= 1 this.controller.playSound("se_balloon")
){ return
this.controller.playSound("se_balloon") }
}else{ }
this.controller.playSound("neiro_1_" + sound)
} }
this.keyTime[sound] = this.keyTime[keyCode] this.controller.playSound("neiro_1_" + sound)
}) })
} }
getKeys(){ getKeys(){
@ -242,6 +241,9 @@ class Keyboard{
} }
} }
waitForKeyup(keyCode, type){ waitForKeyup(keyCode, type){
if(!this.keys[keyCode]){
return
}
if(type === "score"){ if(type === "score"){
this.waitKeyupScore[keyCode] = true this.waitKeyupScore[keyCode] = true
}else if(type === "sound"){ }else if(type === "sound"){

View File

@ -118,6 +118,7 @@ class Loader{
0.5 0.5
) )
snd.sfxLoudGain.setVolume(1.2) snd.sfxLoudGain.setVolume(1.2)
snd.buffer.saveSettings()
this.afterJSCount = 0 this.afterJSCount = 0

View File

@ -112,7 +112,15 @@ class LoadSong{
} }
})) }))
if(songObj.chart){ if(songObj.chart){
this.songData = songObj.chart var reader = new FileReader()
promises.push(pageEvents.load(reader).then(event => {
this.songData = event.target.result.replace(/\0/g, "").split("\n")
}))
if(song.type === "tja"){
reader.readAsText(songObj.chart, "sjis")
}else{
reader.readAsText(songObj.chart)
}
}else{ }else{
promises.push(loader.ajax(this.getSongPath(song)).then(data => { promises.push(loader.ajax(this.getSongPath(song)).then(data => {
this.songData = data.replace(/\0/g, "").split("\n") this.songData = data.replace(/\0/g, "").split("\n")

View File

@ -69,25 +69,25 @@ class Mekadon{
type = "don" type = "don"
} }
} }
if(type == "daiDon" && playDai){ if(type === "daiDon" && playDai){
this.setKey(kbd["don_l"], ms) this.setKey(kbd["don_l"], ms)
this.setKey(kbd["don_r"], ms) this.setKey(kbd["don_r"], ms)
this.lr = false this.lr = false
keyDai = true keyDai = true
}else if(type == "don" || type == "daiDon" || drumrollNotes && score !== 2){ }else if(type === "don" || type === "daiDon" || drumrollNotes && score !== 2){
this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"], ms) this.setKey(this.lr ? kbd["don_l"] : kbd["don_r"], ms)
this.lr = !this.lr this.lr = !this.lr
}else if(type == "daiKa" && playDai){ }else if(type === "daiKa" && playDai){
this.setKey(kbd["ka_l"], ms) this.setKey(kbd["ka_l"], ms)
this.setKey(kbd["ka_r"], ms) this.setKey(kbd["ka_r"], ms)
this.lr = false this.lr = false
keyDai = true keyDai = true
}else if(type == "ka" || type == "daiKa" || drumrollNotes){ }else if(type === "ka" || type === "daiKa" || drumrollNotes){
this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"], ms) this.setKey(this.lr ? kbd["ka_l"] : kbd["ka_r"], ms)
this.lr = !this.lr this.lr = !this.lr
} }
if(type === "balloon"){ if(type === "balloon"){
if(circle.requiredHits == 1){ if(circle.requiredHits === 1){
assets.sounds["se_balloon"].play() assets.sounds["se_balloon"].play()
} }
this.game.checkBalloon(circle) this.game.checkBalloon(circle)

View File

@ -10,18 +10,20 @@
this.difficulty = difficulty this.difficulty = difficulty
this.offset = (offset || 0) * -1000 this.offset = (offset || 0) * -1000
this.soundOffset = 0 this.soundOffset = 0
this.noteTypes = [ this.noteTypes = {
{name: false, txt: false}, "0": {name: false, txt: false},
{name: "don", txt: strings.note.don}, "1": {name: "don", txt: strings.note.don},
{name: "ka", txt: strings.note.ka}, "2": {name: "ka", txt: strings.note.ka},
{name: "daiDon", txt: strings.note.daiDon}, "3": {name: "daiDon", txt: strings.note.daiDon},
{name: "daiKa", txt: strings.note.daiKa}, "4": {name: "daiKa", txt: strings.note.daiKa},
{name: "drumroll", txt: strings.note.drumroll}, "5": {name: "drumroll", txt: strings.note.drumroll},
{name: "daiDrumroll", txt: strings.note.daiDrumroll}, "6": {name: "daiDrumroll", txt: strings.note.daiDrumroll},
{name: "balloon", txt: strings.note.balloon}, "7": {name: "balloon", txt: strings.note.balloon},
{name: false, txt: false}, "8": {name: false, txt: false},
{name: "balloon", txt: strings.note.balloon} "9": {name: "balloon", txt: strings.note.balloon},
] "A": {name: "daiDon", txt: strings.note.daiDon},
"B": {name: "daiKa", txt: strings.note.daiKa}
}
this.courseTypes = { this.courseTypes = {
"0": "easy", "0": "easy",
"1": "normal", "1": "normal",
@ -141,6 +143,7 @@
var firstNote = true var firstNote = true
var circles = [] var circles = []
var circleID = 0 var circleID = 0
var regexAZ = /[A-Z]/
var pushMeasure = () => { var pushMeasure = () => {
var note = currentMeasure[0] var note = currentMeasure[0]
@ -321,7 +324,7 @@
}else{ }else{
var string = line.split("") var string = line.toUpperCase().split("")
for(let symbol of string){ for(let symbol of string){
@ -334,7 +337,7 @@
scroll: scroll scroll: scroll
}) })
break break
case "1": case "2": case "3": case "4": case "1": case "2": case "3": case "4": case "A": case "B":
var type = this.noteTypes[symbol] var type = this.noteTypes[symbol]
var circleObj = { var circleObj = {
type: type.name, type: type.name,
@ -413,7 +416,14 @@
currentMeasure = [] currentMeasure = []
break break
default: default:
error = true if(regexAZ.test(symbol)){
currentMeasure.push({
bpm: bpm,
scroll: scroll
})
}else{
error = true
}
break break
} }

View File

@ -856,7 +856,7 @@ class Scoresheet{
this.draw.clean() this.draw.clean()
this.canvasCache.clean() this.canvasCache.clean()
assets.sounds["bgm_result"].stop() assets.sounds["bgm_result"].stop()
snd.musicGain.fadeIn() snd.buffer.loadSettings()
this.redrawRunning = false this.redrawRunning = false
pageEvents.keyRemove(this, "all") pageEvents.keyRemove(this, "all")
pageEvents.remove(this.canvas, ["mousedown", "touchstart"]) pageEvents.remove(this.canvas, ["mousedown", "touchstart"])

View File

@ -107,7 +107,8 @@ class SongSelect{
type: song.type, type: song.type,
offset: song.offset, offset: song.offset,
songSkin: song.song_skin || {}, songSkin: song.song_skin || {},
music: song.music music: song.music,
volume: song.volume
}) })
} }
this.songs.sort((a, b) => { this.songs.sort((a, b) => {
@ -1741,7 +1742,7 @@ class SongSelect{
if(!loadOnly){ if(!loadOnly){
this.preview = songObj.preview_sound this.preview = songObj.preview_sound
this.preview.gain = snd.previewGain this.preview.gain = snd.previewGain
this.previewLoaded(startLoad, songObj.preview_time) this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
} }
}else{ }else{
songObj = {id: id} songObj = {id: id}
@ -1767,7 +1768,7 @@ class SongSelect{
if(currentId === this.previewId){ if(currentId === this.previewId){
songObj.preview_sound = sound songObj.preview_sound = sound
this.preview = sound this.preview = sound
this.previewLoaded(startLoad, songObj.preview_time) this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
var oldPreview = this.previewList.shift() var oldPreview = this.previewList.shift()
if(oldPreview){ if(oldPreview){
@ -1781,11 +1782,12 @@ class SongSelect{
} }
} }
} }
previewLoaded(startLoad, prvTime){ previewLoaded(startLoad, prvTime, volume){
var endLoad = this.getMS() var endLoad = this.getMS()
var difference = endLoad - startLoad var difference = endLoad - startLoad
var minDelay = 300 var minDelay = 300
var delay = minDelay - Math.min(minDelay, difference) var delay = minDelay - Math.min(minDelay, difference)
snd.previewGain.setVolumeMul(volume || 1)
this.preview.playLoop(delay / 1000, false, prvTime) this.preview.playLoop(delay / 1000, false, prvTime)
} }
endPreview(){ endPreview(){
@ -1935,7 +1937,7 @@ class SongSelect{
if(!this.bgmEnabled){ if(!this.bgmEnabled){
snd.musicGain.fadeIn() snd.musicGain.fadeIn()
setTimeout(() => { setTimeout(() => {
snd.musicGain.fadeIn() snd.buffer.loadSettings()
}, 500) }, 500)
} }
this.redrawRunning = false this.redrawRunning = false

View File

@ -3,6 +3,7 @@
var AudioContext = window.AudioContext || window.webkitAudioContext var AudioContext = window.AudioContext || window.webkitAudioContext
this.context = new AudioContext() this.context = new AudioContext()
pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this)) pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this))
this.gainList = []
} }
load(url, local, gain){ load(url, local, gain){
if(local){ if(local){
@ -27,7 +28,9 @@
}) })
} }
createGain(channel){ createGain(channel){
return new SoundGain(this, channel) var gain = new SoundGain(this, channel)
this.gainList.push(gain)
return gain
} }
setCrossfade(gain1, gain2, median){ setCrossfade(gain1, gain2, median){
if(!Array.isArray(gain1)){ if(!Array.isArray(gain1)){
@ -60,6 +63,18 @@
this.context.resume() this.context.resume()
} }
} }
saveSettings(){
for(var i = 0; i < this.gainList.length; i++){
var gain = this.gainList[i]
gain.defaultVol = gain.volume
}
}
loadSettings(){
for(var i = 0; i < this.gainList.length; i++){
var gain = this.gainList[i]
gain.setVolume(gain.defaultVol)
}
}
} }
class SoundGain{ class SoundGain{
constructor(soundBuffer, channel){ constructor(soundBuffer, channel){
@ -85,6 +100,9 @@ class SoundGain{
this.gainNode.gain.value = amount * amount this.gainNode.gain.value = amount * amount
this.volume = amount this.volume = amount
} }
setVolumeMul(amount){
this.setVolume(Math.sqrt(amount * amount * this.defaultVol))
}
setCrossfade(amount){ setCrossfade(amount){
this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount))) this.setVolume(Math.sqrt(Math.sin(Math.PI / 2 * amount)))
} }

View File

@ -1,7 +1,7 @@
function StringsJa(){ function StringsJa(){
this.id = "ja" this.id = "ja"
this.name = "日本語" this.name = "日本語"
this.regex = /^ja$/ this.regex = /^ja$|^ja-/
this.font = "TnT, Meiryo, sans-serif" this.font = "TnT, Meiryo, sans-serif"
this.taikoWeb = "たいこウェブ" this.taikoWeb = "たいこウェブ"

View File

@ -1438,7 +1438,7 @@
ctx.fill() ctx.fill()
ctx.globalAlpha = 1 ctx.globalAlpha = 1
} }
if(!circle.animating){ if(!circle.animating && circle.text){
// Text // Text
var text = circle.text var text = circle.text
var textX = circlePos.x var textX = circlePos.x

View File

@ -20,6 +20,10 @@
</select> </select>
</div> </div>
</div> </div>
<div>Music volume:</div>
<div class="music-volume input-slider">
<span class="reset">x</span><input type="text" value="" readonly><span class="minus">-</span><span class="plus">+</span>
</div>
<label><input class="change-restart" type="checkbox">Restart on change</label> <label><input class="change-restart" type="checkbox">Restart on change</label>
<label class="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label> <label class="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label>
<div class="bottom-btns"> <div class="bottom-btns">