Browse Source

Merge pull request #15967 from Gamblify/AudioRecordingModule

Audio Recording from godot
Rémi Verschelde 7 years ago
parent
commit
91d6fa817e

+ 0 - 114
editor/import/resource_importer_wav.cpp

@@ -526,119 +526,5 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
 	return OK;
 	return OK;
 }
 }
 
 
-void ResourceImporterWAV::_compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data) {
-
-	/*p_sample_data->data = (void*)malloc(len);
-	xm_s8 *dataptr=(xm_s8*)p_sample_data->data;*/
-
-	static const int16_t _ima_adpcm_step_table[89] = {
-		7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
-		19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
-		50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
-		130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
-		337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
-		876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
-		2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
-		5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
-		15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
-	};
-
-	static const int8_t _ima_adpcm_index_table[16] = {
-		-1, -1, -1, -1, 2, 4, 6, 8,
-		-1, -1, -1, -1, 2, 4, 6, 8
-	};
-
-	int datalen = p_data.size();
-	int datamax = datalen;
-	if (datalen & 1)
-		datalen++;
-
-	dst_data.resize(datalen / 2 + 4);
-	PoolVector<uint8_t>::Write w = dst_data.write();
-
-	int i, step_idx = 0, prev = 0;
-	uint8_t *out = w.ptr();
-	//int16_t xm_prev=0;
-	const float *in = p_data.ptr();
-
-	/* initial value is zero */
-	*(out++) = 0;
-	*(out++) = 0;
-	/* Table index initial value */
-	*(out++) = 0;
-	/* unused */
-	*(out++) = 0;
-
-	for (i = 0; i < datalen; i++) {
-		int step, diff, vpdiff, mask;
-		uint8_t nibble;
-		int16_t xm_sample;
-
-		if (i >= datamax)
-			xm_sample = 0;
-		else {
-
-			xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
-			/*
-			if (xm_sample==32767 || xm_sample==-32768)
-				printf("clippy!\n",xm_sample);
-			*/
-		}
-
-		//xm_sample=xm_sample+xm_prev;
-		//xm_prev=xm_sample;
-
-		diff = (int)xm_sample - prev;
-
-		nibble = 0;
-		step = _ima_adpcm_step_table[step_idx];
-		vpdiff = step >> 3;
-		if (diff < 0) {
-			nibble = 8;
-			diff = -diff;
-		}
-		mask = 4;
-		while (mask) {
-
-			if (diff >= step) {
-
-				nibble |= mask;
-				diff -= step;
-				vpdiff += step;
-			}
-
-			step >>= 1;
-			mask >>= 1;
-		};
-
-		if (nibble & 8)
-			prev -= vpdiff;
-		else
-			prev += vpdiff;
-
-		if (prev > 32767) {
-			//printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip up %i\n",i,xm_sample,prev,diff,vpdiff,prev);
-			prev = 32767;
-		} else if (prev < -32768) {
-			//printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip down %i\n",i,xm_sample,prev,diff,vpdiff,prev);
-			prev = -32768;
-		}
-
-		step_idx += _ima_adpcm_index_table[nibble];
-		if (step_idx < 0)
-			step_idx = 0;
-		else if (step_idx > 88)
-			step_idx = 88;
-
-		if (i & 1) {
-			*out |= nibble << 4;
-			out++;
-		} else {
-			*out = nibble;
-		}
-		/*dataptr[i]=prev>>8;*/
-	}
-}
-
 ResourceImporterWAV::ResourceImporterWAV() {
 ResourceImporterWAV::ResourceImporterWAV() {
 }
 }

+ 112 - 1
editor/import/resource_importer_wav.h

@@ -48,7 +48,118 @@ public:
 	virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const;
 	virtual void get_import_options(List<ImportOption> *r_options, int p_preset = 0) const;
 	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
 	virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const;
 
 
