kopia lustrzana https://github.com/backface/turtlestitch
force stereo audio recordings to mono
rodzic
0c0625932f
commit
b107abf56a
|
@ -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**
|
||||
|
|
|
@ -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>
|
||||
|
|
169
src/gui.js
169
src/gui.js
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
Ładowanie…
Reference in New Issue