DSD over DoP for Multi-channel playback

Started by soundgals,

soundgals

Using BASS_DSD with the DoP flag: BASS_DSD_DOP, I am able to play DSD over PCM stereo through BASS. I can't get this to work for multi-channel files though. Multi-channel works fine for PCM or for DSD when I remove the BASS_DSD_DOP flag, so it defaults to PCM playback.

Can you confirm if this is a limitation of BASS when it comes to DSD multi-channel over DoP? If so, are there any plans to implement this?

If not, I may be doing something wrong on my side.

Thanks in advance for your help on this.

Geoff

Ian @ un4seen

I haven't tried it myself but I believe multichannel DoP should be supported. What problem are you having with it? If BASS_DSD_StreamCreateFile is failing, please check the error code with BASS_ErrorGetCode. If it just doesn't sound right (eg. silence) on your DSD device, check that your soundcard's output format matches the DoP stream's format (which you can get from BASS_ChannelGetInfo) so that there's no resampling required (which would corrupt the DSD data). If the problem persists, also confirm what platform you're running this on.

soundgals

Quote from: Ian @ un4seenI haven't tried it myself but I believe multichannel DoP should be supported. What problem are you having with it? If BASS_DSD_StreamCreateFile is failing, please check the error code with BASS_ErrorGetCode. If it just doesn't sound right (eg. silence) on your DSD device, check that your soundcard's output format matches the DoP stream's format (which you can get from BASS_ChannelGetInfo) so that there's no resampling required (which would corrupt the DSD data). If the problem persists, also confirm what platform you're running this on.

Thanks Ian. The platform is MacOS and I'm using the CBass wrapper for BASS. The BASS_DSD_StreamCreateFile is succeeding I believe. because I hear noise(static). I set the devices sample rate and channels to match the incoming data using core audio and if the sample rate of the stream doesn't match the BASS device I re-init BASS with the correct sample rate. Oddly, I don't hear noise on the front L + R channels; just on the other channels of the 5.1 set-up. The output device is set via audio-midi player to output to classic 5.1 speaker mapping.

Ian @ un4seen

If you call BASS_ChannelGetInfo on the DoP stream and BASS_GetInfo, do the BASS_CHANNELINFO freq/chans values match the BASS_INFO freq/speakers values?

Also, is the DSD file playing correctly with other software on the same system?

soundgals

#4
Quote from: Ian @ un4seenIf you call BASS_ChannelGetInfo on the DoP stream and BASS_GetInfo, do the BASS_CHANNELINFO freq/chans values match the BASS_INFO freq/speakers values?

Also, is the DSD file playing correctly with other software on the same system?
I think you may have hit on the problem here. The frequencies match; but the channels don't. The source has 6 channels; but the device has 8 channels. BASS_INFO shows 8 "speakers".

... but then I ask myself, why is this not a problem with PCM multi-channel playback with 6 channels?

To get multi-channel audio to play through this DAC it needs to be set as 8 channels in Audio-Midi set-up (I do this programatically through core audio).

Other software (e.g. HQPlayer) is able to play these 6 channels over DOP as DSD without a problem.


In Audio Midi set-up (as you probably already know) you set your actual number of speakers and the channel mapping within the "Configure speakers..." panel. The physical device that BASS "sees" will always have 8 channels though, for such a device. So they'd only match if the source file itself had 8 channels.

Ian @ un4seen

I think the soundcard output having more channels than the DoP stream should be fine. Does your DSD device indicate when it detects DoP data, and if so, does it do so in this case?

To confirm whether the BASS output is valid DoP data, you could try writing it to a WAV file, like this:

devstream = BASS_StreamCreate(0, 0, 0, STREAMPROC_DEVICE, 0); // get device stream
BASS_Encode_Start(devstream, "output.wav", BASS_ENCODE_PCM | BASS_ENCODE_FP_24BIT, 0, 0); // set 24-bit WAV writer on it
// play DoP stream here
BASS_Encode_Stop(devstream); // stop WAV writer

