Files
lmms/include/NotePlayHandle.h
Michael Gregorius 286e62adf5 Simplify sample frame operations (make it a class) (#7156)
* Remove the struct StereoSample

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

Adjust the calls in `Mixer`  and `Oscilloscope`.

* Simplify AudioEngine::getPeakValues

* Remove surroundSampleFrame

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

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.

* More operators and methods for sampleFrame

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`.

* Adjust DelayEffect to methods in sampleFrame

* Use composition instead of inheritance

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`).

* Apply suggestions from code review

Apply Veratil's suggestions from the code review

Co-authored-by: Kevin Zander <veratil@gmail.com>

* Fix warnings: zeroing non-trivial type

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]

* Remove unnecessary reinterpret_casts

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.

* Clean up zeroSampleFrames code

* Fix warnings in RemotePlugin

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.

* Rename "sampleFrame" to "SampleFrame"

Uppercase the name of `sampleFrame` so that it uses UpperCamelCase convention.

* Move SampleFrame into its own file

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.

* Return reference for += and *=

Return a reference for the compound assignment operators `+=` and `-=`.

* Explicit float constructor

Make the  constructor that takes a `float` explicit.

Remove the assignment operator that takes a `float`. Clients must use the
explicit `float` constructor and assign the result.

Adjust the code in "BitInvader" accordingly.

* Use std::fill in zeroSampleFrames

* Use zeroSampleFrames in sanitize

* Replace max with absMax

Replace `SampleFrame::max` with `SampleFrame::absMax`.

Use `absMax` in `DelayEffect::processAudioBuffer`. This should also fix
a buggy implementation of the peak computation.

Add the function `getAbsPeakValues`. It  computes the absolute peak
values for a buffer.

Remove `AudioEngine::getPeakValues`. It's not really the business of the
audio engine. Let `Mixer` and `Oscilloscope` use `getAbsPeakValues`.

* Replace scalarProduct

Replace the rather mathematical method `scalarProduct` with
`sumOfSquaredAmplitudes`. It was always called on itself anyway.

* Remove comment/TODO

* Simplify sanitize

Simplify the `sanitize` function by getting rid of the `bool found` and
by zeroing the buffer as soon as a problem is found.

* Put pointer symbols next to type

* Code review adjustments

* Remove "#pragme once"
* Adjust name of include guard
* Remove superfluous includes (leftovers from previous code changes)

---------

Co-authored-by: Kevin Zander <veratil@gmail.com>
2024-06-30 20:21:19 +02:00

365 lines
9.4 KiB
C++

/*
* NotePlayHandle.h - declaration of class NotePlayHandle which manages
* playback of a single note by an instrument
*
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of LMMS - https://lmms.io
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program (see COPYING); if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA.
*
*/
#ifndef LMMS_NOTE_PLAY_HANDLE_H
#define LMMS_NOTE_PLAY_HANDLE_H
#include <memory>
#include "BasicFilters.h"
#include "Note.h"
#include "PlayHandle.h"
#include "Track.h"
class QReadWriteLock;
namespace lmms
{
class InstrumentTrack;
class NotePlayHandle;
using NotePlayHandleList = QList<NotePlayHandle*>;
using ConstNotePlayHandleList = QList<const NotePlayHandle*>;
class LMMS_EXPORT NotePlayHandle : public PlayHandle, public Note
{
public:
void * m_pluginData;
std::unique_ptr<BasicFilters<>> m_filter;
// length of the declicking fade in
fpp_t m_fadeInLength;
// specifies origin of NotePlayHandle
enum class Origin
{
MidiClip, /*! playback of a note from a MIDI clip */
MidiInput, /*! playback of a MIDI note input event */
NoteStacking, /*! created by note stacking instrument function */
Arpeggio, /*! created by arpeggio instrument function */
};
NotePlayHandle( InstrumentTrack* instrumentTrack,
const f_cnt_t offset,
const f_cnt_t frames,
const Note& noteToPlay,
NotePlayHandle* parent = nullptr,
int midiEventChannel = -1,
Origin origin = Origin::MidiClip );
~NotePlayHandle() override;
void * operator new ( size_t size, void * p )
{
return p;
}
void setVolume( volume_t volume ) override;
void setPanning( panning_t panning ) override;
int midiKey() const;
int midiChannel() const
{
return m_midiChannel;
}
/*! convenience function that returns offset for the first period and zero otherwise,
used by instruments to handle the offset: instruments have to check this property and
add the correct number of empty frames in the beginning of the period */
f_cnt_t noteOffset() const
{
return m_totalFramesPlayed == 0
? offset()
: 0;
}
const float& frequency() const
{
return m_frequency;
}
/*! Returns frequency without pitch wheel influence */
float unpitchedFrequency() const
{
return m_unpitchedFrequency;
}
//! Get the current per-note detuning for this note
float currentDetuning() const { return m_baseDetuning->value(); }
/*! Renders one chunk using the attached instrument into the buffer */
void play( SampleFrame* buffer ) override;
/*! Returns whether playback of note is finished and thus handle can be deleted */
bool isFinished() const override
{
return m_released && framesLeft() <= 0;
}
/*! Returns number of frames left for playback */
f_cnt_t framesLeft() const;
/*! Returns how many frames have to be rendered in current period */
fpp_t framesLeftForCurrentPeriod() const;
/*! Returns whether the play handle plays on a certain track */
bool isFromTrack( const Track* _track ) const override;
/*! Releases the note (and plays release frames) */
void noteOff( const f_cnt_t offset = 0 );
/*! Returns number of frames to be played until the note is going to be released */
f_cnt_t framesBeforeRelease() const
{
return m_framesBeforeRelease;
}
/*! Returns how many frames were played since release */
f_cnt_t releaseFramesDone() const
{
return m_releaseFramesDone;
}
/*! Returns the number of frames to be played after release according to
the release times in the envelopes */
f_cnt_t actualReleaseFramesToDo() const;
/*! Returns total numbers of frames to play (including release frames) */
f_cnt_t frames() const
{
return m_frames;
}
/*! Sets the total number of frames to play (including release frames) */
void setFrames( const f_cnt_t _frames );
/*! Returns whether note was released */
bool isReleased() const
{
return m_released;
}
bool isReleaseStarted() const
{
return m_releaseStarted;
}
/*! Returns total numbers of frames played so far */
f_cnt_t totalFramesPlayed() const
{
return m_totalFramesPlayed;
}
/*! Returns volume level at given frame (envelope/LFO) */
float volumeLevel( const f_cnt_t frame );
/*! Returns instrument track which is being played by this handle (const version) */
const InstrumentTrack* instrumentTrack() const
{
return m_instrumentTrack;
}
/*! Returns instrument track which is being played by this handle */
InstrumentTrack* instrumentTrack()
{
return m_instrumentTrack;
}
/*! Returns whether note has a parent, e.g. is not part of an arpeggio or a chord */
bool hasParent() const
{
return m_hasParent;
}
/*! Returns origin of note */
Origin origin() const
{
return m_origin;
}
/*! Returns whether note has children */
bool isMasterNote() const
{
return m_subNotes.size() > 0 || m_hadChildren;
}
void setMasterNote()
{
m_hadChildren = true;
setUsesBuffer( false );
}
/*! Returns whether note is muted */
bool isMuted() const
{
return m_muted;
}
/*! Mutes playback of note */
void mute();
/*! Returns index of NotePlayHandle in vector of note-play-handles
belonging to this instrument track - used by arpeggiator.
Ignores child note-play-handles, returns -1 when called on one */
int index() const;
/*! Returns list of note-play-handles belonging to given instrument track.
If allPlayHandles = true, also released note-play-handles and children
are returned */
static ConstNotePlayHandleList nphsOfInstrumentTrack( const InstrumentTrack* Track, bool allPlayHandles = false );
/*! Returns whether given NotePlayHandle instance is equal to *this */
bool operator==( const NotePlayHandle & _nph ) const;
/*! Returns whether NotePlayHandle belongs to pattern track and pattern track is muted */
bool isPatternTrackMuted()
{
return m_patternTrack && m_patternTrack->isMuted();
}
/*! Sets attached pattern track */
void setPatternTrack(Track* t)
{
m_patternTrack = t;
}
/*! Process note detuning automation */
void processTimePos(const TimePos& time, float pitchValue, bool isRecording);
/*! Updates total length (m_frames) depending on a new tempo */
void resize( const bpm_t newTempo );
/*! Set song-global offset (relative to containing MIDI clip) in order to properly perform the note detuning */
void setSongGlobalParentOffset( const TimePos& offset )
{
m_songGlobalParentOffset = offset;
}
/*! Returns song-global offset */
const TimePos& songGlobalParentOffset() const
{
return m_songGlobalParentOffset;
}
void setFrequencyUpdate()
{
m_frequencyNeedsUpdate = true;
}
private:
class BaseDetuning
{
public:
BaseDetuning( DetuningHelper* detuning );
void setValue( float val )
{
m_value = val;
}
float value() const
{
return m_value;
}
private:
float m_value;
} ;
void updateFrequency();
InstrumentTrack* m_instrumentTrack; // needed for calling
// InstrumentTrack::playNote
f_cnt_t m_frames; // total frames to play
f_cnt_t m_totalFramesPlayed; // total frame-counter - used for
// figuring out whether a whole note
// has been played
f_cnt_t m_framesBeforeRelease; // number of frames after which note
// is released
f_cnt_t m_releaseFramesToDo; // total numbers of frames to be
// played after release
f_cnt_t m_releaseFramesDone; // number of frames done after
// release of note
NotePlayHandleList m_subNotes; // used for chords and arpeggios
volatile bool m_released; // indicates whether note is released
bool m_releaseStarted;
bool m_hasMidiNote;
bool m_hasParent; // indicates whether note has parent
NotePlayHandle * m_parent; // parent note
bool m_hadChildren;
bool m_muted; // indicates whether note is muted
Track* m_patternTrack; // related pattern track
// tempo reaction
bpm_t m_origTempo; // original tempo
f_cnt_t m_origFrames; // original m_frames
int m_origBaseNote;
float m_frequency;
float m_unpitchedFrequency;
BaseDetuning* m_baseDetuning;
TimePos m_songGlobalParentOffset;
int m_midiChannel;
Origin m_origin;
bool m_frequencyNeedsUpdate; // used to update pitch
} ;
const int INITIAL_NPH_CACHE = 256;
const int NPH_CACHE_INCREMENT = 16;
class NotePlayHandleManager
{
public:
static void init();
static NotePlayHandle * acquire( InstrumentTrack* instrumentTrack,
const f_cnt_t offset,
const f_cnt_t frames,
const Note& noteToPlay,
NotePlayHandle* parent = nullptr,
int midiEventChannel = -1,
NotePlayHandle::Origin origin = NotePlayHandle::Origin::MidiClip );
static void release( NotePlayHandle * nph );
static void extend( int i );
static void free();
private:
static NotePlayHandle ** s_available;
static QReadWriteLock s_mutex;
static std::atomic_int s_availableIndex;
static int s_size;
};
} // namespace lmms
#endif // LMMS_NOTE_PLAY_HANDLE_H