Unity / Mono

Jun 27, 2012 at 8:45 PM

Hi

I'm very interested in exploring the possibility of getting a working tracker playing module working from within Unity. I'm an old games coder so I could look through the source, but I'd like to ask some advice about how one might best approach this, specifically with regards to the mono sound renderer I saw mentioned on the SharpMod wishlist. It would have to compile as C# Unity script, which uses a modified version of Mono based on the .NET 2.0 Framework.

Not only do I like tracker music and believe it would be great to hear more of it in games, I'm always looking for ways to make the smallest download size possible, and you can't get music much smaller than proper chiptunes. Not only that but a working tracker playing module with a good API could allow the implementation of some creative music-based game designs.

Cheers

Coordinator
Jul 2, 2012 at 4:35 PM

Hi

 

I've not the time to make a Mono version for the moment. So, if you would use the Core of sharpmod, you need to have a Sound buffer access that can read by thread the SharpMod Output. If you take a tour of the differents sound renderer you'll see that any methods used read the player GetBytes() method.  

So, if Unity have a buffered sound system that can accept a byte array, it's possible to make it working :)

I use some .NET 3.5 features in the core, so, there are easy to found and replace with old .Net 2.0 code.

 

In Conclusion, I'm agree with you for the tracker's modules, it's still a good format to provide high quality music with a minimum of file size. It's with this thing in mind that I was motived to make SharpMod :)

 

 

Nov 8, 2012 at 7:28 PM
Edited Nov 9, 2012 at 12:20 AM

I'm also interested in using SharpMod in a Unity project. Unity recently added support for playing MOD/XM files natively, but the project I'm working on requires interaction with (and run-time composition of) chiptunes - SharpMod seems like a perfect fit!

Unfortunately, Unity doesn't support audio engines like NAudio; instead, they expose a callback (OnAudioFilterRead) that gets called every time a chunk of audio is routed through the main filter. It gets called every ~20ms, depending on the samplerate and platform. The buffer passed in is an array of floats, consistently sized to 2048 elements; I'm also given the number of channels. Because it's expecting stereo audio data, I write 1024 samples, between [-1.0f, 1.0f], each time the callback fires.

Because the callback runs on a separate thread, I avoid retrieving bytes from the SharpMod player during the callback. Instead, I create a ring buffer to which I maintain a read/write pointer. I write to it during the script's normal execution and read from it in the OnAudioFilterRead callback.

What I have working at the moment sounds very, very close to what I'd expect to hear, except with a lot more noise and some unusual looking values when I graph out the waveform. I think I may be doing something incorrectly when converting the 16-bit PCM data to signed floats.

Here is my short-to-float conversion function

 

private float ShortToFloat(byte[] bytes, int index)
{
    // Convert unsigned short to float
    float f = (bytes[index]) | (bytes[index + 1] << 8);
        
    f -= 32768f;
    f /= 32768f;

    return f;
}

 

And here is how I read data from the SharpMod player and store it in my circular buffer:

 

// Declared earlier:
// byte[] tmpBuffer = new byte[4096];
// float[] latencyBuffer = new float[LATENCY_BUFFER_SIZE];

int bytesPerSample = Player.MixCfg.Is16Bits ? 2 : 1;
int channels = (Player.MixCfg.Style == SharpMod.Player.RenderingStyle.Mono) ? 1 : 2;
int read = tmpBuffer.Length;

read = Player.GetBytes(tmpBuffer, read);

for (int i = 0; i < read; i += bytesPerSample * channels)
{
    float lSample = ShortToFloat(tmpBuffer, i);
    float rSample = ShortToFloat(tmpBuffer, (i + 2));
                
    latencyBuffer[writePtr] = lSample;
    latencyBuffer[writePtr + 1] = rSample;

    writePtr = (writePtr + 2) % LATENCY_BUFFER_SIZE;
}

 

And here is what the contents of the buffer look like. The top and bottom grey lines mark the +1/-1 boundaries. The sound itself is recognizably the chiptune I'm testing, but everything is heavily amplified with periodic pops and clicks.

If I normalize the PCM data by scaling down by 65,535 instead of 32,768, I wind up with something that looks like this instead. It's much closer to what I'd expect the waveform to look like, but it's still quite wrong.

I feel like I'm overlooking something very obvious, but I'm not quite sure what. If you could provide any insight, it'd be greatly appreciated.

Thank you!

Nov 9, 2012 at 4:10 AM

For those interested, I managed to (partially) solve my problem.

My short to float conversion was all borked. I believe it was trying to cast the first byte as a float, and then bitwise-OR it with the next byte, rather than packing both of them into a short first. The following method is what I'm using now and the results are much, much better.

private float ShortToFloat(byte[] bytes, int index)
{
    // Convert unsigned short to float
    float f = (short)(bytes[index] & 0xff | bytes[index + 1] << 8);
        
    f -= 32768f;
    f /= 32768f;

    f *= 0.75f;

    return f;
}

I'm not quite sure why, but if I don't scale the result down by 75%, the resulting values clip outside of the [-1,1] range. I suspect there's still something wrong with the calculation, but this is loads better than what I tried earlier. I now have a working tracker running in Unity, so yay! 

Thanks so much for doing the heavylifting on this project. So far SharpMod seems really awesome and I'm excited to leverage it in this project of mine.

Coordinator
Nov 9, 2012 at 3:40 PM

Hi !

Good job to do this with Unity. 

I don't know the .NET framework Level implemention of unity, but if it supports Sequential Layouts on Structs you can improve the short to float conversion.

It's with a real pleasure that i'll see what you do with SharpMod if you want to send me a demo.