And then check for DSD markers in the WAV file's data, as described here:

    https://dsd-guide.com/dop-open-standard

It probably won't affect this but you could also try using the latest BASS build available here:

   www.un4seen.com/stuff/bass-osx.zip

soundgals

Thanks for the tips Ian.

The device should indicate 2.8xxx MHZ as it does for Stereo DOP; but when trying to send multi-channel via DoP it's showing 176.4 khz and I get the static noise.

I'll try writing to the WAV file as you suggested and updating to the latest BASS just in case.

soundgals

With a little help from AI, I create some Swift functions to check for DoP markers in a multi-channel DSD file using the DoP flag in BASS. The results were as follows...
--- DSD Playback Diagnostics ---
Channel status: 0
Channel position: 18446744073709551615 bytes
Data available in buffer: 4294967295 bytes
Could not read data from channel, error: 5
Testing for DoP markers on device 129...
Device stream created successfully
Capturing from device: 176400Hz, 8 channels
Analyzing audio output for DoP markers...
--- DoP Analysis Results ---
Total bytes captured: 0
Successful capture attempts: 0
Total DoP markers found: 0

... As I mentioned before the same source plays just fine as 176400 PCM with the DoP flag removed. DSD Stereo also plays just fine with the DoP flag.

So I've come to the conclusion that, at least in the CBass wrapper implementation of BASS I'm using, DoP isn't supported for multi-channel.

It's not a big deal for me; but I would like my app to be able to support this if possible. I'll try up-grading the CBass Swift package, just in case it makes a difference.

If you want me to share any of the diagnostic Swift code with you, of course I can do that.

Ian @ un4seen

If you upload the BASS output WAV file (from the code in my last post), I'll have a look at it. You can upload it here:

    ftp://ftp.un4seen.com/incoming/

