-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Add sample thumbnails #7366
base: master
Are you sure you want to change the base?
Add sample thumbnails #7366
Conversation
… refactor some loops
Factory samples would like to have a word with you |
As long as the factory sample paths remain unique for each sample (which I believe they do) then it won't break. I'm mostly concerned with when the sample name is just the filename. (also changed the PR description a bit) |
You've got some code that is commented out, is it supposed to stay or to be erased? Since this change is also visual, could you provide a screenshot of what it looks like? |
@zachtyll Oh right they were supposed to be removed but I forgot to. Will make a commit to clear them out Here are some pictures and videos (left is the old code and right is the new code): slicert.mp4samples2.mp4The sample in this video is 11 minutes long: automation.mp4 |
I see a number of style issues/some code that can be cleaned up. Instead of making a huge review for style, do I have permission to go in and fix it myself? I want to focus on the implementation here (the design/visualization code), not too much of the style, but its an important problem here still. Edit: But if you would prefer a review, then I don't mind. |
@sakertooth Feel free to push commits to adjust the style. For the implementation adjustments, I think it can benefit from a bit of discussion/reviewing before commits are pushed |
+ Replace while loop with std::find_if
Hey @khoidauminh, I just pushed some style changes, along with some fixes and rearrangements. Let me know if you have any objections with them. I plan to look into this PR more closely soon. Edit: Forgot to mention, one thing I noticed is that there is still a lot of lag when the number of sample clips get around 20 and up and each are around 2 minute long. I think another optimization we might consider is just drawing the necessary region, and not the entire thumbnail (not sure if that will fix the issue, but it was something I had in mind). |
Pushed two commits: one fixes formatting, and the other changes the use of |
I'm eyeing these two lines: lmms/src/gui/SampleThumbnail.cpp Line 146 in 3dd6217
lmms/src/gui/SampleThumbnail.cpp Line 150 in 3dd6217
Is there a performance impact on these? I think if we take an iterator-based approach, we could have almost zero performance cost. I know exactly how to do this cleanly in Rust but in C++ I have no idea. LMMS just moved to C++20 so I think this should be possible. Will take a look at the C++ references. |
You're right. We don't need to copy anything at all and we can take an iterator-based approach to represent the range of samples to consider. Iterators in C++ are the In most cases however, it is simple to consider Sidebar, but just as another good C++ reference, the guidelines are really good. |
… entire thumbnail
…nail width to display width To improve performance with especially long samples (~20 minutes)
QPainter::drawLine calls drawLines with a line count of 1. Therefore, calling drawLine in a loop means we are simply calling drawLines a lot more times than necessary.
Thought using 2 would help performance (when rendering). Maybe it does, but it's still quite slow when rendering a bunch of thumbnails at once.
auto param = SampleThumbnail::VisualizeParameters{}; | ||
param.amplification = sample.amplification(); | ||
param.reversed = sample.reversed(); | ||
param.sampleRect = QRect(startPos, yOffset, sampleWidth, sampleHeight); | ||
param.sampleStart = static_cast<float>(sample.startFrame()) / sample.sampleSize(); | ||
param.sampleEnd = static_cast<float>(sample.endFrame()) / sample.sampleSize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use C++20's designated initializers here if you'd like. And also in a few other places where you create a VisualizeParameters
object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the immediate benefit in doing that, but I agree we could use it here probably.
const auto beginSample = buffer + static_cast<size_t>(std::floor(peakIndex * m_samplesPerPeak)); | ||
const auto endSample = buffer + static_cast<size_t>(std::ceil((peakIndex + 1) * m_samplesPerPeak)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar enough with the logic to know whether the beginSample
and endSample
pointers will always be within the bounds of the array. Maybe you could add an assert
to ensure that endSample < buffer + size
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its worth understanding it so we know for sure. The maximum number peakIndex
will reach is width - 1
. Therefore, endSample = buffer + ceil(width * m_samplesPerPeak) = buffer + ceil(width * size / width)
. width
is an integer type so they cancel out safely, making endSample = buffer + ceil(size)
. Since size
is an integer type as well, endSample = buffer + size
when peakIndex = width - 1
.
The problem here is that I shouldn't be accessing buffer
viewing it as a bunch of SampleFrame
s. In the scenario I explained above, endSample
points to at most one after the last SampleFrame
in the buffer, making endSample->left()
invalid. Instead, I need to be viewing the buffer in a flat manner (i.e., disregarding the notion of a left and right channel and accessing the float
values directly.)
Or, just minus endSample
by 1
and change endSample->left()
to endSample->right() + 1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In summary, the assert(endSample <= buffer + size)
will always pass, but std::minmax_element
can (and probably will) access invalid data.
const auto it = s_sampleThumbnailCacheMap.find(entry); | ||
if (it != s_sampleThumbnailCacheMap.end()) | ||
{ | ||
m_thumbnailCache = it->second; | ||
return; | ||
} | ||
|
||
if (s_sampleThumbnailCacheMap.size() == MaxSampleThumbnailCacheSize) | ||
{ | ||
const auto leastUsed = std::min_element(s_sampleThumbnailCacheMap.begin(), s_sampleThumbnailCacheMap.end(), | ||
[](const auto& a, const auto& b) { return a.second.use_count() < b.second.use_count(); }); | ||
s_sampleThumbnailCacheMap.erase(leastUsed->first); | ||
} | ||
|
||
s_sampleThumbnailCacheMap[std::move(entry)] = m_thumbnailCache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The s_sampleThumbnailCacheMap
accesses aren't thread-safe. Could that be a problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not at all. Visualization only happens on the UI thread. We also only create SampleThumbnails
on the main thread.
Peak* peaks() { return m_peaks.data(); } | ||
const Peak* peaks() const { return m_peaks.data(); } | ||
|
||
int width() const { return m_peaks.size(); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this return std::size_t
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the math involved uses int
quite often (stuff like std::max(rect->x(), thumbnail->width()
), making width
a size_t
is more of an inconvenience than anything else, though one could argue that returning size_t
is more correct here. Using std::max<int>
is an option, but I don't like the possible implicit conversion (which had caused bugs in the past relating to sample waveform visualization)
This is an attempt in adding sample thumbnails in order to speed up rendering (mainly) in the song editor and other places (currently being Audio File Processor, SlicerT and the Automation editor).
Brief summary of the PR:
Bit
is a sample thumbnail, which is a struct containing max, min and rms.Thumbnail
is a vector ofBit
s and aThumbnailCache
is a vector ofThumbnail
s.SampleThumbnail
contains a non-static member that is a shared_ptr to aThumbnailCache
,and the static memberBoth are private.s_sampleThumbnailCacheMap
.How this implementation originally avoided duplicates
s_sampleThumbnailCacheMap
is anstd::map
with keys being the sample file path and the value being a shared_ptr toThumbnailCache
. All thumbnail caches are stored here.This implementation assumes that a unique sample has a unique full file path. If this is violated or the file path is just the file name, the implementation may break completely.
When a sample is loaded into the song editor, the constructor of
SampleThumbnail
looks intos_sampleThumbnailCacheMap
to find the thumbnail cache for this sample. If the sample is new, it proceeds to generate a new thumbnail cache for this sample and insert it into the map. In other places like AFP, SlicerT, when loading a preexisting preset or project, the thumbnail caches might not be generated until you open the plugin GUI, which triggers the paintEvents.The
SampleThumbnail
class attaches itself to classes that uses thumbnails, such as SampleClipView. This ensures that thumbnail cache that is being used will have a use_count() of 2 or more (one in thestd::map
and others in the objects). A use_count() of 1 indicates the sample is out of use and the thumbnail list will be deleted. Cleaning operations is done when loading new samples or closing LMMS.Currently the global map behavior, explained in the collapsed section above has been deprecated in favor of the (in development) sample cache PR #7058.
As of current, the the thumbnail size divisor is exactly 10.
This PR now also makes use of partial rendering, which offloads some computation cost from zooming to other UI updates, such as scrolling, resizing the window, as it now only renders the areas not yet drawn.
Fixes #7576.
Fixes #7546.