This is awkward but I don’t really know how to play sounds and haven’t found satisfactory documentation on these forums. I could figure it out, but being lazy and having no comfortable HW testing set up I figured it could also be good to have this publicly answered in a stack overflow style, also for others.
What I need is a typical scenario: continuously play 8bit sound from a circular buffer at specified rate (8 kHz). Could some knowledgeable person here please post a super simple snippet that does this, along with what I should put in my_settings.h etc.? Thank you very much
EDIT:
Here’s the solution (based on @spinal’s code), thanks to everyone:
From the work I did on my tracker music player and more recently with FM radio hat, I can say that sound on pokitto is both simple and tricky to figure out for a non-audio person like myself to grasp.
I’m not at my computer right now, but if there’s an example of my music player on the forum somewhere it might help with mixing samples together as well as playing different sample rates at the same time. There is a little code in pokitto lib for using my player in hwsound.c or somewhere like that that I used to mix multiple samples together, that might help.
Maybe, it seems a bit rough around the edges, does it actually work? I’d like to edit it a little bit and then put it into my post as an answer. Unfortunately I don’t have Pokitto with me to test. I have a few questions and notes:
What are buffers, currentBuffer, currentPtr, BUFFER_SIZE etc.? I don’t see these declared anywhere. Are they internal Pokttolib things?
What’s endPtr for? You’re not using it anywhere, are you sure it needs to be there?
Why do you need multiple buffers? Are you switching buffers as you do with graphics? With sound I’ve mostly seen using one circular buffer.
filled_buffer = (filled_buffer < NUM_BUFFERS-1) ? filled_buffer+1 : 0; can simply be written as filled_buffer = (filled_buffer + 1) % NUM_BUFFERS;.
What is currentBuffer for? It’s always 0, so if(currentBuffer == filled_buffer) can be rewritten as if(filled_buffer == 0) which then actually means only buffer 0 is ever used.
Cool, I hadn’t thought about replacing sound entirely while keeping everything else.
Don’t you need to define your own dac_write() and ‘manually’ enable DAC if sound is disabled from my_settings.h? At least I get errors about it when compiling.
You can simplify timer initialization a little if you use extern "C" void TIMER32_0_IRQHandler() for timer IRQs, because then you don’t need to change NVIC.
main.cpp:
#include <Pokitto.h>
using Pokitto::Core;
using Pokitto::Display;
// enableDAC() from Pokitto MiniLib
inline void enableDAC() {
volatile unsigned int* PIO1 = (unsigned int*)(0x40044060);
volatile unsigned int* PIO2 = (unsigned int*)(0x400440f0);
volatile unsigned int* DIR1 = (unsigned int*)(0xa0002004);
volatile unsigned int* DIR2 = (unsigned int*)(0xa0002008);
PIO1[28] = PIO1[29] = PIO1[30] = PIO1[31] = 1 << 7;
PIO2[20] = PIO2[21] = PIO2[22] = PIO2[23] = 1 << 7;
*DIR1 |= (1 << 28) | (1 << 29) | (1 << 30) | (1 << 31);
*DIR2 |= (1 << 20) | (1 << 21) | (1 << 22) | (1 << 23);
}
// writeDAC() from Pokitto MiniLib
inline void writeDAC(unsigned char out) {
volatile unsigned char* P1 = (unsigned char*)(0xa0000020);
volatile unsigned char* P2 = (unsigned char*)(0xa0000040);
P1[28] = out & 1; out >>= 1;
P1[29] = out & 1; out >>= 1;
P1[30] = out & 1; out >>= 1;
P1[31] = out & 1; out >>= 1;
P2[20] = out & 1; out >>= 1;
P2[21] = out & 1; out >>= 1;
P2[22] = out & 1; out >>= 1;
P2[23] = out;
}
inline void enableTimer(unsigned int number, unsigned int matchnum, unsigned int frequency) {
volatile unsigned int* SYSCON = (unsigned int*)(0x40048000);
SYSCON[32] |= (number == 0) ? (1 << 9) : (1 << 10); // Enable clock for the timer
unsigned int msel = (SYSCON[2] & 0x1f) + 1; // Get feedback divider value
unsigned int timerFreq = 12000000 * msel / SYSCON[30];
constexpr unsigned int TIMER_0 = 0x40014000, TIMER_1 = 0x40018000;
volatile unsigned int* TIMER = (unsigned int*)((number == 0) ? TIMER_0 : TIMER_1);
TIMER[6 + matchnum] = timerFreq / frequency; // Set match value (timer's duration)
TIMER[5] |= 1 << (3 * matchnum); // Generated interrupt when timer equals match value
TIMER[5] |= 1 << (3 * matchnum + 1); // Reset timer on match
TIMER[1] |= 1 << 0; // Enable the timer counter
// Enable timer interrupt
constexpr unsigned char TIMER_0_IRQ = 18, TIMER_1_IRQ = 19;
volatile unsigned int* NVICTL = (unsigned int*)(0xe000e100);
NVICTL[0] = (number == 0) ? (1 << TIMER_0_IRQ) : (1 << TIMER_1_IRQ);
}
// Timer match number select
constexpr unsigned int MATCHNUM = 1;
unsigned int t = 0;
extern "C" void TIMER32_0_IRQHandler() {
volatile unsigned int* TIMER = (unsigned int*)(0x40014000);
if((TIMER[0] & (1 << MATCHNUM)) != 0) {
TIMER[0] = 1 << MATCHNUM;
// Audio processing code
writeDAC((t>>5)|(t>>4)|t);
++t;
}
}
int main() {
Core::begin();
enableDAC();
constexpr unsigned int frequency = 8000;
enableTimer(0, MATCHNUM, frequency);
while(Core::isRunning()) {
if(Core::update()) {
Display::print(1, 1, "Playing...");
}
}
return 0;
}
These are all pretty hacky ways of playing sounds though, shame there isn’t a nice API for that, especially when sound is pretty important in games. It could be as simple as a single function playSound(char *sound, int length). I still don’t get why three buffers are used and why sound code is mixed with file system code.
Anyway, as soon as I get home I’ll test your solutions and will try to reduce them into a bare minimum snippet which I will post.
Amazing, that could conclude the thread, however by looking at the source the function doesn’t mix multiple SFXs, which is what I’d like it to, so I’ll be looking for other solutions.
For the context, here is my playSound function in SDL, which I’d like to mimic in Pokitto. It works with a single circular buffer that is being played constantly over and over and the playSound function simply adds the SFX to the values in the buffer. The only limitation is that the SFX can’t be longer than the buffer size.
Well, if the circular buffer thing is not important, but you just want to play samples and three simultaneous sounds is enough, then there are the OSC functions.
To play sample on osc1:
Pokitto::Sound::loadSampleToOsc(1, (uint8_t*)beat8k, sizeof(beat8k));
constexpr byte ON = 1;
constexpr byte WAVETYPE_SAMPLE = MAX_WAVETYPES;
constexpr byte LOOP = 0; // 1 to loop the sample
constexpr uint8_t NOTENUM = 37; // 37 sets playing speed to 1:1 (I think)
constexpr uint16_t VOLUME = 254;
// Start playing osc1
setOSC(&osc1, ON, WAVETYPE_SAMPLE, LOOP, 0, 0, NOTENUM, VOLUME, 0, 0, 0, 0, 0, 0, 0, 0, 0);