A few seconds of playback will suffice (you don't need to play the entire DSD file).

soundgals

Thanks Ian.

I did attempt this; but I always got error 33 when trying to start the encoder.

Below is the Swift code I attempted to use to get this working. Perhaps you can spot where I might be going wrong.

stream = BASS_DSD_StreamCreateURL(theURL, 0, DWORD(BASS_DSD_DOP | BASS_SAMPLE_FLOAT | BASS_STREAM_STATUS), nil, nil, DWORD(BASS_CONFIG_DSD_FREQ))
            if stream == 0{
                let error = BASS_ErrorGetCode()
                print("Can't create stream", error)
            }
           
            var channelInfo = BASS_CHANNELINFO()
            BASS_ChannelGetInfo(stream, &channelInfo)
            // try to change the device's rate
            print("Channels: \(channelInfo.chans)")
            if (info.freq != channelInfo.freq){ // the rates don't match
                BASS_Init(device, channelInfo.freq, DWORD(BASS_DEVICE_FREQ | BASS_DEVICE_REINIT), nil, nil)
            }
            var devstream: HSTREAM = 0

            // Define the STREAMPROC function type to match what BASS expects
            typealias STREAMPROC = @convention(c) (HSTREAM, UnsafeMutableRawPointer?, DWORD, UnsafeMutableRawPointer?) -> DWORD

            // Create STREAMPROC_DEVICE by casting the special value -2
            let STREAMPROC_DEVICE = unsafeBitCast(-2, to: STREAMPROC?.self)

            // Create the device stream
            devstream = BASS_StreamCreate(0, 0, 0, STREAMPROC_DEVICE, nil)

            if devstream == 0{
                let error = BASS_ErrorGetCode()
                print("Can't create stream", error)
            }
            let musicUrl = FileManager.default.urls(for: .musicDirectory, in: .userDomainMask).first!
            // BASS requires the "file://" prefix to be removed from the url in string format
            let destinationUrl = musicUrl.appendingPathComponent("output.wav").absoluteString.replacingOccurrences(of: "file://", with: "")
            BASS_ChannelPlay(stream, 1)
            let encoder = BASS_Encode_Start(devstream, destinationUrl,  DWORD(BASS_ENCODE_PCM | BASS_ENCODE_FP_24BIT | BASS_ENCODE_AUTOFREE), nil, nil)
           
            if encoder == 0{
                let error = BASS_ErrorGetCode()
                print("Can't start the encoder", error)
            }
            BASS_Encode_Stop(devstream)

Ian @ un4seen

Error code 33 is BASS_ERROR_CREATE, which means that BASS_Encode_Start was unable to create the file, possibly because the app doesn't have permission to write in the requested path. You could try "/tmp/output.wav" instead.

Note the BASS_Encode_Stop call should come some time (eg. a few seconds) after you've started playing the DSD file.

soundgals

I've created an 8 second file. Tried to connect to your ftp as a guest; but it wouldn't allow me to upload the file there. It's 32.1mb.
Please advise how I can get it to you. Thanks.


Ian @ un4seen

That file does appear to contain DoP data in 5 channels (L+R+C+RL+RR) and 3 empty channels, so I guess the DSD file you were playing has 5 channels? One issue I notice is that the stream is being ramped-in, which won't work properly on DoP data and should be disabled. You can disable it via the BASS_ATTRIB_NORAMP option:

BASS_ChannelSetAttribute(stream, BASS_ATTRIB_NORAMP, 1); // disable ramping

If your DSD device still doesn't detect DoP data, please post the soundcard's settings in the Audio MIDI Setup, including the "Configure Speakers" settings. Make sure all channel volumes are at 1.0.

soundgals

Quote from: Ian @ un4seenThat file does appear to contain DoP data in 5 channels (L+R+C+RL+RR) and 3 empty channels, so I guess the DSD file you were playing has 5 channels? One issue I notice is that the stream is being ramped-in, which won't work properly on DoP data and should be disabled. You can disable it via the BASS_ATTRIB_NORAMP option:

Indeed it was a 5 channel file, in that case. I will try the BASS_ATTRIB_NORAMP option. I guess I should be using that for all DoP data including stereo? The stereo played fine though.

Quote from: Ian @ un4seenBASS_ChannelSetAttribute(stream, BASS_ATTRIB_NORAMP, 1); // disable ramping

If your DSD device still doesn't detect DoP data, please post the soundcard's settings in the Audio MIDI Setup, including the "Configure Speakers" settings. Make sure all channel volumes are at 1.0.
]
I'll let you know, one way or the other and if it still doesn't work, I'll send you the Audio MIDI Setup data.

soundgals

Unfortunately this made no difference. So I'm attaching screenshots of Audio-Midi Set-up
Audio-Midi set-up

Ian @ un4seen

Quote from: soundgalsI will try the BASS_ATTRIB_NORAMP option. I guess I should be using that for all DoP data including stereo? The stereo played fine though.

