Playing a sound from a generated buffer in a Windows 8 app
Asked Answered
R

2

6

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();
    }
Randyranee answered 22/9, 2012 at 20:7 Comment(1)
I am looking for a solution where I should be able to play audio directly from buffered audio packets in windows phone 8? Can you redirect to an working example.Epilepsy
S
10

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

    }
Saveloy answered 22/9, 2012 at 22:34 Comment(10)
Great, thanks. I've expanded your example out with the creation of the XAudio2 instance. I'm now generating a sound from the buffer. It seems a bit distorted at the moment. I'll need to double check my FillBuffer method, which I also added to your answer for others to use.Randyranee
My FillBuffer method used to populate a byte[] when I was working in XNA. Removing the bitwise & and the shift right >> operators fixed this issue. I now get a clear tone generated.Randyranee
This has a bug: totalTime += 2 means it's generating something twice the frequency. It should be totalTime++. (Tested it myself!)Kosse
@DanielBallinger, if you can, would you be able to share your example? I've been wanting to learn how to do what you're doing here. I'm new to WinRT and sound generation. I was wanting to try to code a simple piano app for my kids to play with.Detoxicate
@Jim, The answer covers most of what is required to generate a basic Sine Wave. What more did you want to cover?Randyranee
@DanielBallinger, I guess I was hoping for just a simple Hello World sound example. But perhaps I can take this and try to work it out the rest. I've just not worked with SharpDX or DirectX at all. I was hoping to have a XAML interface and just play different sounds as elements were pressed on the UI. I'm just looking for a starter example...thanks...Detoxicate
So I finally got around to this example but I am stuck on what values to pass to the FillBuffer method, specifically sampleRate and frequency. I just need a starting point where I can hear sound and from there I can play around with it.Detoxicate
Actually, I was able to get sound to play by using this example! code.google.com/p/sharpdx/source/browse/Samples/XAudio2/…Detoxicate
Will the same work for windows phone 8? Can anyone point to an example where we can play buffered audio packets on the fly?Epilepsy
Even when FillBuffer is called, no sound is produced.Vanhomrigh
B
2

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.

Bamboo answered 29/11, 2012 at 21:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.