How to use the BASS audio library in UWP development

The BASS audio library is a solid library to decode and play many audio formats, supports gapless playback (using the mixer, not used in this example) and equalizer (not used in this example).

The library is available also for Windows Store development.

To reference the BASS audio library in your UWP project remember to add conditional references based on platform in the csproj file

<ItemGroup Condition="'$(Platform)' == 'x86'">
  <Content Include="$(SolutionDir)bass\Windows 10\x86\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64'">
  <Content Include="$(SolutionDir)bass\Windows 10\x64\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'ARM'">
  <Content Include="$(SolutionDir)bass\Windows 10\arm\bass.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Let’s see how we can use the library in a background audio player.

First we need the background task. Here we initialize the BASS audio library, and free it when the task ends. Since we are going to use BASS only to decode the audio we can use the “no sound” device.

using System;
using Un4seen.Bass;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.Media.Playback;

namespace BackgroundPlayer
{
  public sealed class Player : IBackgroundTask
  {
    BackgroundTaskDeferral m_Deferral;
    BassMediaSource m_MediaSourceAdapter = null;

    public void Run(IBackgroundTaskInstance taskInstance)
    {
      m_Deferral = taskInstance.GetDeferral();
      taskInstance.Canceled += TaskInstance_Canceled;
      taskInstance.Task.Completed += TaskInstance_Completed;

      BassNet.Registration("Your email", "Your registration key");
      if (!Bass.BASS_Init(0, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero)) {
        BASSError err = Bass.BASS_ErrorGetCode();
        m_Deferral.Complete();
        return;
      }

      BackgroundMediaPlayer.MessageReceivedFromForeground += OnMessageReceived;
    }

    private void TaskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
      Bass.BASS_Free();
      m_Deferral.Complete();
    }

    void TaskInstance_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
    {
      Bass.BASS_Free();
      m_Deferral.Complete();
    }
  }
}

Now we need to play the file when the foreground app asks for it. Communication between the foreground app and the background task is made through messages.

When the background task receives a “file” request message it opens the file using BASS (we’ll see later how) and sets the media source of the bacgkround media player.

private async void OnMessageReceived(object sender, MediaPlayerDataReceivedEventArgs args)
{
  ValueSet msg = args.Data;

  if (msg.ContainsKey("file")) {
    string audioFile = msg["file"] as string;
    if (!string.IsNullOrEmpty(audioFile)) {
      m_MediaSourceAdapter = await BassMediaSource.CreateAsync(audioFile);
      BackgroundMediaPlayer.Current.Source = MediaSource.CreateFromIMediaSource(m_MediaSourceAdapter.GetMediaSource());
      BackgroundMediaPlayer.Current.Play();
    }
  }
}

The BassMediaSource class implements IMediaSource and is the one responsible of opening and playing the file.

The code is quite simple: we need to create the decode stream for the BASS audio library and listen to the MediaSource events (Closed, Starting, SampleRequested).

public async Task InitializeAsync()
{
  StorageFile sFile = await StorageFile.GetFileFromPathAsync(m_FilePath);
  BasicProperties prop = await sFile.GetBasicPropertiesAsync();
  m_FileSize = prop.Size;

  m_BassHandle = Bass.BASS_StreamCreateFile(m_FilePath, 0, (long)m_FileSize, BASSFlag.BASS_STREAM_DECODE);
  if (m_BassHandle == 0) {
    BASSError err = Bass.BASS_ErrorGetCode();
    System.Diagnostics.Debug.WriteLine("InitializeAsync error {0}", err);
  }

  BASS_CHANNELINFO cInfo = Bass.BASS_ChannelGetInfo(m_BassHandle);
  if (cInfo == null) {
    BASSError err = Bass.BASS_ErrorGetCode();
    System.Diagnostics.Debug.WriteLine("InitializeAsync error {0}", err);
  }
  long len = Bass.BASS_ChannelGetLength(m_BassHandle, BASSMode.BASS_POS_BYTES);
  double secs = Bass.BASS_ChannelBytes2Seconds(m_BassHandle, len);
  uint bits = 16;
  if (cInfo.Is32bit)
    bits = 32;
  else if (cInfo.Is8bit)
    bits = 8;

  AudioEncodingProperties pcmprops = AudioEncodingProperties.CreatePcm((uint)cInfo.freq, (uint)cInfo.chans, bits);
  m_MediaStreamSource = new MediaStreamSource(new AudioStreamDescriptor(pcmprops));
  m_MediaStreamSource.CanSeek = true;
  m_MediaStreamSource.BufferTime = TimeSpan.Zero;
  m_MediaStreamSource.Duration = TimeSpan.FromSeconds(secs);
  m_MediaStreamSource.Closed += mss_Closed;
  m_MediaStreamSource.Starting += mss_Starting;
  m_MediaStreamSource.SampleRequested += mss_SampleRequested;
} // InitializeAsync

The SampleRequested event occurs when then audio player needs a decoded sample to play. Here we just need to decode the requested data and return it to the player.

void mss_SampleRequested(MediaStreamSource sender, MediaStreamSourceSampleRequestedEventArgs args)
{
  var deferral = args.Request.GetDeferral();
  System.Diagnostics.Debug.WriteLine("mss_SampleRequested");

  try {
    byte[] buffer = new byte[4096];
    int decoded = Bass.BASS_ChannelGetData(m_BassHandle, buffer, buffer.Length);
    if (decoded == -1) {
      BASSError err = Bass.BASS_ErrorGetCode();
      System.Diagnostics.Debug.WriteLine("mss_SampleRequested error {0}", err);
    } else {
      double secs = Bass.BASS_ChannelBytes2Seconds(m_BassHandle, decoded);

      MediaStreamSample sample = MediaStreamSample.CreateFromBuffer(buffer.AsBuffer(), TimeSpan.FromSeconds(m_SecondsPosition));
      sample.Duration = TimeSpan.FromSeconds(secs);
      m_SecondsPosition += sample.Duration.TotalSeconds;
      args.Request.Sample = sample;
    }
  } catch { }
  deferral.Complete();
} // mss_SampleRequested

The application lets you select a file from your music library (files outside of your music library won’t work because the application does not have access to external folders).

Here’s the full source code of the test program (remember to set your BASS registration info)

Download source code

Have fun! 😉

GPD Win: Fix game crash in full screen through HDMI

Life is strange crashed during startup on my GPD Win when using HDMI output (I think I read about people having the same problem with other games).

After messing with the display settings I found a working configuration that I think might fix the same issue with other games.

Open the Intel display settings window and

  • Set the mode to Extended desktop and select your external monitor as the primary display
    My GPD Win screen went to portrait mode (rotation 0) but you can restore it by setting rotation to 270.
  • Set the display resolution to 1280×720 (it was 1920×1080 by default)
  • Done, you can play Life is strange 😉

GPD Win Keepalive version 1.0.0 released

I recently received my GPD Win, but it has a problem with the SD card reader: after a short period of time the SD card goes to a “sleep” state and it takes a couple of seconds to wake up.

This really is a problem when playing games (mainly emulator’s ROMS) so, waiting for a real fix, I made a small utility that keeps alive the SD card by just writing a 0 byte file to the SD card every X seconds. By doing this the SD won’t go to sleep. 😉

scr01

The utility can be set to autostart with Windows and stays in the tray.

You can download it here.

You can download the source code here.

Have fun! 😉

Midi2MP3Gui 1.0.0 released

Do you need to convert a MIDI file to MP3?
Do you need to mute a channel or want to change channels volume or the track’s tempo?

 

Download Midi2MP3Gui, you’ll have your MP3 in seconds. 😉