Sounds in Q-Bert remake

So as simply as making snd[t].volume range from 0 to 255 instead of 100 will be faster? sounds useful.

[edit]
I think once I have this game out of the way, I should separate the sound stuff out into a separate example, currently it is capable of playing a sample at any (?) speed or volume, possibly multiple sounds at once, it might be helpful for other people.

2 Likes

shift by 1 bit ( >>1) is same as /2
shift by 2 bits (>>2) is /4
shift by 3 (>>3) is /8 and so on

ARM Cortex M0+ (crowd cheering) has a so called ā€˜barrel shifterā€™ meaning any number of bits can be shifted in one instruction. Division by powers of two is very fast.

Atmel AVR (audience hissing) has no barrel shifter. Shifting by 8 bits takes 8 instructions :frowning:

5 Likes

No, shifting by 8 bits (for a 16 bit value) takes one move from one 8 bit register to another, and zeroing out the register that was moved.

yes, in the specific case of 8 bits that is so. but as a general rule, there is no n bit shift single cycle operand as on the ARM Cortex. That was what I intended to say.

and yes, i know and have used combinations of multiplications and bit shifts on atmegas to get around this missing barrel shifter issue.

1 Like

Shifting a 32 bit value (u32a >>= 8) on an AVR:

    1a60:	80 91 46 01 	lds	r24, 0x0146	; 0x800146 <u32a>
    1a64:	90 91 47 01 	lds	r25, 0x0147	; 0x800147 <u32a+0x1>
    1a68:	a0 91 48 01 	lds	r26, 0x0148	; 0x800148 <u32a+0x2>
    1a6c:	b0 91 49 01 	lds	r27, 0x0149	; 0x800149 <u32a+0x3>
    1a70:	69 2f       	mov	r22, r25
    1a72:	7a 2f       	mov	r23, r26
    1a74:	8b 2f       	mov	r24, r27
    1a76:	99 27       	eor	r25, r25
    1a78:	60 93 46 01 	sts	0x0146, r22	; 0x800146 <u32a>
    1a7c:	70 93 47 01 	sts	0x0147, r23	; 0x800147 <u32a+0x1>
    1a80:	80 93 48 01 	sts	0x0148, r24	; 0x800148 <u32a+0x2>
    1a84:	90 93 49 01 	sts	0x0149, r25	; 0x800149 <u32a+0x3>

Shifting a 16 bit value (u16a >>= 8):

    1aa0:	80 90 45 01 	lds	r8, 0x0145	; 0x800145 <u16a+0x1>
    1aa4:	91 2c       	mov	r9, r1
    1aa6:	90 92 45 01 	sts	0x0145, r9	; 0x800145 <u16a+0x1>
    1aaa:	80 92 44 01 	sts	0x0144, r8	; 0x800144 <u16a>

GCC is being silly. The movs could have been elided.

2 Likes

You mean Z = (2 * (A + B)) - ((A * B) / 128) - 256?

If so thatā€™s an easy change:

std::uint8_t mixToth(std::uint8_t sample0, std::uint8_t sample1)
{
	constexpr std::uint8_t maximum = 0xFF;
	constexpr std::uint8_t midpoint = 0x7F;

	const unsigned int s0 = static_cast<unsigned int>(sample0);
	const unsigned int s1 = static_cast<unsigned int>(sample1);
	
	const unsigned int result = 2 * (s0 + s1) - ((s0 * s1) / midpoint) - maximum;
	
	return static_cast<std::uint8_t>((result < maximum) ? result : maximum);
}

Thatā€™s confusing.

This is why Iā€™m terrible with sound, the idea that negative values are audible always throws me.
Does that mean amplitude is actually the absolute distance from the midpoint (0 signed, 127 unsigned)?

Does that imply negative pressure is audible?
(Iā€™ve forgotten a large chunk of what I learnt for my GCSEs because I never had a use for it.)

@spinal In other words, fixed point arithmetic.

This is why so many programmers are obsessed with powers of two.
Working with powers of two is almost always faster because mathetmatical operations can be replaced with bitwise operations (sometimes called bithacks/bithax).

@spinal
To clarify why this is, pow(2, 1) == 2, pow(2, 2) == 4, pow(2, 3) == 8 (where pow is expotentiation, or ā€˜to the power ofā€™).

Or to put it another way, shifting right divides by 2, so shifting right a second time divides by 2 again, and dividing by 2 twice is the same as dividing by 4.
I.e. ((x >> 1) >> 1) == (x >> 2) == ((x / 2) / 2) == (x / 4)

As for why shifting works as division and multiplication by a power of two, you need to understand binary to see why.

This was always something that annoyed me with AVR chips.
Not having a barrel shifter is criminal.

Actually, the compiler doesnā€™t always perform that optimisation.
See here:

(I still havenā€™t got round to fixing the issue yet, itā€™s somewhere on my ever growing todo list.)

You need to come to terms with the idea that the absolute value is meaningless. Only the velocity matters. When you make your first transistor amp and you read -200V peak from your 3.3V circuit, then youā€™re getting the feeling you need your head checked. The world, alas, is not digital :wink:

Iā€™ve heard a legend it was an honest mistake and the oversight was noticed only after the chip had been taped out. Whether this story is true, i donā€™t know.

Iā€™m going to have to look at this, it currently sounds awful when playing multiple samples.

may i ask why dont you just play data bytes from each sample consequtively and let the inertia of the circuit deal with the mixing?

Yes but it seems more complicated than this:
Z=(A*B)/128 for A and B < 128
and
Z = (2 * (A+B))-((A*B)/128) - 256 otherwise

I did attempt it first but it sounded horrible. Maybe I did it wrong. I will give I another try I guess.

any bins i could play through the oscilloscope?

1 Like

In that case:

std::uint8_t mixToth(std::uint8_t sample0, std::uint8_t sample1)
{
	constexpr std::uint8_t maximum = 0xFF;
	constexpr std::uint8_t midpoint = 0x7F;

	const unsigned int s0 = static_cast<unsigned int>(sample0);
	const unsigned int s1 = static_cast<unsigned int>(sample1);
	
	const unsigned int averaged = ((s0 * s1) / midpoint);
	const unsigned int result = (s0 < midpoint && s1 < midpoint) ? averaged : ((s0 + s1) * 2) - averaged - maximum;
	
	return static_cast<std::uint8_t>((result < maximum) ? result : maximum);
}

Though that might be a bit of a problem in a loop because of the division.

Also, now I think about it, the compiler might be able to optimise better with result <= maximum.


That sounded like ā€œdivision must be a software operation if thereā€™s no FPUā€.

I presume you meant ā€œthereā€™s no integer division hardware and not floating point division hardware, therefore division is done by a software routineā€?

1 Like

yes. thats what i meant

1 Like

Just checking to be sure.

hmmmm, so if when adding 2 or more samples, if it clips at 255 it will result in silence instead of sound?

Audio is a waveform so it should not clip at 255 all the time.

If you have two equal waves, but opposite phase you get a silence :wink:

Sure, but when adding multiple waveforms together, when the data is already 8bit will easily (usually?) result in overflow/clipping.

clipping causes nasty distortion. if you add alot of stuff and get >255 values, uint8_t wraps around and you get all sorts of crap (sorry but that is the right word) coming out