How to play sounds?

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 :slight_smile:

EDIT:

Here’s the solution (based on @spinal’s code), thanks to everyone:

// CC0

#include "Pokitto.h"
#include "clock_11u6x.h"
#include "timer_11u6x.h"

uint8_t sample = 0;
uint32_t samples = 0;

void onTimer()
{
  if (Chip_TIMER_MatchPending(LPC_TIMER32_0, 1))
  {
    Chip_TIMER_ClearMatch(LPC_TIMER32_0, 1);

    sample += samples / 16384 + 1;
    samples++;

    Pokitto::dac_write(sample);
  }
}

void timerInit(uint32_t samplingRate)
{
  Chip_TIMER_Init(LPC_TIMER32_0);
  Chip_TIMER_Reset(LPC_TIMER32_0);
  Chip_TIMER_MatchEnableInt(LPC_TIMER32_0, 1);
  Chip_TIMER_SetMatch(LPC_TIMER32_0, 1,
    (Chip_Clock_GetSystemClockRate() / samplingRate));
  Chip_TIMER_ResetOnMatchEnable(LPC_TIMER32_0, 1);
  Chip_TIMER_Enable(LPC_TIMER32_0);

  #define weirdNumber ((IRQn_Type) 18)
  NVIC_ClearPendingIRQ(weirdNumber);
  NVIC_SetVector(weirdNumber, (uint32_t) &onTimer);
  NVIC_EnableIRQ(weirdNumber);
  #undef weirdNumber
}

int main ()
{
  Pokitto::Core::begin();
  timerInit(8000);
  while(Pokitto::Core::isRunning());
}

My_settings.h:

#define PROJ_SCREENMODE 13
#define PROJ_HIRES 0
#define PROJ_ENABLE_SOUND 1
#define PROJ_STREAMING_MUSIC 0
#define PROJ_STREAM_TO_DAC 1
#define PROJ_AUD_FREQ 8000
1 Like

Probably not an answer, but here’s a related thread:

Also, a similar thread:

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.

Does this answer your question?

main.cpp:

#include <Pokitto.h>

using Core = Pokitto::Core;
using Display = Pokitto::Display;
using Sound = Pokitto::Sound;

unsigned int t = 0;

void fillBuffer(unsigned int buf_index) {
    unsigned char* buf = buffers[buf_index];
    
    // Fill buffer with some bytebeat
    for(unsigned int idx = 0; idx < BUFFER_SIZE; ++idx) {
        buf[idx] = (t>>5)|(t>>4)|t;
        ++t;
    }
}

int main () {
    Core::begin();
    
    // Set global audio variables
    currentBuffer = 0;
    currentPtr = buffers[currentBuffer];
    endPtr = currentPtr + BUFFER_SIZE;
    
    // Fill first audio buffer
    uint32_t filled_buffer = 0;
    fillBuffer(filled_buffer);
    
    // Init audio stream
    Sound::ampEnable(true);
    Sound::playMusicStream();
    
    constexpr unsigned int NUM_BUFFERS = 4;
    
    while(Core::isRunning()) {
        if(Core::update()) {
            Display::print(1, 1, "Playing...");
            
            if(currentBuffer == filled_buffer) {
                // Fill next buffer
                filled_buffer = (filled_buffer < NUM_BUFFERS-1) ? filled_buffer+1 : 0;
                fillBuffer(filled_buffer);
            }
        }
    }
    return 0;
}

My_settings.h:

#ifndef MY_SETTINGS_H
#define MY_SETTINGS_H

#define PROJ_HIRES 0

#define PROJ_ENABLE_SOUND 1
#define PROJ_STREAMING_MUSIC 1
#define PROJ_STREAM_TO_DAC 1
#define PROJ_AUD_FREQ 8000

#endif
2 Likes

Thank you!

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? :slight_smile:
  • 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.

Yes, I’ve tested it on hw.

Correct, those are all variables in Pokittolib and used by audio routines.

I don’t, but Pokittolib reserves 4 buffers when you define PROJ_STREAMING_MUSIC.

True. I just prefer not to use division or modulo.

How is this?
Settings.h should have sound disabled for this to work well as it uses the same timer setup (stolen from pokittolib).

playsound.txt (171.8 KB)

Had to upload it up as source was too big for forum (there’s a sample in there)

1 Like

Why does the library do that? I can’t see a reason.

I still think the program doesn’t work the way you intended because of the if condition in the main loop that is only ever true once.

That looks pretty nice and simple actually, it may be what I am looking for but I’ll have to study it a bit.

It was optimized for PetitFS. The buffers were the same size as the SD reading buffers.

I’ve used it before. The currentBuffer in the if condition is one of the Pokittolib internal variables and it’s updated in pokSoundIRQ().

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;
}

My_settings.h

#ifndef MY_SETTINGS_H
#define MY_SETTINGS_H

#define PROJ_HIRES 0

#define PROJ_ENABLE_SOUND 0     // 0 = all sound functions disabled

#endif

I don’t know. It seems to work fine for me. I’ll double check my settings when I get a chance.

[edit]
My_settings.h in my project is all commented out.

I do get some redefinition errors, but nothing that prevents it compiling.

Sound is enabled by default, so just commenting it out doesn’t disable sound. There is this in Pokittolib’s Pokitto_settings.h:

#ifndef PROJ_ENABLE_SOUND
    #define POK_ENABLE_SOUND 1

You need to #define PROJ_ENABLE_SOUND 0 to actually disable it.

I stand corrected. I which case, yes, you need the sound enabled:-)

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.

3 Likes

If that’s what you want, it exists:
PokittoSound::playSFX( const uint8_t *sfx, uint32_t length )

3 Likes

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);
1 Like

Sorry if this seems impertinent, but why are there bytes there?
Are those supposed to be std::byte or Arduino’s weird byte alias for uint8_t?

That I don’t know. From Synth/Synth_oscfuncs.cpp:
void setOSC(OSC* o,byte on=1, byte wave=1, byte loop=0, byte echo=0, byte adsr=0, uint8_t notenumber=25, uint16_t volume=127, uint16_t attack=0, uint16_t decay=0, uint16_t sustain=0, uint16_t release=0, int16_t maxbend=0, int16_t bendrate=0, uint8_t arpmode = 0, uint8_t overdrive=0, uint8_t kick=0)

1 Like