veandco / go-sdl2-examples

This is where all go-sdl2 examples are stored
91 stars 34 forks source link

How userData should be used on the SDL_AUDIO subsystem #14

Closed LCRERGO closed 1 year ago

LCRERGO commented 1 year ago

I've been trying to adapt audio/audio.go to pass toneHz, sampleHz, dPhase into SineWave, but I have no clue how it should be done

veeableful commented 1 year ago

Hi @LCRERGO, thanks for submitting an issue! We haven't figured out a way to allow users to easily pass user data yet as it requires C code but this method could work by creating a C function that Go code can use.

audio.h

typedef struct UserData {
    int toneHz;
    int sampleHz;
    float dPhase;
} UserData;

UserData* CreateUserData(int toneHz, int sampleHz, float dPhase);
void DestroyUserData(UserData*);

audio.c

#include <audio.h>
#include <stdlib.h>

UserData* CreateUserData(int toneHz, int sampleHz, float dPhase)
{
    UserData* userdata = malloc(sizeof(UserData));
    userdata->toneHz = toneHz;
    userdata->sampleHz = sampleHz;
    userdata->dPhase = dPhase;
    return userdata;
}

void DestroyUserData(UserData* userdata)
{
    if (userdata)
        free(userdata);
}

audio.go

package main

/*
#include <audio.h>

typedef unsigned char Uint8;
void SineWave(void *userdata, Uint8 *stream, int len);
*/
import "C"
import (
    "log"
    "math"
    "reflect"
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

const (
    toneHz   = 440
    sampleHz = 48000
    dPhase   = 2 * math.Pi * toneHz / sampleHz
)

//export SineWave
func SineWave(userdata unsafe.Pointer, stream *C.Uint8, length C.int) {
    n := int(length)
    hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(stream)), Len: n, Cap: n}
    buf := *(*[]C.Uint8)(unsafe.Pointer(&hdr))

    myUserData := (*C.UserData)(userdata)
    log.Println("ToneHZ:", myUserData.toneHz)
    log.Println("SampleHZ:", myUserData.sampleHz)
    log.Println("DPhase:", myUserData.dPhase)

    var phase float64
    for i := 0; i < n; i += 2 {
        phase += dPhase
        sample := C.Uint8((math.Sin(phase) + 0.999999) * 128)
        buf[i] = sample
        buf[i+1] = sample
    }
}

func main() {
    if err := sdl.Init(sdl.INIT_AUDIO); err != nil {
        log.Println(err)
        return
    }
    defer sdl.Quit()

    userData := C.CreateUserData(toneHz, sampleHz, dPhase)
    defer C.DestroyUserData(userData)

    spec := &sdl.AudioSpec{
        Freq:     sampleHz,
        Format:   sdl.AUDIO_U8,
        Channels: 2,
        Samples:  sampleHz,
        Callback: sdl.AudioCallback(C.SineWave),
        UserData: unsafe.Pointer(userData),
    }
    if err := sdl.OpenAudio(spec, nil); err != nil {
        log.Println(err)
        return
    }
    sdl.PauseAudio(false)
    sdl.Delay(5000) // play audio for long enough to understand whether it works
    sdl.CloseAudio()
}
LCRERGO commented 1 year ago

Thanks it worked. I've adapted your code to work in a single gofile and I'll post here for completness

package audio

// #include <stdlib.h>
// #include <stdint.h>
// #include <math.h>
// typedef unsigned char Uint8;
// typedef uint32_t Uint32;
// void SineWave(void *userdata, Uint8 *stream, int len);
//
// typedef struct UserData {
//      int frequency;
//      int sampleRate;
//      float dPhase;
// } UserData;
//
// __attribute__((weak))
// UserData* NewUserData(Uint32 frequency, Uint32 sampleRate) {
//    UserData* userdata = malloc(sizeof(UserData));
//    userdata->frequency = frequency;
//    userdata->sampleRate = sampleRate;
//    userdata->dPhase = 2 * M_PI * frequency / sampleRate;
//
//    return userdata;
// }
//
// __attribute__((weak))
// void DestroyUserData(UserData* userdata)
// {
//    if (userdata)
//        free(userdata);
// }
import "C"
import (
    "math"
    "reflect"
    "unsafe"

    "github.com/veandco/go-sdl2/sdl"
)

type AudioSubsystem struct {
    SampleRate int
}

func NewAudioSubsystem(sampleRate int) *AudioSubsystem {
    if err := sdl.Init(sdl.INIT_AUDIO); err != nil {
        panic(err)
    }

    return &AudioSubsystem{
        SampleRate: sampleRate,
    }
}

func Beep(audio AudioSubsystem, frequency, duration int) {
    userData := C.NewUserData(C.Uint32(frequency), C.Uint32(audio.SampleRate))
    defer C.DestroyUserData(userData)

    spec := sdl.AudioSpec{
        Freq:     int32(audio.SampleRate),
        Format:   sdl.AUDIO_U8,
        Channels: 2,
        Samples:  512,
        Callback: sdl.AudioCallback(C.SineWave),
        UserData: unsafe.Pointer(userData),
    }

    if err := sdl.OpenAudio(&spec, nil); err != nil {
        panic(err)
    }

    sdl.PauseAudio(false)
    sdl.Delay(uint32(duration))
}

func Destroy(audio *AudioSubsystem) {
    sdl.CloseAudio()
}

//export SineWave
func SineWave(userdata unsafe.Pointer, stream *C.Uint8, len C.int) {
    n := int(len)
    hdr := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(stream)), Len: n, Cap: n}
    buf := *(*[]C.Uint8)(unsafe.Pointer(&hdr))

    data := (*C.UserData)(userdata)

    var phase float64
    for i := 0; i < n; i += 2 {
        phase += float64(data.dPhase)
        sample := C.Uint8((math.Sin(phase) + 0.999999) * 128)
        buf[i] = sample
        buf[i+1] = sample
    }
}