Ramping-in would corrupt the first few milliseconds of DoP data, but I would expect it to play fine after that. Still a good idea to disable ramping on DoP streams (I'll have BASSDSD do that automatically in the next release).

Quote from: soundgalsUnfortunately this made no difference. So I'm attaching screenshots of Audio-Midi Set-up
Audio-Midi set-up

All of the channel volume sliders there should be set to "1,0". If doing that doesn't fix the problem, I think I'll have to send you a debug BASS version to get more info.

soundgals

Quote from: Ian @ un4seenAll of the channel volume sliders there should be set to "1,0". If doing that doesn't fix the problem, I think I'll have to send you a debug BASS version to get more info.
Thanks Ian. The only reason some of those sliders were set to minimum values was to avoid the noise that was being generated. I could see if the multi-channel DoP DSD was working by checking the sample rate showing on the front of the DAC. If it shows 176.4 I'm not getting DSD over DoP. For that it would have to read the correct DSD sample rate, as it does for stereo.

Ian @ un4seen

OK, so just to be clear, the problem does still happen when all volume sliders are set to 1.0?

Is stereo DoP playback still working when the soundcard is set to 8 channels? Or do you need to set the soundcard to stereo first?

soundgals

Quote from: Ian @ un4seenOK, so just to be clear, the problem does still happen when all volume sliders are set to 1.0?

Is stereo DoP playback still working when the soundcard is set to 8 channels? Or do you need to set the soundcard to stereo first?

Yes, the problem does still happen when I set all volume sliders to 1.0. I made a separate test for this.

DoP playback only works for stereo when I first set the device to 2 channels.


Ian @ un4seen

#20
Quote from: soundgalsDoP playback only works for stereo when I first set the device to 2 channels.

That suggests to me that the device will only switch to DSD mode if all channels contain DoP data, ie. the empty channels are preventing it. You could try using a DSP function on the device to fill the empty channels with DoP/DSD silence, like this:

BASS_ChannelSetDSP(devstream, DSDSilenceDSP, 0, 0); // set DSP function on device stream

void CALLBACK DSDSilenceDSP(HDSP handle, DWORD channel, void *buffer, DWORD length, void *user)
{
    float *fbuffer = (float*)buffer; // floating-point data
    int dop = 0;
    for (int a = 0; a < length / sizeof(float); a++) {
        int s = fbuffer[a] * 8388608.f; // get sample
        if ((s & 0xffff0000) == 0x50000 || (s & 0xffff0000) == 0xfffa0000 || (s & 0xffff0000) == 0xffaa0000)) { // a DoP marker
            dop = s & 0xffff0000; // retain it
        } else if (!s && dop) { // empty
            s = dop | 0x6969; // DoP marker + DSD silence value
            fbuffer[a] = s / 8388608.f; // replace empty sample
        } else
            break; // not DoP data
    }
}

soundgals

#21
Quote from: Ian @ un4seen
Quote from: soundgalsDoP playback only works for stereo when I first set the device to 2 channels.

That suggests to me that the device will only switch to DSD mode if all channels contain DoP data, ie. the empty channels are preventing it. You could try using a DSP function on the device to fill the empty channels with DoP/DSD silence, like this:

BASS_ChannelSetDSP(devstream, DSDSilenceDSP, 0, 0); // set DSP function on device stream

void CALLBACK DSDSilenceDSP(HDSP handle, DWORD channel, void *buffer, DWORD length, void *user)
{
    float *fbuffer = (float*)buffer; // floating-point data
    int dop = 0;
    for (int a = 0; a < length / sizeof(float); a++) {
        int s = fbuffer[a] * 16777216.f; // get sample
        if ((s & 0xffff0000) == 0x50000 || (s & 0xffff0000) == 0xfffa0000 || (s & 0xffff0000) == 0xffaa0000)) { // a DoP marker
            dop = s & 0xffff0000; // retain it
        } else if (!s && dop) { // empty
            s = dop | 0x6969; // DoP marker + DSD silence value
            fbuffer[a] = s / 16777216.f; // replace empty sample
        } else
            break; // not DoP data
    }
}

I've noticed some 5.1 channel files actually have nothing on the .1 (LFE) channel. How will that effect this? If the channel exists in the original file is that enough, or does there actually need to be some content?

Ian @ un4seen

If a channel exists (even if it's just silence) in the DSD file then BASSDSD (with BASS_DSD_DOP set) will convert it to DoP, and nothing more needs to be done for it. The DSP function above would only process channels that the DSD file doesn't have.

soundgals

OK thanks. I applied dsdSilenceDSP to the devStream making sure that all channels were set to 1.0 and that NORAMP is set, and unfortunately the result is the same; just noise.

Should I definitely be applying it to the devStream, not the stream created by BASS_DSD_StreamCreateURL?

Ian @ un4seen

Yes, the DSP function needs to be set on the device, not the DoP stream (all its channels are already DoP). Please upload a new output.wav file (from BASS_Encode_Start), to check that the DSP function is working properly. Also confirm how many channels the played DSD file has.