From 95686a637cd705c301c39191f330196eec7c8129 Mon Sep 17 00:00:00 2001 From: oc Date: Fri, 29 Dec 2017 12:28:59 +0800 Subject: [PATCH] Music --- index.html | 1 + libs/core.js | 320 ++++++++++++----- libs/data.js | 6 +- libs/events.js | 80 +++-- libs/floors/test.js | 48 --- libs/items.js | 3 + libs/thirdparty/mid.js | 701 +++++++++++++++++++++++++++++++++++++ libs/thirdparty/mid.min.js | 3 + libs/ui.js | 5 +- main.js | 27 +- sounds/058-Slow01.mid | Bin 0 -> 6183 bytes sounds/G2_koko.mid | Bin 0 -> 31666 bytes sounds/star.mid | Bin 0 -> 15102 bytes test.js | 0 14 files changed, 1016 insertions(+), 178 deletions(-) delete mode 100644 libs/floors/test.js create mode 100644 libs/thirdparty/mid.js create mode 100644 libs/thirdparty/mid.min.js create mode 100644 sounds/058-Slow01.mid create mode 100644 sounds/G2_koko.mid create mode 100644 sounds/star.mid create mode 100644 test.js diff --git a/index.html b/index.html index 44c759a0..ae063d97 100644 --- a/index.html +++ b/index.html @@ -113,5 +113,6 @@ 此浏览器不支持HTML5 + \ No newline at end of file diff --git a/libs/core.js b/libs/core.js index b40b7d91..d89d08cf 100644 --- a/libs/core.js +++ b/libs/core.js @@ -7,12 +7,14 @@ function core() { this.statusBar = {}; this.canvas = {}; this.images = []; - this.sounds = {}; + this.bgms = []; + this.sounds = []; this.floorIds = []; this.floors = {}; this.firstData = {}; this.material = { 'images': {}, + 'bgms': {}, 'sounds': {}, 'ground': null, 'items': {}, @@ -35,12 +37,12 @@ function core() { 'openDoorAnimate': null } this.musicStatus = { - 'isIOS': false, - 'loaded': false, - 'bgmStatus': false, - 'soundStatus': true, - 'playedSound': null, - 'playedBgm': null, + 'audioContext': null, // WebAudioContext + 'startDirectly': false, // 是否直接播放(加载)音乐 + 'bgmStatus': false, // 是否播放BGM + 'soundStatus': true, // 是否播放SE + 'playingBgm': null, // 正在播放的BGM + 'isPlaying': false, } // 样式 this.domStyle = { @@ -102,11 +104,12 @@ function core() { /////////// 系统事件相关 /////////// -core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds, floors, coreData) { +core.prototype.init = function (dom, statusBar, canvas, images, bgms, sounds, floorIds, floors, coreData) { core.dom = dom; core.statusBar = statusBar; core.canvas = canvas; core.images = images; + core.bgms = bgms; core.sounds = sounds; core.floorIds = floorIds; core.floors = floors; @@ -134,16 +137,43 @@ core.prototype.init = function (dom, statusBar, canvas, images, sounds, floorIds core.material.icons = core.icons.getIcons(); core.material.events = core.events.getEvents(); - // test if iOS - core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); - var userAgent = navigator.userAgent; - if (userAgent.indexOf('iPhone') > -1 || userAgent.indexOf('iPad') > -1) { - console.log("你的设备为iphone,不自动播放音乐!"); - core.musicStatus.isIOS = true; - core.musicStatus.soundStatus = false; + + if (location.protocol.indexOf("http")==0) { + window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext; + try { + core.musicStatus.audioContext = new window.AudioContext(); + } catch (e) { + console.log("该浏览器不支持AudioContext"); + core.musicStatus.audioContext = null; + } } + // 音效设置部分 + var isPC = true; + ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"].forEach(function (t) { + if (navigator.userAgent.indexOf(t)>=0) isPC=false; + }); + if (isPC) { + // 如果是PC端直接加载 + core.musicStatus.startDirectly = true; + } + else { + var connection = navigator.connection; + if (core.isset(connection) && connection.type=='wifi') + core.musicStatus.startDirectly = true; + } + + // 先从存储中读取BGM状态 + core.musicStatus.bgmStatus = core.getLocalStorage('bgmStatus', true); + if (!core.musicStatus.startDirectly) // 如果当前网络环境不允许 + core.musicStatus.bgmStatus = false; + core.setLocalStorage('bgmStatus', core.musicStatus.bgmStatus); + + core.musicStatus.soundStatus = core.getLocalStorage('soundStatus', true); + core.setLocalStorage('soundStatus', core.musicStatus.soundStatus); + + // switchs core.flags.battleAnimate = core.getLocalStorage('battleAnimate', core.flags.battleAnimate); core.flags.displayEnemyDamage = core.getLocalStorage('enemyDamage', core.flags.displayEnemyDamage); @@ -232,7 +262,7 @@ core.prototype.loader = function (callback) { core.material.images.autotile[autotileId]=image; if (Object.keys(core.material.images.autotile).length==autotileIds.length) { // 音频 - core.loadSounds(callback); + core.loadMusic(callback); } }) } @@ -259,65 +289,112 @@ core.prototype.loadImage = function (imgName, callback) { } } -core.prototype.loadSounds = function (callback) { - for (var key in core.sounds) { - for (var i = 0; i < core.sounds[key].length; i++) { - var soundName=core.sounds[key][i]; - soundName = soundName.split('-'); - var sound = new Audio(); - sound.preload = 'none'; - sound.src = 'sounds/' + soundName[0] + '.' + key; - if (soundName[1] == 'loop') { - sound.loop = 'loop'; +core.prototype.loadMusic = function (callback) { + + core.bgms.forEach(function (t) { + + // 判断是不是mid + if (/^.*\.mid$/.test(t)) { + + if (core.musicStatus.audioContext!=null) { + core.material.bgms[t] = 'loading'; + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'sounds/'+t, true); + xhr.overrideMimeType("text/plain; charset=x-user-defined"); + xhr.onload = function(e) { //下载完成 + try { + var ff = []; + var mx = this.responseText.length; + for (var z = 0; z < mx; z++) + ff[z] = String.fromCharCode(this.responseText.charCodeAt(z) & 255); + var shouldStart = core.material.bgms[t] == 'starting'; + core.material.bgms[t] = AudioPlayer(core.musicStatus.audioContext, Replayer(MidiFile(ff.join("")), Synth(44100)), true); + + if (shouldStart) + core.playBgm(t); + } + catch (ee) { + console.log(ee); + core.material.bgms[t] = null; + } + + }; + xhr.ontimeout = function(e) { + console.log(e); + core.material.bgms[t] = null; + } + xhr.onerror = function(e) { + console.log(e); + core.material.bgms[t] = null; + } + xhr.send(); + } + else { + core.material.bgms[t] = null; } - - if (!core.isset(core.material.sounds[key])) - core.material.sounds[key] = {}; - core.material.sounds[key][soundName[0]] = sound; } - } + else { + var music = new Audio(); + if (!core.musicStatus.startDirectly) + music.preload = 'none'; // 默认不加载 + music.src = 'sounds/'+t; + music.loop = 'loop'; + core.material.bgms[t] = music; + } + }); + + core.sounds.forEach(function (t) { + + if (core.musicStatus.audioContext != null) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'sounds/'+t, true); + xhr.responseType = 'arraybuffer'; + xhr.onload = function(e) { //下载完成 + try { + core.musicStatus.audioContext.decodeAudioData(this.response, function (buffer) { + core.material.sounds[t] = buffer; + }, function (e) { + console.log(e); + core.material.sounds[t] = null; + }) + } + catch (ee) { + console.log(ee); + core.material.sounds[t] = null; + } + }; + + xhr.ontimeout = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.onerror = function(e) { + console.log(e); + core.material.sounds[t] = null; + } + xhr.send(); + } + else { + var music = new Audio(); + music.src = 'sounds/'+t; + core.material.sounds[t] = music; + } + + }); + + // 直接开始播放 + if (core.musicStatus.startDirectly && core.bgms.length>0) + core.playBgm(core.bgms[0]); + callback(); } -core.prototype.loadSound = function() { - if (!core.isset(core.material.sounds.mp3)) return; - if (core.musicStatus.isIOS) return; - if (core.musicStatus.loaded) return; - core.musicStatus.loaded=true; - console.log("加载音乐"); - - var toLoadList = []; - - for (var key in core.material.sounds) { - for (var name in core.material.sounds[key]) { - toLoadList.push(core.material.sounds[key][name]); - } - } - core.loadSoundItem(toLoadList); -} - -core.prototype.loadSoundItem = function (toLoadList) { - if (toLoadList.length==0) { - if (core.musicStatus.bgmStatus>0) return; - core.musicStatus.bgmStatus=1; - if (core.musicStatus.soundStatus) - core.playBgm('bgm', 'mp3'); - return; - } - var item = toLoadList.shift(); - item.oncanplay = function() { - core.loadSoundItem(toLoadList); - } - item.load(); -} - core.prototype.isPlaying = function() { if (core.isset(core.status.played) && core.status.played) return true; return false; } - core.prototype.clearStatus = function() { // 停止各个Timeout和Interval for (var i in core.interval) { @@ -1472,7 +1549,7 @@ core.prototype.openDoor = function (id, x, y, needKey, callback) { } } // open - core.playSound("door", "ogg"); + core.playSound("door.ogg"); var state = 0; var doorId = id; if (!(doorId.substring(doorId.length-4)=="Door")) { @@ -1518,7 +1595,7 @@ core.prototype.battle = function (id, x, y, force, callback) { }); } else { - core.playSound('attack', 'ogg'); + core.playSound('attack.ogg'); core.afterBattle(id, x, y, callback); } } @@ -1613,7 +1690,7 @@ core.prototype.changeFloor = function (floorId, stair, heroLoc, time, callback) } window.setTimeout(function () { - core.playSound('floor', 'mp3'); + core.playSound('floor.mp3'); core.mapChangeAnimate('show', time/2, function () { // 根据文字判断是否斜体 @@ -2770,7 +2847,7 @@ core.prototype.getNextItem = function() { core.prototype.getItem = function (itemId, itemNum, itemX, itemY, callback) { // core.getItemAnimate(itemId, itemNum, itemX, itemY); - core.playSound('item', 'ogg'); + core.playSound('item.ogg'); var itemCls = core.material.items[itemId].cls; core.items.getItemEffect(itemId, itemNum); core.removeBlock(itemX, itemY); @@ -3375,23 +3452,106 @@ core.prototype.isset = function (val) { return true } -core.prototype.playSound = function (soundName, soundType) { - if (!core.musicStatus.soundStatus || !core.musicStatus.loaded) { +core.prototype.playBgm = function (bgm) { + + // 如果不允许播放 + if (!core.musicStatus.bgmStatus) return; + // 音频不存在 + if (!core.isset(core.material.bgms[bgm])) return; + + // 延迟播放 + if (core.material.bgms[bgm] == 'loading') { + core.material.bgms[bgm] = 'starting'; return; } - if (!core.isset(core.material.sounds[soundType][soundName])) return; - core.musicStatus.playedSound = core.material.sounds[soundType][soundName]; - core.musicStatus.playedSound.play(); + + try { + // 如果当前正在播放,且和本BGM相同,直接忽略 + if (core.musicStatus.playingBgm == bgm && core.musicStatus.isPlaying) { + return; + } + // 如果正在播放中,暂停 + if (core.isset(core.musicStatus.playingBgm) && core.musicStatus.isPlaying) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + // 播放当前BGM + core.musicStatus.playingBgm = bgm; + core.material.bgms[bgm].play(); + core.musicStatus.isPlaying = true; + + } + catch (e) { + console.log("无法播放BGM "+bgm); + console.log(e); + core.musicStatus.playingBgm = null; + } } -core.prototype.playBgm = function (bgmName, bgmType) { - if (core.musicStatus.isIOS || !core.musicStatus.loaded) return; - if (core.isset(core.musicStatus.playedBgm)) { - core.musicStatus.playedBgm.pause(); +core.prototype.pauseBgm = function () { + // 直接暂停播放 + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].pause(); + } + core.musicStatus.isPlaying = false; + } + catch (e) { + console.log("无法暂停BGM "+bgm); + console.log(e); + } +} + +core.prototype.resumeBgm = function () { + // 恢复BGM + try { + if (core.isset(core.musicStatus.playingBgm)) { + core.material.bgms[core.musicStatus.playingBgm].play(); + core.musicStatus.isPlaying = true; + } + else { + if (core.bgms.length>0) { + core.playBgm(core.bgms[0]); + core.musicStatus.isPlaying = true; + } + } + } + catch (e) { + console.log("无法恢复BGM "+bgm); + console.log(e); + } +} + +core.prototype.playSound = function (sound) { + + // 如果不允许播放 + if (!core.musicStatus.soundStatus) return; + // 音频不存在 + if (!core.isset(core.material.sounds[sound])) return; + + try { + if (core.musicStatus.audioContext != null) { + var source = core.musicStatus.audioContext.createBufferSource(); + source.buffer = core.material.sounds[sound]; + source.connect(core.musicStatus.audioContext.destination); + try { + source.start(0); + } + catch (e) { + try { + source.noteOn(0); + } + catch (ee) { + } + } + } + else { + core.material.sounds[sound].play(); + } + } + catch (eee) { + console.log("无法播放SE "+bgm); + console.log(eee); } - core.musicStatus.playedBgm = core.material.sounds[bgmType][bgmName]; - if (core.musicStatus.soundStatus) - core.musicStatus.playedBgm.play(); } core.prototype.changeSoundStatus = function () { diff --git a/libs/data.js b/libs/data.js index a454c296..870ffb38 100644 --- a/libs/data.js +++ b/libs/data.js @@ -140,12 +140,12 @@ data.prototype.init = function() { // 系统FLAG,在游戏运行中中请不要修改它。 this.flags = { /****** 状态栏相关 ******/ - "enableFloor": false, // 是否在状态栏显示当前楼层 - "enableLv": true, // 是否在状态栏显示当前等级 + "enableFloor": true, // 是否在状态栏显示当前楼层 + "enableLv": false, // 是否在状态栏显示当前等级 "enableMDef": true, // 是否在状态栏及战斗界面显示魔防(护盾) "enableMoney": true, // 是否在状态栏、怪物手册及战斗界面显示金币 "enableExperience": true, // 是否在状态栏、怪物手册及战斗界面显示经验 - "enableLevelUp": true, // 是否允许等级提升(进阶);如果上面enableExperience为false,则此项恒视为false + "enableLevelUp": false, // 是否允许等级提升(进阶);如果上面enableExperience为false,则此项恒视为false "enableDebuff": true, // 是否涉及毒衰咒;如果此项为false则不会在状态栏中显示毒衰咒的debuff ////// 上述的几个开关将直接影响状态栏的显示效果 ////// /****** 道具相关 ******/ diff --git a/libs/events.js b/libs/events.js index 051996a0..7d8f955f 100644 --- a/libs/events.js +++ b/libs/events.js @@ -133,6 +133,17 @@ events.prototype.afterChangeFloor = function (floorId) { this.doEvents(core.floors[floorId].firstArrive); core.setFlag("visited_"+floorId, true); } + + // 播放BGM + if (floorId == 'sample0') { + core.playBgm('bgm.mp3'); + } + if (floorId == 'sample1') { + core.playBgm('star.mid'); + } + if (floorId == 'sample2') { + core.playBgm('058-G2_koko.mid'); + } } ////// 实际事件的处理 ////// @@ -285,11 +296,6 @@ events.prototype.doAction = function() { block = block.block; if (core.isset(block.event) && block.event.trigger=='action') { // 触发 - /* - core.status.event = {'id': 'action', 'data': { - 'list': core.clone(block.event.data), 'x': block.x, 'y': block.y, 'callback': core.status.event.data.callback - }} - */ core.status.event.data.list = core.clone(block.event.data); core.status.event.data.x=block.x; core.status.event.data.y=block.y; @@ -298,11 +304,21 @@ events.prototype.doAction = function() { this.doAction(); break; case "playSound": - var name=data.name.split("."); - if (name.length==2) - core.playSound(name[0],name[1]); + core.playSound(data.name); this.doAction(); break; + case "playBgm": + core.playBgm(data.name); + this.doAction(); + break + case "pauseBgm": + core.pauseBgm(); + this.doAction(); + break + case "resumeBgm": + core.resumeBgm(); + this.doAction(); + break case "setValue": try { var value=core.calValue(data.value); @@ -623,6 +639,12 @@ events.prototype.changeLight = function(x, y) { // 改变灯后的事件 events.prototype.afterChangeLight = function(x,y) { +} + +// 使用炸弹/圣锤后的事件 +events.prototype.afterUseBomb = function () { + + } // 存档事件前一刻的处理 @@ -651,6 +673,13 @@ events.prototype.keyDownCtrl = function () { } } +events.prototype.clickConfirmBox = function (x,y) { + if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) + core.status.event.data.yes(); + if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) + core.status.event.data.no(); +} + events.prototype.keyUpConfirmBox = function (keycode) { if (keycode==37) { core.status.event.selection=0; @@ -673,12 +702,6 @@ events.prototype.keyUpConfirmBox = function (keycode) { } } } -events.prototype.clickConfirmBox = function (x,y) { - if ((x == 4 || x == 5) && y == 7 && core.isset(core.status.event.data.yes)) - core.status.event.data.yes(); - if ((x == 7 || x == 8) && y == 7 && core.isset(core.status.event.data.no)) - core.status.event.data.no(); -} // 正在处理事件时的点击操作... events.prototype.clickAction = function (x,y) { @@ -769,7 +792,6 @@ events.prototype.keyUpBook = function (keycode) { } } -// 飞行器 events.prototype.clickFly = function(x,y) { if ((x==10 || x==11) && y==9) core.ui.drawFly(core.status.event.data-1); if ((x==10 || x==11) && y==5) core.ui.drawFly(core.status.event.data+1); @@ -1115,21 +1137,27 @@ events.prototype.keyUpSL = function (keycode) { events.prototype.clickSwitchs = function (x,y) { if (x<5 || x>7) return; var choices = [ - "背景音乐", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" + "背景音乐", "背景音效", "战斗动画", "怪物显伤", "领域显伤", "返回主菜单" ]; var topIndex = 6 - parseInt((choices.length - 1) / 2); if (y>=topIndex && y> 4; + event.channel = eventTypeByte & 0x0f; + event.type = 'channel'; + switch (eventType) { + case 0x08: + event.subtype = 'noteOff'; + event.noteNumber = param1; + event.velocity = stream.readInt8(); + return event; + case 0x09: + event.noteNumber = param1; + event.velocity = stream.readInt8(); + if (event.velocity == 0) { + event.subtype = 'noteOff'; + } else { + event.subtype = 'noteOn'; + } + return event; + case 0x0a: + event.subtype = 'noteAftertouch'; + event.noteNumber = param1; + event.amount = stream.readInt8(); + return event; + case 0x0b: + event.subtype = 'controller'; + event.controllerType = param1; + event.value = stream.readInt8(); + return event; + case 0x0c: + event.subtype = 'programChange'; + event.programNumber = param1; + return event; + case 0x0d: + event.subtype = 'channelAftertouch'; + event.amount = param1; + return event; + case 0x0e: + event.subtype = 'pitchBend'; + event.value = param1 + (stream.readInt8() << 7); + return event; + default: + throw "Unrecognised MIDI event type: " + eventType + } + } + } + + stream = Stream(data); + var headerChunk = readChunk(stream); + if (headerChunk.id != 'MThd' || headerChunk.length != 6) { + throw "Bad .mid file - header not found"; + } + var headerStream = Stream(headerChunk.data); + var formatType = headerStream.readInt16(); + var trackCount = headerStream.readInt16(); + var timeDivision = headerStream.readInt16(); + + if (timeDivision & 0x8000) { + throw "Expressing time division in SMTPE frames is not supported yet" + } else { + ticksPerBeat = timeDivision; + } + + var header = { + 'formatType': formatType, + 'trackCount': trackCount, + 'ticksPerBeat': ticksPerBeat + } + var tracks = []; + for (var i = 0; i < header.trackCount; i++) { + tracks[i] = []; + var trackChunk = readChunk(stream); + if (trackChunk.id != 'MTrk') { + throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id; + } + var trackStream = Stream(trackChunk.data); + while (!trackStream.eof()) { + var event = readEvent(trackStream); + tracks[i].push(event); + //console.log(event); + } + } + + return { + 'header': header, + 'tracks': tracks + } +} +function Replayer(midiFile, synth) { + var trackStates = []; + var beatsPerMinute = 120; + var ticksPerBeat = midiFile.header.ticksPerBeat; + var channelCount = 16; + var channels = []; + var nextEventInfo; + var samplesToNextEvent; + + function Channel() { + + var generatorsByNote = {}; + var currentProgram = PianoProgram; + + function noteOn(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + /* playing same note before releasing the last one. BOO */ + generatorsByNote[note].noteOff(); /* TODO: check whether we ought to be passing a velocity in */ + } + generator = currentProgram.createNote(note, velocity); + synth.addGenerator(generator); + generatorsByNote[note] = generator; + } + function noteOff(note, velocity) { + if (generatorsByNote[note] && !generatorsByNote[note].released) { + generatorsByNote[note].noteOff(velocity); + } + } + function setProgram(programNumber) { + currentProgram = PROGRAMS[programNumber] || PianoProgram; + } + + return { + 'noteOn': noteOn, + 'noteOff': noteOff, + 'setProgram': setProgram + } + } + + function getNextEvent() { + var ticksToNextEvent = null; + var nextEventTrack = null; + var nextEventIndex = null; + + for (var i = 0; i < trackStates.length; i++) { + if ( + trackStates[i].ticksToNextEvent != null + && (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent) + ) { + ticksToNextEvent = trackStates[i].ticksToNextEvent; + nextEventTrack = i; + nextEventIndex = trackStates[i].nextEventIndex; + } + } + if (nextEventTrack != null) { + /* consume event from that track */ + var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex]; + if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) { + trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime; + } else { + trackStates[nextEventTrack].ticksToNextEvent = null; + } + trackStates[nextEventTrack].nextEventIndex += 1; + /* advance timings on all tracks by ticksToNextEvent */ + for (var i = 0; i < trackStates.length; i++) { + if (trackStates[i].ticksToNextEvent != null) { + trackStates[i].ticksToNextEvent -= ticksToNextEvent + } + } + nextEventInfo = { + 'ticksToEvent': ticksToNextEvent, + 'event': nextEvent, + 'track': nextEventTrack + } + var beatsToNextEvent = ticksToNextEvent / ticksPerBeat; + var secondsToNextEvent = beatsToNextEvent / (beatsPerMinute / 60); + samplesToNextEvent += secondsToNextEvent * synth.sampleRate; + } else { + nextEventInfo = null; + samplesToNextEvent = null; + self.finished = true; + } + } + + function generate(samples) { + var data = new Array(samples*2); + var samplesRemaining = samples; + var dataOffset = 0; + + while (true) { + if (samplesToNextEvent != null && samplesToNextEvent <= samplesRemaining) { + /* generate samplesToNextEvent samples, process event and repeat */ + var samplesToGenerate = Math.ceil(samplesToNextEvent); + if (samplesToGenerate > 0) { + synth.generateIntoBuffer(samplesToGenerate, data, dataOffset); + dataOffset += samplesToGenerate * 2; + samplesRemaining -= samplesToGenerate; + samplesToNextEvent -= samplesToGenerate; + } + + handleEvent(); + getNextEvent(); + } else { + /* generate samples to end of buffer */ + if (samplesRemaining > 0) { + synth.generateIntoBuffer(samplesRemaining, data, dataOffset); + samplesToNextEvent -= samplesRemaining; + } + break; + } + } + return data; + } + + function handleEvent() { + var event = nextEventInfo.event; + switch (event.type) { + case 'meta': + switch (event.subtype) { + case 'setTempo': + beatsPerMinute = 60000000 / event.microsecondsPerBeat + } + break; + case 'channel': + switch (event.subtype) { + case 'noteOn': + channels[event.channel].noteOn(event.noteNumber, event.velocity); + break; + case 'noteOff': + channels[event.channel].noteOff(event.noteNumber, event.velocity); + break; + case 'programChange': + //console.log('program change to ' + event.programNumber); + channels[event.channel].setProgram(event.programNumber); + break; + } + break; + } + } + + function reset() { + for (var i = 0; i < midiFile.tracks.length; i++) { + trackStates[i] = { + 'nextEventIndex': 0, + 'ticksToNextEvent': ( + midiFile.tracks[i].length ? + midiFile.tracks[i][0].deltaTime : + null + ) + }; + } + for (var i = 0; i < channelCount; i++) { + channels[i] = Channel(); + } + samplesToNextEvent = 0; + getNextEvent(); + } + + reset(); + + var self = { + 'reset': reset, + 'generate': generate, + 'finished': false + } + return self; +} +/* Wrapper for accessing strings through sequential reads */ +function Stream(str) { + var position = 0; + + function read(length) { + var result = str.substr(position, length); + position += length; + return result; + } + + /* read a big-endian 32-bit integer */ + function readInt32() { + var result = ( + (str.charCodeAt(position) << 24) + + (str.charCodeAt(position + 1) << 16) + + (str.charCodeAt(position + 2) << 8) + + str.charCodeAt(position + 3)); + position += 4; + return result; + } + + /* read a big-endian 16-bit integer */ + function readInt16() { + var result = ( + (str.charCodeAt(position) << 8) + + str.charCodeAt(position + 1)); + position += 2; + return result; + } + + /* read an 8-bit integer */ + function readInt8(signed) { + var result = str.charCodeAt(position); + if (signed && result > 127) result -= 256; + position += 1; + return result; + } + + function eof() { + return position >= str.length; + } + + /* read a MIDI-style variable-length integer + (big-endian value in groups of 7 bits, + with top bit set to signify that another byte follows) + */ + function readVarInt() { + var result = 0; + while (true) { + var b = readInt8(); + if (b & 0x80) { + result += (b & 0x7f); + result <<= 7; + } else { + /* b is the last byte */ + return result + b; + } + } + } + + return { + 'eof': eof, + 'read': read, + 'readInt32': readInt32, + 'readInt16': readInt16, + 'readInt8': readInt8, + 'readVarInt': readVarInt + } +} +function SineGenerator(freq) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var phase = t / period; + var result = Math.sin(phase * 2 * Math.PI); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function SquareGenerator(freq, phase) { + var self = {'alive': true}; + var period = sampleRate / freq; + var t = 0; + + self.generate = function(buf, offset, count) { + for (; count; count--) { + var result = ( (t / period) % 1 > phase ? 1 : -1); + buf[offset++] += result; + buf[offset++] += result; + t++; + } + } + + return self; +} + +function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) { + var self = {'alive': true} + var attackTime = sampleRate * attackTimeS; + var decayTime = sampleRate * (attackTimeS + decayTimeS); + var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime); + var releaseTime = null; /* not known yet */ + var endTime = null; /* not known yet */ + var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS); + var t = 0; + + self.noteOff = function() { + if (self.released) return; + releaseTime = t; + self.released = true; + endTime = releaseTime + sampleRate * releaseTimeS; + } + + self.generate = function(buf, offset, count) { + if (!self.alive) return; + var input = new Array(count * 2); + for (var i = 0; i < count*2; i++) { + input[i] = 0; + } + child.generate(input, 0, count); + + childOffset = 0; + while(count) { + if (releaseTime != null) { + if (t < endTime) { + /* release */ + while(count && t < endTime) { + var ampl = sustainAmplitude - releaseRate * (t - releaseTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* dead */ + self.alive = false; + return; + } + } else if (t < attackTime) { + /* attack */ + while(count && t < attackTime) { + var ampl = attackAmplitude * t / attackTime; + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else if (t < decayTime) { + /* decay */ + while(count && t < decayTime) { + var ampl = attackAmplitude - decayRate * (t - attackTime); + buf[offset++] += input[childOffset++] * ampl; + buf[offset++] += input[childOffset++] * ampl; + t++; + count--; + } + } else { + /* sustain */ + while(count) { + buf[offset++] += input[childOffset++] * sustainAmplitude; + buf[offset++] += input[childOffset++] * sustainAmplitude; + t++; + count--; + } + } + } + } + + return self; +} + +function midiToFrequency(note) { + return 440 * Math.pow(2, (note-69)/12); +} + +PianoProgram = { + 'attackAmplitude': 0.2, + 'sustainAmplitude': 0.1, + 'attackTime': 0.02, + 'decayTime': 0.3, + 'releaseTime': 0.02, + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128), + this.attackTime, this.decayTime, this.releaseTime + ); + } +} + +StringProgram = { + 'createNote': function(note, velocity) { + var frequency = midiToFrequency(note); + return ADSRGenerator( + SineGenerator(frequency), + 0.5 * (velocity / 128), 0.2 * (velocity / 128), + 0.4, 0.8, 0.4 + ); + } +} + +PROGRAMS = { + 41: StringProgram, + 42: StringProgram, + 43: StringProgram, + 44: StringProgram, + 45: StringProgram, + 46: StringProgram, + 47: StringProgram, + 49: StringProgram, + 50: StringProgram +}; + +function Synth(sampleRate) { + + var generators = []; + + function addGenerator(generator) { + generators.push(generator); + } + + function generate(samples) { + var data = new Array(samples*2); + generateIntoBuffer(samples, data, 0); + return data; + } + + function generateIntoBuffer(samplesToGenerate, buffer, offset) { + for (var i = offset; i < offset + samplesToGenerate * 2; i++) { + buffer[i] = 0; + } + for (var i = generators.length - 1; i >= 0; i--) { + generators[i].generate(buffer, offset, samplesToGenerate); + if (!generators[i].alive) generators.splice(i, 1); + } + } + + return { + 'sampleRate': sampleRate, + 'addGenerator': addGenerator, + 'generate': generate, + 'generateIntoBuffer': generateIntoBuffer + } +} diff --git a/libs/thirdparty/mid.min.js b/libs/thirdparty/mid.min.js new file mode 100644 index 00000000..434a8ae9 --- /dev/null +++ b/libs/thirdparty/mid.min.js @@ -0,0 +1,3 @@ +var sampleRate=44100;function AudioPlayer(generator,opts){if(!opts){opts={}}var latency=opts.latency||1;var checkInterval=latency*100;var audioElement=new Audio();var webkitAudio=window.AudioContext||window.webkitAudioContext;var requestStop=false;if(audioElement.mozSetup){audioElement.mozSetup(2,sampleRate);var buffer=[];var minBufferLength=latency*2*sampleRate;var bufferFillLength=Math.floor(latency*sampleRate);function checkBuffer(){if(buffer.length){var written=audioElement.mozWriteAudio(buffer);buffer=buffer.slice(written)}if(buffer.length';document.body.appendChild(c);var swf=document.getElementById("da-swf");var minBufferDuration=latency*1000;var bufferFillLength=latency*sampleRate;function write(data){var out=new Array(data.length);for(var i=data.length-1;i!=0;i--){out[i]=Math.floor(data[i]*32768)}return swf.write(out.join(" "))}function checkBuffer(){if(swf.bufferedDuration()>4;event.channel=eventTypeByte&15;event.type="channel";switch(eventType){case 8:event.subtype="noteOff";event.noteNumber=param1;event.velocity=stream.readInt8();return event;case 9:event.noteNumber=param1;event.velocity=stream.readInt8();if(event.velocity==0){event.subtype="noteOff"}else{event.subtype="noteOn"}return event;case 10:event.subtype="noteAftertouch";event.noteNumber=param1;event.amount=stream.readInt8();return event;case 11:event.subtype="controller";event.controllerType=param1;event.value=stream.readInt8();return event;case 12:event.subtype="programChange";event.programNumber=param1;return event;case 13:event.subtype="channelAftertouch";event.amount=param1;return event;case 14:event.subtype="pitchBend";event.value=param1+(stream.readInt8()<<7);return event;default:throw"Unrecognised MIDI event type: "+eventType}}}stream=Stream(data);var headerChunk=readChunk(stream);if(headerChunk.id!="MThd"||headerChunk.length!=6){throw"Bad .mid file - header not found"}var headerStream=Stream(headerChunk.data);var formatType=headerStream.readInt16();var trackCount=headerStream.readInt16();var timeDivision=headerStream.readInt16();if(timeDivision&32768){throw"Expressing time division in SMTPE frames is not supported yet"}else{ticksPerBeat=timeDivision}var header={"formatType":formatType,"trackCount":trackCount,"ticksPerBeat":ticksPerBeat};var tracks=[];for(var i=0;i0){synth.generateIntoBuffer(samplesToGenerate,data,dataOffset);dataOffset+=samplesToGenerate*2;samplesRemaining-=samplesToGenerate;samplesToNextEvent-=samplesToGenerate}handleEvent();getNextEvent()}else{if(samplesRemaining>0){synth.generateIntoBuffer(samplesRemaining,data,dataOffset); +samplesToNextEvent-=samplesRemaining}break}}return data}function handleEvent(){var event=nextEventInfo.event;switch(event.type){case"meta":switch(event.subtype){case"setTempo":beatsPerMinute=60000000/event.microsecondsPerBeat}break;case"channel":switch(event.subtype){case"noteOn":channels[event.channel].noteOn(event.noteNumber,event.velocity);break;case"noteOff":channels[event.channel].noteOff(event.noteNumber,event.velocity);break;case"programChange":channels[event.channel].setProgram(event.programNumber);break}break}}function replay(audio){console.log("replay");audio.write(generate(44100));setTimeout(function(){replay(audio)},10)}var self={"replay":replay,"generate":generate,"finished":false};return self}function Stream(str){var position=0;function read(length){var result=str.substr(position,length);position+=length;return result}function readInt32(){var result=((str.charCodeAt(position)<<24)+(str.charCodeAt(position+1)<<16)+(str.charCodeAt(position+2)<<8)+str.charCodeAt(position+3));position+=4;return result}function readInt16(){var result=((str.charCodeAt(position)<<8)+str.charCodeAt(position+1));position+=2;return result}function readInt8(signed){var result=str.charCodeAt(position);if(signed&&result>127){result-=256}position+=1;return result}function eof(){return position>=str.length}function readVarInt(){var result=0;while(true){var b=readInt8();if(b&128){result+=(b&127);result<<=7}else{return result+b}}}return{"eof":eof,"read":read,"readInt32":readInt32,"readInt16":readInt16,"readInt8":readInt8,"readVarInt":readVarInt}}function SineGenerator(freq){var self={"alive":true};var period=sampleRate/freq;var t=0;self.generate=function(buf,offset,count){for(;count;count--){var phase=t/period;var result=Math.sin(phase*2*Math.PI);buf[offset++]+=result;buf[offset++]+=result;t++}};return self}function SquareGenerator(freq,phase){var self={"alive":true};var period=sampleRate/freq;var t=0;self.generate=function(buf,offset,count){for(;count;count--){var result=((t/period)%1>phase?1:-1);buf[offset++]+=result;buf[offset++]+=result;t++}};return self}function ADSRGenerator(child,attackAmplitude,sustainAmplitude,attackTimeS,decayTimeS,releaseTimeS){var self={"alive":true};var attackTime=sampleRate*attackTimeS;var decayTime=sampleRate*(attackTimeS+decayTimeS);var decayRate=(attackAmplitude-sustainAmplitude)/(decayTime-attackTime);var releaseTime=null;var endTime=null;var releaseRate=sustainAmplitude/(sampleRate*releaseTimeS);var t=0;self.noteOff=function(){if(self.released){return}releaseTime=t;self.released=true;endTime=releaseTime+sampleRate*releaseTimeS};self.generate=function(buf,offset,count){if(!self.alive){return}var input=new Array(count*2);for(var i=0;i=0;i--){generators[i].generate(buffer,offset,samplesToGenerate);if(!generators[i].alive){generators.splice(i,1)}}}return{"sampleRate":sampleRate,"addGenerator":addGenerator,"generate":generate,"generateIntoBuffer":generateIntoBuffer}}; \ No newline at end of file diff --git a/libs/ui.js b/libs/ui.js index 62ef59e8..ab9e226e 100644 --- a/libs/ui.js +++ b/libs/ui.js @@ -334,7 +334,8 @@ ui.prototype.drawSwitchs = function() { core.status.event.id = 'switchs'; var choices = [ - "背景音乐:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), + "背景音乐:"+(core.musicStatus.bgmStatus ? "[ON]" : "[OFF]"), + "背景音效:"+(core.musicStatus.soundStatus ? "[ON]" : "[OFF]"), "战斗动画: " + (core.flags.battleAnimate ? "[ON]" : "[OFF]"), "怪物显伤: " + (core.flags.displayEnemyDamage ? "[ON]" : "[OFF]"), "领域显伤: " + (core.flags.displayExtraDamage ? "[ON]" : "[OFF]"), @@ -571,7 +572,7 @@ ui.prototype.drawBattleAnimate = function(monsterId, callback) { core.fillText("ui", "S", right_start-8, 208+15, "#FFFFFF", "italic bold 40px Verdana"); var battleInterval = setInterval(function() { - core.playSound("attack", "ogg"); + core.playSound("attack.ogg"); if (turn==0) { // 勇士攻击 diff --git a/main.js b/main.js index fe644e79..e5996f71 100644 --- a/main.js +++ b/main.js @@ -45,12 +45,13 @@ function main() { ]; this.images = [ 'animates', 'enemys', 'hero', 'items', 'npcs', 'terrains' - // Autotile 动态添加 ]; - this.sounds = { - 'mp3': ['bgm-loop', 'floor'], - 'ogg': ['attack', 'door', 'item'] - } + this.bgms = [ // 在此存放所有的bgm,和文件名一致。第一项为默认播放项 + '058-Slow01.mid', 'bgm.mp3', 'G2_koko.mid', 'star.mid' + ]; + this.sounds = [ // 在此存放所有的SE,和文件名一致 + 'floor.mp3', 'attack.ogg', 'door.ogg', 'item.ogg' + ] this.statusBar = { 'image': { 'floor': document.getElementById('img-floor'), @@ -97,7 +98,7 @@ function main() { // 如果要进行剧本的修改请务必将其改成false。 this.floorIds = [ // 在这里按顺序放所有的楼层;其顺序直接影响到楼层传送器的顺序和上楼器/下楼器的顺序 - "sample0", "sample1", "sample2", "test" + "sample0", "sample1", "sample2" ] //------------------------ 用户修改内容 END ------------------------// @@ -119,7 +120,7 @@ main.prototype.init = function () { coreData[name] = main[name]; } main.loaderFloors(function() { - main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.sounds, main.floorIds, main.floors, coreData); + main.core.init(main.dom, main.statusBar, main.canvas, main.images, main.bgms, main.sounds, main.floorIds, main.floors, coreData); main.core.resize(main.dom.body.clientWidth, main.dom.body.clientHeight); }) }); @@ -222,18 +223,6 @@ main.dom.body.onselectstart = function () { return false; } -document.onmousemove = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - -document.ontouchstart = function() { - try { - main.core.loadSound(); - }catch (e) {} -} - main.dom.data.onmousedown = function (e) { try { e.stopPropagation(); diff --git a/sounds/058-Slow01.mid b/sounds/058-Slow01.mid new file mode 100644 index 0000000000000000000000000000000000000000..05ce9228c590fc77d3959b0c8230c51ce44ea5ff GIT binary patch literal 6183 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 literal 0 HcmV?d00001 diff --git a/sounds/star.mid b/sounds/star.mid new file mode 100644 index 0000000000000000000000000000000000000000..cc78e5902ba32478f7d12796376e7e2ef8887485 GIT binary patch literal 15102 zcmeI3O>Z056^8G~N(iXnqJlJMNi-c&mPAXUw1_MSa0&s55r__g z1ZeDqJ!Hwrq6;xJIe$anilj}P?~gx`Ggta^CtkHyLSi>XxVwxJMJvcAAkS$()48c^}ExP{Y%Ab92BpeSiY&$N2#8r@sj(}-SJX#X+jPr zN{dTlrP;eNLu2dnX`wg0&oc>6 zX2zI#l4xEGc?_Dlc8FY&BUp}LG2lf3D+1QZhfyVqHN7K314PVc7(8q!bRC(20Ek26*&Ur%mGB*B%G{w zB2k`##B)LJVPH`>HC(KtMgk=2=)y(eb#&sQ@Osn}6}l*#YR|GPvIgXAxDpe_)mP*MYNkU|aNAQcpZ&_N@J z!*CZNDAa-KaFHrr;&*}Hpp=@E@*qDZ$``5R0ESGv11F)w01ih10m4BR!sSAH7EF-I z?<5OjB_&N+3t5vOO}!RO1Zf@?VgIjp@Bh>%b83zKY_0E(T4O(3V?XY)$@p zf1kSSG`K4p+>}i1mw{L@cde4D_3KNex{5Ax+nz$7~V!H@< z2!e14c?bt_5+2+ML44uCIgSw3DB{4L5?m=Lp+hGG2_X(S5Qw{9nz3nr@~2v$qEw{c8CVIP=nhEqiV=TSm7jaV4}i47hEX_ zp@T*c2X@K;LBfbbM(aaR_7DWk2{?#Tghx9{ERRSgl7Vm$1PLJyf*=_Msd5r%m5I1r}qf<7)y6k0hfORn-?C*Jh`iuJXRkfZ-JL^2dt+VH?v*-PnJ#T&Pl(Rmc zF5kX4y>R?r(+j=#CKx9e#~H^NM;J#KZQF~)DYnHQ<9w_wzHPhh{7m?^^NYuS#`^jm z?fmWQyPJRf{t~ax-g~y~ruDIHx1FB}-*$c`d=o$0b`w9_cH8-x@NMU3!Z-1=Z8!0= zZMU7D3I8h3TUUAB8f6@1%roX0vy53r+x7x+3T^Rooafr&+qT=z&xCI~KNG%*pKZH| zpKZJC{7m?^^E2U__}R9b_}RAG&d-EzJ3kY?iJxt|iJxt|?fgvmw)2a}Z|fC}sLx(i z8}y1c=#OvEE83t}^nhN`2EC#UdPTP$&>wH|JXED$)TCcjrC-#fUsR=E)TCcjrC(&- z&ht!_eo+{oeo>WvQ5fI0{r^1wt_Gfh#!VWtt?_vLV_gk&HPF>SR|8!QbTtrYfM?t) zJ)|Z*q$)k6COxDoJ)|Z*q$)k6rfoaVxK(;cVSIW>ReDHaeA{;0`I+!-=NFH^_dH#t(H9pB`X>5Hi9|+X= z;z9FF!jqXHW}YN^Q4AL`Xy)1>a>W3_1_%}dE-(L+WU`|VWgIH?x59vf;yg$VHSo&> zc$okf7%wnJJ^5;;##b{`ca~+?uX@z;;Fx+HG`2pMFC1!o;XuU#o=JFeFu=@{M9+)i zJO<5NJ4CKH2k~btX$c%nKFMf)h) zL(xEFLFhd+E-Lm=wXg9R0TMpC@KCsqPCOLuM?F!Yhr+4$EXyLRkOg6{rcv*M zyYr{Yw|+@o{