Author Topic: Shifting Playback Rate  (Read 578 times)

Hanuman

  • Posts: 89
Shifting Playback Rate
« on: 23 Aug '22 - 02:31 »
BassFx has good support for altering tempo and pitch. BassMix allows resampling 44100 to 48000 for playback without altering the sound.

Some audio players have the option of altering the playback rate which affects both the speed and the pitch, without any kind of compensation.

If I want to do a lossless conversion by altering the rate, how can I do that with BASS?


The steps I want to do to preserve maximum quality are:
1. Resample 44100 to around 48888.88
2. Rate shift to 48000
3. (optional) Adjust tempo

I'm creating a BASS stream, then a BassMix stream around it, then a BassFx stream around it.

We talked about an issue getting stream length with BassMix; I confirm that it's fixed in the latest version.

On a separate note, I'm having an issue with seeking not working anymore with this altered setup. I tried seeking on the BassMix and on the BassFx stream and neither worked.

Code: [Select]
ManagedBass.Bass.ChannelSetPosition(_chan,
    ManagedBass.Bass.ChannelSeconds2Bytes(_chan, value.TotalSeconds));

Another issue I saw. BassInfo.SampleRate says that it works only on MacOS and Windows; and on Linux it returns 44100. As far as I know, it's generally 48000 on Linux. I don't think that 44100 is a good default value to return. It kind of sucks that it doesn't behave as expected just on Linux though.

Ian @ un4seen

  • Administrator
  • Posts: 24732
Re: Shifting Playback Rate
« Reply #1 on: 23 Aug '22 - 14:37 »
BassFx has good support for altering tempo and pitch. BassMix allows resampling 44100 to 48000 for playback without altering the sound.

Some audio players have the option of altering the playback rate which affects both the speed and the pitch, without any kind of compensation.

If I want to do a lossless conversion by altering the rate, how can I do that with BASS?

The BASS_ATTRIB_FREQ setting (via BASS_ChannelSetAttribute) controls the playback rate, but note that a rate change will require resampling if it doesn't match the device's output rate (see BASS_GetInfo for that), so it won't be totally lossless.

On a separate note, I'm having an issue with seeking not working anymore with this altered setup. I tried seeking on the BassMix and on the BassFx stream and neither worked.

Code: [Select]
ManagedBass.Bass.ChannelSetPosition(_chan,
    ManagedBass.Bass.ChannelSeconds2Bytes(_chan, value.TotalSeconds));

It indeed isn't possible to seek on mixers directly, because a mixer doesn't know what position its sources should be at for a particular mix position. You will need to seek on a mixer's source(s) instead, ie. set them as they would be at the wanted mix position.

Another issue I saw. BassInfo.SampleRate says that it works only on MacOS and Windows; and on Linux it returns 44100. As far as I know, it's generally 48000 on Linux. I don't think that 44100 is a good default value to return. It kind of sucks that it doesn't behave as expected just on Linux though.

On Windows and macOS, BASS can get and use the native output rate. Unfortunately, that doesn't appear to be possible when using PulseAudio/PipeWire on Linux - they report all rates are supported and automatically resample when required. So BASS's output rate is likely to be as set in the BASS_Init "freq" parameter then - are you setting that to 44100 or 48000?

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #2 on: 23 Aug '22 - 19:16 »
Bass_Init has a default of 44100 if I remember, while I was told that it's not really used and doesn't matter

So for Linux, to output at best quality without extra resampling, I must ask the user to configure it manually -- when that option really is only useful for Linux

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #3 on: 23 Aug '22 - 19:27 »
I had already tried setting the Frequency attribute on the mixing channel and it didn't have any effect on the pitch.

Code: [Select]
ManagedBass.Bass.ChannelSetAttribute(_chanMix, ChannelAttribute.Frequency, freqOut *  2);
AH! If I set it on the source stream, it works!

Seeking works too.

Wait --

If I want to do a Rate shift, I set Frequency it on the Source.

If I set it on the Mixer channel, does it do a Resampling instead?

Looks like it, just want to confirm.

Edit: No it's not working, the 2nd set Frequency on _chanMix seems to be cancelling out the first one set!

Plus ideally I'd want them in reverse order; first do the resampling, then the rate adjustment. Not sure whether that makes a difference or not.

Code: [Select]
// Rate Shift
ManagedBass.Bass.ChannelSetAttribute(_chanIn, ChannelAttribute.Frequency, _chanInfo.Frequency * r);
// Resampling
ManagedBass.Bass.ChannelSetAttribute(_chanMix, ChannelAttribute.Frequency, 48000);
// Tempo adjustment
// ManagedBass.Bass.ChannelSetAttribute(_chanMix, ChannelAttribute.Tempo,
//     EffectsTempoCompensation == TempoCompensation.Precise ? t : 1);
« Last Edit: 23 Aug '22 - 20:03 by Hanuman »

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #4 on: 23 Aug '22 - 20:37 »
_chanIn is the source stream
_chanMix is the BassMix stream
_chanOut is the BassFx stream

It works if I do just the rate shift.

Uncommenting the resample line breaks it; leaving it to do implicit "might" be fine.

Tempo Adjustment isn't taking effect either.

Code: [Select]
// Optimized pitch shifting: increased quality at the cost of pitch/speed rounding error.
// I = Input sample rate    (44100)
// O = Output sample rate   (48000)
// P = Pitch shift          (432/440)
// Pitch shifting steps:
// 1. Resample: O / I / P (round to closest fraction, eR = error)
// 2. Rate shift: P / (1 + eR), should give 48000 output
// 3. Tempo adjustment: -R (round to closest fraction, eT = error) -- skip if EffectsTempoCompensation = None
var freqSrc = _chanInfo.Frequency;
var freqOut = ManagedBass.Bass.GetInfo(out var info) ? info.SampleRate : 48000;
var s = Fraction.RoundToFraction((double)freqOut / freqSrc / Pitch, out var eS);
var r = Pitch / (1 + eS);
var t = Fraction.RoundToFraction(-r, out var eT);

// Rate Shift
ManagedBass.Bass.ChannelSetAttribute(_chanIn, ChannelAttribute.Frequency, _chanInfo.Frequency * r);
// Resampling
// ManagedBass.Bass.ChannelSetAttribute(_chanMix, ChannelAttribute.Frequency, freqOut);
// Tempo adjustment
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.Tempo,
     EffectsTempoCompensation == TempoCompensation.Optimized ? (1.0 / t - 1.0) * 100.0 : 0);

I tried inverting BassFx and BassMix streams; that didn't help.
« Last Edit: 23 Aug '22 - 21:08 by Hanuman »

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #5 on: 23 Aug '22 - 21:20 »
Oh wait. Perhaps I don't even need BassMix at all.

Now I'm running BassFx over the source stream.

Tempo shifts works for values above 0 but not for values below 0! 200 accelerates it; -200 has no effect.

Code: [Select]
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.Tempo, -200);
Bug? ah works with -50 but not -200, there's a hidden limit

OK I need BassMix into the chain otherwise Rate Shift doesn't work; but if I add it then Tempo Shift doesn't work. It's one or the other...
« Last Edit: 23 Aug '22 - 21:31 by Hanuman »

Ian @ un4seen

  • Administrator
  • Posts: 24732
Re: Shifting Playback Rate
« Reply #6 on: 24 Aug '22 - 16:26 »
Bass_Init has a default of 44100 if I remember, while I was told that it's not really used and doesn't matter

So for Linux, to output at best quality without extra resampling, I must ask the user to configure it manually -- when that option really is only useful for Linux

The BASS_Init "freq" parameter doesn't affect the output by default when BASS is able to get the device's native rate. Unfortunately, that doesn't appear to be possible on Linux, so BASS will ask the device/ALSA to use the closest supported rate to the "freq" parameter value without resampling. PulseAudio/PipeWire ignore the no resampling requests and just resample anyway if needed, but they will set the final output to the requested rate if possible (ie. if the device supports it and nothing else is already playing at another rate) to avoid resampling.

