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

[WIP] Livemetadata PR #1675

Closed
wants to merge 79 commits into from
Closed

[WIP] Livemetadata PR #1675

wants to merge 79 commits into from

Conversation

davidhm
Copy link
Contributor

@davidhm davidhm commented May 20, 2018

This is only intended for code review atm, not to be merged.

#include "track/trackplaytimers.h"

TrackTimers::TimerQt::TimerQt()
{
Copy link
Member

Choose a reason for hiding this comment

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

Why do you wrap the QT timer 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.

Because QTimer isn't a subclass of my TrackTimer. And I need TrackTimer to mock timers, otherwise I would have to actually wait until a track is played halfway to check if the signals are correctly emitted.

@@ -70,7 +71,11 @@ Track::Track(
m_record(trackId),
m_bDirty(false),
m_bMarkedForMetadataExport(false),
m_analyzerProgress(-1) {
m_analyzerProgress(-1),
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that it is appropriate to store transient state of an application component directly inside the Track object. For example m_analyzerProgress does not belong in here and will be removed soon (#1624).

You should keep track of the tracks that are currently played in your component and manage this information there. Connect to the PlayerManager or individual decks for getting notified about which tracks you need to observe.

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 get what you mean exactly? All info I've put in track is the number of ms it has been played along with some timers to update this number and whether if the track is scrobbable or not. Doesn't this information belong to the track? Should I keep a list of the tracks being currently played and update their played time in the player manager?

Copy link
Contributor

Choose a reason for hiding this comment

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

The Track object is already huge and becomes unmanageable if every aspect is thrown in there. It should contain only persistent state that needs to be synchronized with the database and the corresponding file, but not arbitrary transient application state.

Please try to keep the state that is needed for live metadata broadcasting private and local within the corresponding component or module. Use signals/slots for synchronization instead of distributing implementation details into public, shared global state.

Think in terms of modular microservices, even in the context of a desktop application. Consume signals/notifications from other components as input and produce output by emitting signals. All transient state should be isolated within the corresponding module.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okey, I get it now. I will move my logic away from Track.

TrackTimers::TimerQt::TimerQt()
{
connect(&m_Timer,SIGNAL(timeout()),
this,SIGNAL(timeout()));
Copy link
Member

Choose a reason for hiding this comment

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

Qt tries hard to emit the timeout() signal as precise as possible.
Unfortunately this interferes with the waveforms, also living in the main thread, which really need this exactness.
As workaround, we have a less exact GUI Tick timer that fires all 50 ms.
It is [Master]", "guiTick50ms"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, this is why I used an elapsed timer with less granularity. I will use this one then.

@daschuer
Copy link
Member

It is hard to check the code, without knowing what it should do.
Can you outline the requirements on your wiki page?

Some words or drawings about the data-flow and the related classes would also be nice. This should help to avoid later round-trips, because of some design issues.

@davidhm
Copy link
Contributor Author

davidhm commented May 22, 2018

Alright, I'm gonna draw some diagrams.

m_CPXFaderCurve(ConfigKey(EngineXfader::kXfaderConfigKey,
"xFaderCurve"),this),

m_CPXFaderCalibration(ConfigKey(EngineXfader::kXfaderConfigKey,
Copy link
Member

Choose a reason for hiding this comment

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

Can't you call the function that calculates the xfader gain instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I need the correct parameters which are given by those Control objects, aren't they? I copied the code from EngineMaster.

}
QMutexLocker locker(&m_mutex);
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin();
while(it != m_trackList.end()) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you can use here to the new
C++11 range based for loop.

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 forgot, thanks.

QMutexLocker locker(&m_mutex);
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin();
while(it != m_trackList.end()) {
if ((*it)->m_pTrack == pPausedTrack and (*it)->m_pPlayer == sourceDeck) {
Copy link
Member

Choose a reason for hiding this comment

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

We use && instead of and

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why? Aren't and, not and or more explicative?

Copy link
Member

Choose a reason for hiding this comment

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

Probably yes, but we just don't use it. I am in doubt that many C++ developers even know that "and" exists.

Copy link
Contributor

Choose a reason for hiding this comment

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

...including me ;) Please don't use those operator synonyms. I've never seen anyone using them before. They seem to be supported for backward compatibility with C ISO/IEC 646 where they are defined as macros in a separate header file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okey guys, your style, your rules. But I have to say, every programming course I took first year in C++ told me to use "and", "or" and "not" because they are more expressive. And I have to agree ;)

QMutexLocker locker(&m_mutex);
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin();
while(it != m_trackList.end()) {
if ((*it)->m_pTrack == pPausedTrack and (*it)->m_pPlayer == sourceDeck) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is the deck important here?
Please add source code comments that explains this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because a track can be playing on a deck and be paused while the same track plays in another deck and not be paused. It's important that both the deck and the track match. I will add comments.

Copy link
Member

Choose a reason for hiding this comment

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

So if a track plays in two decks, with significant volume, the timer counts twice as fast?

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 just realized there's several things wrong with this code. First, it uses pausePlayedTime in slotTrackResumed, which is clearly wrong. Two, a same track should have only one timer. Otherwise, if you have the same track in two decks and keep switching the cross fader back and forth the signal will be sent much later. Thanks for pointing it out.

break;
}
}
if (allPaused)
Copy link
Member

Choose a reason for hiding this comment

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

Is this one the line, that is related to the deadlock?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

if (m_pElapsedTimer->isValid()) {
m_playedMs += m_pElapsedTimer->elapsed();
m_pElapsedTimer->invalidate();
QObject::disconnect(m_pTimer.get(),SIGNAL(timeout()),
Copy link
Member

Choose a reason for hiding this comment

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

Disconnect locks a muted internally. You should not connect and disconnect signals after initialize.
Do you use Qt5? I am suffering from bufferunderflows. Maybe this is related.
Does it still deadlock if you remove this line?
Is there a way around using a timer signal 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.

Unfortunately, there's a deadlock even if I remove every single connect and disconnect call inside that class. I tried using valgrind but it's giving me an illegal instruction error. I'm going to investigate further. Also, I don't use Qt5, I will check on that.

Copy link
Member

Choose a reason for hiding this comment

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

Qt5 will introduce new problems. I would stick with Qt4 in this case.
I will have a look into this soon today.

}
}
if (allPaused)
pausedTrackInfo->m_trackInfo.pausePlayedTime();
Copy link
Member

Choose a reason for hiding this comment

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

You crash is not a deadlock, it is a Segmentation fault, which happens when you access memory, not allocated by the process. In this case, you access memory via the uninitialized pointer pausedTrackInfo.

It is a good idea to always initialize empty pointers with "nullptr". Than you can decorate your code with
DEBUG_ASSERT(pausedTrackInfo) in various places to detect such design issues early.
If you start mixxx with --debugAssertBreak under GDB, it will hit a breakpoint, instead of just abort.
Very handy :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow, how can a seg fault keep all threads in a futex? Wouldn't it kill the process? Thanks for the help.

Copy link
Member

@daschuer daschuer Jun 1, 2018

Choose a reason for hiding this comment

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

It actually kills the process here on my Ubuntu Trusty.

Copy link
Member

Choose a reason for hiding this comment

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

Mybe we have an additional deadlock?

pausedTrackInfo = trackInfo;
for (BaseTrackPlayer *player : trackInfo->m_players) {
BaseTrackPlayerImpl *playerImpl =
qobject_cast<BaseTrackPlayerImpl*>(player);
Copy link
Member

Choose a reason for hiding this comment

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

Avoid upcasting. here it is better to put the required functions as pure virtuals into the base class.

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 didn't want to pollute the interface with functions I need in case someone complains. But I will if you're ok.

Copy link
Member

Choose a reason for hiding this comment

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

The readability of the whole code base finally counts.

void ScrobblingManager::slotTrackResumed(TrackPointer pResumedTrack) {
//Extra functional decision to only track main decks.
Deck *sourceDeck = qobject_cast<Deck*>(sender());
if (sourceDeck == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

if (!sourceDeck) { or == nullptr

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 saw the specification of qobject_cast and it returns 0, I read the standard and nullptr doesn't have to be 0, same with NULL that is implementation defined. So I went with 0 because that's what the Qt doc says. Though I agree it's more readable.

Copy link
Member

Choose a reason for hiding this comment

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

They mean the actual register value. This is relevant if you reinterpret a nullptr as integer. It is not guarantied that this is 0. It is more about type safety here.
0 is an integer literal, that is casted implicit to the nullptr type. Using !.. or nullptr avoids this implicit cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, ok, then I will use !


void ScrobblingManager::slotTrackResumed(TrackPointer pResumedTrack) {
//Extra functional decision to only track main decks.
Deck *sourceDeck = qobject_cast<Deck*>(sender());
Copy link
Member

Choose a reason for hiding this comment

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

Please avoid these kind of implication. It is better to maintain to send the desire informations explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okey.

bool trackAlreadyAdded = false;
for (TrackInfo *trackInfo : m_trackList) {
if (trackInfo->m_pTrack == pNewTrack) {
trackInfo->m_players.append(newDeck);
Copy link
Member

@daschuer daschuer Jun 1, 2018

Choose a reason for hiding this comment

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

Storing a pointer you not own is always kind of dangerous, because you don't know the live time of it.
It is better to use a shared pointer, or the group string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the player manager doesn't destroy the decks until it itself gets destroyed, and this object gets destroyed too. But I will use a group string because shared pointer implies modifying player manager.

}
}
if (!trackAlreadyAdded) {
TrackInfo *newTrackInfo = new TrackInfo(pNewTrack,newDeck);
Copy link
Member

Choose a reason for hiding this comment

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

you can consider to make it a unique pointer.
To make it work, m_trackList has to be a std:: container.

break;
QLinkedList<BaseTrackPlayer*>::iterator it =
trackInfo->m_players.begin();
while (it != trackInfo->m_players.end()) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this an endless loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing it out

class BaseTrackPlayer;
class PlayerManager;

class ScrobblingManager : public QObject {
Copy link
Member

Choose a reason for hiding this comment

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

I think the name of this class is not self explaining.
How many "Current tracks" we will have finally?

  • The current scrobble track
  • The current Track for Metadata Streaming (RDS)
  • The current Track for history playlist
  • The current Track driving the master clock for beat matching (@kshitij98 project)

Will this become a CurrentTrackManager or CurrentTrackCalculator?

@kshitij98: could you reuse portions of this code? Or could you share your rules for a master clock track?

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 current scrobble track, metadata streaming and history playlist are all the same, the one given by PlayerInfo.h. This manager takes into account when a track is loaded, paused, etc. and checks if they're audible every second to update the timers, and when they are it sends a scrobble signal.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kshitij98: could you reuse portions of this code? Or could you share your rules for a master clock track?

PlayerInfo::getCurrentPlayingTrack returns the most audible track which should drive the master clock. This is not a part of my project so I will be able to continue working on it only once I'm done with my project, or I have sufficient time in between.

Related: https://bugs.launchpad.net/mixxx/+bug/1770239

@daschuer
Copy link
Member

There is some work left to do.
Are you able to help? We need an independent code review.
And someone who is able to cut out merge able branches.

@daschuer
Copy link
Member

https://github.com/daschuer/mixxx/tree/Livemetadata
Is a rebased Version. Unfortunately it has new merge conflicts now.
I think I will rebase it again.

@nonchip
Copy link

nonchip commented Oct 19, 2019

Are you able to help?

in the sense that i technically know C++, yeah, but haven't done anything with QT in ages and never even taken a look at the mixxx codebase, so not sure if i'd be able to offer much help, i'd probably be more of a hindrance :P

@daschuer
Copy link
Member

One of the main reason Mixxx is so vivit, is the underestimated
second use case, that many developers including myself use it to learn coding and developing in an open source team. You are very welcome to do the same.
In this case here, you unbiased view will help us to optimize the code for easy understanding.
https://www.mixxx.org/wiki/doku.php/bugfix_workflow helps for a good start.

@nonchip
Copy link

nonchip commented Oct 20, 2019

good point, but given i know nothing about the codebase, very little about the used frameworks, and have never done whatever an "independent code review" entails i'm afraid i'm currently the wrong one for that task :/

honestly i'd probably do a better job trying to implement a new feature in a foreign codebase than to review someone else's code

@davidhm
Copy link
Contributor Author

davidhm commented Oct 22, 2019

I'll try to continue this once I'm done with my master's. Right now I'm starting my Master's thesis and working at the same time, so it's gonna be quite hard for me to do extra work on the side.

@Be-ing Be-ing modified the milestones: 2.4.0, stalled Apr 6, 2020
@ligi
Copy link

ligi commented Jun 21, 2020

Would love this feature! Hope it get's finished/merged soon

@Be-ing
Copy link
Contributor

Be-ing commented Jun 21, 2020

@ligi are you volunteering to finish it?

@github-actions
Copy link

This PR is marked as stale because it has been open 90 days with no activity.

@github-actions github-actions bot added the stale Stale issues that haven't been updated for a long time. label Oct 20, 2020
@Be-ing Be-ing changed the base branch from master to main October 23, 2020 23:50
@poelzi
Copy link
Contributor

poelzi commented Dec 17, 2020

I will pick this one up and get it merged

@daschuer
Copy link
Member

Great, thank you very much!

What is you approach?

@poelzi
Copy link
Contributor

poelzi commented Dec 17, 2020

I just need to remote control mix a little bit. When leaving the room I want to pause it from home assitant and use my kdeconnect controls on the mobile etc.
For better control, we should finish the OSC api, but for simple scriping etc, the dbus api is actually the better one.

@daschuer
Copy link
Member

That sound good.

Here are many conflicts. Do you plan to solve them or to start over?

@uklotzde
Copy link
Contributor

That sound good.

Here are many conflicts. Do you plan to solve them or to start over?

I recommend to start over and only borrow what is needed, be it ideas or code.

@uklotzde
Copy link
Contributor

I just need to remote control mix a little bit. When leaving the room I want to pause it from home assitant and use my kdeconnect controls on the mobile etc.
For better control, we should finish the OSC api, but for simple scriping etc, the dbus api is actually the better one.

But isn't D-Bus is only available on Linux? We should use cross-platform solutions for application features if ever possible.

@daschuer
Copy link
Member

But isn't D-Bus is only available on Linux? We should use cross-platform solutions for application features if ever possible.

Yes it is Linux only. But it is THE interface to add info to the system tray. There is no other way than using platform dependent solutions for this.

@uklotzde
Copy link
Contributor

But isn't D-Bus is only available on Linux? We should use cross-platform solutions for application features if ever possible.

Yes it is Linux only. But it is THE interface to add info to the system tray. There is no other way than using platform dependent solutions for this.

If it is only supposed to be used for the system tray ok. But if it is intended as a general purpose IPC interface than not.

@github-actions github-actions bot removed the stale Stale issues that haven't been updated for a long time. label Dec 18, 2020
@daschuer
Copy link
Member

@poelzi: Please have a look at #3483 which is a working version.
Can you please do some brief tests with it?
Does it work for you?

@Holzhaus
Copy link
Member

Holzhaus commented Apr 2, 2021

I guess we can close this in favor of #3483. If not, please reopen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.