Merge remote-tracking branch 'upstream/master' into refactor-samplebuffer

This commit is contained in:
sakertooth
2023-09-04 19:38:44 -04:00
80 changed files with 389 additions and 243 deletions

View File

@@ -333,12 +333,9 @@ void AudioEngine::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames )
const surroundSampleFrame * AudioEngine::renderNextBuffer()
void AudioEngine::renderStageNoteSetup()
{
m_profiler.startPeriod();
s_renderingThread = true;
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::NoteSetup);
if( m_clearSignal )
{
@@ -387,9 +384,15 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
m_newPlayHandles.free( e );
e = next;
}
}
// STAGE 1: run and render all play handles
AudioEngineWorkerThread::fillJobQueue<PlayHandleList>( m_playHandles );
void AudioEngine::renderStageInstruments()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Instruments);
AudioEngineWorkerThread::fillJobQueue(m_playHandles);
AudioEngineWorkerThread::startAndWaitForJobs();
// removed all play handles which are done
@@ -417,16 +420,28 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
++it;
}
}
}
void AudioEngine::renderStageEffects()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Effects);
// STAGE 2: process effects of all instrument- and sampletracks
AudioEngineWorkerThread::fillJobQueue(m_audioPorts);
AudioEngineWorkerThread::startAndWaitForJobs();
}
// STAGE 3: do master mix in mixer
void AudioEngine::renderStageMix()
{
AudioEngineProfiler::Probe profilerProbe(m_profiler, AudioEngineProfiler::DetailType::Mixing);
Mixer *mixer = Engine::mixer();
mixer->masterMix(m_outputBufferWrite);
emit nextAudioBuffer(m_outputBufferRead);
runChangesInModel();
@@ -435,10 +450,22 @@ const surroundSampleFrame * AudioEngine::renderNextBuffer()
EnvelopeAndLfoParameters::instances()->trigger();
Controller::triggerFrameCounter();
AutomatableModel::incrementPeriodCounter();
}
const surroundSampleFrame *AudioEngine::renderNextBuffer()
{
m_profiler.startPeriod();
s_renderingThread = true;
renderStageNoteSetup(); // STAGE 0: clear old play handles and buffers, setup new play handles
renderStageInstruments(); // STAGE 1: run and render all play handles
renderStageEffects(); // STAGE 2: process effects of all instrument- and sampletracks
renderStageMix(); // STAGE 3: do master mix in mixer
s_renderingThread = false;
m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod );
m_profiler.finishPeriod(processingSampleRate(), m_framesPerPeriod);
return m_outputBufferRead;
}

View File

@@ -24,6 +24,8 @@
#include "AudioEngineProfiler.h"
#include <cstdint>
namespace lmms
{
@@ -38,10 +40,24 @@ AudioEngineProfiler::AudioEngineProfiler() :
void AudioEngineProfiler::finishPeriod( sample_rate_t sampleRate, fpp_t framesPerPeriod )
{
int periodElapsed = m_periodTimer.elapsed();
// Time taken to process all data and fill the audio buffer.
const unsigned int periodElapsed = m_periodTimer.elapsed();
// Maximum time the processing can take before causing buffer underflow. Convert to us.
const uint64_t timeLimit = static_cast<uint64_t>(1000000) * framesPerPeriod / sampleRate;
const float newCpuLoad = periodElapsed / 10000.0f * sampleRate / framesPerPeriod;
m_cpuLoad = std::clamp<int>((newCpuLoad * 0.1f + m_cpuLoad * 0.9f), 0, 100);
// Compute new overall CPU load and apply exponential averaging.
// The result is used for overload detection in AudioEngine::criticalXRuns()
// → the weight of a new sample must be high enough to allow relatively fast changes!
const auto newCpuLoad = 100.f * periodElapsed / timeLimit;
m_cpuLoad = newCpuLoad * 0.1f + m_cpuLoad * 0.9f;
// Compute detailed load analysis. Can use stronger averaging to get more stable readout.
for (std::size_t i = 0; i < DetailCount; i++)
{
const auto newLoad = 100.f * m_detailTime[i] / timeLimit;
const auto oldLoad = m_detailLoad[i].load(std::memory_order_relaxed);
m_detailLoad[i].store(newLoad * 0.05f + oldLoad * 0.95f, std::memory_order_relaxed);
}
if( m_outputFile.isOpen() )
{

View File

@@ -1106,16 +1106,16 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate)
{
QMutexLocker m(&m_clipMutex);
if( m_timeMap.size() < 2 && numToGenerate > 0 )
for (int i = 0; i < numToGenerate && it != m_timeMap.end(); ++i, ++it)
{
it.value().setInTangent(0);
it.value().setOutTangent(0);
return;
}
for( int i = 0; i < numToGenerate; i++ )
{
if( it == m_timeMap.begin() )
if (it + 1 == m_timeMap.end())
{
// Previously, the last value's tangent was always set to 0. That logic was kept for both tangents
// of the last node
it.value().setInTangent(0);
it.value().setOutTangent(0);
}
else if (it == m_timeMap.begin())
{
// On the first node there's no curve behind it, so we will only calculate the outTangent
// and inTangent will be set to 0.
@@ -1123,14 +1123,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate)
it.value().setInTangent(0);
it.value().setOutTangent(tangent);
}
else if( it+1 == m_timeMap.end() )
{
// Previously, the last value's tangent was always set to 0. That logic was kept for both tangents
// of the last node
it.value().setInTangent(0);
it.value().setOutTangent(0);
return;
}
else
{
// When we are in a node that is in the middle of two other nodes, we need to check if we
@@ -1159,7 +1151,6 @@ void AutomationClip::generateTangents(timeMap::iterator it, int numToGenerate)
it.value().setOutTangent(outTangent);
}
}
it++;
}
}