I had already tried setting the Frequency attribute on the mixing channel and it didn't have any effect on the pitch.

Code: [Select]
ManagedBass.Bass.ChannelSetAttribute(_chanMix, ChannelAttribute.Frequency, freqOut *  2);
AH! If I set it on the source stream, it works!

The BASS_ATTRIB_FREQ setting has no direct effect on decoding channels (with BASS_STREAM_DECODE), ie. it doesn't change the decoded data. In your case, the mixer is a decoding channel feeding a BASS_FX tempo stream. So you would need to apply the BASS_ATTRIB_FREQ setting to the tempo stream. Or indeed the source, because BASSmix will apply its sources' BASS_ATTRIB_FREQ settings in its output mix.

Plus ideally I'd want them in reverse order; first do the resampling, then the rate adjustment. Not sure whether that makes a difference or not.

The rate adjustment involves resampling, so you could combine it with your other resampling step rather than having them separate, unless you're resampling specifically for some other processing that's in between?

Oh wait. Perhaps I don't even need BassMix at all.

Now I'm running BassFx over the source stream.

If you're only using BASSmix for resampling then you could indeed have the BASS_FX tempo processing handle that instead, ie. use BASS_ATTRIB_TEMPO_FREQ instead of BASS_ATTRIB_FREQ. The quality might not be as good (BASS_ATTRIB_SRC doesn't apply), but if you're already changing the tempo or pitch then BASS_FX will be resampling anyway, so you may as well avoid the extra resampling step.

Tempo shifts works for values above 0 but not for values below 0! 200 accelerates it; -200 has no effect.

Code: [Select]
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.Tempo, -200);
Bug? ah works with -50 but not -200, there's a hidden limit

The BASS_ATTRIB_TEMPO setting is a percentage (of the original tempo) and has a valid range of -95 to +5000.

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #7 on: 24 Aug '22 - 19:30 »
Tempo that was not working was due to a math error causing Tempo to fall below -100.

Now I got this: _chanSrc (44100hz) => _chanMix (44100hz) => _chanOut (BassFX)

Code: [Select]
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.Frequency, _chanInfo.Frequency * r);
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.Tempo, (1.0 / -t - 1.0) * 100.0);
ManagedBass.Bass.ChannelSetAttribute(_chanOut, ChannelAttribute.SampleRateConversion, 4);

The original idea is:
1. Resample: O / I / P (round to closest fraction, eR = error)
2. Rate shift: P / (1 + eR), should give 48000 output
3. Tempo adjustment: -R (round to closest fraction, eT = error)

Here, Rate Shift happens first. Tempo second, over a reduced frequency. Then implicit resample to 48000.

It works and I get increased quality! BUT -- Tempo is going to have increased rounding errors due to running at 43204.4hz instead of 48000hz.

Setting Frequency is lossless so it doesn't matter what sample rate it runs on; but Tempo rounding errors do matter. How could I run the Tempo algorithm over a 48000hz stream?

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #8 on: 25 Aug '22 - 01:46 »
Got it. I create the BassMix stream with freq 48000 in the constructor; then adjust the rate shift to be based on 48000 instead of 44100 and it works!

I then get
Resample first
Rate second
Tempo third

As was my goal!

This pitch-shifting method is definitely a hack but there's indeed a noticeable quality increase! Clearer sound.

Actually the sound clarity difference is impressive.
« Last Edit: 25 Aug '22 - 01:55 by Hanuman »

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #9 on: 5 Oct '22 - 08:05 »
It's working for live playback, but not when encoding!

I create stream (chan), then mixer (chanMix), then tempo (chanOut), and encode on chanOut. File is encoded but tempo is not getting applied! Why?

