添加语音播放功能
This commit is contained in:
parent
137984c0ae
commit
761426c1ff
|
@ -0,0 +1,61 @@
|
|||
|
||||
|
||||
class Adpcm extends AudioCoder
|
||||
{
|
||||
constructor(wasm, importObj)
|
||||
{
|
||||
super(wasm, importObj);
|
||||
this._indexDe = Adpcm._currentIndex;
|
||||
this._indexEn = Adpcm._currentIndex + 1;
|
||||
Adpcm._currentIndex = (Adpcm._currentIndex + 2) & 0x3F;
|
||||
this._wasm.instance.exports._initAdpcmState(this._indexDe);
|
||||
this._wasm.instance.exports._initAdpcmState(this._indexEn);
|
||||
}
|
||||
|
||||
resetDecodeState(state)
|
||||
{
|
||||
this._wasm.instance.exports._resetAdpcmState(this._indexDe, state.valprev, state.index);
|
||||
}
|
||||
|
||||
resetEncodeState(state)
|
||||
{
|
||||
this._wasm.instance.exports._resetAdpcmState(this._indexEn, state.valprev, state.index);
|
||||
}
|
||||
|
||||
getDecodeState()
|
||||
{
|
||||
this._wasm.instance.exports._getAdpcmState(this._indexDe, 10236, 10238);
|
||||
return new Adpcm.State(this._memory[10236] + (this._memory[10237] << 8), this._memory[10238]);
|
||||
}
|
||||
|
||||
getEncodeState()
|
||||
{
|
||||
this._wasm.instance.exports._getAdpcmState(this._indexEn, 10236, 10238);
|
||||
return new Adpcm.State(this._memory[10236] + (this._memory[10237] << 8), this._memory[10238]);
|
||||
}
|
||||
|
||||
decode(data)
|
||||
{
|
||||
this._copyToMemory(data);
|
||||
this._wasm.instance.exports._decodeAdpcm(this._indexDe, 0, 10240, data.byteLength << 1);
|
||||
return new Int16Array(this._memory.buffer, 10240, data.byteLength << 1);
|
||||
}
|
||||
|
||||
encode(data)
|
||||
{
|
||||
this._copyToMemory(data);
|
||||
this._wasm.instance.exports._encodeAdpcm(this._indexEn, 0, 10240, data.byteLength >>> 1);
|
||||
return new Uint8Array(this._memory.buffer, 10240, data.byteLength >>> 2);
|
||||
}
|
||||
}
|
||||
|
||||
Adpcm._currentIndex = 0;
|
||||
|
||||
Adpcm.State = class
|
||||
{
|
||||
constructor(valprev, index)
|
||||
{
|
||||
this.valprev = valprev;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,80 @@
|
|||
|
||||
class PCMPlayer
|
||||
{
|
||||
constructor(channels, sampleRate)
|
||||
{
|
||||
this._samples = new Float32Array();
|
||||
this._flushingTime = 200;
|
||||
this._channels = channels;
|
||||
this._sampleRate = sampleRate;
|
||||
this._flush = this._flush.bind(this);
|
||||
this._audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
this._gainNode = this._audioCtx.createGain();
|
||||
this._gainNode.gain.value = 1;
|
||||
this._gainNode.connect(this._audioCtx.destination);
|
||||
this._startTime = this._audioCtx.currentTime;
|
||||
this._interval = setInterval(this._flush, this._flushingTime);
|
||||
}
|
||||
|
||||
setVolume(volume)
|
||||
{
|
||||
this._gainNode.gain.value = volume;
|
||||
}
|
||||
|
||||
close()
|
||||
{
|
||||
if(this._interval)
|
||||
{
|
||||
clearInterval(this._interval);
|
||||
}
|
||||
this._audioCtx.close();
|
||||
};
|
||||
|
||||
feed(data)
|
||||
{
|
||||
let tmp = new Float32Array(this._samples.length + data.length);
|
||||
tmp.set(this._samples, 0);
|
||||
tmp.set(data, this._samples.length);
|
||||
this._samples = tmp;
|
||||
};
|
||||
|
||||
_flush()
|
||||
{
|
||||
if(!this._channels || !this._sampleRate || !this._samples.length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let bufferSource = this._audioCtx.createBufferSource();
|
||||
let length = this._samples.length / this._channels;
|
||||
let audioBuffer = this._audioCtx.createBuffer(this._channels, length, this._sampleRate);
|
||||
for (let channel = 0; channel != this._channels; ++channel)
|
||||
{
|
||||
let audioData = audioBuffer.getChannelData(channel);
|
||||
let offset = channel;
|
||||
let decrement = 50;
|
||||
for (let i = 0; i != length; ++i)
|
||||
{
|
||||
audioData[i] = this._samples[offset];
|
||||
if (i < 50)
|
||||
{
|
||||
audioData[i] = (audioData[i] * i) / 50;
|
||||
}
|
||||
if (i >= (length - 51))
|
||||
{
|
||||
audioData[i] = (audioData[i] * decrement--) / 50;
|
||||
}
|
||||
offset += this._channels;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._startTime < this._audioCtx.currentTime)
|
||||
{
|
||||
this._startTime = this._audioCtx.currentTime;
|
||||
}
|
||||
bufferSource.buffer = audioBuffer;
|
||||
bufferSource.connect(this._gainNode);
|
||||
bufferSource.start(this._startTime);
|
||||
this._startTime += audioBuffer.duration;
|
||||
this._samples = new Float32Array();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
|
||||
class Std
|
||||
{
|
||||
constructor()
|
||||
{}
|
||||
|
||||
static memmem(data1, data1Offset, data2)
|
||||
{
|
||||
for (let i = 0; i <= data1.byteLength - data2.byteLength - data1Offset; ++i)
|
||||
{
|
||||
let j = 0;
|
||||
for (; j != data2.byteLength; ++j)
|
||||
{
|
||||
if(data1[i + j + data1Offset] != data2[j])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(j >= data2.byteLength)
|
||||
{
|
||||
return i + data1Offset;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static memcmp(data1, data1Offset, data2)
|
||||
{
|
||||
for(let i = 0; i != data2.byteLength; ++i)
|
||||
{
|
||||
if(data1[i + data1Offset] != data2[i])
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static memcpy(data1, data1Offset, data2, data2Begin, data2End)
|
||||
{
|
||||
data1.set(data2.subarray(data2Begin, data2End), data1Offset);
|
||||
}
|
||||
|
||||
static milliSecondTime()
|
||||
{
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
static shortToFloatData(input)
|
||||
{
|
||||
let inputSamples = input.length;
|
||||
let output = new Float32Array(inputSamples);
|
||||
for(let i = 0; i != inputSamples; ++i)
|
||||
{
|
||||
output[i] = input[i] / 32768;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static floatToShortData(input)
|
||||
{
|
||||
let inputSamples = input.length;
|
||||
let output = new Int16Array(inputSamples);
|
||||
for(let i = 0; i != inputSamples; ++i)
|
||||
{
|
||||
output[i] = input[i] * 32768;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static downsampleBuffer(buffer, rate, sampleRate)
|
||||
{
|
||||
if(rate == sampleRate)
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
else if(rate > sampleRate)
|
||||
{
|
||||
throw "rate > sampleRate error !!";
|
||||
}
|
||||
let sampleRateRatio = sampleRate / rate;
|
||||
let newLength = Math.ceil(buffer.length / sampleRateRatio) & 0xFFFC;
|
||||
let result = new Float32Array(newLength);
|
||||
let offsetResult = 0;
|
||||
let offsetBuffer = 0;
|
||||
while (offsetResult != result.length)
|
||||
{
|
||||
let nextOffsetBuffer = offsetBuffer + sampleRateRatio;
|
||||
let accum = 0;
|
||||
let count = 0;
|
||||
let currentOffset = Math.ceil(offsetBuffer);
|
||||
let currentNextOffset = Math.ceil(nextOffsetBuffer);
|
||||
for (let i = currentOffset; i != currentNextOffset && i != buffer.length; ++i)
|
||||
{
|
||||
accum += buffer[i];
|
||||
++count;
|
||||
}
|
||||
result[offsetResult] = accum / count;
|
||||
++offsetResult;
|
||||
offsetBuffer = nextOffsetBuffer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class Result
|
||||
{
|
||||
constructor(data, type, time, errorCode, duration = 20)
|
||||
{
|
||||
this.data = data;
|
||||
this.type = type;
|
||||
this.time = time;
|
||||
this.duration = duration;
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
static makeErrorResult(errorCode)
|
||||
{
|
||||
return new Result(null, -1, -1, errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
Result.ErrorCode = class
|
||||
{
|
||||
constructor()
|
||||
{}
|
||||
}
|
||||
|
||||
Result.ErrorCode.SUCCESS = 0;
|
||||
Result.ErrorCode.PARAM_ERROR = 1000;
|
||||
Result.ErrorCode.PARAM_CHANGE = 2000;
|
||||
Result.ErrorCode.FAIL = 3000;
|
||||
Result.ErrorCode.NO_INIT_ERROR = Result.ErrorCode.FAIL + 1;
|
||||
Result.ErrorCode.CACHE_MAX_ERROR = Result.ErrorCode.FAIL + 2;
|
||||
|
||||
Result.Type = class
|
||||
{
|
||||
constructor()
|
||||
{}
|
||||
}
|
||||
|
||||
Result.Type.H264_I_FRAME = 0;
|
||||
Result.Type.H264_P_FRAME = 1;
|
||||
Result.Type.H264_B_FRAME = 2;
|
||||
Result.Type.AUDIO = 3;
|
||||
Result.Type.TRANS_DATA = 4;
|
||||
Result.Type.FMP4_HEAD = 5;
|
||||
Result.Type.FMP4_BODY = 6;
|
||||
|
||||
class AudioCoder
|
||||
{
|
||||
constructor(wasm, importObj)
|
||||
{
|
||||
if(importObj.memoryBase < 102400)
|
||||
{
|
||||
throw new Error("too small");
|
||||
}
|
||||
this._importObj = importObj;
|
||||
this._wasm = wasm;
|
||||
this._memory = new Uint8Array(this._importObj.env.memory.buffer);
|
||||
}
|
||||
|
||||
_copyToMemory(data)
|
||||
{
|
||||
if(data.byteLength > (this._importObj.env.memoryBase >>> 6))
|
||||
{
|
||||
throw new Error("overflow");
|
||||
}
|
||||
this._memory.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
[{"code":"ja","ckey":{"card":"13d8dabd83269c4946209d26692a1368","skill":"d33ec928559f36b3cba58716eb2c5fa2"},"updateTime":1585310723424},{"code":"en","ckey":{"card":"075de686722cbe62610b4a7f48371f0f","skill":"6fbaad4aee7583b5e021f29e090a9615"},"updateTime":1585211879627},{"code":"ko","ckey":{"card":"43fca07a247da1f8bbd40d6eb9e0cf64","skill":"cec27bcb8b26117ddd30a974483d99e6"},"updateTime":1585311674027}]
|
||||
[{"code":"ja","ckey":{"card":"f34fb150917adf414fa5bee4cf158eef","skill":"d33ec928559f36b3cba58716eb2c5fa2"},"updateTime":1585564929421},{"code":"en","ckey":{"card":"17f0ce55bcad7f4c6bf858b61a2ae81b","skill":"6fbaad4aee7583b5e021f29e090a9615"},"updateTime":1585564806456},{"code":"ko","ckey":{"card":"a10cf2f7e66084ce6d93fd07cc325e5c","skill":"cec27bcb8b26117ddd30a974483d99e6"},"updateTime":1585564806456}]
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,6 +14,11 @@
|
|||
<script type="text/javascript" src="script.js"></script>
|
||||
<script type="text/javascript" src="library/localforage.min.js"></script>
|
||||
<script type="text/javascript" src="library/html2canvas.min.js"></script>
|
||||
<!--▼ADPCM播放相关-->
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/std.js"></script>
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/pcm_player.js"></script>
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/adpcm.js"></script>
|
||||
<!--▲ADPCM播放相关-->
|
||||
<script type="text/javascript">
|
||||
const solo = false;
|
||||
const teamsCount = 2;
|
||||
|
|
|
@ -85,6 +85,75 @@ function getQueryString(name,url) {
|
|||
}
|
||||
}
|
||||
|
||||
//数组去重
|
||||
/* https://www.cnblogs.com/baiyangyuanzi/p/6726258.html
|
||||
* 实现思路:获取没重复的最右一值放入新数组。
|
||||
* (检测到有重复值时终止当前循环同时进入顶层循环的下一轮判断)*/
|
||||
Array.prototype.uniq = function()
|
||||
{
|
||||
let temp = [];
|
||||
const l = this.length;
|
||||
for(let i = 0; i < l; i++) {
|
||||
for(let j = i + 1; j < l; j++){
|
||||
if (this[i] === this[j]){
|
||||
i++;
|
||||
j = i;
|
||||
}
|
||||
}
|
||||
temp.push(this[i]);
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
//▼ADPCM播放相关,来自 https://github.com/jy4340132/aaa
|
||||
const pcmMemory = new WebAssembly.Memory({initial: 256, maximum: 256});
|
||||
|
||||
const pcmImportObj = {
|
||||
env: {
|
||||
abortStackOverflow: () => { throw new Error("overflow"); },
|
||||
table: new WebAssembly.Table({ initial: 0, maximum: 0, element: "anyfunc" }),
|
||||
tableBase: 0,
|
||||
memory: pcmMemory,
|
||||
memoryBase: 102400,
|
||||
STACKTOP: 0,
|
||||
STACK_MAX: pcmMemory.buffer.byteLength,
|
||||
}
|
||||
};
|
||||
|
||||
let pcmPlayer = null;
|
||||
let adpcm_wasm = null;
|
||||
|
||||
function decodeAudio(fileName, decodeCallback)
|
||||
{
|
||||
if(pcmPlayer != null)
|
||||
{
|
||||
pcmPlayer.close();
|
||||
}
|
||||
pcmPlayer = new PCMPlayer(1, 44100);
|
||||
fetch(fileName).then((response) => response.arrayBuffer())
|
||||
.then((bytes) => {
|
||||
let audioData = new Uint8Array(bytes);
|
||||
let step = 160;
|
||||
for(let i = 0; i < audioData.byteLength; i += step)
|
||||
{
|
||||
let pcm16BitData = decodeCallback(audioData.slice(i, i + step));
|
||||
let pcmFloat32Data = Std.shortToFloatData(pcm16BitData);
|
||||
pcmPlayer.feed(pcmFloat32Data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetch("library/jy4340132-aaa/adpcm.wasm").then((response) => response.arrayBuffer())
|
||||
.then((bytes) => WebAssembly.instantiate(bytes, pcmImportObj))
|
||||
.then((wasm) => {
|
||||
adpcm_wasm = wasm;
|
||||
/*addButton("adpcm").onclick = function () {
|
||||
let decoder = new Adpcm(wasm, pcmImportObj);
|
||||
decoder.resetDecodeState(new Adpcm.State(0, 0));
|
||||
decodeAudio("demo.adpcm", decoder.decode.bind(decoder));
|
||||
}*/
|
||||
});
|
||||
//▲ADPCM播放相关
|
||||
|
||||
//计算用了多少潜觉格子
|
||||
function usedHole(latent)
|
||||
{
|
||||
|
|
35
script.js
35
script.js
|
@ -16,25 +16,6 @@ var showSearch; //整个程序都可以用的显示搜索函数
|
|||
const dataStructure = 3; //阵型输出数据的结构版本
|
||||
const className_displayNone = "display-none";
|
||||
|
||||
//数组去重
|
||||
/* https://www.cnblogs.com/baiyangyuanzi/p/6726258.html
|
||||
* 实现思路:获取没重复的最右一值放入新数组。
|
||||
* (检测到有重复值时终止当前循环同时进入顶层循环的下一轮判断)*/
|
||||
Array.prototype.uniq = function()
|
||||
{
|
||||
let temp = [];
|
||||
const l = this.length;
|
||||
for(let i = 0; i < l; i++) {
|
||||
for(let j = i + 1; j < l; j++){
|
||||
if (this[i] === this[j]){
|
||||
i++;
|
||||
j = i;
|
||||
}
|
||||
}
|
||||
temp.push(this[i]);
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
//队员基本的留空
|
||||
var Member = function(){
|
||||
this.id=0;
|
||||
|
@ -1051,9 +1032,10 @@ function initialize()
|
|||
const awokenCountLabel = monEditAwokensRow.querySelector(".awoken-count");
|
||||
const monEditAwokens = Array.from(monEditAwokensRow.querySelectorAll(".awoken-ul input[name='awoken-number']"));
|
||||
function checkAwoken(){
|
||||
const card = Cards[editBox.mid];
|
||||
const value = parseInt(this.value,10);
|
||||
awokenCountLabel.innerHTML = value;
|
||||
if (value>0 && value==(Cards[editBox.mid].awakenings.length))
|
||||
if (value>0 && value==(card.awakenings.length))
|
||||
awokenCountLabel.classList.add("full-awoken");
|
||||
else
|
||||
awokenCountLabel.classList.remove("full-awoken");
|
||||
|
@ -1061,6 +1043,19 @@ function initialize()
|
|||
}
|
||||
monEditAwokens.forEach(akDom=>akDom.onclick = checkAwoken);
|
||||
|
||||
const monEditAwokensLabel = Array.from(monEditAwokensRow.querySelectorAll(".awoken-ul .awoken-icon"));
|
||||
function playVoiceAwoken(){ //点击label才播放语音
|
||||
const card = Cards[editBox.mid];
|
||||
if (parseInt(this.getAttribute("data-awoken-icon"),10) === 63)
|
||||
{
|
||||
let decoder = new Adpcm(adpcm_wasm, pcmImportObj);
|
||||
decoder.resetDecodeState(new Adpcm.State(0, 0));
|
||||
decodeAudio(`sound/voice/jp/padv${PrefixInteger(card.voiceId,3)}.wav`, decoder.decode.bind(decoder));
|
||||
console.log(`sound/voice/jp/padv${PrefixInteger(card.voiceId,3)}.wav`);
|
||||
}
|
||||
}
|
||||
monEditAwokensLabel.forEach(akDom=>akDom.onclick = playVoiceAwoken);
|
||||
|
||||
//超觉醒
|
||||
const monEditSAwokensRow = settingBox.querySelector(".row-mon-super-awoken");
|
||||
monEditSAwokensRow.swaokenIndex = -1;
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
<script type="text/javascript" src="script.js"></script>
|
||||
<script type="text/javascript" src="library/localforage.min.js"></script>
|
||||
<script type="text/javascript" src="library/html2canvas.min.js"></script>
|
||||
<!--▼ADPCM播放相关-->
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/std.js"></script>
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/pcm_player.js"></script>
|
||||
<script type="text/javascript" src="library/jy4340132-aaa/adpcm.js"></script>
|
||||
<!--▲ADPCM播放相关-->
|
||||
<script type="text/javascript">
|
||||
const solo = true;
|
||||
const teamsCount = 1;
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue