@spinal The sound effects are very nice in your game. Are you using playMusicStream()? What is the sample rate?
Iāll just link this tweet here, so that other readers know what we are talking about.
nope, but iām assuming it works roughly the same way.
This could probably do with a lot of tidying up, but itās working OK for now.
Iām updating the audio buffer in quarter chunks simply because I cant detect the playing position fast enough to fill the buffer when it loops, not without messing with the playing loop in HWSound and I donāt know what else might be using that.
This way seems to work well enough, there is very little crackling.
settings.h
#define PROJ_SOUND_BUFFERED 1
#define PROJ_ENABLE_SOUND 1
#define PROJ_AUD_FREQ 11025
#include "HWSound.h"
bool playSample = 0;
int soundPoint;
const uint8_t *currentSound;
int currentSoundSize=0;
int oldQuart;
// Clear the buffer, might not be required as it should self-clear when updating
void emptyBuffer(){
for(int t=0; t<SBUFSIZE;){
soundbuf[++t]=0;
}
}
// start a sound playing
void playSound(const unsigned char *sound, uint16_t soundSize){
soundPoint=0;
currentSound = sound;
currentSoundSize = soundSize;
playSample = 1;
}
// update teh sound buffer, call as often as possible
void updateSample(){
int quart = soundbufindex / 512;
int sndOffset[]={512,1024,1536,0};
if(oldQuart != quart){
oldQuart = quart;
for(int t=0; t<SBUFSIZE/4;){
if(playSample==0){
soundbuf[t+sndOffset[quart]]=0;
}else{
soundbuf[t+sndOffset[quart]]=currentSound[soundPoint];
if(++soundPoint >= currentSoundSize){
playSample=0;
}
}
t++;
}
}
}
// in main function before loop
pokPlayStream(); // start pokitto sound function
emptyBuffer(); // clear the sound buffer in case of junk data
not sure if its necessary to update outside of the display function, but it cant hurt.
while (game.isRunning()) {
updateSample(); // call outside of display loop for more speed
if(game.update()){
// to play a sound
playSound(snd_death, sizeof(snd_death));
where snd_sample is a const char array of raw data. this can be saved as raw from a sound editor, or just remove the first 44 bytes from a .wav file, then simply copy the data using a hex editor or something.
const unsigned char snd_death[20964] = {
0x80, 0x80, 0x7F, 0x80, 0x80, 0x7F, 0x80, 0x80, 0x80, 0x7F, 0x80, 0x80,
0x81, 0x80, 0x7F, 0x80, 0x81, 0x81, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x81,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x82, 0x86, 0x89, 0x8D,
0x90, 0x94, 0x96, 0x9A, 0x80, 0x78, 0x7A, 0x78, 0x79, 0x78, 0x78, 0x7B,
... etc.
Thanks, looks a very simple and clean implementation.
I was contemplating trying to play a couple of sounds at once, not 100% sure the effort would be worth it, or even noticeable.
So in case you want to change the pitch of the sounds: the playback speed of the audio interrupt is not fixed. For the purpose of playing musical notes, several interrupt frequency tables are provided. But in your case you could play back at any frequency you wish. Just do the same calls as are done in the audio init routine and replace with another frequency. Then push out the sound
Yes, unless there will be a background music also.
@spinal Quick and dirty frequency change on the fly. Replace POK_AUD_FRQ with a variable
/* Initialize 32-bit timer 0 clock */
Chip_TIMER_Init(LPC_TIMER32_0);
/* Timer rate is system clock rate */
timerFreq = Chip_Clock_GetSystemClockRate();
/* Timer setup for match and interrupt at TICKRATE_HZ */
Chip_TIMER_Reset(LPC_TIMER32_0);
/* Enable both timers to generate interrupts when time matches */
Chip_TIMER_MatchEnableInt(LPC_TIMER32_0, 1);
/* Setup 32-bit timer's duration (32-bit match time) */
Chip_TIMER_SetMatch(LPC_TIMER32_0, 1, (timerFreq / POK_AUD_FREQ));
/* Setup both timers to restart when match occurs */
Chip_TIMER_ResetOnMatchEnable(LPC_TIMER32_0, 1);
/* Start both timers */
Chip_TIMER_Enable(LPC_TIMER32_0);
/* Clear both timers of any pending interrupts */
#define TIMER_32_0_IRQn 18
NVIC_ClearPendingIRQ((IRQn_Type)TIMER_32_0_IRQn);
/* Redirect IRQ vector - Jonne*/
NVIC_SetVector((IRQn_Type)TIMER_32_0_IRQn, (uint32_t)&TIMER32_0_IRQHandler);
/* Enable both timer interrupts */
NVIC_EnableIRQ((IRQn_Type)TIMER_32_0_IRQn);
EDIT: I will make an additional soundInit version that allows a free frequency setting in the next push to pokittolib
Iām going to have a try at mixing 2 samples at a time but Iām not at home. Would I be correct in thinking something like this
Sample = ( sample1 - 128) + ( sample2 - 128) /2 + 128;
For 8bit unsigned pcm?
- 128
is probably going to cause underflow, is that intended?
Dunno, depends what underflow is.
Assuming from ā8 bit unsigned pcmā that sample1
is std::uint8_t
then underflow is what happens when you do 64 - 128
and end up with 192
.
Basically if thereās only 8 bits you can only represent so many numbers, so when you try to subtract a large integer from a small integer you get āunderflowā.
OK, I follow.
so I would have to recast the variable to a signed format first, then perform add themā¦?
It seems that I was over thinking it. The sample data doesnāt seem to be -127 to 127, but rather 0 to 255.
int mix_sample(int a, int b) {
int sample = a+b;
if(sample>255)sample=255;
return sample;
}
That should probably be:
std::uint8_t mix(std::uint8_t sample0, std::uint8_t sample1)
{
constexpr std::uint8_t maximum = 0xFF;
const unsigned int result = ((static_cast<unsigned int>(sample0) + static_cast<unsigned int>(sample1)) / 2);
return static_cast<std::uint8_t>((result < maximum) ? result : maximum);
}
But I donāt know, I havenāt really looked into PCM.
All I know is that 128 is supposed to be the ācentrepointā (whatever that is).
Thatās the theory I was working with, however, I did get a little confused remembering that zeroing out the sound buffer resulted in silence. Together with saving the audio as 8bit unsigned pcm seemed to play correctly.
So at this point I am not 100% sure how to audio format works, it doesnāt seem to be -127 to 127 as I expected.
Iāve got a vague idea of how PCM works now, but more importantly I found this:
http://www.vttoth.com/CMS/index.php/technical-notes/68
Which suggests the following mathematical function is better for mixing:
And hereās a Pharap-approved C++ implementation of it:
std::uint8_t mixToth(std::uint8_t sample0, std::uint8_t sample1)
{
constexpr std::uint8_t maximum = 0xFF;
const unsigned int s0 = static_cast<unsigned int>(sample0);
const unsigned int s1 = static_cast<unsigned int>(sample1);
const unsigned int result = (s0 + s1) - ((s0 * s1) / maximum);
return static_cast<std::uint8_t>((result < maximum) ? result : maximum);
}
(I considered using std::max
but Iām not actually sure if references can bind to constexpr
expressions or integer literals, Iāll have to look it up.)
(This could be made constexpr
with some effort, but Iām not sure youād benefit from it since youāre unlikely to be mixing at compile time.)
0ā¦255 unsigned. 127 is āsilenceā and midpointā¦ but so is also any number x if the signal does not change. Sound, as you know is variations in air pressure
Since 127 is the midpoint I think you might need the second part of that link you provided ( the mixing function that consists of 2 parts)
I found that link yesterday and came up with the following alteration to allow up to 10 (although I doubt Iād use that many samples at once)
uint8_t mixSound()
{
int temp = 0;
int multi = 0;
int sampleCount=0;
for(int t=0; t<10; t++){
if(snd[t].playSample!=0){
temp += (snd[t].currentSound[snd[t].soundPoint]*snd[t].volume)/100;
multi *= (snd[t].currentSound[snd[t].soundPoint]*snd[t].volume)/100;
sampleCount++;
}
}
temp -= (multi/256);
return static_cast<uint8_t>((temp < 0xFF) ? temp : 0xFF);
}
2 things:
- thou shalt not divide in a tight routine
- thou especially shalt not use 10 factorials to do so
Thats tens of instructions because DIV is a software operation on a device without an FPU
To handle volume control fast: multiply by volume (0-255) (mult=1 instruction) then shift right by 8 bits (1 instruction) to get a ratio of 1/256