thetwom / toc2

Metronome app
GNU General Public License v3.0
144 stars 23 forks source link

Start in Realtime #53

Open bigboipete opened 1 year ago

bigboipete commented 1 year ago

Currently the app has a lag of a few tenths of a second after hitting Start until the metronome starts. This makes it almost impossible to get a synchronous start of the metronome along with a playing musician.

It would be a good improvement to shorten this delay as close to zero as possible.

This is to check with the metronome during playing, if you are able to start a certain tempo by yourself. To achieve this, a minimized lag between start button and the actual start of the metronome is crucial.

I know all "realtime" stuff is hard to achieve, but maybe there are some possible tweaks to get closer to it.

thetwom commented 1 year ago

True, the time for the first click is quite substantial. I will see, if this can be improved.

One side note, if you don't care too much about the speed, you can use "tap-in" while the metronome is running. This at least "tries" to synchronise with your tapping ...

bigboipete commented 1 year ago

For the scenario I am talking about speed is important, because the check is e.g. for a song with defined target bpm, that should be played even without a metronome in correct tempo and the played bpms need to be verified.

By the way talking about tapping: How does the app calculate the resulting bpm? Is it just calculating the time from one tap to another for each tap? That's at least my conclusion from the actual display...

To me for e.g. measuring an unknown song-speed an average out of the last n taps would be better for that. The process of tapping is not too accurate and an average value would better narrow down the target bpm. The high change rate of the bpm value as it is now makes it harder to estimate the "final" bpms. But this could be another improvement.

bigboipete commented 1 year ago

And of course, thanks for taking care of the start timing. You're doing a great support- and maintenance-job for your metronome app!

thetwom commented 1 year ago

By the way talking about tapping: How does the app calculate the resulting bpm? Is it just calculating the time from one tap to another for each tap? That's at least my conclusion from the actual display...

To me for e.g. measuring an unknown song-speed an average out of the last n taps would be better for that. The process of tapping is not too accurate and an average value would better narrow down the target bpm. The high change rate of the bpm value as it is now makes it harder to estimate the "final" bpms. But this could be another improvement.

The tap-in does indeed use a kind of "floating average". Something like average = a last_measured_time + (1 - a) previous_average. The behavior could be changed by modifying the "a" (it is hardcoded though, so uses actually can't change it), but one must be very careful here, since the reaction to changes in speed will get worse.

bigboipete commented 1 year ago

First of all to understand the formula: "a" is a given number of taps, right?

If I'm not mistaken here's my quick calculation concept, that would lead to a more stable bpm display over time (could have flaws, it's literally a "quick concept"...).

The actual very high change-rate of the bpm value on the display is the issue to me. If my assumptions and conclusions about the mechanism is right, I would suggest to bind "a" dynamicaly with the amount of taps by just counting the taps and (if necessary) freeze it at a rather high value, so that the effect of the last_measured_time outliers compared to the actual time/bpm value decreases the longer you tap and the displayed bpm value becomes more and more stable.

It could be discussed how this would interfere with changing target bpms (tap speed) while tapping and whether a reset mechanism would be necessary or not. But a reset could be also integrated in the logic: If the app detects n (significant) outliers in a row, it could be considered a new intended speed, resetting "a" and re-starting calculation.

If "a" is a different value, could you please clarify?

Btw. do you want me to put this topic in another issue? It doesn't have anything to do with the "Start in Realtime" request.

thetwom commented 1 year ago

First of all, I don't care about if this is here or in a separate topic :-). As long as I am able to take track of the issues, it is just fine ...

About the formula, it's different from what you understand. Lets rewrite it:

average_bpm = last_detected_bpm * a + previous_average * (1 - a) a is a constant smaller than 1.0. E.g. if a = 1/2: average_bpm = (last_detected_bpm + previous_average) / 2 Meaning that your last detected bpm, which is quite inaccurate, updates your previous average with a specific weight (a). If the weight is small (e.g. a=0.1), the last detected bpm only slightly corrects the previously computed average, and if it is very large (e.g a=1), the last detected value will more or less define the new average.

What you are suggesting is: average_bpm = (last_detected_bpm + bpm_before_last_detected_bpm + bpm_before_...) / number_of_values So it is a simple average of a certain number of previous values.

What is the difference? In the current implementation, the latest detected bpm has always a higher weight than the ones before, in your suggestion all bpm-value have the same weight. What is more suitable depends on the use case. If you want to "detect a bpm from a given song with constant bpm", then your suggestion will work more stable. If you say you want to detect a bpm but it might vary with time, the current implementation is more suitable.

To be honest, I only rarely use the tap-in and mostly I use it to detect bpms of songs, where it works quite accurately. But for these cases, I don't have to quickly get a accurate value. I guess, one would have to try different implementations, weights, number of averaging values to find a good compromise.

Long story ... I find it feels more "natural", if the last value had more dominance than the previous ones. But I might be an option to allow the user to change the parameter a. Maybe in a few days or weeks, I find time to solve the current "realtime issue", and could also expose the parameter, so you could give it a try.

One word about bpm-changes: I didn't talk about this earlier, but this is handled separately. Averages are computed only over "similar" values ...

darrellenns commented 1 year ago

The start lag is a significant issue for me as well. Often I need to start a click mid-song, but that's pretty much impossible with the lag.

Regarding the tap tempo filtering - the two formulas discussed basically amount to two different types of low-pass filters. The current one is a single pole IIR filter, and the proposed plain average one is a FIR filter. From what I've seen, most tap tempos seem to be the FIR/average type, taking the average of something like the last 3 or 4 taps. But really this could just be a preference/feel thing. A FIR filter that gives more weight to recent taps would also possible - it just needs some different coefficients for each of the previous taps.

thetwom commented 1 year ago

Yes, I will definitely try to improve the lag. Didn't find time yet, but hopefully soon.

Thanks for the different view regarding the "tap tempo filtering". Of course you are right, and the better view of things would be filtering, removing the "high frequency components". As you say, in the end it should make not a big difference, but it would be interesting to know if people really "feel a difference". Maybe it would be worth to introduce something, what you call FIR filter with weights, since I guess, it is the most flexible one ...

thetwom commented 1 year ago

I did move part of the player-startup work somewhere else, so that the lag should hopefully be less. Would be interesting hear if this already helps for you: v4.5.2-rc1.

thetwom commented 1 year ago

Another update, with some lib updates v4.5.2-rc2. For me, the latency between pressing play and playing the first note seems to be short enough now ... lets see what our experience is ....

darrellenns commented 1 year ago

On my Pixel 5 with v4.5.2-rc2, there is still way to much lag to start it on time. I'd estimate it's about 250ms of latency (an eighth note at 120bpm).

Just a thought - is it waiting for the animation of the play button to complete before starting? It seems like the time of the animation is very close to the lag time. That could just be a coincidence though.

thetwom commented 1 year ago

Thanks for testing. This is of course not the feedback I was waiting for :-) . The playing is done on a completely different thread and it is started directly after "releasing" the button. I guess, this happens consistently and not just sometimes?

This is of course quite difficult to test now, but I will have a further look. Maybe it is worth testing without animation ... I will ask for feedback, as soon as I figured out more ...

thetwom commented 1 year ago

Did you actually see any improvement compared to the older versions?

darrellenns commented 1 year ago

Yes - it was around 500ms in 4.5.1.

thetwom commented 1 year ago

Is the Pixel 5 already on Android 12? Or do you use any other OS?

darrellenns commented 1 year ago

Yes - Android 12 (Google firmware - not a custom ROM)

darrellenns commented 1 year ago

I also just tested in android studio emulator with a local build of the master branch. It's a bit better there, but still significant. Around 150ms. I used Pixel 5 with Android 12 as the emulator device as well.

thetwom commented 1 year ago

I tried around a bit and also wrote out some log messages, which report a time stamp. For different devices the reported lag is well below 100ms. However, this is the time from releasing "play" up to writing to the first data to the audio sink, which all which I can control to some extent.

If you can run the code in android studio, maybe you can also measure, so that we can at least get some insights, where it lags in your case. You would have to check out the logtime-branch and install it. Then in the logcat, filter for timecheck and you should get some logs similar to: grafik To the left, you see the time stamps for the different logs: ... play pressed: This is the time, when you release the play-button ... writing first samples ...: This is the time, when we write the first data to the audio sink In the example, the time inbetween is ...31.186 - ...31.133 = ~50ms, it was recorded on Android 12 in the emulator.

I wonder where most of the time is spend in your case. I must admit, that I don't know how accurate the log time stamp is, but guess it's good enough.

Note, that the play-button logging is only installed for the play-button on the main-screen, but not in the scenes-screen play button.

thetwom commented 1 year ago

Here is a new testing release with adapted tap-in. Would be nice to know, if this behaves better: v4.6.0-rc1. The latency between pressing play and playing the first note is not changed compared to the previous rcs ...

jpggithub commented 1 year ago

I tried the v4.6.0-rc1.

The latency is imperceptible for me (Lenovo P2, lineageos18.1, Android 11 without gapps).

thetwom commented 1 year ago

thanks for testing. Good to know, that the latency is much improved. Still it would be interesting, what takes so long as @darrellenns experiences. At least, I could release the new version and try improving the situation, when I have more data. But for now I still have a tap-in bug to fix. Hopefully, I can finish this soon and then release ....

thetwom commented 1 year ago

I also just tested in android studio emulator with a local build of the master branch. It's a bit better there, but still significant. Around 150ms. I used Pixel 5 with Android 12 as the emulator device as well.

It struck me recently, that the sound output path has some latency. Depending on the device this can indeed be in the range of 100ms and more. Especially, for bluetooth it can be significantly more. So I fear, that no further improvement is possible.

bigboipete commented 1 year ago

Apart from what's technically feasible, the audio latency at start is a real downside of the app. Even though I don't need it too often, it makes it impossible to start the metronome in sync with a song playing e.g. from another audio source to evaluate its BPM value.

Another way to circumvent this would be a smoother display of BPM while tapping (see my posts above) i.e. in a way that it becomes more and more stable towards a kind of "final" value.

From my perspective the usecase of finding an almost exact (!) BPM value of a playing song can currently not be achieved with the app. That's a real pitty, because other features, like the whole scenes thing, are in an excellent state.

thetwom commented 1 year ago

This answer is a bit surprising to me. The startup latency was decreased substantially, and that was also the feedback in this thread. If this is still a problem, I would not have closed it. Are you using the latest version? Did you realize an improvement? If not, it would be of course o interest to see why is that. 100ms seems to be something which can be hardware specific. If you are using bluetooth, I guess it can be much more.

Regarding the tap-in logic, I changed it for a simple average quite a long time ago, but recently it used a maximum of 5 values as average. We could of course try to increase this for a test and see if this helps. Having an almost exact bpm of a playing song, and allowing the metronome to follow it is of course not really easy, since any remaining error will accumulate. Here you are of course right, that you would need many many clicks.

bigboipete commented 1 year ago

My apologies, first, because I'm actually still on 4.6.1. as of Nov 2022. And second, if my post sounded rude somehow, that wasn't my intention. It was rather meant like a "famous last words" for the closed thread (which obviously failed big time).

4.7. is not yet available on F-Droid which is my everyday source for app updates (which also skips all pre-releases). So, I will get back here, as soon as I got 4.7. on my phone.

Btw. I never use Bluetooth with the app. Sound always comes straight out of the device's speakers. And my perspective on the latency and tap/bpm display is certainly heavily biased by "my" usecase of finding the BPMs of an unknown song. Of course there might be other usecases that are perfectly covered by the current capabilities of the app.

Anyway now I know where I'm at and will definitely check 4.7 and upcoming 4.8! And thank you very much for still taking care of every half-baked feedback to improve the app.

thetwom commented 1 year ago

No need for apologies. Obviously the problem is not solved for you, so it was more like a misunderstanding.

Regarding the version, 4.6.1 is fine, newer versions won't really change things regrading tap in and latency. So were there any improvements since thread start for you (somewhere around 4.5.2)? How big is the latency now? Did you experience less latency for other sound-related apps?

Regarding tap-in, as said, we could think of trying around with larger averaging values, but then I would depend on your feedback. Just one more question: To play to along to a song exactly, it would require that you can actually set the right tempo. The standard settings of the app has a resolution of 1bpm, so if the song is somewhere between, (e.g. 120.3), then you will never be able to exactly synchronize. The app allows in the settings to increase this resolution but still it would require to match the resolved values ...

bigboipete commented 1 year ago

Yes, of course. The latency improved along the way. I'm not sure if I get you right, when asking about latency on other sound-related apps. Do you mean any or other metronome apps?

I tried Chubby Click, which has probably the same latency as your app. If you are talking about other sound-related, but not metronome apps, these doesn't have the requirement of a "sync start". So I never evaluated latency there, because it simply doesn't matter to me.

After getting into the topic again, I want to detail some figures from a drummers perspective. All that is completely apart from what's technically doable in a regular Android device.

Tight drumming, i.e. drummer following click, is at max playing around a few hundredth of a second off the beat. Everything beyond, at least more than one tenth of a second, starts sounding sloppy and not precise anymore.

In general these time figures for "being on the beat" also would apply in terms of latency, if you want to do a precise manual sync-start to evaluate a song tempo (after narrowing it down by tapping to a range of ~5 BPM). And the same also applies to a sync-start in the scenario described in my initial post (check a playing musician with click). And apart from the bare latency figures, but for your testing: If you try it, you will instantly "feel" whether this scenario is working or not.

Additionally regarding my feedback on tap-in, it comes from the same scenario: With the current change-rate of the resulting BPM values while tapping, it's very hard to narrow a potential target-bpm-range down to 5 or even lower.

Finally a resolution of 1 bpm in the app is sufficient from my experience. If a song really has a quantized speed of 120.3 bpm, you would probably end up measuring 120 bpm. It would take probably half a song to check and notice the divergence. Such an "outlier" shouldn't bother, but on the other hand I do like to hit it with the given 1-beat-precision.

Again this is only my main usecase. Others might have other scenarios of app usage in mind and get along with the current app-state totally fine.

thetwom commented 1 year ago

Thanks for the detailed answer.

I asked about any sound app, since as mentioned earlier, the main latency left seems to be the latency of the sound system, something which (to my knowledge) cannot really be changed at least with the standard provided tools for programming an app. Now, in my experience it is quite risky to say "it cannot improved" anymore, since there might be solutions I don't know about. And the first step in this direction of knowing of being wrong is to see other apps which do a better job :-).

~5bpm tolerance is quite a lot, fully agree. And that is where feedback becomes extremely important.

Thinking about it, if you a simple algorithm and want to accurately analyze high speeds, say 240bpm, and assuming you can tap in with 0.2s accuracy, you might have to tap in for 30 seconds (over 100 beats!) and more to get stable results. For 120bpm, it would be only half the time. On the other side, high number of beats are prone to missing a beat, and not detecting that kills the precision. So, in the end it appears more difficult than I expected :-).

bigboipete commented 1 year ago

To understand the reasons behind the current implementation and the chosen "tap formula", that delivers these high change rates and outliers on the displayed bpm figure, I wonder what is the use case that led to the decision implementing it the way it is now?

thetwom commented 1 year ago

... the decision implementing it the way it is now ... The current implementation is quite straightforward, simple and I did not do any precision estimations, to realize that a precision of 0.5% and better is not so easy to achieve with a few taps :smile: I didn't do further research here, but I would expect that advanced statistical methods are needed here ...

thetwom commented 1 year ago

Ok, here is now an improved tap-in behavior v4.8.0-rc3:

thetwom commented 1 year ago

I did a few more changes and released the latest version v4.8.0. Should be available on fdroid and play store soon. Issue persists, that you need a lot of taps for obtaining accuracies of 1% and better :-).

bigboipete commented 2 months ago

Tapping BPMs looks much smoother in 4.11.0 than last time I checked it.