-	void _compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data);
+	static void _compress_ima_adpcm(const Vector<float> &p_data, PoolVector<uint8_t> &dst_data) {
+		/*p_sample_data->data = (void*)malloc(len);
+		xm_s8 *dataptr=(xm_s8*)p_sample_data->data;*/
+
+		static const int16_t _ima_adpcm_step_table[89] = {
+			7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+			19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+			50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+			130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+			337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+			876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+			2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+			5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+			15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+		};
+
+		static const int8_t _ima_adpcm_index_table[16] = {
+			-1, -1, -1, -1, 2, 4, 6, 8,
+			-1, -1, -1, -1, 2, 4, 6, 8
+		};
+
+		int datalen = p_data.size();
+		int datamax = datalen;
+		if (datalen & 1)
+			datalen++;
+
+		dst_data.resize(datalen / 2 + 4);
+		PoolVector<uint8_t>::Write w = dst_data.write();
+
+		int i, step_idx = 0, prev = 0;
+		uint8_t *out = w.ptr();
+		//int16_t xm_prev=0;
+		const float *in = p_data.ptr();
+
+		/* initial value is zero */
+		*(out++) = 0;
+		*(out++) = 0;
+		/* Table index initial value */
+		*(out++) = 0;
+		/* unused */
+		*(out++) = 0;
+
+		for (i = 0; i < datalen; i++) {
+			int step, diff, vpdiff, mask;
+			uint8_t nibble;
+			int16_t xm_sample;
+
+			if (i >= datamax)
+				xm_sample = 0;
+			else {
+
+				xm_sample = CLAMP(in[i] * 32767.0, -32768, 32767);
+				/*
+				if (xm_sample==32767 || xm_sample==-32768)
+					printf("clippy!\n",xm_sample);
+				*/
+			}
+
+			//xm_sample=xm_sample+xm_prev;
+			//xm_prev=xm_sample;
+
+			diff = (int)xm_sample - prev;
+
+			nibble = 0;
+			step = _ima_adpcm_step_table[step_idx];
+			vpdiff = step >> 3;
+			if (diff < 0) {
+				nibble = 8;
+				diff = -diff;
+			}
+			mask = 4;
+			while (mask) {
+
+				if (diff >= step) {
+
+					nibble |= mask;
+					diff -= step;
+					vpdiff += step;
+				}
+
+				step >>= 1;
+				mask >>= 1;
+			};
+
+			if (nibble & 8)
+				prev -= vpdiff;
+			else
+				prev += vpdiff;
+
+			if (prev > 32767) {
+				//printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip up %i\n",i,xm_sample,prev,diff,vpdiff,prev);
+				prev = 32767;
+			} else if (prev < -32768) {
+				//printf("%i,xms %i, prev %i,diff %i, vpdiff %i, clip down %i\n",i,xm_sample,prev,diff,vpdiff,prev);
+				prev = -32768;
+			}
+
+			step_idx += _ima_adpcm_index_table[nibble];
+			if (step_idx < 0)
+				step_idx = 0;
+			else if (step_idx > 88)
+				step_idx = 88;
+
+			if (i & 1) {
+				*out |= nibble << 4;
+				out++;
+			} else {
+				*out = nibble;
+			}
+			/*dataptr[i]=prev>>8;*/
+		}
+	}
 
 
 	virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
 	virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = NULL);
 
 

+ 74 - 0
scene/resources/audio_stream_sample.cpp

@@ -29,6 +29,8 @@
 /*************************************************************************/
 /*************************************************************************/
 
 
 #include "audio_stream_sample.h"
 #include "audio_stream_sample.h"
