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,
'type': song_type,
'offset': song[13],
'song_skin': song_skin_out
'song_skin': song_skin_out,
'volume': song[16]
})
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"))
return diag

View File

@ -21,6 +21,7 @@ class Controller{
assets.songs.forEach(song => {
if(song.id == this.selectedSong.folder){
this.mainAsset = song.sound
this.volume = song.volume || 1
}
})
@ -35,6 +36,9 @@ class Controller{
if(syncWith){
this.syncWith = syncWith
}
if(this.multiplayer !== 2){
snd.musicGain.setVolumeMul(this.volume)
}
this.game.run()
this.view.run()
if(this.multiplayer === 1){
@ -161,8 +165,26 @@ class Controller{
if(this.multiplayer){
new LoadSong(this.selectedSong, false, true, this.touchEnabled)
}else{
var taikoGame = new Controller(this.selectedSong, this.songData, this.autoPlayEnabled, false, this.touchEnabled)
taikoGame.run()
new Promise(resolve => {
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){
@ -232,6 +254,7 @@ class Controller{
this.stopMainLoop()
this.keyboard.clean()
this.view.clean()
snd.buffer.loadSettings()
if(!this.multiplayer){
debugObj.controller = null

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -112,7 +112,15 @@ class LoadSong{
}
}))
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{
promises.push(loader.ajax(this.getSongPath(song)).then(data => {
this.songData = data.replace(/\0/g, "").split("\n")

View File

@ -69,25 +69,25 @@ class Mekadon{
type = "don"
}
}
if(type == "daiDon" && playDai){
if(type === "daiDon" && playDai){
this.setKey(kbd["don_l"], ms)
this.setKey(kbd["don_r"], ms)
this.lr = false
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.lr = !this.lr
}else if(type == "daiKa" && playDai){
}else if(type === "daiKa" && playDai){
this.setKey(kbd["ka_l"], ms)
this.setKey(kbd["ka_r"], ms)
this.lr = false
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.lr = !this.lr
}
if(type === "balloon"){
if(circle.requiredHits == 1){
if(circle.requiredHits === 1){
assets.sounds["se_balloon"].play()
}
this.game.checkBalloon(circle)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,10 @@
</select>
</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 class="autoplay-label"><input class="autoplay" type="checkbox">Auto play</label>
<div class="bottom-btns">