Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AudioStreamWAV locked to 8bit despite having a 16bit option #83912

Open
Tracked by #76797
PeterMarques opened this issue Oct 25, 2023 · 4 comments
Open
Tracked by #76797

AudioStreamWAV locked to 8bit despite having a 16bit option #83912

PeterMarques opened this issue Oct 25, 2023 · 4 comments

Comments

@PeterMarques
Copy link

PeterMarques commented Oct 25, 2023

Godot version

4.1

System information

linux

Issue description

Yes, the data() Method indeed alerts you that it wants a signed 8 bit array (-128 to 127), but there is a format property that has the enum:

● FORMAT_8_BITS = 0
8-bit audio codec.
● FORMAT_16_BITS = 1
16-bit audio codec.
● FORMAT_IMA_ADPCM = 2
Audio is compressed using IMA ADPCM.

So, it should have suport for 16 bit audio in the data.

But, the alert indeed shows to be true, and even if setting the audio to be 16 bits, a random generator from -64 to 64, produces the same dinamic range, in either 8 or 16 bits.

And the lenght of the 16 bit file is half the lenght of the 8 bit one.
Thats odd.

image

So, the bitchange instead of changing the depth of volume, changes the lenght of execution.

High resolution audio is a need, and the lenght issue is a undefined behaviours, so that is pretty bugged.

Thats it.

Steps to reproduce

Run the project and see the files
(ajust the path)

Minimal reproduction project


extends Node
func _ready():
	var rec1 = AudioStreamWAV.new()
	var rec2 = AudioStreamWAV.new()
	var rec3 = AudioStreamWAV.new()
	var rec4 = AudioStreamWAV.new() 
	
	var arr1 = PackedByteArray([])
	var arr2 = PackedByteArray([])
	
	rec1.format = AudioStreamWAV.FORMAT_8_BITS
	rec2.format = AudioStreamWAV.FORMAT_16_BITS
	rec3.format = AudioStreamWAV.FORMAT_8_BITS
	rec4.format = AudioStreamWAV.FORMAT_16_BITS
	
	for each in 44100 *10:
		arr1.append(randi_range(pow(2,6)*-1,pow(2,6)))
	
	for each in 44100 *10:
		arr2.append(randi_range(pow(2,14)*-1,pow(2,14)))
		
	rec1.set_data(arr1)
	rec1.save_to_wav("/home/*USER*/Downloads/Godot4/rec1")
	
	rec2.set_data(arr1)
	rec2.save_to_wav("/home/*USER*/Downloads/Godot4/rec2")
	
	rec3.set_data(arr2)
	rec3.save_to_wav("/home/*USER*/Downloads/Godot4/rec3")
	
	rec4.set_data(arr2)
	rec4.save_to_wav("/home/*USER*/Downloads/Godot4/rec4")
@PeterMarques PeterMarques changed the title AudioStreamWAV locked to 8bit despite having 16 option AudioStreamWAV locked to 8bit despite having a 16bit option Oct 25, 2023
@akien-mga
Copy link
Member

akien-mga commented Oct 25, 2023

PackedByteArray is an array of bytes (8-bit). I assume for things to work with the 16-bit mode, you would need to manually decompose your 16-bit numbers into two bytes (two PackedByteArray elements).

Edit: Checked the code and confirmed:

	int byte_pr_sample = 0;
	switch (format) {
		case AudioStreamWAV::FORMAT_8_BITS:
			byte_pr_sample = 1;
			break;
		case AudioStreamWAV::FORMAT_16_BITS:
			byte_pr_sample = 2;
			break;
		case AudioStreamWAV::FORMAT_IMA_ADPCM:
			byte_pr_sample = 4;
			break;
	}

If so this would need better documentation.

@akien-mga
Copy link
Member

akien-mga commented Oct 25, 2023

I'm not expert on audio or byte array stuff, but I believe you'd have to do something like this for your 16-bit samples:

var size = 44100 * 10
arr2.resize(size * 2)
for each in size:
	var sample = randi_range(-pow(2, 14), pow(2, 14))
	arr2.encode_s16(each * 2, sample)

