Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding RadioLanewayCrossfade transition #13750

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

davidlmorris
Copy link
Contributor

@davidlmorris davidlmorris commented Oct 10, 2024

RadioLanewayCrossfade transition.
See AutoDj: New transition mode 'Radio Laneway' #13716.

This transition mode is designed for social functions, especially playing popular music, and is intended to sound like a commercial radio station. For this purpose, it is vastly superior to existing transmission modes, and for cold endings especially, it consistently performs better than most people I suspect (well, me at least!).

This adds two new Cues, FadeIn and FadeOut. (cf analyzersilence.cpp pN60dBSound and kSilenceThreshold.) The FadeIn range is the time from the Main cue point (or first sound if the MainCue is not set) to the first time a track reaches kFadeInThreshold which is set to -27 dBV (or 0.0447f). The distance between these is the fadeInLength. The FadeOut range is from the last time a track was at kFadeOutThreshold which is set to -12 dBV (or 0.2511f), to the last sound. The distance between these is the fadeOutLength. The values are selected based on "sounding good" while reducing the chance of pathological times for fade-ins or outs. Note that tracks always start a full volume (cf. TransitionMode::FixedStartCenterSkipSilence #13628).

This transition mode does the following:

  1. If a playing (from) track has reached the FadeOut cue start, then a crossfade starting center is initiated, with the transmission time taking the fadeOutLength, or value from the spin box (fixed value), whichever is smaller. This fits most cases and provides a smooth crossfade on tracks with a fadeout, and excellent timing when a track has a sharp or cold ending.

  2. If the next (to) track has a fadeInLength greater than 0.25 seconds then the next (to) track starts a fadeInLength value (playNextPos) earlier than the current (from) deck fadeOutStart. This covers the case where we have a slow fade-in of a song, for example, when Boston's "More Than A Feeling", comes next after a song with a cold start, removing the perception of a gap, while preserving the cold ending. (In this example it is about 3 seconds earlier, though the new track is not perceptible during a cold ending). Note that the crossfader holds in the center while the next (to) track starts, and starts moving the fader once fadeOutStart is reached. If the fadeOutLength + playNextPos difference is greater than the spin box, then revert to item 1 above using (fixed value) calculated from fadeOutLength.

  3. If the next (to) track has a duration of less than 15 seconds then this track is likely a jingle or sample. In this case, the fadeInLength is 0 and the fadeOutLength is 1. This way we have a fast neat transition while maximizing the time for the next track to load.

Adding RadioLanewayCrossfade transition.
See mixxxdj#13716
(Wow this is fussy!).
More style issues.
Yet more code style issues (Space at end of line.)
More Code tidy
Missing case in cueinfo.cpp
avoiding a DEBUG_ASSERT(!"Unknown Loop Type");
fix up tooltip
Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't bother looking at the autodj for now.

src/analyzer/analyzersilence.cpp Outdated Show resolved Hide resolved
Comment on lines 55 to 68
template<typename ForwardIterator>
ForwardIterator last_fade_in_sound(ForwardIterator begin, ForwardIterator end) {
return std::find_if(begin, end, [](const auto elem) {
return fabs(elem) >= kFadeInThreshold;
});
}

template<typename Iterator>
Iterator first_fade_out_sound(Iterator begin, Iterator end) {
return std::find_if(begin, end, [](const auto elem) {
return fabs(elem) >= kFadeOutThreshold;
});
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a copy of the above for very little reason. Consider deduplicating by simply taking the Threshold as a parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second one goes backward, while the first is strictly forward. I spent a long time playing around with these, trying to determine why I was getting weird results (and so I was keen to keep them separate). It turns out AutoDj processing can take place before a track is fully analysed, so I am less invested in that now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions are identical. In this context ForwardIterator and Iterator are just names that resolve to the underlying iterator implementation when called. So there is no difference in terms of what function can be called when. A std::reverse_iterator is a "bidirectional iterator" and thus also a "forward iterator".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::find_if only requires a forward iterator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you like I'll refactor all of those into one, including the db60.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prolog isn't really a functional language though, its declaritive. If you're coming from the performance angle, I can recommend you to learn rust. Its amazing how nice code can look if its functional-ish while still being blazingly fast. The bulk of code is primarily written because its old and there was nothing better back then thats still performant. That is not the case anymore today. C++ is also a multi-paradigmatic language which includes all the necessary tools to clarify as functional as well. Anyways, it's not very productive to argue about this here. I was just trying to encourage you to broaden your horizon a little bit ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are technically correct. As I remember it at the time Prolog was considered to be like a functional language... and the way it works has similarities at least. Haskal was a thing. There was an elective for Lambda Calculus and Haskal. Looked too much like math (and formal logic which I did in philosophy) to attract my interest. I did Networking and Databases. This was the late 80's early 90's as a mature-age student.

As for Rust, this is one I have already downloaded and considered for my microcontroller projects, mostly in the wake of Counterstrike. I've backed off a little since then because it doesn't have the lib support yet (for that area). (And let's face it it is a tad faddish at the moment - yes I am saying that like it is a bad thing - even if Rust will stick.)

As for my horizons, you do realise that I am in my 60's and that I have been doing this for longer (probably) than you have been around, having first programmed on a Z-80 back in the very early 80's, right? When I started PC's weren't even a thing. The Commodore 64 was a year or two away. And, I had to build my first computer. Back then you could understand everything that was happening inside the computer. I nearly built the EDUC-8 back in 1975 when I was still in high school, but could not afford it, and I also doubled the utility of it (I was right - though it would have been a great learning exercise. And it was complex (for the time). Switches and lights... no KB. Back then I was focused on sound and mixers. And I was more results-focused. I wanted to mix and sound like the Radio (strangely that hasn't changed!) In the next seven years, computers went from unobtainable to commonplace - the Commodore 64 I purchased in 1983 was my favourite.)

There is a time in your life when you are young when the "latest and greatest" (= fashionable) always seems to be the best. And, old fogeys don't seem to adjust well. However, the 'latest and greatest' doesn't always work out, even if the latter statement is still probably true. 'Amiga' anyone?

You clearly know your stuff, much more than I did at your age. (I am making assumptions about your age - please correct me if I am wrong). Since you have taken the liberty of recommending things to me, how about you take a look at Steve McConnell's "Code Complete"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your recommendation. I've studied some of Robert C. Martins books so far but "Code Complete" also seems interesting. I will try to get a hold of a copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've read some Uncle Bob stuff. I should check out his book(s?). What I liked about Steve's work was it was so concrete (practical), and backed up by evidence.

So to the matter at hand. Is there anything else that we need to do to get this merged?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I currently don't have the time to give this yet another proper review. Please consult with @daschuer on the next steps.

Comment on lines +158 to +160
"volume last falls below -12Db or at the spin box setting which ever\n"
"is lower, and potentially starts the next earlier if it starts below\n"
"-27Db.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its very easy to change the threshold in the analyzer but forget to update it here. Do you think its valuable to have the thresholds documented here or can we just omit them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I nearly made it a more general statement (e.g., 'a certain level'), but I figured someone would want to know the exact values. It may well be worth making it more general for the reason you suggested.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had also the demand to configure the threshold.
But thats is IMHO only a workaround for the replay gain/loudness issue. A perfect solution would be to use a certain Loudness level after applying replay gain. Did you consider to implement that?

src/library/autodj/dlgautodj.cpp Show resolved Hide resolved
@davidlmorris
Copy link
Contributor Author

didn't bother looking at the autodj for now.

I suggest you do. This transition is groundbreaking.

and removal of commented code.
Yet more code format issues.
Yet even more code style issues.
Comment correction
Adjust lasttSampleBeforeFadeOut (and avoid a regression with lastSoundSample) to bring it into line with mixxxdj#13545
(I assume I was working off an older code base.)
@davidlmorris
Copy link
Contributor Author

I noticed #13545 which had fixed a regression issue that was more recent than the code I was working from, so I have "re-re-verted" this so the issue won't suddenly appear again. In my testing, though, I didn't see this problem occur.

@davidlmorris
Copy link
Contributor Author

@daschuer ping

@daschuer
Copy link
Member

I am a bit puzzled how this iteracts with manual set intros and outros. While this works reasonable good by default for radio mixes, it will probably fail for tracks with club intros and outros or track with ambience noise at the beginning or end.

This is IMHO a perfect solution for the default case where a track has only an IntroStrat and an OutroEnd Cue set.
Is this new analyzer suitable to set the IntroEnd and OutroSTart Cue?

Did you consider to priorize the manual set intro and outro, to give the user the power to adjust exceptional tracks?

// the way Mixxx works).
// Don't change these to const as they are used only to feed the
// fade thresholds which are consts below themselves while we work
// out which one is best, and you'll just be adding to exe size.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused constexpr are not contribute to the EXE size. There might be only a "unused" warning.

Now we have unused macros instead. I would prefer to use constexpr and comment out the unused versions to mute warnings. This would be more expensive than the comment.

At least the comment needs to be corrected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole thing is a comment in case the chosen values are not the best. I should just delete the lot. I believe I have good values here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit puzzled how this iteracts with manual set intros and outros.

You asked about this before. It does not interact or use manual set intros and outros cues at all. It runs as discussed in AutoDj: New transition mode 'Radio Laneway' #13716

I've yet to find a track where it isn't 'good enough', and in some cases does consistently better than me (where I might miss 1 in 10 cold endings), so I'm not sure I agree with you about not working with ambient noise at the beginning or end, but since I can't claim any great familiarity with club music I will leave that for you to decide.

Why not try it and see?

return ret;
}

// Find the number of first sound sample where the sound is above kFadeInThreshold (-27db)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Number" is ambiguous here better "index"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Comment on lines +158 to +160
"volume last falls below -12Db or at the spin box setting which ever\n"
"is lower, and potentially starts the next earlier if it starts below\n"
"-27Db.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had also the demand to configure the threshold.
But thats is IMHO only a workaround for the replay gain/loudness issue. A perfect solution would be to use a certain Loudness level after applying replay gain. Did you consider to implement that?

FadeIn = 9, // First time sound reaches a certain level
// (cf analyzersilence.cpp); not shown to user
FadeOut = 10 // Last time sound reaches a certain level
// (cf analyzersilence.cpp); not shown to user
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add also a comment about the level itself.
Is it the last time of full volume, or just an alternative silence threshold?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A perfect solution would be to use a certain Loudness level after applying replay gain. Did you consider to implement that?

I did, but I was struggling to see how the track could have silence markers and not be normalised. I haven't quite understood the order of operation, and how often the analysis section is called. And importantly if normalisation is before or after silence detection. It seems to be resolved several times as far as I can tell... (mostly from the spurious results I was getting while a track was being loaded). All of which says this probably needs to be considered well before implementation. Right now, I'm not sure I know enough. But I am happy to take a look. Should this be a discussion in chat, since this is a separate topic?

Please add also a comment about the level itself.
Is it the last time of full volume, or just an alternative silence threshold?

It is the last or first time the sound reaches a certain level. Since full volume is subjective, I thought this was more precise. It is like an alternative silence threshold but is different because there are two different threshold values.

(I guess this isn't clear - so perhaps I should add something like: So to be clear, this is the last sample in the track where the volume was above the FadeOut level, and the first sample in the track where the sound volume was above the FadeIn level.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    FadeIn = 9,     // Range from the start of the track to the first sample in
                    // the track where the sound volume was above the
                    // kFadeInThreshold (cf analyzersilence.cpp);
                    // not shown to user
    FadeOut = 10    // Range from the last sample in the track where
                    // the sound volume was above the kFadeOutThreshold
                    // to the end of the track (cf analyzersilence.cpp);
                    // not shown to user

#define N_25DB_FADE_THRESHOLD 0.0562f
#define N_27DB_FADE_THRESHOLD 0.0447f
#define N_30DB_FADE_THRESHOLD 0.0316f
#define N_40DB_FADE_THRESHOLD 0.01f
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to consider how to handle future changes.
Since the thresholds are used fully automated without any user feedback a layer change if the values will mess up the database with old and new values.
Therfore a kind of "versioning" is require. That on case of a value change the old one found in database is no longer used.

We can easily add new cue type for this. But we need to prepare the code in a way that this is clear for future reader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(My intent with those comments was to have a placeholder for the values while I played around. Assume those are deleted by the time someone is making a change here. They really are not necessary now)

But I understand the main point. It is the same problem as the -60db setting.

There would be a cost if we do a lot of processing every time a track is loaded. Should we have a Cue value that is simply a version flag associated with every track, which could be checked by a future version to fix any issues? No cue = 4.x or earlier?

For the user adjustment, I suggest that the next step would be to add some UI elements to make this all visible and allow the user adjust. We also discussed this. Happy to work on it as a next step (when I get time). But I think this might be a future version change, based on any perceived need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I sent that I realised 'Cue value' may not make sense. But at least have a flag with the track data that shows a version number. Or, alternatively have a cueinfo.version setting for each cue (though that smells of data duplication). After a very quick look at the code base - noting exceptions for things like "Serato's tag data", I realise even this might be problematic. So this too is also, worthy of a chat discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what changes that have been requested remain incomplete at this stage.

Fix some comments
Line endings yet AGAIN!
Comment changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants