BASS_Mixer_ChannelGetPosition reporting incorrect time at a single moment

Started by Salman Ahmed,

ppy

We're going to attempt to push this out in the next release.

@Ian, is there a good way of knowing when changes like this are back in the mainstream download URL?

Ian @ un4seen

You can subscribe to the RSS feed to receive notification of new releases:

    www.un4seen.com/rss.xml

The next BASS_FX release will be using some new add-on stuff from BASS 2.4.18 (it will still be compatible with older versions), so the BASS 2.4.18 release should come first (that should be fairly soon).

Wieku

After retesting it more with tempo adjustments it seems that position deviates all over the place when BASS_ATTRIB_TEMPO is used. Scaled BASS_ATTRIB_FREQ seems to work correctly. It does not happen if mixer is in decode mode (recording to file using 1ms chunks), only when attached to the output device.

BASS Version:
BASS Version:        2.4.17.41
BASS FX Version:     2.4.12.14
BASS Mix Version:    2.4.12.8

This is taken from github, lower row is the current behavior:
https://img.wieku.me/25/447332772-a5578cfa-dbe2-4029-91fd-7013bb06ef5e.mp4

This is my own recording, balls should move at a constant speed along the track but you can notice that they "jerk" (speed up and down) a little: https://img.wieku.me/25/2025-06-10%2018-22-28.mp4

This is what I see in minimal example:
Timestamp: 1804.019ms Pos: 1816.5532879819ms deviation 12.534ms
Timestamp: 1854.205ms Pos: 1866.8934240363ms deviation 12.688ms
Timestamp: 1904.211ms Pos: 1910.9070294785ms deviation 6.696ms
Timestamp: 1954.310ms Pos: 1968.2766439909ms deviation 13.967ms
Timestamp: 2004.432ms Pos: 2014.1043083900ms deviation 9.672ms
Timestamp: 2054.698ms Pos: 2068.5034013605ms deviation 13.805ms
Timestamp: 2104.735ms Pos: 2118.7301587302ms deviation 13.995ms
Timestamp: 2154.797ms Pos: 2162.8344671202ms deviation 8.037ms
Timestamp: 2204.942ms Pos: 2216.5306122449ms deviation 11.589ms
Timestamp: 2255.093ms Pos: 2269.7052154195ms deviation 14.612ms
Timestamp: 2305.223ms Pos: 2321.9501133787ms deviation 16.727ms
Timestamp: 2355.477ms Pos: 2368.9795918367ms deviation 13.503ms
Timestamp: 2405.612ms Pos: 2413.8775510204ms deviation 8.266ms
Timestamp: 2455.836ms Pos: 2465.6009070295ms deviation 9.765ms
Timestamp: 2505.879ms Pos: 2515.2834467120ms deviation 9.405ms
Timestamp: 2556.084ms Pos: 2573.3106575964ms deviation 17.226ms
Timestamp: 2606.143ms Pos: 2617.8458049887ms deviation 11.703ms
Timestamp: 2656.347ms Pos: 2669.2743764172ms deviation 12.927ms
Timestamp: 2706.454ms Pos: 2719.0476190476ms deviation 12.594ms
Deviation doesn't account for play delay but it should be consistent.

Test code:
package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -Wl,-rpath,$ORIGIN -L${SRCDIR} -lbass -lbass_fx -lbassmix
#include "bass.h"
#include "bass_fx.h"
#include "bassmix.h"
*/
import "C"

import (
    "fmt"
    "time"
    "unsafe"
)

func main() {
    C.BASS_SetConfig(C.BASS_CONFIG_DEV_NONSTOP, 1)

    C.BASS_SetConfig(C.BASS_CONFIG_VISTA_TRUEPOS, 0)

    C.BASS_SetConfig(68, 1) //OLDGAPS

    C.BASS_Init(-1, 44100, 0, nil, nil)

    masterMixer := C.BASS_Mixer_StreamCreate(44100, 2, C.BASS_MIXER_NONSTOP)
    C.BASS_ChannelSetAttribute(masterMixer, C.BASS_ATTRIB_BUFFER, 0)
    C.BASS_ChannelSetDevice(masterMixer, C.BASS_GetDevice())

    C.BASS_ChannelPlay(masterMixer, 0)

    channel := C.BASS_StreamCreateFile(0, unsafe.Pointer(C.CString("test.mp3")), 0, 0, C.BASS_STREAM_DECODE|C.BASS_STREAM_PRESCAN)

    channel = C.BASS_FX_TempoCreate(channel, C.BASS_FX_FREESOURCE|C.BASS_STREAM_DECODE)

    speed := 0.5

    C.BASS_ChannelSetAttribute(channel, C.BASS_ATTRIB_VOL, C.float(0.2))
    C.BASS_ChannelSetAttribute(channel, C.BASS_ATTRIB_TEMPO, C.float((speed-1.0)*100))

    C.BASS_Mixer_StreamAddChannel(masterMixer, channel, C.BASS_MIXER_CHAN_NORAMPIN|C.BASS_MIXER_CHAN_BUFFER)
   
    stTime := time.Now().UnixNano()

    for i := 0; i <= 20000; i++ {
        ts := float64(time.Now().UnixNano()-stTime) / 1000000 * speed

        musicPos := float64(C.BASS_ChannelBytes2Seconds(channel, C.BASS_Mixer_ChannelGetPosition(channel, C.BASS_POS_BYTE))) * 1000

        fmt.Println(fmt.Sprintf("Timestamp: %.3fms Pos: %.10fms deviation %.3fms", ts, musicPos, musicPos-ts))

        time.Sleep(time.Millisecond * 100)
    }
}

Ian @ un4seen

That looks like it may be related to what I mentioned earlier in this thread (reply #10) about the BASS_FX update trying to more accurately report the current source position, which may advance at a slightly varying rate because the tempo processing uses varying amounts of source data for each output block. To test that theory, do you have the same issue if you switch back to the release BASS_FX version?

Wieku

Quote from: Ian @ un4seendo you have the same issue if you switch back to the release BASS_FX version?

Nope, ofc with 2.4.12.6 there's the previous issue but if played from the start time tracking seems to be stable:
Timestamp: 25921.723ms Pos: 25886.5079365079ms deviation -35.215ms
Timestamp: 25972.223ms Pos: 25936.9841269841ms deviation -35.238ms
Timestamp: 26022.232ms Pos: 25986.9841269841ms deviation -35.248ms
Timestamp: 26072.319ms Pos: 26037.2335600907ms deviation -35.086ms
Timestamp: 26122.332ms Pos: 26086.9841269841ms deviation -35.348ms
Timestamp: 26172.613ms Pos: 26137.3696145125ms deviation -35.243ms
Timestamp: 26222.902ms Pos: 26187.5736961451ms deviation -35.329ms
Timestamp: 26273.269ms Pos: 26237.9818594104ms deviation -35.287ms
Timestamp: 26323.723ms Pos: 26288.4807256236ms deviation -35.242ms
Timestamp: 26373.998ms Pos: 26338.4807256236ms deviation -35.517ms
Timestamp: 26423.998ms Pos: 26388.6394557823ms deviation -35.359ms
Timestamp: 26474.302ms Pos: 26438.8888888889ms deviation -35.413ms
Timestamp: 26524.706ms Pos: 26489.4557823129ms deviation -35.250ms
Timestamp: 26574.723ms Pos: 26539.4784580499ms deviation -35.244ms
Timestamp: 26624.726ms Pos: 26589.4784580499ms deviation -35.247ms
Timestamp: 26674.979ms Pos: 26639.4784580499ms deviation -35.501ms
Timestamp: 26725.223ms Pos: 26689.9773242630ms deviation -35.246ms
Timestamp: 26775.721ms Pos: 26740.4988662132ms deviation -35.222ms
Timestamp: 26825.816ms Pos: 26790.6575963719ms deviation -35.159ms
Timestamp: 26876.208ms Pos: 26840.9750566893ms deviation -35.233ms
Timestamp: 26926.301ms Pos: 26890.9750566893ms deviation -35.326ms
Timestamp: 26976.722ms Pos: 26941.4739229025ms deviation -35.248ms
Timestamp: 27026.806ms Pos: 26991.4739229025ms deviation -35.332ms
Timestamp: 27077.301ms Pos: 27041.9727891156ms deviation -35.329ms
Timestamp: 27127.302ms Pos: 27091.9727891156ms deviation -35.329ms
Timestamp: 27177.312ms Pos: 27141.9727891156ms deviation -35.339ms
Timestamp: 27227.804ms Pos: 27192.4716553288ms deviation -35.333ms

Ian @ un4seen

Would you prefer BASS_FX to still calculate the position like that even though it may be less accurate? If so, perhaps an option can be added for that.

If smoothness is most important then another thing you could try is the new BASS_POS_RAW mode, which gives a monotonic output byte count without any consideration for seeking/looping or tempo processing. That's coming in the BASS 2.4.18 release but is also in the latest build:

    www.un4seen.com/stuff/bass.zip

Wieku

Quote from: Ian @ un4seenWould you prefer BASS_FX to still calculate the position like that even though it may be less accurate? If so, perhaps an option can be added for that.

That would be great! At least in rhythm games' case, low latency and time smoothness is most important, real input buffer position not so much as that introduces stutters because time progression won't be linear.

I will take a look at BASS_POS_RAW, but it seems it will require additional code to monitor seeks/pauses etc.

Ian @ un4seen

OK. Here's a BASS_FX update for you to try:

    www.un4seen.com/stuff/bass_fx.zip

It adds a BASS_ATTRIB_TEMPO_OPTION_OLDPOS option to enable using the old (BASS_FX 2.4.12.6) position calculation:

BASS_ChannelSetAttribute(tempostream, BASS_ATTRIB_TEMPO_OPTION_OLDPOS, 1); // use old position calculation

The setting can be changed at any time, but probably best to call BASS_ChannelSetPosition afterwards to reset the position tracking.

bdach

Hi,

I also work on osu! along with Salman and ppy earlier from the thread.

Quote from: Ian @ un4seenOK. Here's a BASS_FX update for you to try: (...) It adds a BASS_ATTRIB_TEMPO_OPTION_OLDPOS option to enable using the old (BASS_FX 2.4.12.6) position calculation:

Reporting back that in testing this option seems to be doing the job and getting rid of the uneven progression of track position. Could I ask for linux / macOS binaries with this change included so that we can push them out for testing with users? Or are they already accessible under the standard urls mentioned earlier in this thread? Thanks in advance.

Ian @ un4seen

Good to hear the new option is working well for you so far. As requested, here are Linux and macOS versions of the BASS_FX update:

    www.un4seen.com/stuff/bass_fx-linux.zip
    www.un4seen.com/stuff/bass_fx-osx.zip


ppy

Ian,

Thanks for the multiple updates. Unfortunately we just can't seem to move forward here.

Users are reporting "higher audio latency" (potentially translates to playback position being different to what it used to be, relative to actual audible track time) and "weird glitches where seeks don't behave correctly". I don't have any solid information yet and am still trying to reproduce in a way I can convey this better to you, but just posting initially in case you have any ideas.

We're really trying to just get things in a stable non-buggy state but not having much luck 😅

ppy

User investigation:

> The (broken) files contain a Xing header, which contains various information, most importantly the encoder delay. And I'm guessing on the new version of BASS, it'll only recognize the Xing header if the encoder name has LAME in it (talks about hardcoding strings and magic numbers!). All I did was changing the encoder name in the Xing header to starts with LAME (see attached images), and that makes BASS recognize the header and trim off the 25ms of silence at the start of the file

Sample audio tracks: https://www.icloud.com/iclouddrive/019-Ncn3fZFLbnYVjGPemlXYg#sample_audio
`audio_good` - matches expectations of current position
`audio_bad` - is off by ~25ms from expectations on the latest pre-release bass build.

Ian @ un4seen

The standard Xing header doesn't include encoder delay info, but a LAME extension adds that, as described here:

   http://gabriel.mp3-tech.org/mp3infotag.html

BASS currently detects an extended header by the presence of the "LAME" string, so it isn't detected in the audio_bad.mp3 case because that has "Lavc" there instead. I'll check if an extended header can be reliably detected some other way.


longnguyen2004

Hi, original investigator here!

I think that it's enough to just add extra checks for "Lavf" and "Lavc", since ffmpeg is the only program to my knowledge that sets the encoder name to something other than LAME. ffmpeg itself also checks for the LAME Info header the same way

Ian @ un4seen

Here's an update for you to try:

    www.un4seen.com/stuff/bass.zip

It will accept anything except an empty string for the encoder name in the header (in case other names are used there now or in future).

ppy

Thank you for the update. Initial testing shows it to work very well.

We're also tracking one more issue, which is that we're receiving `BASS_ERROR_UNKNOWN` (-1) from BASS_Mixer_ChannelGetPos after a seek/unpause operation on a track. It only seems to occur on windows, and seems to have changed from previous. I'm still working to create an isolated reproduction to send over, but let me know if you have any ideas.

Ian @ un4seen

BASS_ERROR_UNKNOWN can result from BASS not having record of the source's position at the time that BASS_Mixer_ChannelGetPosition is requesting (it may request an earlier position to account for latency). Are you able to reproduce the problem yourself? If so, to perhaps narrow it down, please see if it happens when switching back to the release BASS (2.4.17) and/or BASSmix (2.4.12) versions.

Also, are you using BASS_Mixer_ChannelSetPosition or BASS_ChannelSetPosition for seeking? If the latter, try the former.

ppy

Ian,

I come back with two reproduction cases which are odd to us. These are tested on windows. I believe at least the second of the two does not occur on macOS/linux.

First is a deadlock involving mixers and the `BASS_POS_MIXER_RESET` flag:

Bass.Init();

int mixer = BassMix.CreateMixerStream(44100, 2, BassFlags.Default);

// IMPORTANT: only occurs with zero buffer setting
Bass.ChannelSetAttribute(mixer, ChannelAttribute.Buffer, 0);

Bass.ChannelPlay(mixer);

int track = Bass.CreateStream("test.ogg", 0, 0, BassFlags.Decode | BassFlags.Prescan);

BassMix.MixerAddChannel(mixer, track, BassFlags.MixerChanBuffer);

long seekTarget = Bass.ChannelSeconds2Bytes(track, 10);

// IMPORTANT: only occurs with repeated set position calls.
// IMPORTANT: only occurs with BASS_POS_MIXER_RESET.
for (int j = 0; j < 100; j++)
    BassMix.ChannelSetPosition(track, seekTarget, PositionFlags.MixerReset);

Bass.Free();

Second is reported time going backwards after a mixer reset (we would hope that this is never the case so we can rely on the time value we fetch immediately after a SetPosition):

Bass.Init();

int mixer = BassMix.CreateMixerStream(44100, 2, BassFlags.MixerNonStop);
Bass.ChannelSetAttribute(mixer, ChannelAttribute.Buffer, 0);
Bass.ChannelPlay(mixer);

int track = Bass.CreateStream("test.ogg", 0, 0, BassFlags.Decode | BassFlags.Prescan);

BassMix.MixerAddChannel(mixer, track, BassFlags.MixerChanBuffer);

for (int i = 1; i < 100; i++)
{
    double length = Bass.ChannelBytes2Seconds(track, Bass.ChannelGetLength(track));
    double seekTargetSeconds = length / 100 * i;
    long seekTarget = Bass.ChannelSeconds2Bytes(track, seekTargetSeconds);

    Console.WriteLine($"Seeking to {seekTargetSeconds}...");
    BassMix.ChannelSetPosition(track, seekTarget, PositionFlags.MixerReset);

    loop(seekTarget);
}

Bass.Free();

void loop(long minimumAllowedTime)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();

    while (sw.ElapsedMilliseconds < 100)
    {
        long pos = BassMix.ChannelGetPosition(track);
        if (pos == -1)
            continue;

        var lastSeconds = Bass.ChannelBytes2Seconds(track, minimumAllowedTime);
        var seconds = Bass.ChannelBytes2Seconds(track, pos);

        if (Math.Abs(seconds - lastSeconds) > 1)
        {
            Console.WriteLine($"Backwards seek {lastSeconds} -> {seconds}");
            Environment.Exit(-1);
        }

        minimumAllowedTime = pos;
    }
}

Output for this looks like:

Seeking to 3.159261041666667...
Seeking to 6.318522083333334...
Seeking to 9.477783125...
Seeking to 12.637044166666668...
Seeking to 15.796305208333335...
Seeking to 18.95556625...
Seeking to 22.114827291666668...
Backwards seek 22.1148125 -> 6.398666666666666

---

Both of these seem to be reproducible with any audio file.

Please let me know if you need any further details.

Ian @ un4seen

Thanks for the test code. Both issues are related to the combination of BASS_POS_MIXER_RESET and disabled playback buffering (BASS_ATTRIB_BUFFER=0). Here's a BASS update that should fix it:

    www.un4seen.com/stuff/bass.zip

Let me know if you still see either problem happening.

ppy

Thanks Ian, both issues look fixed.

A couple of quick ones:

- I know we only detected this issue on windows, but is this the case somehow? For peace of mind can you confirm that other platform's beta versions are in a good state for us to update them (iOS/android/macOS/linux)?
- In our existing code, we were using a different method of flushing/resetting the mixer position:

Bass_ChannelSetPosition(mixer_handle, 0);
which looks to have the same effect as the `MixerReset` flag. Can you confirm this flag is a good replacement for that code?

Thank you!

Ian @ un4seen

Good to hear the update is working well. The problem could potentially also affect other platforms (it isn't something Windows-specific), so here are updates for them too:

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

The BASS_POS_MIXER_RESET flag does indeed avoid the need for a separate BASS_ChannelSetPosition call on the mixer to clear its playback buffer.

ppy

Thanks for the response. Sorry to be a pain but are mobile platforms also potentially affected?