Edit: It may need to be encode_u16, not sure what's the deal with signedness here. It seems to read the 8-bit stuff as signed and make it unsigned, but read the 16-bit stuff as unsigned directly?

	// Add data
	Vector<uint8_t> stream_data = get_data();
	const uint8_t *read_data = stream_data.ptr();
	switch (format) {
		case AudioStreamWAV::FORMAT_8_BITS:
			for (unsigned int i = 0; i < data_bytes; i++) {
				uint8_t data_point = (read_data[i] + 128);
				file->store_8(data_point);
			}
			break;
		case AudioStreamWAV::FORMAT_16_BITS:
			for (unsigned 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 AudioStreamWAV::FORMAT_IMA_ADPCM:
			//Unimplemented
			break;
	}

@PeterMarques
Copy link
Author

PeterMarques commented Oct 25, 2023

I'm not expert on audio or byte array stuff, but I believe you'd have to do something like this for your 16-bit samples:

var size = 44100 * 10
arr2.resize(size * 2)
for each in size:
	var sample = randi_range(-pow(2, 14), pow(2, 14))
	arr2.encode_s16(each * 2, sample)

Edit: It may need to be encode_u16

It is indeed the solution to push the 16 bits long frames via encode_u16 as your script shows.

The code


extends Node
func _ready():
	var size = 44100 *10
	var rec1 = AudioStreamWAV.new()
	var rec2 = AudioStreamWAV.new()
	var rec3 = AudioStreamWAV.new()
	var rec4 = AudioStreamWAV.new() 
	
	var arr1 = PackedByteArray([])
	var arr2 = PackedByteArray([])
	
	rec1.format = AudioStreamWAV.FORMAT_8_BITS
	rec2.format = AudioStreamWAV.FORMAT_16_BITS
	rec3.format = AudioStreamWAV.FORMAT_8_BITS
	rec4.format = AudioStreamWAV.FORMAT_16_BITS
	
	for each in size:
		arr1.append(randi_range(pow(2,6)*-1,pow(2,6)))
	
	arr2.resize(size * 2)
	for each in size:
		arr2.encode_u16(each*2,randi_range(pow(2,14)*-1,pow(2,14)))
		
	rec1.set_data(arr1)
	rec1.save_to_wav("/home/peterpm/Downloads/Godot4/rec1")
	
	rec2.set_data(arr1)
	rec2.save_to_wav("/home/peterpm/Downloads/Godot4/rec2")
	
	rec3.set_data(arr2)
	rec3.save_to_wav("/home/peterpm/Downloads/Godot4/rec3")
	
	rec4.set_data(arr2)
	rec4.save_to_wav("/home/peterpm/Downloads/Godot4/rec4")

now produces the files:
image

And the rec4 file is the 16bit file with the 16 bit sound, as it need to be.

Should i close the issue or it need to stay opens to fix the docs?

@PeterMarques
Copy link
Author

PeterMarques commented Oct 25, 2023

And if working with stereo audio, the channel works in a pair.
So, you need to create the 2 buffers and intercalate then so it works correctly.

extends Node
func _ready():
	var size = 44100 *10
	var rec4 = AudioStreamWAV.new() 
	
	var arr2 = PackedByteArray([])
	var arr3 = PackedByteArray([])
	var arr4 = PackedByteArray([])
	
	rec4.format = AudioStreamWAV.FORMAT_16_BITS
	
	rec4.stereo = true
	
	arr2.resize(size * 2)
	arr3.resize(size * 2)
	for each in size:
		arr2.encode_u16(each*2,randi_range(pow(2,14)*-1,pow(2,14)))
	for each in size:
		arr3.encode_u16(each*2,randi_range(pow(2,12)*-1,pow(2,12)))
	
	for each in size:
		arr4.append(arr2[each*2])
		arr4.append(arr2[(each*2)+1])
		
		arr4.append(arr3[each*2])
		arr4.append(arr3[(each*2)+1])
	
	rec4.set_data(arr4)
	rec4.save_to_wav("/home/peterpm/Downloads/Godot4/rec4")

generates the expected:
image

So it need to have that in the docs as well.

All that is counterproductive, but its the nature of the object, but lacks the ease of usage of Vector2 in the AudioStreamGenerator in the pushframe(), that handles both the bitdepth and the stereo issue in a single pass

well, just need better documentation in the AudioStreamWAV object.

And, there is another more performant way to mescle the arrays?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants