Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playing a sound from a generated buffer in a Windows 8 app

I'm porting some C# Windows Phone 7 apps over to Windows 8.

The phone apps used an XNA SoundEffect to play arbitrary sounds from a buffer. In the simplest cases I'd just create a sine wave of the required duration and frequency. Both the duration and frequency can vary greatly, so I'd prefer not to rely on MediaElements (unless there is someway to shift the frequency of a base file, but that will only help me with the single frequency generation).

What is the equivalent of an XNA SoundEffectInstance in WinRT?

I assume I'll need to use DirectX for this, but I'm not sure how to go about this from an otherwise C#/XAML app. I've had a look at SharpDX, but it didn't seem to have the DirectSound, SecondaryBuffer, SecondaryBuffer classes that I assume I'd need to use.

I've made a number of assumptions above. It may be I'm looking for the wrong classes or there is an entirely separate way to generate arbitrary sound from a Windows 8 app.


I found an example using XAudio2 from SharpDX to play a wav file via an AudioBuffer. This seems promising, I'd just need to substitute my generated audio buffer for the native file stream.

PM> Install-Package SharpDX

PM> Install-Package SharpDX.XAudio2

    public void PlaySound()
    {
        XAudio2 xaudio;
        MasteringVoice masteringVoice;

        xaudio = new XAudio2();
        masteringVoice = new MasteringVoice(xaudio);

        var nativefilestream = new NativeFileStream(
            @"Assets\SpeechOn.wav",
            NativeFileMode.Open,
            NativeFileAccess.Read,
            NativeFileShare.Read);

        var soundstream = new SoundStream(nativefilestream);


        var waveFormat = soundstream.Format;
        var buffer = new AudioBuffer
        {
            Stream = soundstream.ToDataStream(),
            AudioBytes = (int)soundstream.Length,
            Flags = BufferFlags.EndOfStream
        };

        var sourceVoice = new SourceVoice(xaudio, waveFormat, true);

        // There is also support for shifting the frequency.
        sourceVoice.SetFrequencyRatio(0.5f);

        sourceVoice.SubmitSourceBuffer(buffer, soundstream.DecodedPacketsInfo);

        sourceVoice.Start();
    }
like image 937
Daniel Ballinger Avatar asked Sep 22 '12 20:09

Daniel Ballinger


2 Answers

The only way to generate dynamic sound in Win8RT is to use XAudio2, so you should be able to do this with SharpDX.XAudio2.

Instead of using NativeFileStream, just instantiate a DataStream directly giving your managed buffer (or you can use an unmanaged buffer or let DataStream instantiate one for you). The code would be like this:

// Initialization phase, keep this buffer during the life of your application
// Allocate 10s at 44.1Khz of stereo 16bit signals
var myBufferOfSamples = new short[44100 * 10 * 2];

// Create a DataStream with pinned managed buffer
var dataStream = DataStream.Create(myBufferOfSamples, true, true);

var buffer = new AudioBuffer
        {
            Stream = dataStream,
            AudioBytes = (int)dataStream.Length,
            Flags = BufferFlags.EndOfStream
        };

//...
// Fill myBufferOfSamples
//...

// PCM 44.1Khz stereo 16 bit format
var waveFormat = new WaveFormat();

XAudio2 xaudio = new XAudio2();
MasteringVoice masteringVoice = new MasteringVoice(xaudio);
var sourceVoice = new SourceVoice(xaudio, waveFormat, true);

// Submit the buffer
sourceVoice.SubmitSourceBuffer(buffer, null);

// Start playing
sourceVoice.Start();

Sample method to fill the buffer with a Sine wave:

    private void FillBuffer(short[] buffer, int sampleRate, double frequency)
    {
        double totalTime = 0;

        for (int i = 0; i < buffer.Length - 1; i += 2)
        {
            double time = (double)totalTime / (double)sampleRate;
            short currentSample = (short)(Math.Sin(2 * Math.PI * frequency * time) * (double)short.MaxValue);

            buffer[i] = currentSample; //(short)(currentSample & 0xFF);
            buffer[i + 1] = currentSample; //(short)(currentSample >> 8);

            totalTime += 2;
        }

    }
like image 75
xoofx Avatar answered Nov 10 '22 03:11

xoofx


You can also use WASAPI to play dynamically-generated sound buffers in WinRT. (xaudio2 isn't the only solution).

I wrote sample code for it in VB here (the C# will be essentially the same): http://www.codeproject.com/Articles/460145/Recording-and-playing-PCM-audio-on-Windows-8-VB

I believe that the NAudio guy is planning to translate+incorporate my sample code into NAudio, for a Win8-supported version, so that'll be easier to use.

like image 2
Lucian Wischik Avatar answered Nov 10 '22 05:11

Lucian Wischik