Przeglądaj źródła

Merge pull request #261 from mattybrad/wav2sketch_js

Wav2sketch js
dds
Paul Stoffregen 3 lat temu
rodzic
commit
62f93e6e8c
Brak konta powiązanego z adresem e-mail autora
3 zmienionych plików z 261 dodań i 1 usunięć
  1. +0
    -1
      extras/wav2sketch/wav2sketch.c
  2. +26
    -0
      extras/wav2sketch/wav2sketch.html
  3. +235
    -0
      extras/wav2sketch/wav2sketch.js

+ 0
- 1
extras/wav2sketch/wav2sketch.c Wyświetl plik

@@ -354,4 +354,3 @@ void die(const char *format, ...)
fprintf(stderr, "\n");
exit(1);
}


+ 26
- 0
extras/wav2sketch/wav2sketch.html Wyświetl plik

@@ -0,0 +1,26 @@
<html>
<head>
<title>wav2sketch</title>
</head>
<body>
<h1>Teensy Audio Wav2Sketch Utility</h1>
<p>Upload an audio file to convert it into Teensy audio format.</p>
<form>
<label>Output sample rate</label>
<select id="sampleRate">
<option value="auto">Auto</option>
<option value="44100">44100</option>
<option value="22050">22050</option>
<option value="11025">11025</option>
</select><br/><br/>
<label>Encoding</label>
<select id="encoding">
<option value="u-law">u-law</option>
<option value="PCM">PCM</option>
</select><br/><br/>
<input id="audioFileChooser" name="audioFileChooser" type="file" accept="audio/*" multiple>
<div id="outputFileHolder"></div>
</form>
<script type="text/javascript" src="wav2sketch.js"></script>
</body>
</html>

+ 235
- 0
extras/wav2sketch/wav2sketch.js Wyświetl plik

@@ -0,0 +1,235 @@
/*
WAV2SKETCH utility - converts audio files to Teensy code
Javascript version by Matt Bradshaw, converted from original by Paul Stoffregen

HOW IT WORKS:
File loader listens for a user choosing a file
Check desired sample rate and encoding
If no desired sample rate chosen, file's header is read to see if the sample rate can be determined
Create an OfflineAudioContext with appropriate sample rate
Read data from audio file as a series of floating point numbers
Convert these floating point numbers to unsigned integers
Add padding required by Teensy audio library
Pack unsigned integers into 32-bit words, with u-law encoding if desired
*/

var audioFileChooser = document.getElementById('audioFileChooser');

audioFileChooser.addEventListener('change', readFile);

if(!window.OfflineAudioContext) alert("Browser does not support OfflineAudioContext");

function readFile() {
for(var i = 0; i < audioFileChooser.files.length; i++) {
var fileReader = new FileReader();
var sampleRateChoice = document.getElementById('sampleRate').value;
var encodingChoice = document.getElementById('encoding').value;
if(sampleRateChoice == "auto") sampleRateChoice = null;
else sampleRateChoice = parseInt(sampleRateChoice);
fileReader.readAsArrayBuffer(audioFileChooser.files[i]);
fileReader.addEventListener('load', function(fileName, ev) {
processFile(ev.target.result, fileName, sampleRateChoice, encodingChoice);
}.bind(null, audioFileChooser.files[i].name));
}
}

function processFile(file, fileName, sampleRateChoice, encodingChoice) {
// determine sample rate
// ideas borrowed from https://github.com/ffdead/wav.js
var sampleRate = 0;
var encoding = encodingChoice;
if(!sampleRateChoice) {
try {
var sampleRateBytes = new Uint8Array(file, 24, 4);
for(var i = 0; i < sampleRateBytes.length; i ++) {
sampleRate |= sampleRateBytes[i] << (i*8);
}
} catch(err) {
console.log('problem reading sample rate');
}
if([44100, 22050, 11025].indexOf(sampleRate) == -1) {
sampleRate = 44100;
}
} else {
sampleRate = sampleRateChoice;
}

var context = new OfflineAudioContext(1, 100*sampleRate, sampleRate); // arbitrary 100 seconds max length for now, nothing that long would fit on a Teensy anyway
context.decodeAudioData(file, function(buffer) {
var monoData = [];
if(buffer.numberOfChannels == 1) {
monoData = buffer.getChannelData(0);
} else if(buffer.numberOfChannels == 2) {
var leftData = buffer.getChannelData(0);
var rightData = buffer.getChannelData(1);
for(var i=0;i<buffer.length;i++) {
monoData[i] = (leftData[i] + rightData[i]) / 2;
}
} else {
alert("ONLY MONO AND STEREO FILES ARE SUPPORTED");
// NB - would be trivial to add support for n channels, given that everything ends up mono anyway
}
var padLength;
var encodingCode = '0';
var sampleRateCode;
if(encoding == 'u-law') encodingCode = '0';
else encodingCode = '8'; // PCM
if(sampleRate == 44100) {
padLength = padding(monoData.length, 128);
sampleRateCode = '1';
} else if(sampleRate == 22050) {
padLength = padding(monoData.length, 64);
sampleRateCode = '2';
} else if(sampleRate == 11025) {
padLength = padding(monoData.length, 32);
sampleRateCode = '3';
}

var ulawOut = [];
for(var i = 0; i < monoData.length; i ++) {
ulawOut.push(ulaw_encode(toInteger(monoData[i]*0x7fff)));
}
window.ulawOut = ulawOut;
var outputData;
if(encoding == 'u-law') {
outputData = createULawWords(ulawOut, padLength);
} else {
outputData = createWords(monoData, padLength);
}

var statusInt = (monoData.length).toString(16);
while(statusInt.length < 6) statusInt = '0' + statusInt;
if(monoData.length>0xFFFFFF) alert("DATA TOO LONG");
statusInt = '0x' + encodingCode + sampleRateCode + statusInt;
outputData.unshift(statusInt);

var outputFileHolder = document.getElementById('outputFileHolder');
var downloadLink1 = document.createElement('a');
var downloadLink2 = document.createElement('a');
var formattedName = fileName.split('.')[0].split(' ').join('');
formattedName = formattedName.charAt(0).toUpperCase() + formattedName.slice(1).toLowerCase();
downloadLink1.href = generateOutputFile(generateCPPFile(fileName, formattedName, outputData, sampleRate, encoding));
downloadLink1.setAttribute('download', 'AudioSample' + formattedName + '.cpp');
downloadLink1.innerHTML = 'Download AudioSample' + formattedName + '.cpp';
downloadLink2.href = generateOutputFile(generateHeaderFile(formattedName, outputData));
downloadLink2.setAttribute('download', 'AudioSample' + formattedName + '.h');
downloadLink2.innerHTML = 'Download AudioSample' + formattedName + '.h';
outputFileHolder.appendChild(downloadLink1);
outputFileHolder.appendChild(document.createElement('br'));
outputFileHolder.appendChild(downloadLink2);
outputFileHolder.appendChild(document.createElement('br'));
});
}