+#include "io/marshalls.h"
+#include "os/file_access.h"
 
 
 void AudioStreamPlaybackSample::start(float p_from_pos) {
 void AudioStreamPlaybackSample::start(float p_from_pos) {
 
 
@@ -509,6 +511,76 @@ PoolVector<uint8_t> AudioStreamSample::get_data() const {
 	return pv;
 	return pv;
 }
 }
 
 
+void AudioStreamSample::save_to_wav(String p_path) {
+	if (format == AudioStreamSample::FORMAT_IMA_ADPCM) {
+		WARN_PRINTS("Saving IMA_ADPC samples are not supported yet");
+		return;
+	}
+
+	int sub_chunk_2_size = data_bytes; //Subchunk2Size = Size of data in bytes
+
+	// Format code
+	// 1:PCM format (for 8 or 16 bit)
+	// 3:IEEE float format
+	int format_code = (format == FORMAT_IMA_ADPCM) ? 3 : 1;
+
+	int n_channels = stereo ? 2 : 1;
+
+	long sample_rate = mix_rate;
+
+	int byte_pr_sample = 0;
+	switch (format) {
+		case AudioStreamSample::FORMAT_8_BITS: byte_pr_sample = 1; break;
+		case AudioStreamSample::FORMAT_16_BITS: byte_pr_sample = 2; break;
+		case AudioStreamSample::FORMAT_IMA_ADPCM: byte_pr_sample = 4; break;
+	}
+
+	String file_path = p_path;
+	if (!(file_path.substr(file_path.length() - 4, 4) == ".wav")) {
+		file_path += ".wav";
+	}
+
+	Error err;
+	FileAccess *file = FileAccess::open(file_path, FileAccess::WRITE, &err); //Overrides existing file if present
+
+	// Create WAV Header
+	file->store_string("RIFF"); //ChunkID
+	file->store_32(sub_chunk_2_size + 36); //ChunkSize = 36 + SubChunk2Size (size of entire file minus the 8 bits for this and previous header)
+	file->store_string("WAVE"); //Format
+	file->store_string("fmt "); //Subchunk1ID
+	file->store_32(16); //Subchunk1Size = 16
+	file->store_16(format_code); //AudioFormat
+	file->store_16(n_channels); //Number of Channels
+	file->store_32(sample_rate); //SampleRate
+	file->store_32(sample_rate * n_channels * byte_pr_sample); //ByteRate
+	file->store_16(n_channels * byte_pr_sample); //BlockAlign = NumChannels * BytePrSample
+	file->store_16(byte_pr_sample * 8); //BitsPerSample
+	file->store_string("data"); //Subchunk2ID
+	file->store_32(sub_chunk_2_size); //Subchunk2Size
+
+	// Add data
+	PoolVector<uint8_t>::Read read_data = get_data().read();
+	switch (format) {
+		case AudioStreamSample::FORMAT_8_BITS:
+			for (int i = 0; i < data_bytes; i++) {
+				uint8_t data_point = (read_data[i] + 128);
+				file->store_8(data_point);
+			}
+			break;
+		case AudioStreamSample::FORMAT_16_BITS:
+			for (int i = 0; i < data_bytes / 2; i++) {
+				uint16_t data_point = decode_uint16(&read_data[i * 2]);
+				file->store_16(data_point);
+			}
+			break;
+		case AudioStreamSample::FORMAT_IMA_ADPCM:
+			//Unimplemented
+			break;
+	}
+
+	file->close();
+}
+
 Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() {
 Ref<AudioStreamPlayback> AudioStreamSample::instance_playback() {
 
 
 	Ref<AudioStreamPlaybackSample> sample;
 	Ref<AudioStreamPlaybackSample> sample;
@@ -545,6 +617,8 @@ void AudioStreamSample::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo);
 	ClassDB::bind_method(D_METHOD("set_stereo", "stereo"), &AudioStreamSample::set_stereo);
 	ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo);
 	ClassDB::bind_method(D_METHOD("is_stereo"), &AudioStreamSample::is_stereo);
 
 
+	ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamSample::save_to_wav);
+
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_data", "get_data");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong"), "set_loop_mode", "get_loop_mode");

+ 2 - 0
scene/resources/audio_stream_sample.h

@@ -140,6 +140,8 @@ public:
 	void set_data(const PoolVector<uint8_t> &p_data);
 	void set_data(const PoolVector<uint8_t> &p_data);
 	PoolVector<uint8_t> get_data() const;
 	PoolVector<uint8_t> get_data() const;
 
 
+	void save_to_wav(String p_path);
+
 	virtual Ref<AudioStreamPlayback> instance_playback();
 	virtual Ref<AudioStreamPlayback> instance_playback();
 	virtual String get_stream_name() const;
 	virtual String get_stream_name() const;
 
 

+ 1 - 0
servers/audio/audio_effect.h

@@ -39,6 +39,7 @@ class AudioEffectInstance : public Reference {
 
 
 public:
 public:
 	virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) = 0;
 	virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) = 0;
