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

Simplify sample frame operations (make it a class) #7156

Open
wants to merge 18 commits into
base: master
Choose a base branch
from

Conversation

michaelgregorius
Copy link
Contributor

@michaelgregorius michaelgregorius commented Mar 23, 2024

Turn sampleFrame into a class

This pull request turns sampleFrame into a class with useful constructors, operators and methods which greatly simplify working with it.

Please refer to the first adjustments in some core classes and plugins to see how much clearer and concise the code becomes. With these changes it becomes possible to remove rather complex code that is repeated over and over again in the code base.

Removal of surroundSampleFrame

The type surroundSampleFrame is removed to enable turning sampleFrame into a class. This feature is not supported anyway (was it ever?).

In the old implementation and with the default definition surroundSampleFrame was interchangeable with sampleFrame because both resolved to an array with two entries. Some code relied on this and thus if the line #define LMMS_DISABLE_SURROUND was commented out in lmms_basics.h then the code did not compile anymore. The reason was that doing so turned surroundSampleFrame into an array with four values instead of two.

I am also not aware of any support for surround sound in the application. The faders and mixers do not seem to support more that two channels and the instruments and effects all expect a sampleFrame, i.e. stereo channels. It therefore makes sense to remove the "feature" so that it does not hinder further improvement and development of the application.

Proposed next steps

I propose the following next steps:

  • Merge this pull request as a first "commitment" to the removal of the surround sample frame and the new functionality of sampleFrame.
  • Create further pull requests to continue with the adjustment of other code and plugins over time.
  • Introduce a buffer class that knows its length and that provides certain functionalities: amplification, finding the max, etc. This will also make the interfaces of the instruments and effects nicer.
  • If wanted implement support for more than two channels. This would be a rather big undertaking obviously. The master bus has to expose all audio outputs and the mixer must support appropriate routing of channels.

The "commitment" in the first step is a "safeguard" for me so that I do not spend weeks to adjust the code only for the pull request to be denied.

Note

It's best to review to pull request commit-wise. Commits 1c86e7e and 974fbac should be most interesting ones as they show what's possible now.

Remove the struct `StereoSample`. Let `AudioEngine::getPeakValues` return a `sampleFrame` instead.

Adjust the calls in `Mixer`  and `Oscilloscope`.
Some code assumes that `surroundSampleFrame` is interchangeable with `sampleFrame`. Thus, if the line `#define LMMS_DISABLE_SURROUND` is commented out in `lmms_basics.h` then the code does not compile anymore because `surroundSampleFrame` now is defined to be an array with four values instead of two. There also does not seem to be any support for surround sound (four channels instead of two) in the application. The faders and mixers do not seem to support more that two channels and the instruments and effects all expect a `sampleFrame`, i.e. stereo channels. It therefore makes sense to remove the "feature" because it also hinders the improvement of `sampleFrame`, e.g. by making it a class with some convenience methods that act on `sampleFrame` instances.

All occurrences of `surroundSampleFrame` are replaced with `sampleFrame`.

The version of `BufferManager::clear` that takes a `surroundSampleFrame` is removed completely.

The define `SURROUND_CHANNELS` is removed. All its occurrences are replaced with `DEFAULT_CHANNELS`.

Most of the audio devices classes, i.e. classes that inherit from `AudioDevice`, now clamp the configuration parameter between two values of `DEFAULT_CHANNELS`. This can be improved/streamlined later.

`BYTES_PER_SURROUND_FRAME` has been removed as it was not used anywhere anyway.
Make `sampleFrame` a class with several convenience methods. As a first step and demonstration adjust the follow methods to make use of the new functionality:
* `AudioEngine::getPeakValues`: Much more concise now.
* `lmms::MixHelpers::sanitize`: Better structure, better readable, less dereferencing and juggling with indices.
* `AddOp`, `AddMultipliedOp`, `multiply`: Make use of operators. Might become superfluous in the future.
Add some more operators and methods to `sampleFrame`:
* Constructor which initializes both channels from a single sample value
* Assignment operator from a single sample value
* Addition/multiplication operators
* Scalar product

Adjust some more plugins to the new functionality of `sampleFrame`.
@messmerd
Copy link
Member

As a class, sampleFrame should be renamed SampleFrame to follow the UpperCamelCase convention.