function ulaw_encode(audio)
{
var mag, neg; // both uint32

// http://en.wikipedia.org/wiki/G.711
if (audio >= 0) {
mag = audio;
neg = 0;
} else {
mag = audio * -1;
neg = 0x80;
}
mag += 128;
if (mag > 0x7FFF) mag = 0x7FFF;
if (mag >= 0x4000) return neg | 0x70 | ((mag >> 10) & 0x0F); // 01wx yz00 0000 0000
if (mag >= 0x2000) return neg | 0x60 | ((mag >> 9) & 0x0F); // 001w xyz0 0000 0000
if (mag >= 0x1000) return neg | 0x50 | ((mag >> 8) & 0x0F); // 0001 wxyz 0000 0000
if (mag >= 0x0800) return neg | 0x40 | ((mag >> 7) & 0x0F); // 0000 1wxy z000 0000
if (mag >= 0x0400) return neg | 0x30 | ((mag >> 6) & 0x0F); // 0000 01wx yz00 0000
if (mag >= 0x0200) return neg | 0x20 | ((mag >> 5) & 0x0F); // 0000 001w xyz0 0000
if (mag >= 0x0100) return neg | 0x10 | ((mag >> 4) & 0x0F); // 0000 0001 wxyz 0000
return neg | 0x00 | ((mag >> 3) & 0x0F); // 0000 0000 1wxy z000
}

function createWords(audioData, padLength) {
var totalLength = audioData.length + padLength;
var outputData = [];
for(var i = 0; i < totalLength; i += 2) {
var a = toUint16(i<audioData.length?audioData[i]*0x7fff:0x0000);
var b = toUint16(i+1<audioData.length?audioData[i+1]*0x7fff:0x0000);
var out = (65536*b + a).toString(16);
while(out.length < 8) out = '0' + out;
out = '0x' + out;
outputData.push(out);
}
return outputData;
}

function createULawWords(audioData, padLength) {
var totalLength = audioData.length + padLength;
var outputData = [];
for(var i = 0; i < totalLength; i += 4) {
var a = i<audioData.length ? audioData[i] : 0;
var b = i+1<audioData.length ? audioData[i+1] : 0;
var c = i+2<audioData.length ? audioData[i+2] : 0;
var d = i+3<audioData.length ? audioData[i+3] : 0;
var out = (a + 0x100*b + 0x10000*c + 0x1000000*d).toString(16);
while(out.length < 8) out = '0' + out;
out = '0x' + out;
outputData.push(out);
}
return outputData;
}

// http://2ality.com/2012/02/js-integers.html
function toInteger(x) {
x = Number(x);
return Math.round(x);
//return x < 0 ? Math.ceil(x) : Math.floor(x);
}

function modulo(a, b) {
return a - Math.floor(a/b)*b;
}

function toUint16(x) {
return modulo(toInteger(x), Math.pow(2, 16));
}

function toUint8(x) {
return modulo(toInteger(x), Math.pow(2, 8));
}

// compute the extra padding needed
function padding(sampleLength, block) {
var extra = sampleLength % block;
if (extra == 0) return 0;
return block - extra;
}

function generateOutputFile(fileContents) {
var textFileURL = null;
var blob = new Blob([fileContents], {type: 'text/plain'});
textFileURL = window.URL.createObjectURL(blob);
return textFileURL;
}

function formatAudioData(audioData) {
var outputString = '';
for(var i = 0; i < audioData.length; i ++) {
if(i%8==0 && i>0) outputString += '\n';
outputString += audioData[i] + ',';
}
return outputString;
}

function generateCPPFile(fileName, formattedName, audioData, sampleRate, encodingType) {
var out = "";
out += '// Audio data converted from audio file by wav2sketch_js\n\n';
out += '#include "AudioSample' + formattedName + '.h"\n\n';
out += '// Converted from ' + fileName + ', using ' + sampleRate + ' Hz, 16 bit ' + encodingType + ' encoding\n';
out += 'const unsigned int AudioSample' + formattedName + '[' + audioData.length + '] = {\n';
out += formatAudioData(audioData) + '\n};';
return out;
}

function generateHeaderFile(formattedName, audioData) {
var out = "";
out += '// Audio data converted from audio file by wav2sketch_js\n\n';
out += 'extern const unsigned int AudioSample' + formattedName + '[' + audioData.length + '];';
return out;
}

Ładowanie…
Anuluj
Zapisz