+	virtual bool process_silence() const { return false; }
 };
 };
 
 
 class AudioEffect : public Resource {
 class AudioEffect : public Resource {

+ 259 - 0
servers/audio/effects/audio_effect_record.cpp

@@ -0,0 +1,259 @@
+/*************************************************************************/
+/*  audio_effect_record.cpp                                              */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "audio_effect_record.h"
+
+void AudioEffectRecordInstance::process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count) {
+	if (!is_recording) {
+		return;
+	}
+
+	//Add incoming audio frames to the IO ring buffer
+	const AudioFrame *src = p_src_frames;
+	AudioFrame *rb_buf = ring_buffer.ptrw();
+	for (int i = 0; i < p_frame_count; i++) {
+		rb_buf[ring_buffer_pos & ring_buffer_mask] = src[i];
+		ring_buffer_pos++;
+	}
+}
+
+bool AudioEffectRecordInstance::process_silence() {
+	return true;
+}
+
+void AudioEffectRecordInstance::_io_thread_process() {
+
+	//Reset recorder status
+	thread_active = true;
+	ring_buffer_pos = 0;
+	ring_buffer_read_pos = 0;
+
+	//We start a new recording
+	recording_data.resize(0); //Clear data completely and reset length
+	is_recording = true;
+
+	while (is_recording) {
+		//Check: The current recording has been requested to stop
+		if (is_recording && !base->should_record) {
+			is_recording = false;
+		}
+
+		//Case: Frames are remaining in the buffer
+		if (ring_buffer_read_pos < ring_buffer_pos) {
+			//Read from the buffer into recording_data
+			_io_store_buffer();
+		}
+		//Case: The buffer is empty
+		else if (is_recording) {
+			//Wait to avoid too much busy-wait
+			OS::get_singleton()->delay_usec(500);
+		}
+	}
+
+	thread_active = false;
+}
+
+void AudioEffectRecordInstance::_io_store_buffer() {
+	int to_read = ring_buffer_pos - ring_buffer_read_pos;
+
+	AudioFrame *rb_buf = ring_buffer.ptrw();
+
+	while (to_read) {
+		AudioFrame buffered_frame = rb_buf[ring_buffer_read_pos & ring_buffer_mask];
+		recording_data.push_back(buffered_frame.l);
+		recording_data.push_back(buffered_frame.r);
+
+		ring_buffer_read_pos++;
+		to_read--;
+	}
+}
+
+void AudioEffectRecordInstance::_thread_callback(void *_instance) {
+
+	AudioEffectRecordInstance *aeri = reinterpret_cast<AudioEffectRecordInstance *>(_instance);
+
+	aeri->_io_thread_process();
+}
+
+void AudioEffectRecordInstance::init() {
+	io_thread = Thread::create(_thread_callback, this);
+}
+
+Ref<AudioEffectInstance> AudioEffectRecord::instance() {
+	Ref<AudioEffectRecordInstance> ins;
+	ins.instance();
+	ins->base = Ref<AudioEffectRecord>(this);
+	ins->is_recording = false;
+
+	//Re-using the buffer size calculations from audio_effect_delay.cpp
+	float ring_buffer_max_size = IO_BUFFER_SIZE_MS;
+	ring_buffer_max_size /= 1000.0; //convert to seconds
+	ring_buffer_max_size *= AudioServer::get_singleton()->get_mix_rate();
+
+	int ringbuff_size = ring_buffer_max_size;
+
+	int bits = 0;
+
+	while (ringbuff_size > 0) {
+		bits++;
+		ringbuff_size /= 2;
+	}
+
+	ringbuff_size = 1 << bits;
+	ins->ring_buffer_mask = ringbuff_size - 1;
+	ins->ring_buffer_pos = 0;
+
+	ins->ring_buffer.resize(ringbuff_size);
+
+	ins->ring_buffer_read_pos = 0;
+
+	ensure_thread_stopped();
+	current_instance = ins;
+	if (should_record) {
+		ins->init();
+	}
+
+	return ins;
+}
+
+void AudioEffectRecord::ensure_thread_stopped() {
+	should_record = false;
+	if (current_instance != 0 && current_instance->thread_active) {
+		Thread::wait_to_finish(current_instance->io_thread);
+	}
+}
+
+void AudioEffectRecord::set_should_record(bool p_record) {
+	if (p_record) {
+		ensure_thread_stopped();
+		current_instance->init();
+	}
+
+	should_record = p_record;
+}
+
+bool AudioEffectRecord::get_should_record() const {
+	return should_record;
+}
+
+void AudioEffectRecord::set_format(AudioStreamSample::Format p_format) {
+	format = p_format;
+}
+
+AudioStreamSample::Format AudioEffectRecord::get_format() const {
+	return format;
+}
+
+Ref<AudioStreamSample> AudioEffectRecord::get_recording() const {
+	AudioStreamSample::Format dst_format = format;
+	bool stereo = true; //forcing mono is not implemented
+
+	PoolVector<uint8_t> dst_data;
+
+	if (dst_format == AudioStreamSample::FORMAT_8_BITS) {
+		int data_size = current_instance->recording_data.size();
+		dst_data.resize(data_size);
+		PoolVector<uint8_t>::Write w = dst_data.write();
+
+		for (int i = 0; i < data_size; i++) {
+			int8_t v = CLAMP(current_instance->recording_data[i] * 128, -128, 127);
+			w[i] = v;
+		}
+	} else if (dst_format == AudioStreamSample::FORMAT_16_BITS) {
+		int data_size = current_instance->recording_data.size();
+		dst_data.resize(data_size * 2);
+		PoolVector<uint8_t>::Write w = dst_data.write();
+
+		for (int i = 0; i < data_size; i++) {
+			int16_t v = CLAMP(current_instance->recording_data[i] * 32768, -32768, 32767);
+			encode_uint16(v, &w[i * 2]);
+		}
+	} else if (dst_format == AudioStreamSample::FORMAT_IMA_ADPCM) {
+		//byte interleave
+		Vector<float> left;
+		Vector<float> right;
+
+		int tframes = current_instance->recording_data.size() / 2;
+		left.resize(tframes);
+		right.resize(tframes);
+
+		for (int i = 0; i < tframes; i++) {
+			left.set(i, current_instance->recording_data[i * 2 + 0]);
+			right.set(i, current_instance->recording_data[i * 2 + 1]);
+		}
+
+		PoolVector<uint8_t> bleft;
+		PoolVector<uint8_t> bright;
+
+		ResourceImporterWAV::_compress_ima_adpcm(left, bleft);
+		ResourceImporterWAV::_compress_ima_adpcm(right, bright);
+
+		int dl = bleft.size();
+		dst_data.resize(dl * 2);
+
+		PoolVector<uint8_t>::Write w = dst_data.write();
+		PoolVector<uint8_t>::Read rl = bleft.read();
+		PoolVector<uint8_t>::Read rr = bright.read();
+
+		for (int i = 0; i < dl; i++) {
+			w[i * 2 + 0] = rl[i];
+			w[i * 2 + 1] = rr[i];
+		}
+	} else {
+		ERR_EXPLAIN("format not implemented");
+	}
+
+	Ref<AudioStreamSample> sample;
+	sample.instance();
+	sample->set_data(dst_data);
+	sample->set_format(dst_format);
+	sample->set_mix_rate(AudioServer::get_singleton()->get_mix_rate());
+	sample->set_loop_mode(AudioStreamSample::LOOP_DISABLED);
+	sample->set_loop_begin(0);
+	sample->set_loop_end(0);
+	sample->set_stereo(stereo);
+
+	return sample;
+}
+
+void AudioEffectRecord::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("set_should_record", "record"), &AudioEffectRecord::set_should_record);
+	ClassDB::bind_method(D_METHOD("get_should_record"), &AudioEffectRecord::get_should_record);
+	ClassDB::bind_method(D_METHOD("set_format", "format"), &AudioEffectRecord::set_format);
+	ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format);
+	ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording);
+
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "should_record"), "set_should_record", "get_should_record");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format");
+}
+
+AudioEffectRecord::AudioEffectRecord() {
+	format = AudioStreamSample::FORMAT_16_BITS;
+}

