AudioTransportSource - transport bar Slider error


#1

Hello,
I am making simply audio player. And I have transport bar (Slider) to make:
AudioTransportSource::setPosition(Slider::getValue())

My Slider has range set like that:
audioPosSlider.setRange(0.0, transportSource.getLengthInSeconds());

And of course it’s updated every time new audio file is loaded.

But when I am dragging my audioPosSlider I often get error in AudioTransportSource::getNextAudioBlock. The error is on the line:
masterSource->getNextAudioBlock (info);

And it says: Built-in Output (15): EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0

Have no idea how to deal with that. What causes such error?

I tried to perform pausing audio playback when dragging with such lambdas:

 audioPositionSlider.onDragStart = [this] { if(playWasClicked) transportSource.stop(); };
 audioPositionSlider.onDragEnd = [this] { if(playWasClicked) transportSource.start(); };

I am even not sure if it helps, but even so, there is some delay on transportSource.stop();. And it causes annoying delay (lagging) when start dragging. And with that solution there is another issue: I don’t hear the audio when dragging, but I like to keep that rewinding sound effect.

So I have no Idea how to deal with that.

Could anyone give some advice.

For any help thanks in advance.
Best regards


#2

setPosition crashing is probably due to a thread safety issue. Are you doing any thread synchronization in your code?


#3

Sorry but I have no idea what is thread synchronization, and how to do that, so the more I don’t know if I am doing that. :slight_smile:


#4

OK, I suppose thread synchronisation has something to do with something like for example using GUI threads in audio thread (in example in getNextAudioBlock), which - as far as I know - is not allowed, and I try as much as I can to avoid such situations. But not sure if you mean that. But what should be mention: when I used sometime ago GUI methods in audio thread, I got such error immediately after compile. But now I get that error very seldom (yes I know I told on the beginning that it’s “often”, but I lied :slight_smile:, or just my English is lame, and sometimes not sure what I say). But to the point, error is happen very rarely, it’s difficult to figure it out when exactly it happens. Sometime I move my transport slider very dynamically for 20 seconds and nothing happens, but sometimes I make some small move and BANG - error.


#5

I mean you should not use non-thread safe objects/methods from multiple threads without synchronization. For example the GUI Slider events run in the GUI thread and the getNextAudioBlock which uses the AudioTransportSource runs in the audio thread. Code running in the GUI thread generally speaking should not assume it can call methods of objects like AudioTransportSource without doing thread synchronization. (Vice versa it’s completely forbidden and enforced to crash in some methods in JUCE. Non-GUI threads must not call methods of GUI objects.)


#6

So how to provide such synchronisation. It sounds like it’s something very difficult and complicated. But maybe some advice could help?


#7

It’s a very complicated topic and to do it “properly” you could spend years learning it. However, for basic purposes using Juce’s CriticalSection usually works fine.


#8

I think you have a rough idea, but maybe should have a more in-depth-read on that topic. First of all, you can not “use the GUI threads in audio thread”.

Think of two threads as two functions running completely in parallel. Now this is what actually happens in your Audio App with GUI:
There is one thread that calls all the GUI-related functions like paint, resized or also Silder::onDragStart / Slider::onDragEnd as those are triggered by the user interacting with the UI.
There is another thread that waits for your Audio Hardware to call it back when there are new samples or if it’s ready to receive samples from your software. If this happens, this thread calls processBlock

Now both threads don’t know each other and they do their work independent from each other. Now think what happens if the GUI thread decides to change the position that should be played (which will cause some internal data structures of AudioTransportSource to change) while the other thread is in the middle of reading data from the AudioTransportSource? It’s very likely that you will mess up the internal state of the AudioTransportSource in a way that the audio thread won’t be able to finish the read operation it was just performing. And this is what Xenakios suspected to happen in your application.

What you need is some kind of synchronization, so that the GUI thread waits until a processBlock in progress has finished before it actually executes its call to the AudioTransportSource


#9

OK, so I am almost sure my error is caused by:

void AudioPlayer::sliderValueChanged       (Slider *slider)
{
    if (slider == &audioPositionSlider)
    {
        transportSource.setPosition(audioPositionSlider.getValue()); // that line make mess in threads?
    }
}

So instead:
transportSource.setPosition(audioPositionSlider.getValue());

Should I use some value, like:
audioPos = audioPositionSlider.getValue();

And then in audio thread call the:
transportSource.setPosition(audioPos);

Is that good solution?


#10

That’s a bit tricky because you probably need to avoid doing the setPosition calls repeatedly and needlessly. So you would need to keep track of whether the requested new position has actually changed since the last call to getNextAudioBlock.


#11

Yes, this is one of the typical approaches that you could use for such a scenario. However you should do this only for calls that return after a short period of time. TBH, I don’t know was AudioTransportSource::setPosition does internally, but I would suspect it to be okay to be called on the audio thread.

However, you should add a few more things. First: Another bool variable like audioPosChangedBySlider that is set true after each slider movement and is set false by the audio thread. Only apply changes on the audio thread if it was set true. Then you want your audioPos variable to be an Atomic<double>, as setting a double could need multiple CPU cycles. If you are unlucky, your audio thread will again read a value that was halfway set by the GUI thread, resulting in some completely unusable data. Atomic prevents you from that, it only allows to read Data that was completely written


#12

I “believe” that 64 bit floating point values are atomic by default with builds for 64 bit CPU architecture. However, there is the chance the compiler might in some case remove accesses to the variable because of optimizations. (Obviously again resulting in non-working results.) Using an explicit atomic variable prevents those harmful optimizations from happening.


#13

Yes, I believe that too. But who knows if this code should not be compiled for a 32 Bit ARM? :smiley:
So I would say even if this will be safe without atomics for 99% of the cases, it’s still good practice to use an atomic here, just to be sure.


#14

Great thanks, I will try atomic<double> I’ve never heard before about such thing.
By the way, of course I know to set some bool to say audio threat “hey position was changed”, to avoid setting position all the time.


#15

OK, but I’ve never used thing like Atomic<double>.
transportSource.setPosition(atomicDoubleValue) doesn’t accept that. So what is most efficient way to get the double from that? I found that atomicDoubleValue.get() works. But not sure if that’s what I want. Does it provide what I want? As I understand Atomic<double> provide that reading it will be possible only when data is completely written. But does Atomic<double>::get() provides the same functionality?

And even the answer is YES, there is another related question: what happen if in audio thread we met situation like that:

if(isPositionChanged)
{
     transportSource.setPosition(atomicDoubleValue.get()); // atomicDoubleValue is not ready to be read
     isPositionChanged = false;
}

What would happen? It will read old value? Or give some error like “data is not readable”? Or what? I hope the first answer is true.


#16

Atomic<double>::get() is exactly what you want.

It will return the previous value as long as the new data has not been written completely.


#17

And one more :slight_smile: . What would happen if isPositionChanged (which is bool) is also in the middle of the set by GUI thread? To be sure, should I also use Atomic<bool> isPositionChanged?
I suppose I need to be very unlucky to meet that, but you know… forewarned is forearmed
:slight_smile:


#18

Yes, the bool should be an atomic too. (Mainly because of the reason the compiler might remove the uses of the variable during optimization if it’s just a plain bool variable. That happens very rarely, but could still happen.)


#19

OK, but for set value for Atomic<double>, could I just use:
atomicDoubleValue = someRegularDouble;
???

Or should I use atomicDoubleValue.set(someRegularDouble) ???

Or it’s doesn’t matter?


#20

Doesn’t seem to matter, they both use the same code. (set() just uses the = operator.)