Basic decoding with edge effects
This commit is contained in:
parent
aa8ac249a0
commit
1f40231b9b
@ -1,8 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
|
||||||
|
set(EMO libmad.bc)
|
||||||
function(em_file name)
|
function(em_file name)
|
||||||
execute_process(COMMAND
|
execute_process(COMMAND
|
||||||
emcc --bind
|
emcc --bind
|
||||||
${CMAKE_SOURCE_DIR}/libmad.bc
|
${CMAKE_SOURCE_DIR}/libmad.bc
|
||||||
|
${CMAKE_SOURCE_DIR}/decoder.cpp
|
||||||
${CMAKE_SOURCE_DIR}/${name}.cpp
|
${CMAKE_SOURCE_DIR}/${name}.cpp
|
||||||
-o ${CMAKE_BINARY_DIR}/${name}.js
|
-o ${CMAKE_BINARY_DIR}/${name}.js
|
||||||
-s WASM=0
|
-s WASM=0
|
||||||
@ -13,3 +16,4 @@ endfunction(em_file)
|
|||||||
configure_file(index.html index.html)
|
configure_file(index.html index.html)
|
||||||
|
|
||||||
em_file(wrapper)
|
em_file(wrapper)
|
||||||
|
|
||||||
|
362
decoder.cpp
Normal file
362
decoder.cpp
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
#include "decoder.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
Decoder::Decoder():
|
||||||
|
state(empty),
|
||||||
|
sampleRate(0),
|
||||||
|
channels(0),
|
||||||
|
cachedLength(0),
|
||||||
|
samplesPerFrame(0),
|
||||||
|
glue(new uint8_t[GLUE_LENGTH]),
|
||||||
|
lastSync(-1),
|
||||||
|
cached(false),
|
||||||
|
synth(),
|
||||||
|
stream(),
|
||||||
|
frame(),
|
||||||
|
context(0),
|
||||||
|
pending()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < GLUE_LENGTH; ++i) {
|
||||||
|
glue[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mad_frame_init(&frame);
|
||||||
|
mad_stream_init(&stream);
|
||||||
|
mad_synth_init(&synth);
|
||||||
|
|
||||||
|
emscripten::val AudioContext = emscripten::val::global("AudioContext");
|
||||||
|
if (!AudioContext.as<bool>()) {
|
||||||
|
AudioContext = emscripten::val::global("webkitAudioContext");
|
||||||
|
}
|
||||||
|
|
||||||
|
context = AudioContext.new_();
|
||||||
|
}
|
||||||
|
|
||||||
|
Decoder::~Decoder()
|
||||||
|
{
|
||||||
|
context.call<void>("close");
|
||||||
|
|
||||||
|
mad_synth_finish(&synth);
|
||||||
|
mad_stream_finish(&stream);
|
||||||
|
mad_frame_finish(&frame);
|
||||||
|
|
||||||
|
delete[] glue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::addFragment(intptr_t bufferPtr, uint32_t length)
|
||||||
|
{
|
||||||
|
if (length < GLUE_LENGTH / 2) {
|
||||||
|
std::cout << "An attempt to add fragment smaller then half of the glue buffer, ignoring";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cout << "Adding new fragment " << length << " bytes long" << std::endl;
|
||||||
|
uint8_t* buffer = (uint8_t(*))bufferPtr;
|
||||||
|
RawBuffer rb = {buffer, length, 0, 0};
|
||||||
|
pending.push_back(rb);
|
||||||
|
|
||||||
|
std::cout << "The state now is " << state << std::endl;
|
||||||
|
switch (state) {
|
||||||
|
case empty:
|
||||||
|
mad_stream_buffer(&stream, buffer, length);
|
||||||
|
|
||||||
|
for (int i = 0; i < GLUE_LENGTH/2; ++i) {
|
||||||
|
glue[i] = buffer[length - GLUE_LENGTH/2 + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
state = onBufferHalf;
|
||||||
|
prepareNextBuffer();
|
||||||
|
break;
|
||||||
|
case onBufferHalf:
|
||||||
|
for (int i = 0; i < GLUE_LENGTH/2; ++i) {
|
||||||
|
glue[GLUE_LENGTH/2 + i] = buffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
state = onBufferFull;
|
||||||
|
break;
|
||||||
|
case onBufferFull:
|
||||||
|
break;
|
||||||
|
case onGlueHalf:
|
||||||
|
for (int i = 0; i < GLUE_LENGTH/2; ++i) {
|
||||||
|
glue[GLUE_LENGTH/2 + i] = buffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
state = onGlueFull;
|
||||||
|
cached = false;
|
||||||
|
prepareNextBuffer();
|
||||||
|
break;
|
||||||
|
case onGlueFull:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emscripten::val Decoder::decode(uint32_t count)
|
||||||
|
{
|
||||||
|
emscripten::val ret = emscripten::val::undefined();
|
||||||
|
|
||||||
|
int available = framesLeft(count);
|
||||||
|
int success = 0;
|
||||||
|
if (available > 0) {
|
||||||
|
ret = context.call<emscripten::val>("createBuffer", channels, available * samplesPerFrame, sampleRate);
|
||||||
|
|
||||||
|
std::vector<emscripten::val> chans(channels, emscripten::val::undefined());
|
||||||
|
for (int i = 0; i < channels; ++i) {
|
||||||
|
chans[i] = ret.call<emscripten::val>("getChannelData", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; success < available; ++i) {
|
||||||
|
int res = mad_frame_decode(&frame, &stream);
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
lastSync = stream.bufend - stream.this_frame;
|
||||||
|
++success;
|
||||||
|
} else {
|
||||||
|
if (MAD_RECOVERABLE(stream.error)) {
|
||||||
|
|
||||||
|
std::cout << "Unexpected error during the decoding process: " << mad_stream_errorstr(&stream) << std::endl;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mad_synth_frame(&synth, &frame);
|
||||||
|
|
||||||
|
for (int j = 0; j < samplesPerFrame; ++j) {
|
||||||
|
for (int k = 0; k < channels; ++k) {
|
||||||
|
float value = mad_f_todouble(synth.pcm.samples[k][j]);
|
||||||
|
chans[k].set(std::to_string(success * samplesPerFrame + j), emscripten::val(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedLength -= available;
|
||||||
|
std::cout << "Processed " << available << " frames, " << success << " successfully, last error " << mad_stream_errorstr(&stream) << std::endl;
|
||||||
|
|
||||||
|
if (cachedLength == 0) {
|
||||||
|
cached = false;
|
||||||
|
prepareNextBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Decoder::hasMore() const
|
||||||
|
{
|
||||||
|
if (pending.size() == 1) {
|
||||||
|
return stream.error != MAD_ERROR_BUFLEN;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Decoder::framesLeft(uint32_t max)
|
||||||
|
{
|
||||||
|
if (state == empty || state == onGlueHalf) {
|
||||||
|
cached = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == false) {
|
||||||
|
mad_stream probe;
|
||||||
|
mad_header ph;
|
||||||
|
initializeProbe(probe);
|
||||||
|
mad_header_init(&ph);
|
||||||
|
|
||||||
|
while (cachedLength < max) {
|
||||||
|
if (mad_header_decode(&ph, &probe) == 0) {
|
||||||
|
if (sampleRate == 0) {
|
||||||
|
sampleRate = ph.samplerate;
|
||||||
|
channels = MAD_NCHANNELS(&ph);
|
||||||
|
samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source
|
||||||
|
} else {
|
||||||
|
if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (probe.next_frame > probe.this_frame) {
|
||||||
|
++cachedLength;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "framesLeft::" << mad_stream_errorstr(&probe) << std::endl;
|
||||||
|
if (!MAD_RECOVERABLE(probe.error)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mad_header_finish(&ph);
|
||||||
|
mad_stream_finish(&probe);
|
||||||
|
std::cout << cachedLength << " frames are available for decoding" << std::endl;
|
||||||
|
cached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::min(cachedLength, max);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::pullBuffer()
|
||||||
|
{
|
||||||
|
mad_header dummy;
|
||||||
|
mad_header_init(&dummy);
|
||||||
|
while(stream.error == 0 || MAD_RECOVERABLE(stream.error)) {
|
||||||
|
mad_header_decode(&dummy, &stream);
|
||||||
|
std::cout << "pullBuffer:: " << mad_stream_errorstr(&stream) << std::endl;
|
||||||
|
}
|
||||||
|
mad_header_finish(&dummy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::changeBuffer()
|
||||||
|
{
|
||||||
|
uint32_t left;
|
||||||
|
switch (state) {
|
||||||
|
case empty:
|
||||||
|
std::cout << "Wrong state on switchBuffer method - empty, aborting" << std::endl;
|
||||||
|
case onBufferHalf:
|
||||||
|
switchToGlue();
|
||||||
|
state = onGlueHalf;
|
||||||
|
break;
|
||||||
|
case onBufferFull:
|
||||||
|
switchToGlue();
|
||||||
|
state = onGlueFull;
|
||||||
|
break;
|
||||||
|
case onGlueHalf:
|
||||||
|
std::cout << "Wrong state on switchBuffer method - onGlueHalf, aborting" << std::endl;
|
||||||
|
break;
|
||||||
|
case onGlueFull:
|
||||||
|
std::cout << "Having another fragment " << pending[0].length << " bytes long" << std::endl;
|
||||||
|
left = (uint64_t)stream.bufend - (uint64_t)stream.next_frame;
|
||||||
|
// std::cout << "MD_LENGTH_BEFORE: " << stream.anc_bitlen << std::endl;
|
||||||
|
mad_stream_buffer(&stream, pending[0].ptr, pending[0].length);
|
||||||
|
|
||||||
|
|
||||||
|
//mad_stream_skip(&stream, left);
|
||||||
|
stream.error = MAD_ERROR_NONE;
|
||||||
|
if (lastSync != -1) {
|
||||||
|
if (lastSync > GLUE_LENGTH / 2) {
|
||||||
|
std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit LASTSYNC (" << lastSync << ")" << std::endl;
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
uint64_t target = (uint64_t)stream.buffer + GLUE_LENGTH / 2 - lastSync;
|
||||||
|
while ((uint64_t)(stream.this_frame) < target) {
|
||||||
|
// std::cout << "pulling" << std::endl;
|
||||||
|
mad_frame_decode(&frame, &stream);
|
||||||
|
}
|
||||||
|
stream.error = MAD_ERROR_NONE;
|
||||||
|
|
||||||
|
// stream.this_frame = stream.buffer + GLUE_LENGTH / 2 - lastSync;
|
||||||
|
// stream.next_frame = stream.buffer + GLUE_LENGTH / 2 - left;
|
||||||
|
// stream.sync = 1;
|
||||||
|
|
||||||
|
lastSync = -1;
|
||||||
|
|
||||||
|
// mad_bitptr next_frame;
|
||||||
|
// mad_bit_init(&next_frame, stream.next_frame);
|
||||||
|
// stream.anc_ptr = stream.ptr;
|
||||||
|
// stream.anc_bitlen = mad_bit_length(&stream.ptr, &next_frame);
|
||||||
|
// mad_bit_finish(&next_frame);
|
||||||
|
//
|
||||||
|
// std::cout << ", MD_LENGTH_AFTER: " << stream.anc_bitlen << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < GLUE_LENGTH/2; ++i) {
|
||||||
|
glue[i] = pending[0].ptr[pending[0].length - GLUE_LENGTH/2 + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
state = onBufferHalf;
|
||||||
|
|
||||||
|
if (pending.size() > 1) {
|
||||||
|
for (int i = 0; i < GLUE_LENGTH/2; ++i) {
|
||||||
|
glue[GLUE_LENGTH/2 + i] = pending[1].ptr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
state = onBufferFull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::prepareNextBuffer()
|
||||||
|
{
|
||||||
|
bool shift;
|
||||||
|
do {
|
||||||
|
shift = false;
|
||||||
|
framesLeft();
|
||||||
|
if (cachedLength == 0 && state != empty && state != onGlueHalf) {
|
||||||
|
pullBuffer();
|
||||||
|
changeBuffer();
|
||||||
|
shift = true;
|
||||||
|
}
|
||||||
|
} while (shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::initializeProbe(mad_stream& probe)
|
||||||
|
{
|
||||||
|
mad_stream_init(&probe);
|
||||||
|
|
||||||
|
probe.buffer = stream.buffer;
|
||||||
|
probe.bufend = stream.bufend;
|
||||||
|
probe.skiplen = stream.skiplen;
|
||||||
|
probe.sync = stream.sync;
|
||||||
|
probe.freerate = stream.freerate;
|
||||||
|
probe.this_frame = stream.this_frame;
|
||||||
|
probe.next_frame = stream.next_frame;
|
||||||
|
probe.ptr.byte = stream.ptr.byte;
|
||||||
|
probe.ptr.cache = stream.ptr.cache;
|
||||||
|
probe.ptr.cache = stream.ptr.cache;
|
||||||
|
probe.anc_ptr.byte = stream.anc_ptr.byte;
|
||||||
|
probe.anc_ptr.cache = stream.anc_ptr.cache;
|
||||||
|
probe.anc_ptr.cache = stream.anc_ptr.cache;
|
||||||
|
probe.anc_bitlen = stream.anc_bitlen;
|
||||||
|
//probe.main_data = stream.main_data;
|
||||||
|
//probe.md_len = stream.md_len;
|
||||||
|
probe.options = stream.options;
|
||||||
|
probe.error = stream.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Decoder::switchToGlue()
|
||||||
|
{
|
||||||
|
std::cout << "Switching to glue" << std::endl;
|
||||||
|
uint32_t left = (uint64_t)stream.bufend - (uint64_t)stream.next_frame;
|
||||||
|
if (left > GLUE_LENGTH / 2) {
|
||||||
|
std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit (" << left << ")" << std::endl;
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
// std::cout << "MD_LENGTH_BEFORE: " << stream.anc_bitlen << std::endl;
|
||||||
|
mad_stream_buffer(&stream, glue, GLUE_LENGTH);
|
||||||
|
|
||||||
|
|
||||||
|
if (lastSync != -1) {
|
||||||
|
if (lastSync > GLUE_LENGTH / 2) {
|
||||||
|
std::cout << "Error: bytes to read in the buffer are more then glue buffer can fit LASTSYNC (" << lastSync << ")" << std::endl;
|
||||||
|
throw 1;
|
||||||
|
}
|
||||||
|
uint64_t target = (uint64_t)stream.bufend - GLUE_LENGTH / 2 - lastSync;
|
||||||
|
while ((uint64_t)(stream.this_frame) < target) {
|
||||||
|
// std::cout << "pulling" << std::endl;
|
||||||
|
mad_frame_decode(&frame, &stream);
|
||||||
|
}
|
||||||
|
stream.error = MAD_ERROR_NONE;
|
||||||
|
// stream.this_frame = stream.bufend - GLUE_LENGTH / 2 - lastSync;
|
||||||
|
// stream.next_frame = stream.bufend - GLUE_LENGTH / 2 - left;
|
||||||
|
// stream.sync = 1;
|
||||||
|
|
||||||
|
// mad_bitptr next_frame;
|
||||||
|
// mad_bit_init(&next_frame, stream.next_frame);
|
||||||
|
// stream.anc_ptr = stream.ptr;
|
||||||
|
// stream.anc_bitlen = mad_bit_length(&stream.ptr, &next_frame);
|
||||||
|
// mad_bit_finish(&next_frame);
|
||||||
|
//
|
||||||
|
// std::cout << ", MD_LENGTH_AFTER: " << stream.anc_bitlen << std::endl;
|
||||||
|
|
||||||
|
lastSync = -1;
|
||||||
|
}
|
||||||
|
//mad_stream_skip(&stream, left);
|
||||||
|
stream.error = MAD_ERROR_NONE;
|
||||||
|
|
||||||
|
//std::cout << "SKIPLEN (" << stream.skiplen << ")" << std::endl;
|
||||||
|
|
||||||
|
std::cout << "Freeing the drained fragment" << std::endl;
|
||||||
|
free(pending[0].ptr);
|
||||||
|
pending.pop_front();
|
||||||
|
}
|
66
decoder.h
Normal file
66
decoder.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef DECODER_H
|
||||||
|
#define DECODER_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo write docs
|
||||||
|
*/
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
#include <emscripten/val.h>
|
||||||
|
#include "mad.h"
|
||||||
|
|
||||||
|
#define GLUE_LENGTH 10240
|
||||||
|
|
||||||
|
class Decoder {
|
||||||
|
public:
|
||||||
|
Decoder();
|
||||||
|
~Decoder();
|
||||||
|
|
||||||
|
void addFragment(intptr_t bufferPtr, uint32_t length);
|
||||||
|
emscripten::val decode(uint32_t count = UINT32_MAX);
|
||||||
|
bool hasMore() const;
|
||||||
|
uint32_t framesLeft(uint32_t max = UINT32_MAX);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
empty,
|
||||||
|
onBufferHalf,
|
||||||
|
onBufferFull,
|
||||||
|
onGlueHalf,
|
||||||
|
onGlueFull
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RawBuffer {
|
||||||
|
uint8_t* ptr;
|
||||||
|
uint32_t length;
|
||||||
|
uint32_t offset;
|
||||||
|
uint32_t gueard;
|
||||||
|
};
|
||||||
|
|
||||||
|
State state;
|
||||||
|
|
||||||
|
uint32_t sampleRate;
|
||||||
|
uint8_t channels;
|
||||||
|
uint32_t cachedLength;
|
||||||
|
uint16_t samplesPerFrame;
|
||||||
|
uint8_t* glue;
|
||||||
|
int32_t lastSync;
|
||||||
|
bool cached;
|
||||||
|
|
||||||
|
mad_synth synth;
|
||||||
|
mad_stream stream;
|
||||||
|
mad_frame frame;
|
||||||
|
emscripten::val context;
|
||||||
|
|
||||||
|
std::deque<RawBuffer> pending;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void pullBuffer();
|
||||||
|
void changeBuffer();
|
||||||
|
void prepareNextBuffer();
|
||||||
|
void initializeProbe(mad_stream& probe);
|
||||||
|
void switchToGlue();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DECODER_H
|
62
index.html
62
index.html
@ -12,24 +12,24 @@
|
|||||||
<script src="wrapper.js"></script>
|
<script src="wrapper.js"></script>
|
||||||
<input type="file" id="file" name="file" />
|
<input type="file" id="file" name="file" />
|
||||||
<script>
|
<script>
|
||||||
|
var minPortion = 20480;
|
||||||
|
var maxPortion = 40960;
|
||||||
var time = 0;
|
var time = 0;
|
||||||
var ctx = new AudioContext();
|
var ctx = new AudioContext();
|
||||||
ctx.suspend();
|
ctx.suspend();
|
||||||
var decoder;
|
var decoder;
|
||||||
|
var fragments = [];
|
||||||
|
var noize = [];
|
||||||
|
var decoding = false;
|
||||||
function onChange(e) {
|
function onChange(e) {
|
||||||
var file = e.target.files[0];
|
var file = e.target.files[0];
|
||||||
|
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
decoder = new Module.Decoder();
|
decoder = new Module.Decoder();
|
||||||
var buffer = reader.result;
|
createFileFragments(reader.result);
|
||||||
var src = new Uint8Array(buffer);
|
|
||||||
var ptr = Module._malloc(buffer.byteLength);
|
|
||||||
var arr = new Uint8Array(Module.HEAPU8.buffer, ptr, buffer.byteLength);
|
|
||||||
arr.set(src, 0);
|
|
||||||
|
|
||||||
decoder.addFragment(ptr, buffer.byteLength);
|
scheduleData();
|
||||||
schedule();
|
|
||||||
ctx.resume();
|
ctx.resume();
|
||||||
}
|
}
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
@ -37,8 +37,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function schedule() {
|
function schedule() {
|
||||||
|
decoding = false;
|
||||||
if (decoder.hasMore()) {
|
if (decoder.hasMore()) {
|
||||||
var audio = decoder.decode(50);
|
var audio = decoder.decode(20);
|
||||||
if (audio) {
|
if (audio) {
|
||||||
var source = ctx.createBufferSource();
|
var source = ctx.createBufferSource();
|
||||||
source.buffer = audio;
|
source.buffer = audio;
|
||||||
@ -47,14 +48,55 @@
|
|||||||
time += audio.duration;
|
time += audio.duration;
|
||||||
|
|
||||||
setTimeout(schedule, Math.floor(audio.duration * 200));
|
setTimeout(schedule, Math.floor(audio.duration * 200));
|
||||||
|
decoding = true;
|
||||||
} else {
|
} else {
|
||||||
decoder.delete();
|
//decoder.delete();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
decoder.delete();
|
//decoder.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scheduleData() {
|
||||||
|
if (fragments.length) {
|
||||||
|
var frag = fragments.shift();
|
||||||
|
|
||||||
|
decoder.addFragment(frag.ptr, frag.length);
|
||||||
|
|
||||||
|
if (fragments.length) {
|
||||||
|
setTimeout(scheduleData, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decoding) {
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFileFragments(buffer) {
|
||||||
|
var src = new Uint8Array(buffer);
|
||||||
|
var shoved = 0;
|
||||||
|
|
||||||
|
while (shoved !== src.length) {
|
||||||
|
var ps = Math.min(Math.floor(Math.random() * (maxPortion - minPortion) + minPortion), src.length - shoved);
|
||||||
|
var ptr = Module._malloc(ps);
|
||||||
|
|
||||||
|
var arr = new Uint8Array(Module.HEAPU8.buffer, ptr, ps);
|
||||||
|
var portion = new Uint8Array(buffer, shoved, ps)
|
||||||
|
arr.set(portion, 0);
|
||||||
|
|
||||||
|
shoved += ps;
|
||||||
|
|
||||||
|
fragments.push({
|
||||||
|
ptr: ptr,
|
||||||
|
length: ps
|
||||||
|
});
|
||||||
|
|
||||||
|
noize.push(Module._malloc(256));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.getElementById("file").addEventListener("change", onChange, false);
|
document.getElementById("file").addEventListener("change", onChange, false);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
139
wrapper.cpp
139
wrapper.cpp
@ -70,142 +70,3 @@ void MadSynth::frame(const mad_frame* frame)
|
|||||||
{
|
{
|
||||||
mad_synth_frame(this, frame);
|
mad_synth_frame(this, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
Decoder::Decoder():
|
|
||||||
sampleRate(0),
|
|
||||||
channels(0),
|
|
||||||
cachedLength(0),
|
|
||||||
samplesPerFrame(0),
|
|
||||||
currentBuffer(0),
|
|
||||||
currentBufferLength(0),
|
|
||||||
synth(),
|
|
||||||
stream(),
|
|
||||||
frame(),
|
|
||||||
context(0)
|
|
||||||
{
|
|
||||||
mad_frame_init(&frame);
|
|
||||||
mad_stream_init(&stream);
|
|
||||||
mad_synth_init(&synth);
|
|
||||||
|
|
||||||
emscripten::val AudioContext = emscripten::val::global("AudioContext");
|
|
||||||
if (!AudioContext.as<bool>()) {
|
|
||||||
printf("No global AudioContext, trying webkitAudioContext\n");
|
|
||||||
AudioContext = emscripten::val::global("webkitAudioContext");
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Got an AudioContext\n");
|
|
||||||
context = AudioContext.new_();
|
|
||||||
}
|
|
||||||
|
|
||||||
Decoder::~Decoder()
|
|
||||||
{
|
|
||||||
context.call<void>("close");
|
|
||||||
|
|
||||||
mad_synth_finish(&synth);
|
|
||||||
mad_stream_finish(&stream);
|
|
||||||
mad_frame_finish(&frame);
|
|
||||||
|
|
||||||
if (currentBuffer != 0) {
|
|
||||||
free(currentBuffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Decoder::addFragment(intptr_t bufferPtr, unsigned long length)
|
|
||||||
{
|
|
||||||
if (currentBuffer != 0) {
|
|
||||||
printf("Adding more then 1 fragment is not supported yet");
|
|
||||||
throw 1;
|
|
||||||
}
|
|
||||||
currentBuffer = (unsigned char(*))bufferPtr;
|
|
||||||
currentBufferLength = length;
|
|
||||||
|
|
||||||
mad_stream_buffer(&stream, currentBuffer, currentBufferLength);
|
|
||||||
framesLeft();
|
|
||||||
}
|
|
||||||
|
|
||||||
emscripten::val Decoder::decode(uint32_t count)
|
|
||||||
{
|
|
||||||
emscripten::val ret = emscripten::val::undefined();
|
|
||||||
|
|
||||||
int available = framesLeft(count);
|
|
||||||
if (available > 0) {
|
|
||||||
ret = context.call<val>("createBuffer", channels, available * samplesPerFrame, sampleRate);
|
|
||||||
|
|
||||||
std::vector<emscripten::val> chans(channels, emscripten::val::undefined());
|
|
||||||
for (int i = 0; i < channels; ++i) {
|
|
||||||
chans[i] = ret.call<val>("getChannelData", i);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < available; ++i) {
|
|
||||||
mad_frame_decode(&frame, &stream);
|
|
||||||
mad_synth_frame(&synth, &frame);
|
|
||||||
|
|
||||||
for (int j = 0; j < samplesPerFrame; ++j) {
|
|
||||||
for (int k = 0; k < channels; ++k) {
|
|
||||||
float value = mad_f_todouble(synth.pcm.samples[k][j]);
|
|
||||||
chans[k].set(std::to_string(i * samplesPerFrame + j), val(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedLength -= available;
|
|
||||||
|
|
||||||
if (cachedLength == 0) {
|
|
||||||
framesLeft();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Decoder::hasMore() const
|
|
||||||
{
|
|
||||||
if (currentBuffer == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream.error != MAD_ERROR_BUFLEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t Decoder::framesLeft(uint32_t max)
|
|
||||||
{
|
|
||||||
if (currentBuffer == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cachedLength == 0) {
|
|
||||||
mad_stream probe;
|
|
||||||
mad_header ph;
|
|
||||||
mad_stream_init(&probe);
|
|
||||||
mad_header_init(&ph);
|
|
||||||
|
|
||||||
mad_stream_buffer(&probe, currentBuffer, currentBufferLength);
|
|
||||||
mad_stream_skip(&probe, stream.skiplen);
|
|
||||||
|
|
||||||
while (cachedLength < max) {
|
|
||||||
if (mad_header_decode(&ph, &probe) == 0) {
|
|
||||||
if (sampleRate == 0) {
|
|
||||||
sampleRate = ph.samplerate;
|
|
||||||
channels = MAD_NCHANNELS(&ph);
|
|
||||||
samplesPerFrame = MAD_NSBSAMPLES(&ph) * 32; //not sure why 32, it's in libmad source
|
|
||||||
} else {
|
|
||||||
if (sampleRate != ph.samplerate || channels != MAD_NCHANNELS(&ph) || samplesPerFrame != MAD_NSBSAMPLES(&ph) * 32) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++cachedLength;
|
|
||||||
} else {
|
|
||||||
if (!MAD_RECOVERABLE(probe.error)) {
|
|
||||||
std::cout << "framesLeft::" << probe.error << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mad_header_finish(&ph);
|
|
||||||
mad_stream_finish(&probe);
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::min(cachedLength, max);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
25
wrapper.h
25
wrapper.h
@ -5,6 +5,7 @@
|
|||||||
#include <emscripten/val.h>
|
#include <emscripten/val.h>
|
||||||
|
|
||||||
#include "mad.h"
|
#include "mad.h"
|
||||||
|
#include "decoder.h"
|
||||||
|
|
||||||
using namespace emscripten;
|
using namespace emscripten;
|
||||||
|
|
||||||
@ -118,29 +119,7 @@ public:
|
|||||||
|
|
||||||
MadSynth* mad_synth_create();
|
MadSynth* mad_synth_create();
|
||||||
|
|
||||||
class Decoder {
|
|
||||||
public:
|
|
||||||
Decoder();
|
|
||||||
~Decoder();
|
|
||||||
|
|
||||||
void addFragment(intptr_t bufferPtr, unsigned long length);
|
|
||||||
val decode(uint32_t count = UINT32_MAX);
|
|
||||||
bool hasMore() const;
|
|
||||||
uint32_t framesLeft(uint32_t max = UINT32_MAX);
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint32_t sampleRate;
|
|
||||||
uint8_t channels;
|
|
||||||
uint32_t cachedLength;
|
|
||||||
uint16_t samplesPerFrame;
|
|
||||||
unsigned char* currentBuffer;
|
|
||||||
unsigned long currentBufferLength;
|
|
||||||
|
|
||||||
mad_synth synth;
|
|
||||||
mad_stream stream;
|
|
||||||
mad_frame frame;
|
|
||||||
val context;
|
|
||||||
};
|
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(jsmad) {
|
EMSCRIPTEN_BINDINGS(jsmad) {
|
||||||
enum_<mad_layer>("mad_layer")
|
enum_<mad_layer>("mad_layer")
|
||||||
|
Loading…
Reference in New Issue
Block a user