Applying an action only to non-integers

I use a BPM analysis program (MixMeister) to add BPM tags to my dance-based tracks. It writes the BPM value to 2 decimal places.

This is nicely accurate, but it does not match the format of the BPM values generated by other sources (which are usually always stated as integers).

In order to maintain consistency with other BPM tags, I wanted to round the decimal BPM value to its nearest integer.

I searched around and found a great action for doing that. I then added command to store the original decimal BPM value under a different tag (for when I might need the more accurate figure for syncing / beatmatching etc.).

Here's the code I'm using:

[#0]
T=5
F=BPM_MIXMEISTER
1=$if2(%BPM_MIXMEISTER%,%BPM%)

[#1]
T=5
F=BPM
1=$left($replace(%BPM%,'.',%DUMMY%)'00',$add($len($div(%BPM%,1)),2))

[#2]
T=5
F=BPM
1=$ifgreater($mod(%BPM%,100),49,$add($div(%BPM%,100),1),$div(%BPM%,100))

This rounding aspect works great (as long as the BPM has no leading zeros), but the simple command I've added to retain the MixMeister generated decimal BPM value is too basic.

It copies any BPM values to the BPM_MIXMEISTER tag, even if it is already an integer or if it's a non-numeric character (e.g. Beatport Web Action Script and others sometimes insert a "?" as the BPM value when the track's BPM is unknown).

I'd therefore like to know:

1. How can I adjust the code so that if the BPM value is only copied to my new BPM_MIXMEISTER tag if it's 'numeric' and also has a decimal point with numbers after it?

In other words, copy values such as these: 126.000 , 104.3 , 80.0 and replace BPM with integer
but do not copy values such as these: 126 , 80, ? , t.dr , 121.g , xyz

2. How does the rounding part of the script need to be adjusted so that it works even if the BPM value has leading zeros?

For example: one track had a BPM value of "091". Running the above script changed the BPM value to "9"

Just a word on accuracy:
as the value is called "beats per minute" it means that if you cut off the decimals of a beat in a 3 minute track you are less than 3 beats off the real value. But as the value is an average anyway the integer value should be just as good as the rounded value. So no-one will notice.
I would say this is a typical case of pseudo-accuracy.

So: my action to clean up the BPMs looks like this:
Replace with regular expression for BPM:
Search string: .\d\d$
Replace with:
(leave empty)

This sounds like misunderstanding and misuse of the action group.
The action group expects the existing tag-field BMP_MIXMEISTER, which contains a decimal number string in the format "123.45" as the Mixmeister BPM value.
If this tag-field does not exist (function $if2()), then the tag-field BPM is read, trying to get an already existing standard BPM integer value.
This algorithm makes sure, ...
... that an already existing BMP_MIXMEISTER value will not be overwritten by accident.
... that an already existing BPM value will be saved into the tag-field BMP_MIXMEISTER, in case of a missing BMP_MIXMEISTER value.
The meaning of the tag-field BMP_MIXMEISTER is, to work as a backup tag-field for the original Mixmeister value, ... or ... as you have found out, for any other value, which has been stored already in the existing BPM tag-field, ... therefore ... you should avoid to apply this action to a file, which has not been treated by the Mixmeister Anaylzer.

Leading zeroes can be removed by ...

$num(%TAG_FIELD%,1)

... or ...

$regexp(%TAG_FIELD%,'^0*',)

... or ...

$trimLeft(%TAG_FIELD%,'0')

The tag-field BPM expects and allows per standard definition only an integer value, in the format of a numeric string value, that means, only whole numbers are allowed, without leading zeroes, without any non-numeric characters.

Examples:

$regexp('121.g','^\d+\.?\d*$','match') no match $regexp('123','^\d+\.?\d*$','match') yes $regexp('123.','^\d+\.?\d*$','match') yes $regexp('123.45678','^\d+\.?\d*$','match') yes $regexp('123.45678','^\d+\.?\d{3}$','match') no match $regexp('123.456','^\d+\.?\d{3}$','match') yes

Example:

BPM <== '000123.4' BPM <== $regexp(%BPM%'00','^0*(\d+)\.(\d\d).*$','$1$2') BPM <== $ifgreater($mod(%BPM%,100),49,$add($div(%BPM%,100),1),$div(%BPM%,100)) BPM ==> 123 BPM <== '000123.5' BPM <== $regexp(%BPM%'00','^0*(\d+)\.(\d\d).*$','$1$2') BPM <== $ifgreater($mod(%BPM%,100),49,$add($div(%BPM%,100),1),$div(%BPM%,100)) BPM ==> 124

Take this as a replacement for the code example in post #1.

DD.20150119.2146.CET, DD.20150120.0908.CET

Thank you for such a detailed response DetlevD.

I'm sure this will work great (just as your other suggestions have done) but Unfortunately I've been struggling to integrate these changes into my existing BPM rounding code.

I tried pasting into the .mta file (above the line of code as shown in your second set of examples) but that just broke the action. I also tried from the Action Groups window, but again I just seemed to break the existing action.

Forgive my ignorance but could you elaborate on how (and where) exactly the new lines of code should be integrated into the existing BPM rounding code.

Many thanks.

Begin Action Group MyMusic#06 Round BPM to integer

Action #1
Actiontype 5: Format value
Field ______: BPM_ORIG
Formatstring: $if2(%BPM_ORIG%,%BPM%)

Action #2
Actiontype 5: Format value
Field ______: BPM
Formatstring: $regexp(%BPM%'00','^0*(\d+).(\d\d).*$','$1$2')

Action #3
Actiontype 5: Format value
Field ______: BPM
Formatstring: $ifgreater($mod(%BPM%,100),49,$add($div(%BPM%,100),1),$div(%BPM%,100))

End Action Group MyMusic#06 Round BPM to integer (3 Actions)

DD.20150120.0939.CET

I have doubts that this calculation is accurate.
Depending on the bpm value there can come up differences, by rounding down or rounding up, that can cause differences for the calculated play length of up to 10 seconds or less or more, depending on the beat per minute and the entire length of a song.

This works too: $num(%BPM%,1)
This returns all the digits from the left edge of the number string, just before any non digit character.

DD.20150120.1414.CET

Thank you so much. I've got it working now, and leading zeros no longer cause any problems.

However, there are still some unwanted behaviors, so I hope you don't mind me asking you for further help to resolve these last few remaining issues.

In my original post I mentioned the following: "How can I adjust the code so that if the BPM value is only copied to my new BPM_MIXMEISTER tag if it's 'numeric' and also has a decimal point with numbers after it?"

Perhaps my statement was misleading because I referred to the Mixmeister tag, but in reality what I was trying to say was that I didn't want any new BPM-related tag at all to be created if the value in the BPM tag is not a numeric value that also contains a decimal point with numbers after it.

So, if the BPM value is a number that contains a decimal point followed by further numbers, then copy the existing value to a new BPM-related tag (e.g. BPM_ORIG) and then replace the existing BPM value with its nearest integer (i.e. apply the rounding code). But for everything else, do not create any new BPM-related tag.

The remaining action that is taken on these 'everything else' values will depend on what they are, but in none of those instances should a new tag be created.

Here's what I'd like:

  • For integers with a trailing decimal point: Strip the decimal point from the value
  • For integers from 20 up to 200: do nothing at all
  • For all integers outside the 20 to 200 range: Delete the BPM tag altogether
  • For values containing non-numeric characters (other than a decimal point): Delete the BPM tag altogether
At the moment, non-numeric BPM values (such as "?" and "unknown") are being written to a new tag (in this case BPM_ORIG) and then the BPM value is written as "0". Neither of these actions is useful, so I'd like to prevent this kind of thing from happening.

Examples:

122. not copied to new tag, trailing decimal stripped = 122
122.00 copied to new tag, rounding applied = 122
122.g not copied to new tag, BPM tag deleted
-122 not copied to new tag, BPM tag deleted
ab122 not copied to new tag, BPM tag deleted
222 not copied to new tag, BPM tag deleted

See above post #3, Examples ...

SOME_TAG_FIELD <== $if($eql('match',
               <!--coloro:#800080--><span style="color:#800080"><!--/coloro-->$regexp<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->(<!--colorc--></span><!--/colorc--><!--coloro:#0000a0--><span style="color:#0000a0"><!--/coloro-->%BPM%<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->,<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->^\d+\.?\d*$<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->,<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->match<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->)<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->)<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->,<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->do this here<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->,<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->do that here<!--colorc--></span><!--/colorc--><!--coloro:#008080--><span style="color:#008080"><!--/coloro-->'<!--colorc--></span><!--/colorc--><!--coloro:#000000--><span style="color:#000000"><!--/coloro-->)<!--colorc--></span><!--/colorc--></b><!--fontc--></span><!--/fontc--><!--sizec--></span><!--/sizec-->

... could be ...

Begin Action Group MyMusic#06 Round BPM to integer

Action #1
Actiontype 5: Format value
Field ______: BPM_ORIG
Formatstring: $if2(%BPM_ORIG%,%BPM%)

Action #2
Actiontype 5: Format value
Field ______: BPM
Formatstring: $if($eql('match',$regexp(%BPM_ORIG%,'^(\d+.\d*)|(\d+)$','match')),%BPM_ORIG%,)

Action #3
Actiontype 5: Format value
Field ______: BPM
Formatstring: $if(%BPM%,$regexp(%BPM%'00','^0*(\d+).(\d\d).*$','$1$2'),)

Action #4
Actiontype 5: Format value
Field ______: BPM
Formatstring: $if(%BPM%,$ifgreater($mod(%BPM%,100),49,$add($div(%BPM%,100),1),$div(%BPM%,100)),)

End Action Group MyMusic#06 Round BPM to integer (4 Actions)

DD.20150120.1808.CET

I am not sure if we speak of the same thing. To em it looks as though you refer to the average bitrate in track with variable bitrate. For this I would agree with you.

Yet for the "beats per minute" calculation (or is it counting?) I still think that if you are 1 beat per minute short in your count, you will only be 3 beats behind after 3 minutes. And this deviation is probably accurate enough.

BPMAnalyzer does not say anything about the accuracy.
Unless you get values without decimals which is usually the case with drum-machine supported music, you should consider a certain jitter in the timing of the drummer.
So in the case of "hand-made" music you should grant a certain variations in tempo.

Or the composer changes the tempo on purpose.
Take for instance John Miles' "Music" which starts at a slow 90 BPM, only to climb to some 130 BPM in the fastest part and dropping to an uncountable classical part which feels like the starting 90 BPM (yet BPMAnalyzer counts an amazing 147 BPMs)
BPMAnalyzer writes a value of 128 BPMfor the whole track.

Hmm, yes ... I have tried to validate my words from above post #6, ...
but anyway I cannot remember what I have calculated in my mind while I wrote the lines.
Probably I moved the decimal point too far to the right side.

You are right, the difference of a fractional BPM value, between rounded down and rounded up, floor and ceiling, is maximal 1 beat, regardless of the given tempo.

For the tempo from 60 to 61 bpm that means a differential time period of about 16,4 ms per minute.
If someone plays the song, side by side, one with 60 bpm and the other with 61 bpm, it will cause a wonderful phase shifted flanger sound or short echo.

Back to the rounding problem, hmm ... is it really a problem anyway?
For normal listeners there should be no problem, ...
but maybe for the DJ-Mixer, who wants to cut and mix exactly on the beat, ....
without jerking in the tempo.

Anyway, this theme has something of an academic discussion.

DD.20150120.2211.CET

OK, I created the action group as specified above, but it's still not working correctly.

I tried testing with various unwanted BPM values to see how they would be handled, but these 'non-compliant' values were still being processed and/or written to a new tag.

Was there something else that I was supposed to do?

Hmm ... my last proposal was designed to save at first the existing BPM value as is (which has been entered manually or stored by Mixmeister Analyzer or so ...), into the BPM_ORIG tag-field to have a backup of the original value (action #1).
Then follows the evaluation whether the existing, previously backuped, BPM_ORIG value matches the given rule (action #2).
If there is no match, then the tag-field BPM will be removed.
If the tag-field BPM exists, then the BPM value will be prepared for the following rounding step (action #3).
If the tag-field BPM exists, then the rounding will be applied (action #4).

Add this action to the action group from above post #8 ...
... to save BPM values only within the range 20...200.

Action #5

Actiontype 5: Format value
Field ______: BPM
Formatstring: $if(%BPM%,$if($or($less(%BPM%,20),$grtr(%BPM%,200)),,%BPM%),)

DD.20150121.1128.CET

Thank you for your explanation. It has helped me to understand one of the unexpected results that I am seeing.

My original action (as stated in the first post in this thread) made a copy of the Mixmeister value not just to backup the original, but specifically because it contained useful information (i.e. a precise BPM analysis accurate to 2 decimal places). This detail would be lost as part of the rounding process, so making a copy of it allowed that extra information to be retained.

It was always my intention that only valid non-integers should be backed up prior to rounding. If it's an integer, or if it's any kind of 'illegal' value, then I did not want it copied or backed up at all. Therefore I was surprised to see 'junk' BPM values (such as "?" or other non-numeric strings) being written into my test music files, because such values would never be of any use to anyone.

When I kept seeing my 'illegal' test values being written to the BPM_ORIG tag, I assumed that something was not working correctly, but it turns out that this was by design because you intended for every value to be backed up regardless.

The reason I have wanted my action groups to be quite thorough with 'validation' (to exclude nonsense or illegal values) was so that I could be confident that any BPM value that was being discarded was genuinely of no use. By removing the value completely, the blank field would alert me to the fact that the BPM originally assigned was not trustworthy and therefore a reliable BPM analysis needs to be done. I would then use Mixmaster, at which time a useful value (that's worth keeping) would be added, and that value would be backed up when the rounding action is applied, because it would meet the validation criteria of being a valid number that was fractional and not whole.

That's what I was trying to explain when I said this in post #7: .... So, if the BPM value is a number that contains a decimal point followed by further numbers, then copy the existing value to a new BPM-related tag ...... But for everything else, do not create any new BPM-related tag.

By the above statement I meant do not write a backup of any kind for invalid valies. Only numbers with fractional values should be backed up. Sorry if my wording was not very clear.

This unexpected behaviour was accompanied by some other issues. I started seeing random jumps in numbers when running the action, and also perfectly valid values were being deleted.

It turns out that the backup might be the culprit. If a file has an illegal value that is backed up to BPM_ORIG, then any valid BPM that is subsequently added will be deleted by the action when it is next run. If you delete the BPM_ORIG tag before running the action, then it seems to work ok. Also, if a file had an valid but inaccurate BPM and when attempting to correct this inaccuracy an invalid value was added, then running the action does not clear this new invalid value as expected, instead it reverts to the previous valid but wrong value.

I had no idea how the action was going to behave from one run to another, so I would not feel confident using this on my real music library.

The first problem could be easily remedied by just not backing up a BPM value unless it is first found to be a valid number that is fractional, but I don't know whether this alone would fix the other unusual behaviours that I'm also seeing.

It seems that if the action is run more than once on the same files after making corrections, then things get weird. Running the action more than once is a likely to happen though, because if I ran the action and found invalid BPM values (which the action would delete), then I would use Mixmeister to replace them with accurate values, after which I would run the action again to round to an integer, but doing that seems to cause very unpredictable outcomes.

Regarding my proposal, where BPM (calculated from BPM_ORIG) can be empty and BPM_ORIG can contain a previously backup-ed invalid value, ...
there is one cleaning step required, before or after applying the Mixmeister Analyzer, ...
which writes to BPM, ... to remove the invalid value in BPM_ORIG, ...
in order to let the $if2 clause work properly.

You are free to create other logic into your action group.
Basically it is recommended to apply a cleaning procedure before importing new valid values.
Such a cleaning procedure does not need too much work, ...
but without cleaned fields, all other algorithms and decisions do need extra, maybe time consuming, evaluations of all bad cases, which are thinkable.

I've never noticed anything like that.
Note this common rule ... 'shit in - shit out' ... ;-).

Yes. This step is recommended before re-/calculating BPM by using Mixmeister Analyzer.
But you are free to create the logic, which you need into your action group.

Yes, that is the meaning of a backup. This allows to see the difference, what was and what is.
Note: in my proposal the given regexp algorithm removes the BMP tag-field, when there is no match.
The backup field can show you why, it still holds the bad value.
If you afterwards create a correct integer or decimal BPM value, ...
and you want to back it up, while using my proposal, ....
then you have previously to remove the bad BPM_ORIG field, before applying the action group.
But you are free to create the logic, which you need into your action group.

Well, I use ths algorithm since years, and I apply it always after running Mixmeister Analyzer.
This action group runs only once, there is never the need to run it twice.
And if it would be executed accidentally again, then nothing changes.

Ok, I do not have so much old crap in the BPM tag-fields like you have, apparently.

There are no unusual behaviours to see.
The behaviour of my proposed action group is defined clearly.
The code accepts an existing BPM value, stores it for sure as BPM_ORIG, ...
then the code checks the BPM_ORIG value against the rule, ...
if the rule does not match, then the BPM value will be removed.

At this step, you may decide, to remove the tag-field BPM_ORIG too, ...
because it may be useless to have a failing value stored as a backup value.

Because of the $if2 clause, you can run the action as many times as you like it, the result is always the same, you could say, it is idiot proof.

No, I think there is no unpredictable behaviour.
The only caveat may be, that the first step in the action group expects a valid BPM value.
So for you environment you may remove this first step and do not use BPM_ORIG, but only BPM.

Add this action to the action group from above post #8 ...
... to remove the tag-field BPM_ORIG in case of there is no tag-field BPM.

Action #6

Actiontype 5: Format value
Field ______: BPM_ORIG
Formatstring: $if(%BPM%,%BPM_ORIG%,)

Now you may run the action group twice ...

  1. before applying Mixmeister
    ... apply Mixmeister ...
  2. after applying Mixmeister

Testfiles ...

20150122.Test.BPM.zip ( 17.59K ) Number of downloads: 1

20150122.Test.BPM.zip (4.04 KB)

DD.20150122.1952.CET, DD.20150123.1120.CET

20150122.Test.BPM.zip (4.04 KB)