View File

@@ -25,6 +25,7 @@
#include <QDomElement>
#include <cassert>
#include "EffectChain.h"
#include "Effect.h"
@@ -162,6 +163,7 @@ void EffectChain::moveDown( Effect * _effect )
if (_effect != m_effects.back())
{
auto it = std::find(m_effects.begin(), m_effects.end(), _effect);
assert(it != m_effects.end());
std::swap(*std::next(it), *it);
}
}
@@ -174,6 +176,7 @@ void EffectChain::moveUp( Effect * _effect )
if (_effect != m_effects.front())
{
auto it = std::find(m_effects.begin(), m_effects.end(), _effect);
assert(it != m_effects.end());
std::swap(*std::prev(it), *it);
}
}

View File

@@ -394,8 +394,8 @@ void Mixer::moveChannelLeft( int index )
else if (m_lastSoloed == b) { m_lastSoloed = a; }
// go through every instrument and adjust for the channel index change
TrackContainer::TrackList songTrackList = Engine::getSong()->tracks();
TrackContainer::TrackList patternTrackList = Engine::patternStore()->tracks();
const TrackContainer::TrackList& songTrackList = Engine::getSong()->tracks();
const TrackContainer::TrackList& patternTrackList = Engine::patternStore()->tracks();
for (const auto& trackList : {songTrackList, patternTrackList})
{

View File

@@ -557,14 +557,20 @@ void NotePlayHandle::updateFrequency()
void NotePlayHandle::processTimePos( const TimePos& time )
void NotePlayHandle::processTimePos(const TimePos& time, float pitchValue, bool isRecording)
{
if( detuning() && time >= songGlobalParentOffset()+pos() )
if (!detuning() || time < songGlobalParentOffset() + pos()) { return; }
if (isRecording && m_origin == Origin::MidiInput)
{
const float v = detuning()->automationClip()->valueAt( time - songGlobalParentOffset() - pos() );
if( !typeInfo<float>::isEqual( v, m_baseDetuning->value() ) )
detuning()->automationClip()->recordValue(time - songGlobalParentOffset() - pos(), pitchValue / 100);
}
else
{
const float v = detuning()->automationClip()->valueAt(time - songGlobalParentOffset() - pos());
if (!typeInfo<float>::isEqual(v, m_baseDetuning->value()))
{
m_baseDetuning->setValue( v );
m_baseDetuning->setValue(v);
updateFrequency();
}
}

View File

@@ -61,7 +61,7 @@ bool PatternStore::play(TimePos start, fpp_t frames, f_cnt_t offset, int clipNum
start = start % (lengthOfPattern(clipNum) * TimePos::ticksPerBar());
TrackList tl = tracks();
const TrackList& tl = tracks();
for (Track * t : tl)
{
if (t->play(start, frames, offset, clipNum))
@@ -117,7 +117,7 @@ int PatternStore::numOfPatterns() const
void PatternStore::removePattern(int pattern)
{
TrackList tl = tracks();
const TrackList& tl = tracks();
for (Track * t : tl)
{
delete t->getClip(pattern);
@@ -134,7 +134,7 @@ void PatternStore::removePattern(int pattern)
void PatternStore::swapPattern(int pattern1, int pattern2)
{
TrackList tl = tracks();
const TrackList& tl = tracks();
for (Track * t : tl)
{
t->swapPositionOfClips(pattern1, pattern2);
@@ -159,7 +159,7 @@ void PatternStore::updatePatternTrack(Clip* clip)
void PatternStore::fixIncorrectPositions()
{
TrackList tl = tracks();
const TrackList& tl = tracks();
for (Track * t : tl)
{
for (int i = 0; i < numOfPatterns(); ++i)
@@ -215,7 +215,7 @@ void PatternStore::updateComboBox()
void PatternStore::currentPatternChanged()
{
// now update all track-labels (the current one has to become white, the others gray)
TrackList tl = Engine::getSong()->tracks();
const TrackList& tl = Engine::getSong()->tracks();
for (Track * t : tl)
{
if (t->type() == Track::Type::Pattern)
@@ -230,7 +230,7 @@ void PatternStore::currentPatternChanged()
void PatternStore::createClipsForPattern(int pattern)
{
TrackList tl = tracks();
const TrackList& tl = tracks();
for (Track * t : tl)
{
t->createClipsForPattern(pattern);

View File

@@ -97,7 +97,7 @@ void RenderManager::renderNextTrack()
// Render the song into individual tracks
void RenderManager::renderTracks()
{
const TrackContainer::TrackList & tl = Engine::getSong()->tracks();
const TrackContainer::TrackList& tl = Engine::getSong()->tracks();
// find all currently unnmuted tracks -- we want to render these.
for (const auto& tk : tl)
@@ -112,7 +112,7 @@ void RenderManager::renderTracks()
}
}
const TrackContainer::TrackList t2 = Engine::patternStore()->tracks();
const TrackContainer::TrackList& t2 = Engine::patternStore()->tracks();
for (const auto& tk : t2)
{
Track::Type type = tk->type();

View File

@@ -383,7 +383,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp
}
values = container->automatedValuesAt(timeStart, clipNum);
TrackList tracks = container->tracks();
const TrackList& tracks = container->tracks();
Track::clipVector clips;
for (Track* track : tracks)

View File

@@ -62,7 +62,7 @@ SET(LMMS_SRCS
gui/instrument/EnvelopeAndLfoView.cpp
gui/instrument/InstrumentFunctionViews.cpp
gui/instrument/InstrumentMidiIOView.cpp
gui/instrument/InstrumentMiscView.cpp
gui/instrument/InstrumentTuningView.cpp
gui/instrument/InstrumentSoundShapingView.cpp
gui/instrument/InstrumentTrackWindow.cpp
gui/instrument/InstrumentView.cpp

View File

@@ -363,10 +363,12 @@ void MainWindow::finalize()
}
edit_menu->addSeparator();
edit_menu->addAction( embed::getIconPixmap( "setup_general" ),
tr( "Settings" ),
this, SLOT(showSettingsDialog()));
connect( edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons()));
edit_menu->addAction(embed::getIconPixmap("microtuner"), tr("Scales and keymaps"),
this, SLOT(toggleMicrotunerWin()));
edit_menu->addAction(embed::getIconPixmap("setup_general"), tr("Settings"),
this, SLOT(showSettingsDialog()));
connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons()));
m_viewMenu = new QMenu( this );
menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) );
@@ -485,10 +487,6 @@ void MainWindow::finalize()
tr("Show/hide project notes") + " (Ctrl+7)", this, SLOT(toggleProjectNotesWin()), m_toolBar);
project_notes_window->setShortcut( Qt::CTRL + Qt::Key_7 );
auto microtuner_window = new ToolButton(embed::getIconPixmap("microtuner"),
tr("Microtuner configuration") + " (Ctrl+8)", this, SLOT(toggleMicrotunerWin()), m_toolBar);
microtuner_window->setShortcut( Qt::CTRL + Qt::Key_8 );
m_toolBarLayout->addWidget( song_editor_window, 1, 1 );
m_toolBarLayout->addWidget( pattern_editor_window, 1, 2 );
m_toolBarLayout->addWidget( piano_roll_window, 1, 3 );
@@ -496,7 +494,6 @@ void MainWindow::finalize()
m_toolBarLayout->addWidget( mixer_window, 1, 5 );
m_toolBarLayout->addWidget( controllers_window, 1, 6 );
m_toolBarLayout->addWidget( project_notes_window, 1, 7 );
m_toolBarLayout->addWidget( microtuner_window, 1, 8 );
m_toolBarLayout->setColumnStretch( 100, 1 );
// setup-dialog opened before?
@@ -1100,10 +1097,6 @@ void MainWindow::updateViewMenu()
tr( "Project Notes" ) + "\tCtrl+7",
this, SLOT(toggleProjectNotesWin())
);
m_viewMenu->addAction(embed::getIconPixmap( "microtuner" ),
tr( "Microtuner" ) + "\tCtrl+8",
this, SLOT(toggleMicrotunerWin())
);
m_viewMenu->addSeparator();

View File

@@ -56,8 +56,8 @@ namespace lmms::gui
MicrotunerConfig::MicrotunerConfig() :
QWidget(),
m_scaleComboModel(nullptr, tr("Selected scale")),
m_keymapComboModel(nullptr, tr("Selected keymap")),
m_scaleComboModel(nullptr, tr("Selected scale slot")),
m_keymapComboModel(nullptr, tr("Selected keymap slot")),
m_firstKeyModel(0, 0, NumKeys - 1, nullptr, tr("First key")),
m_lastKeyModel(NumKeys - 1, 0, NumKeys - 1, nullptr, tr("Last key")),
m_middleKeyModel(DefaultMiddleKey, 0, NumKeys - 1, nullptr, tr("Middle key")),
@@ -75,7 +75,7 @@ MicrotunerConfig::MicrotunerConfig() :
#endif
setWindowIcon(embed::getIconPixmap("microtuner"));
setWindowTitle(tr("Microtuner"));
setWindowTitle(tr("Microtuner Configuration"));
// Organize into 2 main columns: scales and keymaps
auto microtunerLayout = new QGridLayout();
@@ -84,7 +84,7 @@ MicrotunerConfig::MicrotunerConfig() :
// ----------------------------------
// Scale sub-column
//
auto scaleLabel = new QLabel(tr("Scale:"));
auto scaleLabel = new QLabel(tr("Scale slot to edit:"));
microtunerLayout->addWidget(scaleLabel, 0, 0, 1, 2, Qt::AlignBottom);
for (unsigned int i = 0; i < MaxScaleCount; i++)
@@ -102,6 +102,8 @@ MicrotunerConfig::MicrotunerConfig() :
auto loadScaleButton = new QPushButton(tr("Load"));
auto saveScaleButton = new QPushButton(tr("Save"));
loadScaleButton->setToolTip(tr("Load scale definition from a file."));
saveScaleButton->setToolTip(tr("Save scale definition to a file."));
microtunerLayout->addWidget(loadScaleButton, 3, 0, 1, 1);
microtunerLayout->addWidget(saveScaleButton, 3, 1, 1, 1);
connect(loadScaleButton, &QPushButton::clicked, [=] {loadScaleFromFile();});
@@ -112,14 +114,15 @@ MicrotunerConfig::MicrotunerConfig() :
m_scaleTextEdit->setToolTip(tr("Enter intervals on separate lines. Numbers containing a decimal point are treated as cents.\nOther inputs are treated as integer ratios and must be in the form of \'a/b\' or \'a\'.\nUnity (0.0 cents or ratio 1/1) is always present as a hidden first value; do not enter it manually."));
microtunerLayout->addWidget(m_scaleTextEdit, 4, 0, 2, 2);
auto applyScaleButton = new QPushButton(tr("Apply scale"));
auto applyScaleButton = new QPushButton(tr("Apply scale changes"));
applyScaleButton->setToolTip(tr("Verify and apply changes made to the selected scale. To use the scale, select it in the settings of a supported instrument."));
microtunerLayout->addWidget(applyScaleButton, 6, 0, 1, 2);
connect(applyScaleButton, &QPushButton::clicked, [=] {applyScale();});
// ----------------------------------
// Mapping sub-column
//
auto keymapLabel = new QLabel(tr("Keymap:"));
auto keymapLabel = new QLabel(tr("Keymap slot to edit:"));
microtunerLayout->addWidget(keymapLabel, 0, 2, 1, 2, Qt::AlignBottom);
for (unsigned int i = 0; i < MaxKeymapCount; i++)
@@ -137,6 +140,8 @@ MicrotunerConfig::MicrotunerConfig() :
auto loadKeymapButton = new QPushButton(tr("Load"));
auto saveKeymapButton = new QPushButton(tr("Save"));
loadKeymapButton->setToolTip(tr("Load key mapping definition from a file."));
saveKeymapButton->setToolTip(tr("Save key mapping definition to a file."));
microtunerLayout->addWidget(loadKeymapButton, 3, 2, 1, 1);
microtunerLayout->addWidget(saveKeymapButton, 3, 3, 1, 1);
connect(loadKeymapButton, &QPushButton::clicked, [=] {loadKeymapFromFile();});
@@ -181,7 +186,8 @@ MicrotunerConfig::MicrotunerConfig() :
baseFreqSpin->setToolTip(tr("Base note frequency"));
keymapRangeLayout->addWidget(baseFreqSpin, 1, 1, 1, 2);
auto applyKeymapButton = new QPushButton(tr("Apply keymap"));
auto applyKeymapButton = new QPushButton(tr("Apply keymap changes"));
applyKeymapButton->setToolTip(tr("Verify and apply changes made to the selected key mapping. To use the mapping, select it in the settings of a supported instrument."));
microtunerLayout->addWidget(applyKeymapButton, 6, 2, 1, 2);
connect(applyKeymapButton, &QPushButton::clicked, [=] {applyKeymap();});

View File

@@ -248,8 +248,8 @@ void MixerView::refreshDisplay()
// update the and max. channel number for every instrument
void MixerView::updateMaxChannelSelector()
{
TrackContainer::TrackList songTracks = Engine::getSong()->tracks();
TrackContainer::TrackList patternStoreTracks = Engine::patternStore()->tracks();
const TrackContainer::TrackList& songTracks = Engine::getSong()->tracks();
const TrackContainer::TrackList& patternStoreTracks = Engine::patternStore()->tracks();
for (const auto& trackList : {songTracks, patternStoreTracks})
{

View File

@@ -25,6 +25,7 @@
#include "ClipView.h"
#include <set>
#include <cassert>
#include <QMenu>
#include <QMouseEvent>
@@ -545,6 +546,7 @@ DataFile ClipView::createClipDataFiles(
// Insert into the dom under the "clips" element
Track* clipTrack = clipView->m_trackView->getTrack();
int trackIndex = std::distance(tc->tracks().begin(), std::find(tc->tracks().begin(), tc->tracks().end(), clipTrack));
assert(trackIndex != tc->tracks().size());
QDomElement clipElement = dataFile.createElement("clip");
clipElement.setAttribute( "trackIndex", trackIndex );
clipElement.setAttribute( "trackType", static_cast<int>(clipTrack->type()) );
@@ -1308,7 +1310,7 @@ void ClipView::mergeClips(QVector<ClipView*> clipvs)
continue;
}
NoteVector currentClipNotes = mcView->getMidiClip()->notes();
const NoteVector& currentClipNotes = mcView->getMidiClip()->notes();
TimePos mcViewPos = mcView->getMidiClip()->startPosition();
for (Note* note: currentClipNotes)

View File

@@ -69,7 +69,7 @@ void PatternEditor::cloneSteps()
void PatternEditor::removeSteps()
{
TrackContainer::TrackList tl = model()->tracks();
const TrackContainer::TrackList& tl = model()->tracks();
for (const auto& track : tl)
{
@@ -176,7 +176,7 @@ void PatternEditor::updatePosition()
void PatternEditor::makeSteps( bool clone )
{
TrackContainer::TrackList tl = model()->tracks();
const TrackContainer::TrackList& tl = model()->tracks();
for (const auto& track : tl)
{

View File

@@ -741,10 +741,10 @@ void PianoRoll::fitNoteLengths(bool fill)
{
if (!hasValidMidiClip()) { return; }
m_midiClip->addJournalCheckPoint();
m_midiClip->rearrangeAllNotes();
// Reference notes
NoteVector refNotes = m_midiClip->notes();
std::sort(refNotes.begin(), refNotes.end(), Note::lessThan);
const NoteVector& refNotes = m_midiClip->notes();
// Notes to edit
NoteVector notes = getSelectedNotes();
@@ -762,7 +762,7 @@ void PianoRoll::fitNoteLengths(bool fill)
}
int length;
NoteVector::iterator ref = refNotes.begin();
auto ref = refNotes.begin();
for (Note* note : notes)
{
// Fast forward to next reference note
@@ -797,14 +797,11 @@ void PianoRoll::constrainNoteLengths(bool constrainMax)
if (!hasValidMidiClip()) { return; }
m_midiClip->addJournalCheckPoint();
NoteVector notes = getSelectedNotes();
if (notes.empty())
{
notes = m_midiClip->notes();
}
const NoteVector selectedNotes = getSelectedNotes();
const auto& notes = selectedNotes.empty() ? m_midiClip->notes() : selectedNotes;
TimePos bound = m_lenOfNewNotes; // will be length of last note
for (Note* note : notes)
TimePos bound = m_lenOfNewNotes; // will be length of last note
for (auto note : notes)
{
if (constrainMax ? note->length() > bound : note->length() < bound)
{
@@ -1207,11 +1204,11 @@ void PianoRoll::shiftSemiTone(int amount) //Shift notes by amount semitones
auto selectedNotes = getSelectedNotes();
//If no notes are selected, shift all of them, otherwise shift selection
if (selectedNotes.empty()) { return shiftSemiTone(m_midiClip->notes(), amount); }
else { return shiftSemiTone(selectedNotes, amount); }
if (selectedNotes.empty()) { shiftSemiTone(m_midiClip->notes(), amount); }
else { shiftSemiTone(selectedNotes, amount); }
}
void PianoRoll::shiftSemiTone(NoteVector notes, int amount)
void PianoRoll::shiftSemiTone(const NoteVector& notes, int amount)
{
m_midiClip->addJournalCheckPoint();
for (Note *note : notes) { note->setKey( note->key() + amount ); }
@@ -1232,11 +1229,11 @@ void PianoRoll::shiftPos(int amount) //Shift notes pos by amount
auto selectedNotes = getSelectedNotes();
//If no notes are selected, shift all of them, otherwise shift selection
if (selectedNotes.empty()) { return shiftPos(m_midiClip->notes(), amount); }
else { return shiftPos(selectedNotes, amount); }
if (selectedNotes.empty()) { shiftPos(m_midiClip->notes(), amount); }
else { shiftPos(selectedNotes, amount); }
}
void PianoRoll::shiftPos(NoteVector notes, int amount)
void PianoRoll::shiftPos(const NoteVector& notes, int amount)
{
m_midiClip->addJournalCheckPoint();
@@ -4134,9 +4131,9 @@ void PianoRoll::finishRecordNote(const Note & n )
{
if( it->key() == n.key() )
{
Note n1( n.length(), it->pos(),
Note n1(n.length(), it->pos(),
it->key(), it->getVolume(),
it->getPanning() );
it->getPanning(), n.detuning());
n1.quantizeLength( quantization() );
m_midiClip->addNote( n1 );
update();

View File

@@ -49,7 +49,7 @@
#include "InstrumentFunctions.h"
#include "InstrumentFunctionViews.h"
#include "InstrumentMidiIOView.h"
#include "InstrumentMiscView.h"
#include "InstrumentTuningView.h"
#include "InstrumentSoundShapingView.h"
#include "InstrumentTrack.h"
#include "InstrumentTrackView.h"
@@ -255,25 +255,25 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) :
instrumentFunctionsLayout->addStretch();
// MIDI tab
m_midiView = new InstrumentMidiIOView( m_tabWidget );
m_midiView = new InstrumentMidiIOView(m_tabWidget);
// FX tab
m_effectView = new EffectRackView( m_track->m_audioPort.effects(), m_tabWidget );
m_effectView = new EffectRackView(m_track->m_audioPort.effects(), m_tabWidget);
// MISC tab
m_miscView = new InstrumentMiscView( m_track, m_tabWidget );
// Tuning tab
m_tuningView = new InstrumentTuningView(m_track, m_tabWidget);
m_tabWidget->addTab( m_ssView, tr( "Envelope, filter & LFO" ), "env_lfo_tab", 1 );
m_tabWidget->addTab( instrumentFunctions, tr( "Chord stacking & arpeggio" ), "func_tab", 2 );
m_tabWidget->addTab( m_effectView, tr( "Effects" ), "fx_tab", 3 );
m_tabWidget->addTab( m_midiView, tr( "MIDI" ), "midi_tab", 4 );
m_tabWidget->addTab( m_miscView, tr( "Miscellaneous" ), "misc_tab", 5 );
m_tabWidget->addTab(m_ssView, tr("Envelope, filter & LFO"), "env_lfo_tab", 1);
m_tabWidget->addTab(instrumentFunctions, tr("Chord stacking & arpeggio"), "func_tab", 2);
m_tabWidget->addTab(m_effectView, tr("Effects"), "fx_tab", 3);
m_tabWidget->addTab(m_midiView, tr("MIDI"), "midi_tab", 4);
m_tabWidget->addTab(m_tuningView, tr("Tuning and transposition"), "tuning_tab", 5);
adjustTabSize(m_ssView);
adjustTabSize(instrumentFunctions);
m_effectView->resize(EffectRackView::DEFAULT_WIDTH, INSTRUMENT_HEIGHT - 4 - 1);
adjustTabSize(m_midiView);
adjustTabSize(m_miscView);
adjustTabSize(m_tuningView);
// setup piano-widget
m_pianoView = new PianoView( this );
@@ -376,12 +376,14 @@ void InstrumentTrackWindow::modelChanged()
if (m_track->instrument() && m_track->instrument()->flags().testFlag(Instrument::Flag::IsMidiBased))
{
m_miscView->microtunerGroupBox()->hide();
m_tuningView->microtunerNotSupportedLabel()->show();
m_tuningView->microtunerGroupBox()->hide();
m_track->m_microtuner.enabledModel()->setValue(false);
}
else
{
m_miscView->microtunerGroupBox()->show();
m_tuningView->microtunerNotSupportedLabel()->hide();
m_tuningView->microtunerGroupBox()->show();
}
m_ssView->setModel( &m_track->m_soundShaping );
@@ -389,11 +391,11 @@ void InstrumentTrackWindow::modelChanged()
m_arpeggioView->setModel( &m_track->m_arpeggio );
m_midiView->setModel( &m_track->m_midiPort );
m_effectView->setModel( m_track->m_audioPort.effects() );
m_miscView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel);
m_miscView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel());
m_miscView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel());
m_miscView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel());
m_miscView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel());
m_tuningView->pitchGroupBox()->setModel(&m_track->m_useMasterPitchModel);
m_tuningView->microtunerGroupBox()->setModel(m_track->m_microtuner.enabledModel());
m_tuningView->scaleCombo()->setModel(m_track->m_microtuner.scaleModel());
m_tuningView->keymapCombo()->setModel(m_track->m_microtuner.keymapModel());
m_tuningView->rangeImportCheckbox()->setModel(m_track->m_microtuner.keyRangeImportModel());
updateName();
}

View File

@@ -1,8 +1,8 @@
/*
* InstrumentMiscView.cpp - Miscellaneous instrument settings
* InstrumentTuningView.cpp - Instrument settings for tuning and transpositions
*
* Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2020 Martin Pavelek <he29.HS/at/gmail.com>
* Copyright (c) 2020-2022 Martin Pavelek <he29.HS/at/gmail.com>
*
* This file is part of LMMS - https://lmms.io
*
@@ -23,24 +23,28 @@
*
*/
#include "InstrumentMiscView.h"
#include "InstrumentTuningView.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QVBoxLayout>
#include "ComboBox.h"
#include "GroupBox.h"
#include "GuiApplication.h"
#include "gui_templates.h"
#include "InstrumentTrack.h"
#include "LedCheckBox.h"
#include "MainWindow.h"
#include "PixmapButton.h"
namespace lmms::gui
{
InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) :
InstrumentTuningView::InstrumentTuningView(InstrumentTrack *it, QWidget *parent) :
QWidget(parent)
{
auto layout = new QVBoxLayout(this);
@@ -60,6 +64,11 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) :
masterPitchLayout->addWidget(tlabel);
// Microtuner settings
m_microtunerNotSupportedLabel = new QLabel(tr("Microtuner is not available for MIDI-based instruments."));
m_microtunerNotSupportedLabel->setWordWrap(true);
m_microtunerNotSupportedLabel->hide();
layout->addWidget(m_microtunerNotSupportedLabel);
m_microtunerGroupBox = new GroupBox(tr("MICROTUNER"));
m_microtunerGroupBox->setModel(it->m_microtuner.enabledModel());
layout->addWidget(m_microtunerGroupBox);
@@ -67,8 +76,22 @@ InstrumentMiscView::InstrumentMiscView(InstrumentTrack *it, QWidget *parent) :
auto microtunerLayout = new QVBoxLayout(m_microtunerGroupBox);
microtunerLayout->setContentsMargins(8, 18, 8, 8);
auto scaleEditLayout = new QHBoxLayout();
scaleEditLayout->setContentsMargins(0, 0, 4, 0);
microtunerLayout->addLayout(scaleEditLayout);
auto scaleLabel = new QLabel(tr("Active scale:"));
microtunerLayout->addWidget(scaleLabel);
scaleEditLayout->addWidget(scaleLabel);
QPixmap editPixmap(embed::getIconPixmap("edit_draw_small"));
auto editPixButton = new PixmapButton(this, tr("Edit scales and keymaps"));
editPixButton->setToolTip(tr("Edit scales and keymaps"));
editPixButton->setInactiveGraphic(editPixmap);
editPixButton->setActiveGraphic(editPixmap);
editPixButton->setFixedSize(16, 16);
connect(editPixButton, SIGNAL(clicked()), getGUI()->mainWindow(), SLOT(toggleMicrotunerWin()));
scaleEditLayout->addWidget(editPixButton);
m_scaleCombo = new ComboBox();
m_scaleCombo->setModel(it->m_microtuner.scaleModel());

View File

@@ -322,70 +322,65 @@ void PianoView::modelChanged()
// gets the key from the given mouse-position
// Gets the key from the given mouse position
/*! \brief Get the key from the mouse position in the piano display
*
* First we determine it roughly by the position of the point given in
* white key widths from our start. We then add in any black keys that
* might have been skipped over (they take a key number, but no 'white
* key' space). We then add in our starting key number.
*
* We then determine whether it was a black key that was pressed by
* checking whether it was within the vertical range of black keys.
* Black keys sit exactly between white keys on this keyboard, so
* we then shift the note down or up if we were in the left or right
* half of the white note. We only do this, of course, if the white
* note has a black key on that side, so to speak.
*
* This function returns const because there is a linear mapping from
* the point given to the key returned that never changes.
*
* \param _p The point that the mouse was pressed.
* \param p The point that the mouse was pressed.
*/
int PianoView::getKeyFromMouse( const QPoint & _p ) const
int PianoView::getKeyFromMouse(const QPoint& p) const
{
int offset = _p.x() % PW_WHITE_KEY_WIDTH;
if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH;
int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH;
// The left-most key visible in the piano display is always white
const int startingWhiteKey = m_pianoScroll->value();
for( int i = 0; i <= key_num; ++i )
// Adjust the mouse x position as if x == 0 was the left side of the lowest key
const int adjX = p.x() + (startingWhiteKey * PW_WHITE_KEY_WIDTH);
// Can early return for notes too low
if (adjX <= 0) { return 0; }
// Now we can calculate the key number (in only white keys) and the octave
const int whiteKey = adjX / PW_WHITE_KEY_WIDTH;
const int octave = whiteKey / Piano::WhiteKeysPerOctave;
// Calculate for full octaves
int key = octave * KeysPerOctave;
// Adjust for white notes in the current octave
// (WhiteKeys maps each white key to the number of notes to their left in the octave)
key += static_cast<int>(WhiteKeys[whiteKey % Piano::WhiteKeysPerOctave]);
// Might be a black key, which would require further adjustment
if (p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT)
{
if ( Piano::isBlackKey( m_startKey+i ) )
// Maps white keys to neighboring black keys
static constexpr std::array neighboringKeyMap {
std::pair{ 0, 1 }, // C --> no B#; C#
std::pair{ 1, 1 }, // D --> C#; D#
std::pair{ 1, 0 }, // E --> D#; no E#
std::pair{ 0, 1 }, // F --> no E#; F#
std::pair{ 1, 1 }, // G --> F#; G#
std::pair{ 1, 1 }, // A --> G#; A#
std::pair{ 1, 0 }, // B --> A#; no B#
};
const auto neighboringBlackKeys = neighboringKeyMap[whiteKey % Piano::WhiteKeysPerOctave];
const int offset = adjX - (whiteKey * PW_WHITE_KEY_WIDTH); // mouse X offset from white key
if (offset < PW_BLACK_KEY_WIDTH / 2)
{
++key_num;
// At the location of a (possibly non-existent) black key on the left side
key -= neighboringBlackKeys.first;
}
}
for( int i = 0; i >= key_num; --i )
{
if ( Piano::isBlackKey( m_startKey+i ) )
else if (offset > PW_WHITE_KEY_WIDTH - (PW_BLACK_KEY_WIDTH / 2))
{
--key_num;
// At the location of a (possibly non-existent) black key on the right side
key += neighboringBlackKeys.second;
}
// For white keys in between black keys, no further adjustment is needed
}
key_num += m_startKey;
// is it a black key?
if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT )
{
// then do extra checking whether the mouse-cursor is over
// a black key
if( key_num > 0 && Piano::isBlackKey( key_num-1 ) &&
offset <= ( PW_WHITE_KEY_WIDTH / 2 ) -
( PW_BLACK_KEY_WIDTH / 2 ) )
{
--key_num;
}
if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) &&
offset >= ( PW_WHITE_KEY_WIDTH -
PW_BLACK_KEY_WIDTH / 2 ) )
{
++key_num;
}
}
// some range-checking-stuff
return qBound( 0, key_num, NumKeys - 1 );
return std::clamp(key, 0, NumKeys - 1);
}
@@ -396,12 +391,12 @@ int PianoView::getKeyFromMouse( const QPoint & _p ) const
*
* We need to update our start key position based on the new position.
*
* \param _new_pos the new key position.
* \param newPos the new key position, counting only white keys.
*/
void PianoView::pianoScrolled(int new_pos)
void PianoView::pianoScrolled(int newPos)
{
m_startKey = static_cast<Octave>(new_pos / Piano::WhiteKeysPerOctave)
+ WhiteKeys[new_pos % Piano::WhiteKeysPerOctave];
m_startKey = static_cast<Octave>(newPos / Piano::WhiteKeysPerOctave)
+ WhiteKeys[newPos % Piano::WhiteKeysPerOctave];
update();
}

View File

@@ -344,7 +344,7 @@ bool TrackContentWidget::canPasteSelection( TimePos clipPos, const QMimeData* md
const int initialTrackIndex = tiAttr.value().toInt();
// Get the current track's index
const TrackContainer::TrackList tracks = t->trackContainer()->tracks();
const TrackContainer::TrackList& tracks = t->trackContainer()->tracks();
const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), t);
const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1;
@@ -443,7 +443,7 @@ bool TrackContentWidget::pasteSelection( TimePos clipPos, const QMimeData * md,
TimePos grabbedClipPos = clipPosAttr.value().toInt();
// Snap the mouse position to the beginning of the dropped bar, in ticks
const TrackContainer::TrackList tracks = getTrack()->trackContainer()->tracks();
const TrackContainer::TrackList& tracks = getTrack()->trackContainer()->tracks();
const auto currentTrackIt = std::find(tracks.begin(), tracks.end(), getTrack());
const int currentTrackIndex = currentTrackIt != tracks.end() ? std::distance(tracks.begin(), currentTrackIt) : -1;

View File

@@ -24,6 +24,7 @@
*/
#include <algorithm>
#include <QPainter>
#include "AudioEngine.h"
@@ -72,10 +73,9 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
QPainter p( &m_temp );
p.drawPixmap( 0, 0, m_background );
// as load-indicator consists of small 2-pixel wide leds with
// 1 pixel spacing, we have to make sure, only whole leds are
// shown which we achieve by the following formula
int w = ( m_leds.width() * m_currentLoad / 300 ) * 3;
// Normally the CPU load indicator moves smoothly, with 1 pixel resolution. However, some themes may want to
// draw discrete elements (like LEDs), so the stepSize property can be used to specify a larger step size.
int w = (m_leds.width() * std::min(m_currentLoad, 100) / (stepSize() * 100)) * stepSize();
if( w > 0 )
{
p.drawPixmap( 23, 3, m_leds, 0, 0, w,
@@ -91,10 +91,21 @@ void CPULoadWidget::paintEvent( QPaintEvent * )
void CPULoadWidget::updateCpuLoad()
{
// smooth load-values a bit
int new_load = ( m_currentLoad + Engine::audioEngine()->cpuLoad() ) / 2;
if( new_load != m_currentLoad )
// Additional display smoothing for the main load-value. Stronger averaging
// cannot be used directly in the profiler: cpuLoad() must react fast enough
// to be useful as overload indicator in AudioEngine::criticalXRuns().
const int new_load = (m_currentLoad + Engine::audioEngine()->cpuLoad()) / 2;
if (new_load != m_currentLoad)
{
auto engine = Engine::audioEngine();
setToolTip(
tr("DSP total: %1%").arg(new_load) + "\n"
+ tr(" - Notes and setup: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::NoteSetup)) + "\n"
+ tr(" - Instruments: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Instruments)) + "\n"
+ tr(" - Effects: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Effects)) + "\n"
+ tr(" - Mixing: %1%").arg(engine->detailLoad(AudioEngineProfiler::DetailType::Mixing))
);
m_currentLoad = new_load;
m_changed = true;
update();
@@ -102,4 +113,4 @@ void CPULoadWidget::updateCpuLoad()
}
} // namespace lmms::gui
} // namespace lmms::gui

View File

@@ -46,11 +46,11 @@ SimpleTextFloat::SimpleTextFloat() :
m_textLabel = new QLabel(this);
layout->addWidget(m_textLabel);
m_showTimer = new QTimer();
m_showTimer = new QTimer(this);
m_showTimer->setSingleShot(true);
QObject::connect(m_showTimer, &QTimer::timeout, this, &SimpleTextFloat::show);
m_hideTimer = new QTimer();
m_hideTimer = new QTimer(this);
m_hideTimer->setSingleShot(true);
QObject::connect(m_hideTimer, &QTimer::timeout, this, &SimpleTextFloat::hide);
}

View File

@@ -28,6 +28,7 @@
#include "ConfigManager.h"
#include "ControllerConnection.h"
#include "DataFile.h"
#include "GuiApplication.h"
#include "Mixer.h"
#include "InstrumentTrackView.h"
#include "Instrument.h"
@@ -37,6 +38,7 @@
#include "MixHelpers.h"
#include "PatternStore.h"
#include "PatternTrack.h"
#include "PianoRoll.h"
#include "Pitch.h"
#include "Song.h"
@@ -72,6 +74,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
m_microtuner()
{
m_pitchModel.setCenterValue( 0 );
m_pitchModel.setStrictStepSize(true);
m_panningModel.setCenterValue( DefaultPanning );
m_baseNoteModel.setInitValue( DefaultKey );
m_firstKeyModel.setInitValue(0);
@@ -341,9 +344,10 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const TimePos& tim
NotePlayHandleManager::acquire(
this, offset,
typeInfo<f_cnt_t>::max() / 2,
Note( TimePos(), TimePos(), event.key(), event.volume( midiPort()->baseVelocity() ) ),
Note(TimePos(), Engine::getSong()->getPlayPos(Engine::getSong()->playMode()),
event.key(), event.volume(midiPort()->baseVelocity())),
nullptr, event.channel(),
NotePlayHandle::Origin::MidiInput );
NotePlayHandle::Origin::MidiInput);
m_notes[event.key()] = nph;
if( ! Engine::audioEngine()->addPlayHandle( nph ) )
{
@@ -710,7 +714,7 @@ bool InstrumentTrack::play( const TimePos & _start, const fpp_t _frames,
// Handle automation: detuning
for (const auto& processHandle : m_processHandles)
{
processHandle->processTimePos(_start);
processHandle->processTimePos(_start, m_pitchModel.value(), gui::GuiApplication::instance()->pianoRoll()->isRecording());
}
if ( clips.size() == 0 )

View File

@@ -305,7 +305,7 @@ void MidiClip::setStep( int step, bool enabled )
void MidiClip::splitNotes(NoteVector notes, TimePos pos)
void MidiClip::splitNotes(const NoteVector& notes, TimePos pos)
{
if (notes.empty()) { return; }
@@ -472,7 +472,7 @@ MidiClip * MidiClip::nextMidiClip() const
MidiClip * MidiClip::adjacentMidiClipByOffset(int offset) const
{
std::vector<Clip *> clips = m_instrumentTrack->getClips();
auto& clips = m_instrumentTrack->getClips();
int clipNum = m_instrumentTrack->getClipNum(this);
if (clipNum < 0 || clipNum > clips.size() - 1) { return nullptr; }
return dynamic_cast<MidiClip*>(clips[clipNum + offset]);