Code: [Select]
var chanMix = BassMix.CreateMixerStream(sampleRate, chanInfo.Channels, BassFlags.MixerEnd | BassFlags.Decode).Valid();
BassMix.MixerAddChannel(chanMix, chan, BassFlags.MixerChanNoRampin | BassFlags.AutoFree);

// Add tempo effects.
var chanOut = BassFx.TempoCreate(chanMix, BassFlags.Decode).Valid();

// Optimized pitch shifting for increased quality
var r = pitch;
if (settings.RoundPitch)
{
    r = Fraction.RoundToFraction(r);
}
var t = r / settings.Speed;

// 1. Rate Shift (lossless)
Bass.ChannelSetAttribute(chanOut, ChannelAttribute.Frequency, sampleRate * r);
// 2. Resampling to output in _chanMix constructor
// 3. Tempo adjustment
Bass.ChannelSetAttribute(chanOut, ChannelAttribute.Tempo,
    !settings.SkipTempo ? (1.0 / t - 1.0) * 100.0 : 0);

// ...
BassEnc.EncodeStart(chanOut, CommandLine: file.Destination, EncodeFlags.PCM | flags, null).Valid();

var buffer = new byte[32 * 1024];
while (Bass.ChannelGetData(chanOut, buffer, buffer.Length) > -1)
{
}

Ian @ un4seen

  • Administrator
  • Posts: 24732
Re: Shifting Playback Rate
« Reply #10 on: 5 Oct '22 - 14:22 »
It looks like the issue there is the use of BASS_ATTRIB_FREQ (ChannelAttribute.Frequency), which is only applied during playback, ie. it isn't present in the decoded data. You could instead try using the BASS_ATTRIB_TEMPO_FREQ option, which is applied in the tempo processing, so it will be present in the tempo stream's data. Alternatively, BASS_ATTRIB_FREQ is also applied by mixers, so you could plug the tempo stream into a mixer (and set the encoder on that) to have it applied.

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #11 on: 5 Oct '22 - 15:55 »
It looks like the issue there is the use of BASS_ATTRIB_FREQ (ChannelAttribute.Frequency), which is only applied during playback, ie. it isn't present in the decoded data. You could instead try using the BASS_ATTRIB_TEMPO_FREQ option, which is applied in the tempo processing, so it will be present in the tempo stream's data. Alternatively, BASS_ATTRIB_FREQ is also applied by mixers, so you could plug the tempo stream into a mixer (and set the encoder on that) to have it applied.

The idea is to perform lossless pitch-shifting then resampling on a rounded fraction, for minimal loss of data.

If I set it on the tempo stream, then it will first resample, and then process lossy pitch-shifting as a separate operation.

So you're saying to have chan -> mix -> tempo -> mix ?

If I move steps 1 & 2 after tempo, the problem is that tempo will run on 44100 instead of 48000, resulting in loss of data.

Unless I double the sample rate in the first mixer, and re-adjust after. Would doubling sampling rate result in very very negligible quality loss?

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #12 on: 5 Oct '22 - 16:16 »
Alternatively, BASS_ATTRIB_FREQ is also applied by mixers
Well -- I already have a mixer, so why is it not already applying?

Ian @ un4seen

  • Administrator
  • Posts: 24732
Re: Shifting Playback Rate
« Reply #13 on: 5 Oct '22 - 17:36 »
A mixer applies its sources' BASS_ATTRIB_FREQ settings when mixing them. In your case, the mixer is currently the source of the tempo stream rather than the other way round.

Hanuman

  • Posts: 89
Re: Shifting Playback Rate
« Reply #14 on: 6 Oct '22 - 00:35 »
Ah. Correct.

Code: [Select]
// 1. Rate Shift (lossless)
Bass.ChannelSetAttribute(chanOut, ChannelAttribute.Frequency, sampleRate * r);

I replace chanOut with chan and it works.

It slightly changes the math though so need to review the formulas. Just needed to use source sample rate instead of destination sample rate. Works.
« Last Edit: 6 Oct '22 - 05:20 by Hanuman »