@sakertooth
Copy link
Contributor

I cant say I'm a fan of the inheritance to std::array. I would prefer composition over inheritance and move sampleFrame into its own file. Also use the UpperCamelCase style as previously mentioned by @messmerd.

@Veratil
Copy link
Contributor

Veratil commented Mar 27, 2024

I cant say I'm a fan of the inheritance to std::array. I would prefer composition over inheritance and move sampleFrame into its own file.

I agree with this. Don't inherit from STL containers, they don't have virtual destructors.

Using inheritance was the quickest way to enable adding methods to `sampleFrame` without having to reimpement much of `std::array`s interface.

This is changed with this commit. The array is now a member of `sampleFrame` and the interface is extended with the necessary methods `data` and the index operator.

An `average` method was added so that no iterators need to be implemented (see changes in `SampleWaveform.cpp`).
@michaelgregorius
Copy link
Contributor Author

@sakertooth, @Veratil, I agree. It was the quickest way to add methods to sampleFrame. However, this is now fixed with commit bd40852.

Copy link
Contributor

@Veratil Veratil left a comment

Choose a reason for hiding this comment

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

Style fixups

include/lmms_basics.h Outdated Show resolved Hide resolved
include/lmms_basics.h Outdated Show resolved Hide resolved
include/lmms_basics.h Outdated Show resolved Hide resolved
include/lmms_basics.h Outdated Show resolved Hide resolved
include/lmms_basics.h Outdated Show resolved Hide resolved
include/AudioFileFlac.h Outdated Show resolved Hide resolved
include/AudioPortAudio.h Outdated Show resolved Hide resolved
include/AudioSdl.h Outdated Show resolved Hide resolved
include/AudioSoundIo.h Outdated Show resolved Hide resolved
include/Oscilloscope.h Outdated Show resolved Hide resolved
Apply Veratil's suggestions from the code review

Co-authored-by: Kevin Zander <veratil@gmail.com>
@michaelgregorius
Copy link
Contributor Author

@Veratil, thanks for the (as always) thorough review! I have applied all you suggestions with commit 925a58c and came to love the "Add suggestion to batch" feature while doing so. 😅

…sRemoveSurroundSampleFrame

Merge upstream's master into this branch to resolve conflicts in `src/gui/widgets/Oscilloscope.cpp`.
@michaelgregorius
Copy link
Contributor Author

As a class, sampleFrame should be renamed SampleFrame to follow the UpperCamelCase convention.

@messmerd, I'd like to do this as a last step when it is clear that this PR is accepted and will go through before I start this undertaking, especially since it will potentially touch lots of files.

On the other hand it will hopefully just be a mechanical commit that does not need much code review.

@michaelgregorius
Copy link
Contributor Author

I have a local branch ready where sampleFrame is renamed to SampleFrame and where it's moved into its own file.

Copy link
Contributor

@Veratil Veratil left a comment

Choose a reason for hiding this comment

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

I think we're good

@DomClark DomClark self-requested a review March 29, 2024 01:34
…sRemoveSurroundSampleFrame

Conflicts:
* plugins/Delay/DelayEffect.cpp
@michaelgregorius
Copy link
Contributor Author

My plan is to await @DomClark's review, merge this PR if everything is OK and then to do the renaming and the moving in a separate PR that I will create close to this one.

This way it will be clearer to review. Another reason is that there might be build problems because I had to adjust some files, e.g. drivers, "blindly" because I cannot compile them. Doing so will keep things a bit cleaner.

Once the second PR is through the adjustment of all the client code can be continued.

@messmerd
Copy link
Member

A while back I looked at some of the code using sample frames and saw that we were using some reinterpret_casts. It may be a good idea to go through and check that this PR will not introduce undefined behavior in those places

@michaelgregorius
Copy link
Contributor Author

@messmerd, do you mean the casts in LadspaEffect::processAudioBuffer and PlayHandle::buffer? Or are there any other ones?

@messmerd
Copy link
Member

Yes, things like that. We should probably see if the reinterpret_casts can be avoided without negatively impacting performance.

@michaelgregorius
Copy link
Contributor Author

michaelgregorius commented Apr 1, 2024

Edit: Problem is fixed and has been identified as being caused by a merge commit with changes unrelated to this PR (b2f2fc4).