+ 103 - 0
servers/audio/effects/audio_effect_record.h

@@ -0,0 +1,103 @@
+/*************************************************************************/
+/*  audio_effect_record.h                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef AUDIOEFFECTRECORD_H
+#define AUDIOEFFECTRECORD_H
+
+#include "core/os/thread.h"
+#include "editor/import/resource_importer_wav.h"
+#include "io/marshalls.h"
+#include "os/file_access.h"
+#include "os/os.h"
+#include "scene/resources/audio_stream_sample.h"
+#include "servers/audio/audio_effect.h"
+#include "servers/audio_server.h"
+
+class AudioEffectRecord;
+
+class AudioEffectRecordInstance : public AudioEffectInstance {
+	GDCLASS(AudioEffectRecordInstance, AudioEffectInstance)
+	friend class AudioEffectRecord;
+	Ref<AudioEffectRecord> base;
+
+	bool is_recording;
+	Thread *io_thread;
+	bool thread_active = false;
+
+	Vector<AudioFrame> ring_buffer;
+	Vector<float> recording_data;
+
+	unsigned int ring_buffer_pos;
+	unsigned int ring_buffer_mask;
+	unsigned int ring_buffer_read_pos;
+
+	void _io_thread_process();
+	void _io_store_buffer();
+	static void _thread_callback(void *_instance);
+	void _init_recording();
+
+public:
+	void init();
+	virtual void process(const AudioFrame *p_src_frames, AudioFrame *p_dst_frames, int p_frame_count);
+	virtual bool process_silence();
+};
+
+class AudioEffectRecord : public AudioEffect {
+	GDCLASS(AudioEffectRecord, AudioEffect)
+
+	friend class AudioEffectRecordInstance;
+
+	enum {
+		IO_BUFFER_SIZE_MS = 1500
+	};
+
+	bool should_record;
+	Ref<AudioEffectRecordInstance> current_instance;
+
+	AudioStreamSample::Format format;
+
+	void ensure_thread_stopped();
+
+protected:
+	static void _bind_methods();
+	static void debug(uint64_t time_diff, int p_frame_count);
+
+public:
+	Ref<AudioEffectInstance> instance();
+	void set_should_record(bool p_record);
+	bool get_should_record() const;
+	void set_format(AudioStreamSample::Format p_format);
+	AudioStreamSample::Format get_format() const;
+	Ref<AudioStreamSample> get_recording() const;
+
+	AudioEffectRecord();
+};
+
+#endif // AUDIOEFFECTRECORD_H

+ 2 - 2
servers/audio_server.cpp

@@ -332,7 +332,7 @@ void AudioServer::_mix_step() {
 
 
 				for (int k = 0; k < bus->channels.size(); k++) {
 				for (int k = 0; k < bus->channels.size(); k++) {
 
 
-					if (!bus->channels[k].active)
+					if (!(bus->channels[k].active || bus->channels[k].effect_instances[j]->process_silence()))
 						continue;
 						continue;
 					bus->channels.write[k].effect_instances.write[j]->process(bus->channels[k].buffer.ptr(), temp_buffer.write[k].ptrw(), buffer_size);
 					bus->channels.write[k].effect_instances.write[j]->process(bus->channels[k].buffer.ptr(), temp_buffer.write[k].ptrw(), buffer_size);
 				}
 				}
@@ -340,7 +340,7 @@ void AudioServer::_mix_step() {
 				//swap buffers, so internal buffer always has the right data
 				//swap buffers, so internal buffer always has the right data
 				for (int k = 0; k < bus->channels.size(); k++) {
 				for (int k = 0; k < bus->channels.size(); k++) {
 
 
-					if (!buses[i]->channels[k].active)
+					if (!(buses[i]->channels[k].active || bus->channels[k].effect_instances[j]->process_silence()))
 						continue;
 						continue;
 					SWAP(bus->channels.write[k].buffer, temp_buffer.write[k]);
 					SWAP(bus->channels.write[k].buffer, temp_buffer.write[k]);
 				}
 				}

+ 2 - 0
servers/register_server_types.cpp

@@ -48,6 +48,7 @@
 #include "audio/effects/audio_effect_panner.h"
 #include "audio/effects/audio_effect_panner.h"
 #include "audio/effects/audio_effect_phaser.h"
 #include "audio/effects/audio_effect_phaser.h"
 #include "audio/effects/audio_effect_pitch_shift.h"
 #include "audio/effects/audio_effect_pitch_shift.h"
+#include "audio/effects/audio_effect_record.h"
 #include "audio/effects/audio_effect_reverb.h"
 #include "audio/effects/audio_effect_reverb.h"
 #include "audio/effects/audio_effect_stereo_enhance.h"
 #include "audio/effects/audio_effect_stereo_enhance.h"
 #include "audio_server.h"
 #include "audio_server.h"
@@ -138,6 +139,7 @@ void register_server_types() {
 		ClassDB::register_class<AudioEffectLimiter>();
 		ClassDB::register_class<AudioEffectLimiter>();
 		ClassDB::register_class<AudioEffectPitchShift>();
 		ClassDB::register_class<AudioEffectPitchShift>();
 		ClassDB::register_class<AudioEffectPhaser>();
 		ClassDB::register_class<AudioEffectPhaser>();
+		ClassDB::register_class<AudioEffectRecord>();
 	}
 	}
 
 
 	ClassDB::register_virtual_class<Physics2DDirectBodyState>();
 	ClassDB::register_virtual_class<Physics2DDirectBodyState>();