force stereo audio recordings to mono

pull/89/head
jmoenig 2020-01-28 19:18:28 +01:00
rodzic 0c0625932f
commit b107abf56a
4 zmienionych plików z 170 dodań i 8 usunięć

Wyświetl plik

@ -9,6 +9,7 @@
### 2020-01-28
* new dev version
* gui: record sounds in mono
* gui: force stereo audio recordings to mono
## 5.4.4:
* **Notable Fixes**

Wyświetl plik

@ -8,7 +8,7 @@
<script type="text/javascript" src="src/widgets.js?version=2020-01-03"></script>
<script type="text/javascript" src="src/blocks.js?version=2020-01-06"></script>
<script type="text/javascript" src="src/threads.js?version=2019-12-19"></script>
<script type="text/javascript" src="src/objects.js?version=2020-01-11"></script>
<script type="text/javascript" src="src/objects.js?version=2020-01-28"></script>
<script type="text/javascript" src="src/gui.js?version=2020-01-28"></script>
<script type="text/javascript" src="src/paint.js?version=2019-06-27"></script>
<script type="text/javascript" src="src/lists.js?version=2019-12-08"></script>

Wyświetl plik

@ -74,7 +74,7 @@ CommandBlockMorph, BooleanSlotMorph, RingReporterSlotMorph, ScriptFocusMorph,
BlockLabelPlaceHolderMorph, SpeechBubbleMorph, XML_Element, WatcherMorph,
BlockRemovalDialogMorph,TableMorph, isSnapObject, isRetinaEnabled, SliderMorph,
disableRetinaSupport, enableRetinaSupport, isRetinaSupported, MediaRecorder,
Animation, BoxMorph, BlockEditorMorph, BlockDialogMorph*/
Animation, BoxMorph, BlockEditorMorph, BlockDialogMorph, Note*/
// Global stuff ////////////////////////////////////////////////////////
@ -2432,12 +2432,14 @@ IDE_Morph.prototype.recordNewSound = function () {
myself = this;
soundRecorder = new SoundRecorderDialogMorph(
function (sound) {
if (sound) {
myself.currentSprite.addSound(
sound,
function (audio) {
var sound;
if (audio) {
sound = myself.currentSprite.addSound(
audio,
myself.newSoundName('recording')
);
myself.makeSureRecordingIsMono(sound);
myself.spriteBar.tabBar.tabTo('sounds');
myself.hasChangedMedia = true;
}
@ -2447,6 +2449,163 @@ IDE_Morph.prototype.recordNewSound = function () {
soundRecorder.popUp(this.world());
};
IDE_Morph.prototype.makeSureRecordingIsMono = function (sound) {
// private and temporary, a horrible kludge to work around browsers'
// reluctance to implement audio recording constraints that let us
// record sound in mono only. As of January 2020 the audio channelCount
// constraint only works in Firefox, hence this terrible function to
// force convert a stereo sound to mono for Chrome.
// If this code is still here next year, something is very wrong.
// -Jens
decodeSound(sound, makeMono);
function decodeSound(sound, callback) {
var base64, binaryString, len, bytes, i, arrayBuffer, audioCtx;
if (sound.audioBuffer) {
return callback (sound);
}
base64 = sound.audio.src.split(',')[1];
binaryString = window.atob(base64);
len = binaryString.length;
bytes = new Uint8Array(len);
for (i = 0; i < len; i += 1) {
bytes[i] = binaryString.charCodeAt(i);
}
arrayBuffer = bytes.buffer;
audioCtx = Note.prototype.getAudioContext();
sound.isDecoding = true;
audioCtx.decodeAudioData(
arrayBuffer,
function(buffer) {
sound.audioBuffer = buffer;
return callback (sound);
},
function (err) {throw err; }
);
}
function makeMono(sound) {
var samples, audio, blob, reader;
if (sound.audioBuffer.numberOfChannels === 1) {return; }
samples = sound.audioBuffer.getChannelData(0);
audio = new Audio();
blob = new Blob(
[
audioBufferToWav(
encodeSound(samples, 44100).audioBuffer
)
],
{type: "audio/wav"}
);
reader = new FileReader();
reader.onload = function () {
audio.src = reader.result;
sound.audio = audio; // .... aaaand we're done!
sound.audioBuffer = null;
sound.cachedSamples = null;
sound.isDecoding = false;
// console.log('made mono', sound);
};
reader.readAsDataURL(blob);
}
function encodeSound(samples, rate) {
var ctx = Note.prototype.getAudioContext(),
frameCount = samples.length,
arrayBuffer = ctx.createBuffer(1, frameCount, +rate || 44100),
i,
source;
if (!arrayBuffer.copyToChannel) {
arrayBuffer.copyToChannel = function (src, channel) {
var buffer = this.getChannelData(channel);
for (i = 0; i < src.length; i += 1) {
buffer[i] = src[i];
}
};
}
arrayBuffer.copyToChannel(
Float32Array.from(samples),
0,
0
);
source = ctx.createBufferSource();
source.buffer = arrayBuffer;
source.audioBuffer = source.buffer;
return source;
}
function audioBufferToWav(buffer, opt) {
var sampleRate = buffer.sampleRate,
format = (opt || {}).float32 ? 3 : 1,
bitDepth = format === 3 ? 32 : 16,
result;
result = buffer.getChannelData(0);
return encodeWAV(result, format, sampleRate, 1, bitDepth);
}
function encodeWAV(
samples,
format,
sampleRate,
numChannels,
bitDepth
) {
var bytesPerSample = bitDepth / 8,
blockAlign = numChannels * bytesPerSample,
buffer = new ArrayBuffer(44 + samples.length * bytesPerSample),
view = new DataView(buffer);
function writeFloat32(output, offset, input) {
for (var i = 0; i < input.length; i += 1, offset += 4) {
output.setFloat32(offset, input[i], true);
}
}
function floatTo16BitPCM(output, offset, input) {
var i, s;
for (i = 0; i < input.length; i += 1, offset += 2) {
s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function writeString(view, offset, string) {
for (var i = 0; i < string.length; i += 1) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
writeString(view, 0, 'RIFF'); // RIFF identifier
// RIFF chunk length:
view.setUint32(4, 36 + samples.length * bytesPerSample, true);
writeString(view, 8, 'WAVE'); // RIFF type
writeString(view, 12, 'fmt '); // format chunk identifier
view.setUint32(16, 16, true); // format chunk length
view.setUint16(20, format, true); // sample format (raw)
view.setUint16(22, numChannels, true); // channel count
view.setUint32(24, sampleRate, true); // sample rate
// byte rate (sample rate * block align):
view.setUint32(28, sampleRate * blockAlign, true);
// block align (channel count * bytes per sample):
view.setUint16(32, blockAlign, true);
view.setUint16(34, bitDepth, true); // bits per sample
writeString(view, 36, 'data'); // data chunk identifier
// data chunk length:
view.setUint32(40, samples.length * bytesPerSample, true);
if (format === 1) { // Raw PCM
floatTo16BitPCM(view, 44, samples);
} else {
writeFloat32(view, 44, samples);
}
return buffer;
}
};
IDE_Morph.prototype.duplicateSprite = function (sprite) {
var duplicate = sprite.fullCopy();
duplicate.isDown = false;

Wyświetl plik

@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph,
localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph,
HandleMorph, AlignmentMorph, Process, XML_Element, WorldMap, copyCanvas*/
modules.objects = '2020-January-11';
modules.objects = '2020-January-28';
var SpriteMorph;
var StageMorph;
@ -3584,8 +3584,10 @@ SpriteMorph.prototype.reportCostumes = function () {
// SpriteMorph sound management
SpriteMorph.prototype.addSound = function (audio, name) {
var sound = new Sound(audio, name);
this.shadowAttribute('sounds');
this.sounds.add(new Sound(audio, name));
this.sounds.add(sound);
return sound;
};
SpriteMorph.prototype.doPlaySound = function (name) {