This PR should not be merged yet. While removing the reinterpret_casts I noticed that there is a problem with single stream instruments like LB302. Unfortunately, I have no idea what's causing this. I'd be grateful if someone can point me in the right direction.

Steps to reproduce:

  1. Build the branch of this PR.
  2. Start LMMS.
  3. Add an LB302.
  4. Play a note on the LB302, e.g. press a key on the keyboard of the instrument.

A debug build then hangs, i.e. no sound is played. If I break execution then the main thread seems to be in the area of some mutexes/locks.

If I load and play the song "AngryLlama-NewFangled" from the demos which also sports some LB302 instances then the main thread hangs in some drawing code.

Some of the affected instruments:

  • Gig Player
  • LB302
  • Opulenz
  • Sf2 Player

Fix several warnings of the following form:

Warnung: »void* memset(void*, int, size_t)« Säubern eines Objekts von nichttrivialem Typ »class lmms::sampleFrame«; use assignment or value-initialization instead [-Wclass-memaccess]
@michaelgregorius
Copy link
Contributor Author

Fixed some warnings but they have not been the cause of the problem described above.

@michaelgregorius
Copy link
Contributor Author

Git bisect shows commit b2f2fc4 as the first bad commit. Perhaps I just need to merge master as the commit does not seem to have caused problems in master or they have been fixed there in the meantime.

@michaelgregorius
Copy link
Contributor Author

The problem is indeed fixed by a merge with master which was done with commit e9969e7.

Remove some unnecessary reinterpret_casts with regards to `sampleFrame` buffers.

`PlayHandle::m_playHandleBuffer` already is a `sampleFrame*` and does not need a reinterpret_cast anymore.

In `LadspaEffect::processAudioBuffer` the `QVarLengthArray` is now directly initialized as an array of `sampleFrame` instances.

I guess in both places the `sampleFrame` previously was a `surroundSampleFrame` which has been removed.
@michaelgregorius
Copy link
Contributor Author

@messmerd, I have removed two unnecessary reinterpret_casts with commit d573913.

@DomClark, I consider this pull request ready for review again.

…sRemoveSurroundSampleFrame

Conflicts:
* include/AudioDevice.h
* src/core/audio/AudioDevice.cpp
* src/gui/SampleWaveform.cpp
@michaelgregorius
Copy link
Contributor Author

@DomClark, do you still intend to review this pull request? It is already getting stale and is getting the first merge conflicts that must be resolved.

Fix some warnings related to calls to `memcpy` in conjunction with`sampleFrame` which is now a class.

Add the helper functions `copyToSampleFrames` and `copyFromSampleFrames` and use them. The first function copies data from a `float` buffer into a `sampleFrame` buffer and the second copies vice versa.
@DomClark
Copy link
Member

DomClark commented May 9, 2024

Sorry for holding this up - the project is moving quite fast at the moment, and while that's a good thing, there's a bit more going on than I have time to keep up with. I will try to get to this ASAP (hopefully tomorrow).

@michaelgregorius
Copy link
Contributor Author

Thanks @DomClark! You will find that there is a certain trade-off between ease of operations on stereo samples and performance. It is not possible anymore to use memcpy (see for example the changes that had to be made in commit ba30c42). Zeroing buffers or copying them becomes a bit more "involved" (see the changes in include/lmms_basics.h). However, I did not experience any real impact due to the changes.

The plus-side is that operations with stereo sample become much easier and there is less low-level code repetition which should also lead to lesser bugs and better to understand as well as more compact code. Please refer to the first changes that have been made in some plugins.

include/lmms_basics.h Outdated Show resolved Hide resolved
Uppercase the name of `sampleFrame` so that it uses UpperCamelCase convention.
Move the class `SampleFrame` into its own class and remove it from `lmms_basics.h`.

Add forward includes to all headers where possible or include the `SampleFrame` header if it's not just referenced but used.

Add include to all cpp files where necessary.

It's a bit surprising that the `SampleFrame` header does not need to be included much more often in the implementation/cpp files. This is an indicator that it seems to be included via an include chain that at one point includes one of the headers where an include instead of a forward declaration had to be added in this commit.
@michaelgregorius
Copy link
Contributor Author

Pushed the renaming to upper-case SampleFrame and moving into it's own file as requested.

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.

None yet

6 participants