swiftaudiowavpcmaudiobuffer

How to play an array of [Int16] audio samples from memory in Swift


Trying to build a game music player (NSF, SPC etc) for Mac, using the GME library.

I have spent hours upon hours testing so many solutions and tips here on SO, but it seems no solution works well. I have tried many variants of the AVAudioEngine/AVAudioPlayerNode/scheduleBuffer route, but since none of them worked I simply switched to converting the samples to Wav data and playing from memory. That DOES work, however, converting from [Int16] to [UInt8] (in order to create data for a wave array) is very slow. At least for higher sample rates and songs longer than a few seconds. "Clean" code example below. Feedback and suggestions VERY welcome.

Tested:

  1. AVAudioPlayerNode example (cannot get to work, eg. input device not found, no sound etc etc)
  2. Buffer to wav data example (works, but sloooow)

override func viewDidLoad() {
    super.viewDidLoad()

    gme_type_list()

    var emu = gme_new_emu( gme_nsf_type, 48000 ) // 48kHz
    gme_open_file("path-to-file-on-disk.nsf", &emu, 48000) // 48kHz

    let sampleCount: Int32 = 150 * 48000 * 2 // 150 = Lenght in sec, 48kHz
    var output = Array<Int16>.init(repeating: 0, count: sampleCount)

    gme_start_track(emu, 0)
    gme_play(emu, sampleCount, &output) // Generates *sampleCount* samples in Int16 format

    let samples = output.withUnsafeBufferPointer { buffer -> Array<Int16> in
        var result = [Int16]()
        for i in stride(from: buffer.startIndex, to: buffer.endIndex, by: 2) {
            result.append(buffer[i])
        }
        return result
    }

    // Calls a slightly modified version of example 2 above 
    // (to support own samples, in Int16 rather than Float).
    // Works! But "fillWave" method is soooo slow!
    play(samples: samples)
}

Solution

  • Had a quick look at SDL library and its audio capabilities. Seems you can just feed whatever buffer type you want, and it just works:

    var desiredSpec = SDL_AudioSpec()
    desiredSpec.freq = 48000
    desiredSpec.format = SDL_AudioFormat(AUDIO_S16) // Specify Int16 as format
    desiredSpec.samples = 1024
    
    var obtainedSpec = SDL_AudioSpec()
    
    SDL_OpenAudio(&desiredSpec, &obtainedSpec)
    SDL_QueueAudio(1, samples, Uint32(sampleCount)) // Samples and count from original post
    SDL_PauseAudio(0) // Starts playing, virtually no lag!
    

    Would still appreciate any feedback on the original post/question, but in terms of a solution I think this is as good as (or better) than any.