Merge remote-tracking branch 'upstream/master' into refactor-samplebuffer
This commit is contained in:
@@ -701,7 +701,10 @@ void AudioEngine::removeAudioPort(AudioPort * port)
|
||||
|
||||
bool AudioEngine::addPlayHandle( PlayHandle* handle )
|
||||
{
|
||||
if( criticalXRuns() == false )
|
||||
// Only add play handles if we have the CPU capacity to process them.
|
||||
// Instrument play handles are not added during playback, but when the
|
||||
// associated instrument is created, so add those unconditionally.
|
||||
if (handle->type() == PlayHandle::Type::InstrumentPlayHandle || !criticalXRuns())
|
||||
{
|
||||
m_newPlayHandles.push( handle );
|
||||
handle->audioPort()->addPlayHandle( handle );
|
||||
|
||||
@@ -79,6 +79,7 @@ const std::vector<DataFile::UpgradeMethod> DataFile::UPGRADE_METHODS = {
|
||||
&DataFile::upgrade_automationNodes , &DataFile::upgrade_extendedNoteRange,
|
||||
&DataFile::upgrade_defaultTripleOscillatorHQ,
|
||||
&DataFile::upgrade_mixerRename , &DataFile::upgrade_bbTcoRename,
|
||||
&DataFile::upgrade_sampleAndHold , &DataFile::upgrade_midiCCIndexing
|
||||
};
|
||||
|
||||
// Vector of all versions that have upgrade routines.
|
||||
@@ -1703,26 +1704,56 @@ void DataFile::upgrade_mixerRename()
|
||||
{
|
||||
// Change nodename <fxmixer> to <mixer>
|
||||
QDomNodeList fxmixer = elementsByTagName("fxmixer");
|
||||
for (int i = 0; !fxmixer.item(i).isNull(); ++i)
|
||||
for (int i = 0; i < fxmixer.length(); ++i)
|
||||
{
|
||||
fxmixer.item(i).toElement().setTagName("mixer");
|
||||
auto item = fxmixer.item(i).toElement();
|
||||
if (item.isNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
item.setTagName("mixer");
|
||||
}
|
||||
|
||||
// Change nodename <fxchannel> to <mixerchannel>
|
||||
QDomNodeList fxchannel = elementsByTagName("fxchannel");
|
||||
for (int i = 0; !fxchannel.item(i).isNull(); ++i)
|
||||
for (int i = 0; i < fxchannel.length(); ++i)
|
||||
{
|
||||
fxchannel.item(i).toElement().setTagName("mixerchannel");
|
||||
auto item = fxchannel.item(i).toElement();
|
||||
if (item.isNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
item.setTagName("mixerchannel");
|
||||
}
|
||||
|
||||
// Change the attribute fxch of element <instrumenttrack> to mixch
|
||||
QDomNodeList fxch = elementsByTagName("instrumenttrack");
|
||||
for(int i = 0; !fxch.item(i).isNull(); ++i)
|
||||
for (int i = 0; i < fxch.length(); ++i)
|
||||
{
|
||||
if(fxch.item(i).toElement().hasAttribute("fxch"))
|
||||
auto item = fxch.item(i).toElement();
|
||||
if (item.isNull())
|
||||
{
|
||||
fxch.item(i).toElement().setAttribute("mixch", fxch.item(i).toElement().attribute("fxch"));
|
||||
fxch.item(i).toElement().removeAttribute("fxch");
|
||||
continue;
|
||||
}
|
||||
if (item.hasAttribute("fxch"))
|
||||
{
|
||||
item.setAttribute("mixch", item.attribute("fxch"));
|
||||
item.removeAttribute("fxch");
|
||||
}
|
||||
}
|
||||
// Change the attribute fxch of element <sampletrack> to mixch
|
||||
fxch = elementsByTagName("sampletrack");
|
||||
for (int i = 0; i < fxch.length(); ++i)
|
||||
{
|
||||
auto item = fxch.item(i).toElement();
|
||||
if (item.isNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.hasAttribute("fxch"))
|
||||
{
|
||||
item.setAttribute("mixch", item.attribute("fxch"));
|
||||
item.removeAttribute("fxch");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1762,6 +1793,44 @@ void DataFile::upgrade_bbTcoRename()
|
||||
}
|
||||
|
||||
|
||||
// Set LFO speed to 0.01 on projects made before sample-and-hold PR
|
||||
void DataFile::upgrade_sampleAndHold()
|
||||
{
|
||||
QDomNodeList elements = elementsByTagName("lfocontroller");
|
||||
for (int i = 0; i < elements.length(); ++i)
|
||||
{
|
||||
if (elements.item(i).isNull()) { continue; }
|
||||
auto e = elements.item(i).toElement();
|
||||
// Correct old random wave LFO speeds
|
||||
if (e.attribute("wave").toInt() == 6)
|
||||
{
|
||||
e.setAttribute("speed",0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Update MIDI CC indexes, so that they are counted from 0. Older releases of LMMS
|
||||
//! count the CCs from 1.
|
||||
void DataFile::upgrade_midiCCIndexing()
|
||||
{
|
||||
static constexpr std::array attributesToUpdate{"inputcontroller", "outputcontroller"};
|
||||
|
||||
QDomNodeList elements = elementsByTagName("Midicontroller");
|
||||
for(int i = 0; i < elements.length(); i++)
|
||||
{
|
||||
if (elements.item(i).isNull()) { continue; }
|
||||
auto element = elements.item(i).toElement();
|
||||
for (const char* attrName : attributesToUpdate)
|
||||
{
|
||||
if (element.hasAttribute(attrName))
|
||||
{
|
||||
int cc = element.attribute(attrName).toInt();
|
||||
element.setAttribute(attrName, cc - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataFile::upgrade()
|
||||
{
|
||||
// Runs all necessary upgrade methods
|
||||
|
||||
@@ -24,18 +24,57 @@
|
||||
|
||||
|
||||
#include "InstrumentPlayHandle.h"
|
||||
#include "Instrument.h"
|
||||
#include "InstrumentTrack.h"
|
||||
#include "Engine.h"
|
||||
#include "AudioEngine.h"
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
|
||||
InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) :
|
||||
PlayHandle( Type::InstrumentPlayHandle ),
|
||||
m_instrument( instrument )
|
||||
InstrumentPlayHandle::InstrumentPlayHandle(Instrument * instrument, InstrumentTrack* instrumentTrack) :
|
||||
PlayHandle(Type::InstrumentPlayHandle),
|
||||
m_instrument(instrument)
|
||||
{
|
||||
setAudioPort( instrumentTrack->audioPort() );
|
||||
setAudioPort(instrumentTrack->audioPort());
|
||||
}
|
||||
|
||||
void InstrumentPlayHandle::play(sampleFrame * working_buffer)
|
||||
{
|
||||
InstrumentTrack * instrumentTrack = m_instrument->instrumentTrack();
|
||||
|
||||
// ensure that all our nph's have been processed first
|
||||
auto nphv = NotePlayHandle::nphsOfInstrumentTrack(instrumentTrack, true);
|
||||
|
||||
bool nphsLeft;
|
||||
do
|
||||
{
|
||||
nphsLeft = false;
|
||||
for (const NotePlayHandle * constNotePlayHandle : nphv)
|
||||
{
|
||||
if (constNotePlayHandle->state() != ThreadableJob::ProcessingState::Done &&
|
||||
!constNotePlayHandle->isFinished())
|
||||
{
|
||||
nphsLeft = true;
|
||||
NotePlayHandle * notePlayHandle = const_cast<NotePlayHandle *>(constNotePlayHandle);
|
||||
notePlayHandle->process();
|
||||
}
|
||||
}
|
||||
}
|
||||
while (nphsLeft);
|
||||
|
||||
m_instrument->play(working_buffer);
|
||||
|
||||
// Process the audio buffer that the instrument has just worked on...
|
||||
const fpp_t frames = Engine::audioEngine()->framesPerPeriod();
|
||||
instrumentTrack->processAudioBuffer(working_buffer, frames, nullptr);
|
||||
}
|
||||
|
||||
bool InstrumentPlayHandle::isFromTrack(const Track* track) const
|
||||
{
|
||||
return m_instrument->isFromTrack(track);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
} // namespace lmms
|
||||
|
||||
@@ -88,6 +88,7 @@ void LfoController::updateValueBuffer()
|
||||
{
|
||||
m_phaseOffset = m_phaseModel.value() / 360.0;
|
||||
float phase = m_currentPhase + m_phaseOffset;
|
||||
float phasePrev = 0.0f;
|
||||
|
||||
// roll phase up until we're in sync with period counter
|
||||
m_bufferLastUpdated++;
|
||||
@@ -102,21 +103,45 @@ void LfoController::updateValueBuffer()
|
||||
ValueBuffer *amountBuffer = m_amountModel.valueBuffer();
|
||||
int amountInc = amountBuffer ? 1 : 0;
|
||||
float *amountPtr = amountBuffer ? &(amountBuffer->values()[ 0 ] ) : &amount;
|
||||
Oscillator::WaveShape waveshape = static_cast<Oscillator::WaveShape>(m_waveModel.value());
|
||||
|
||||
for( float& f : m_valueBuffer )
|
||||
{
|
||||
const float currentSample = m_sampleFunction != nullptr
|
||||
? m_sampleFunction( phase )
|
||||
// TODO C++20: Deprecated, use std::atomic<std::shared_ptr> instead
|
||||
: Oscillator::userWaveSample(std::atomic_load(&m_userDefSampleBuffer).get(), phase);
|
||||
float currentSample = 0;
|
||||
switch (waveshape)
|
||||
{
|
||||
case Oscillator::WaveShape::WhiteNoise:
|
||||
{
|
||||
if (absFraction(phase) < absFraction(phasePrev))
|
||||
{
|
||||
// Resample when phase period has completed
|
||||
m_heldSample = m_sampleFunction(phase);
|
||||
}
|
||||
currentSample = m_heldSample;
|
||||
break;
|
||||
}
|
||||
case Oscillator::WaveShape::UserDefined:
|
||||
{
|
||||
currentSample = Oscillator::userWaveSample(std::atomic_load(&m_userDefSampleBuffer).get(), phase);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (m_sampleFunction != nullptr)
|
||||
{
|
||||
currentSample = m_sampleFunction(phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f = std::clamp(m_baseModel.value() + (*amountPtr * currentSample / 2.0f), 0.0f, 1.0f);
|
||||
|
||||
phasePrev = phase;
|
||||
phase += 1.0 / m_duration;
|
||||
amountPtr += amountInc;
|
||||
}
|
||||
|
||||
m_currentPhase = absFraction( phase - m_phaseOffset );
|
||||
m_currentPhase = absFraction(phase - m_phaseOffset);
|
||||
m_bufferLastUpdated = s_periods;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,4 +196,4 @@ auto SampleBuffer::empty() const -> bool
|
||||
return m_data.empty();
|
||||
}
|
||||
|
||||
} // namespace lmms
|
||||
} // namespace lmms
|
||||
|
||||
@@ -137,25 +137,29 @@ void SampleClip::setSampleBuffer( SampleBuffer* sb )
|
||||
|
||||
|
||||
|
||||
void SampleClip::setSampleFile( const QString & _sf )
|
||||
void SampleClip::setSampleFile(const QString & sf)
|
||||
{
|
||||
int length;
|
||||
if ( _sf.isEmpty() )
|
||||
{ //When creating an empty sample clip make it a bar long
|
||||
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
|
||||
float den = Engine::getSong()->getTimeSigModel().getDenominator();
|
||||
length = DefaultTicksPerBar * ( nom / den );
|
||||
}
|
||||
else
|
||||
{ //Otherwise set it to the sample's length
|
||||
auto buffer = gui::SampleLoader::createBufferFromFile(_sf);
|
||||
int length = 0;
|
||||
|
||||
if (!sf.isEmpty())
|
||||
{
|
||||
//Otherwise set it to the sample's length
|
||||
auto buffer = gui::SampleLoader::createBufferFromFile(sf);
|
||||
// TODO C++20: Deprecated, use std::atomic<std::shared_ptr> instead
|
||||
std::atomic_store(&m_sample, std::make_shared<Sample>(std::move(buffer)));
|
||||
length = sampleLength();
|
||||
}
|
||||
changeLength(length);
|
||||
|
||||
setStartTimeOffset( 0 );
|
||||
if (length == 0)
|
||||
{
|
||||
//If there is no sample, make the clip a bar long
|
||||
float nom = Engine::getSong()->getTimeSigModel().getNumerator();
|
||||
float den = Engine::getSong()->getTimeSigModel().getDenominator();
|
||||
length = DefaultTicksPerBar * (nom / den);
|
||||
}
|
||||
|
||||
changeLength(length);
|
||||
setStartTimeOffset(0);
|
||||
|
||||
emit sampleChanged();
|
||||
emit playbackPositionChanged();
|
||||
|
||||
@@ -72,21 +72,20 @@ void MidiController::updateName()
|
||||
|
||||
|
||||
|
||||
void MidiController::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset )
|
||||
void MidiController::processInEvent(const MidiEvent& event, const TimePos& time, f_cnt_t offset)
|
||||
{
|
||||
unsigned char controllerNum;
|
||||
switch( event.type() )
|
||||
switch(event.type())
|
||||
{
|
||||
case MidiControlChange:
|
||||
controllerNum = event.controllerNumber();
|
||||
|
||||
if( m_midiPort.inputController() == controllerNum + 1 &&
|
||||
( m_midiPort.inputChannel() == event.channel() + 1 ||
|
||||
m_midiPort.inputChannel() == 0 ) )
|
||||
if (m_midiPort.inputController() == controllerNum &&
|
||||
(m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0))
|
||||
{
|
||||
unsigned char val = event.controllerValue();
|
||||
m_previousValue = m_lastValue;
|
||||
m_lastValue = (float)( val ) / 127.0f;
|
||||
m_lastValue = static_cast<float>(val) / 127.0f;
|
||||
emit valueChanged();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "MidiEventProcessor.h"
|
||||
#include "Note.h"
|
||||
#include "Song.h"
|
||||
#include "MidiController.h"
|
||||
|
||||
|
||||
namespace lmms
|
||||
@@ -54,8 +55,8 @@ MidiPort::MidiPort( const QString& name,
|
||||
m_mode( mode ),
|
||||
m_inputChannelModel( 0, 0, MidiChannelCount, this, tr( "Input channel" ) ),
|
||||
m_outputChannelModel( 1, 0, MidiChannelCount, this, tr( "Output channel" ) ),
|
||||
m_inputControllerModel( 0, 0, MidiControllerCount, this, tr( "Input controller" ) ),
|
||||
m_outputControllerModel( 0, 0, MidiControllerCount, this, tr( "Output controller" ) ),
|
||||
m_inputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Input controller" )),
|
||||
m_outputControllerModel(MidiController::NONE, MidiController::NONE, MidiControllerCount - 1, this, tr( "Output controller" )),
|
||||
m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed input velocity" ) ),
|
||||
m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ),
|
||||
m_fixedOutputNoteModel( -1, -1, MidiMaxKey, this, tr( "Fixed output note" ) ),
|
||||
@@ -436,4 +437,4 @@ void MidiPort::invalidateCilent()
|
||||
}
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
} // namespace lmms
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QHBoxLayout>
|
||||
@@ -126,7 +125,7 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
m_filterEdit->setPlaceholderText( tr("Search") );
|
||||
m_filterEdit->setClearButtonEnabled( true );
|
||||
connect( m_filterEdit, SIGNAL( textEdited( const QString& ) ),
|
||||
this, SLOT( filterItems( const QString& ) ) );
|
||||
this, SLOT( filterAndExpandItems( const QString& ) ) );
|
||||
|
||||
auto reload_btn = new QPushButton(embed::getIconPixmap("reload"), QString(), searchWidget);
|
||||
reload_btn->setToolTip( tr( "Refresh list" ) );
|
||||
@@ -145,53 +144,95 @@ FileBrowser::FileBrowser(const QString & directories, const QString & filter,
|
||||
auto filterFocusShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this, SLOT(giveFocusToFilter()));
|
||||
filterFocusShortcut->setContext(Qt::WidgetWithChildrenShortcut);
|
||||
|
||||
m_previousFilterValue = "";
|
||||
|
||||
reloadTree();
|
||||
show();
|
||||
}
|
||||
|
||||
bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item )
|
||||
void FileBrowser::saveDirectoriesStates()
|
||||
{
|
||||
m_savedExpandedDirs = m_fileBrowserTreeWidget->expandedDirs();
|
||||
}
|
||||
|
||||
void FileBrowser::restoreDirectoriesStates()
|
||||
{
|
||||
// call with item=NULL to filter the entire tree
|
||||
expandItems(nullptr, m_savedExpandedDirs);
|
||||
}
|
||||
|
||||
bool FileBrowser::filterAndExpandItems(const QString & filter, QTreeWidgetItem * item)
|
||||
{
|
||||
// Call with item = nullptr to filter the entire tree
|
||||
|
||||
if (item == nullptr)
|
||||
{
|
||||
// First search character so need to save current expanded directories
|
||||
if (m_previousFilterValue.isEmpty())
|
||||
{
|
||||
saveDirectoriesStates();
|
||||
}
|
||||
|
||||
m_previousFilterValue = filter;
|
||||
}
|
||||
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
// Restore previous expanded directories
|
||||
if (item == nullptr)
|
||||
{
|
||||
restoreDirectoriesStates();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool anyMatched = false;
|
||||
|
||||
int numChildren = item ? item->childCount() : m_fileBrowserTreeWidget->topLevelItemCount();
|
||||
for( int i = 0; i < numChildren; ++i )
|
||||
|
||||
for (int i = 0; i < numChildren; ++i)
|
||||
{
|
||||
QTreeWidgetItem * it = item ? item->child( i ) : m_fileBrowserTreeWidget->topLevelItem(i);
|
||||
|
||||
// is directory?
|
||||
if( it->childCount() )
|
||||
auto d = dynamic_cast<Directory*>(it);
|
||||
if (d)
|
||||
{
|
||||
// matches filter?
|
||||
if( it->text( 0 ).
|
||||
contains( filter, Qt::CaseInsensitive ) )
|
||||
if (it->text(0).contains(filter, Qt::CaseInsensitive))
|
||||
{
|
||||
// yes, then show everything below
|
||||
it->setHidden( false );
|
||||
filterItems( QString(), it );
|
||||
it->setHidden(false);
|
||||
it->setExpanded(true);
|
||||
filterAndExpandItems(QString(), it);
|
||||
anyMatched = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// only show if item below matches filter
|
||||
bool didMatch = filterItems( filter, it );
|
||||
it->setHidden( !didMatch );
|
||||
// Expanding is required when recursive to load in its contents, even if it's collapsed right afterward
|
||||
it->setExpanded(true);
|
||||
|
||||
bool didMatch = filterAndExpandItems(filter, it);
|
||||
it->setHidden(!didMatch);
|
||||
it->setExpanded(didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
}
|
||||
// a standard item (i.e. no file or directory item?)
|
||||
else if( it->type() == QTreeWidgetItem::Type )
|
||||
{
|
||||
// hide if there's any filter
|
||||
it->setHidden( !filter.isEmpty() );
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// file matches filter?
|
||||
bool didMatch = it->text( 0 ).
|
||||
contains( filter, Qt::CaseInsensitive );
|
||||
it->setHidden( !didMatch );
|
||||
anyMatched = anyMatched || didMatch;
|
||||
auto f = dynamic_cast<FileItem*>(it);
|
||||
if (f)
|
||||
{
|
||||
// File
|
||||
bool didMatch = it->text(0).contains(filter, Qt::CaseInsensitive);
|
||||
it->setHidden(!didMatch);
|
||||
anyMatched = anyMatched || didMatch;
|
||||
}
|
||||
|
||||
// A standard item (i.e. no file or directory item?)
|
||||
else
|
||||
{
|
||||
// Hide if there's any filter
|
||||
it->setHidden(!filter.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,15 +242,20 @@ bool FileBrowser::filterItems( const QString & filter, QTreeWidgetItem * item )
|
||||
|
||||
void FileBrowser::reloadTree()
|
||||
{
|
||||
QList<QString> expandedDirs = m_fileBrowserTreeWidget->expandedDirs();
|
||||
const QString text = m_filterEdit->text();
|
||||
m_filterEdit->clear();
|
||||
if (m_filterEdit->text().isEmpty())
|
||||
{
|
||||
saveDirectoriesStates();
|
||||
}
|
||||
|
||||
m_fileBrowserTreeWidget->clear();
|
||||
|
||||
QStringList paths = m_directories.split('*');
|
||||
|
||||
if (m_showUserContent && !m_showUserContent->isChecked())
|
||||
{
|
||||
paths.removeAll(m_userDir);
|
||||
}
|
||||
|
||||
if (m_showFactoryContent && !m_showFactoryContent->isChecked())
|
||||
{
|
||||
paths.removeAll(m_factoryDir);
|
||||
@@ -222,9 +268,15 @@ void FileBrowser::reloadTree()
|
||||
addItems(path);
|
||||
}
|
||||
}
|
||||
expandItems(nullptr, expandedDirs);
|
||||
m_filterEdit->setText( text );
|
||||
filterItems( text );
|
||||
|
||||
if (m_filterEdit->text().isEmpty())
|
||||
{
|
||||
restoreDirectoriesStates();
|
||||
}
|
||||
else
|
||||
{
|
||||
filterAndExpandItems(m_filterEdit->text());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -240,12 +292,16 @@ void FileBrowser::expandItems(QTreeWidgetItem* item, QList<QString> expandedDirs
|
||||
{
|
||||
// Expanding is required when recursive to load in its contents, even if it's collapsed right afterward
|
||||
if (m_recurse) { d->setExpanded(true); }
|
||||
|
||||
d->setExpanded(expandedDirs.contains(d->fullName()));
|
||||
|
||||
if (m_recurse && it->childCount())
|
||||
{
|
||||
expandItems(it, expandedDirs);
|
||||
}
|
||||
}
|
||||
|
||||
it->setHidden(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +472,6 @@ QList<QString> FileBrowserTreeWidget::expandedDirs( QTreeWidgetItem * item ) con
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void FileBrowserTreeWidget::keyPressEvent(QKeyEvent * ke )
|
||||
{
|
||||
// Shorter names for some commonly used properties of the event
|
||||
|
||||
@@ -84,7 +84,6 @@ SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) :
|
||||
|
||||
// setup line edit for changing sample track name
|
||||
m_nameLineEdit = new QLineEdit;
|
||||
m_nameLineEdit->setFont(pointSize<9>(m_nameLineEdit->font()));
|
||||
connect(m_nameLineEdit, SIGNAL(textChanged(const QString&)),
|
||||
this, SLOT(textChanged(const QString&)));
|
||||
|
||||
|
||||
@@ -523,7 +523,8 @@ void MidiClipView::paintEvent( QPaintEvent * )
|
||||
p.scale(width(), height() - distanceToTop - 2 * notesBorder);
|
||||
|
||||
// set colour based on mute status
|
||||
QColor noteFillColor = muted ? getMutedNoteFillColor() : getNoteFillColor();
|
||||
QColor noteFillColor = muted ? getMutedNoteFillColor().lighter(200)
|
||||
: (c.lightness() > 175 ? getNoteFillColor().darker(400) : getNoteFillColor());
|
||||
QColor noteBorderColor = muted ? getMutedNoteBorderColor()
|
||||
: ( m_clip->hasColor() ? c.lighter( 200 ) : getNoteBorderColor() );
|
||||
|
||||
|
||||
@@ -130,7 +130,10 @@ QPixmap* PianoRoll::s_toolKnife = nullptr;
|
||||
|
||||
SimpleTextFloat * PianoRoll::s_textFloat = nullptr;
|
||||
|
||||
static std::array<QString, 12> s_noteStrings {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||
static std::array<QString, 12> s_noteStrings {
|
||||
"C", "C\u266F / D\u266D", "D", "D\u266F / E\u266D", "E", "F", "F\u266F / G\u266D",
|
||||
"G", "G\u266F / A\u266D", "A", "A\u266F / B\u266D", "B"
|
||||
};
|
||||
|
||||
static QString getNoteString(int key)
|
||||
{
|
||||
@@ -1719,10 +1722,10 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
const NoteVector & notes = m_midiClip->notes();
|
||||
|
||||
// will be our iterator in the following loop
|
||||
auto it = notes.begin() + notes.size() - 1;
|
||||
auto it = notes.rbegin();
|
||||
|
||||
// loop through whole note-vector...
|
||||
for( int i = 0; i < notes.size(); ++i )
|
||||
while (it != notes.rend())
|
||||
{
|
||||
Note *note = *it;
|
||||
TimePos len = note->length();
|
||||
@@ -1747,7 +1750,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
{
|
||||
break;
|
||||
}
|
||||
--it;
|
||||
++it;
|
||||
}
|
||||
|
||||
// first check whether the user clicked in note-edit-
|
||||
@@ -1769,7 +1772,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
Note * created_new_note = nullptr;
|
||||
// did it reach end of vector because
|
||||
// there's no note??
|
||||
if( it == notes.begin()-1 )
|
||||
if (it == notes.rend())
|
||||
{
|
||||
is_new_note = true;
|
||||
m_midiClip->addJournalCheckPoint();
|
||||
@@ -1816,8 +1819,8 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
// reset it so that it can be used for
|
||||
// ops (move, resize) after this
|
||||
// code-block
|
||||
it = notes.begin();
|
||||
while( it != notes.end() && *it != created_new_note )
|
||||
it = notes.rbegin();
|
||||
while (it != notes.rend() && *it != created_new_note)
|
||||
{
|
||||
++it;
|
||||
}
|
||||
@@ -1933,7 +1936,7 @@ void PianoRoll::mousePressEvent(QMouseEvent * me )
|
||||
{
|
||||
// erase single note
|
||||
m_mouseDownRight = true;
|
||||
if( it != notes.begin()-1 )
|
||||
if (it != notes.rend())
|
||||
{
|
||||
m_midiClip->addJournalCheckPoint();
|
||||
m_midiClip->removeNote( *it );
|
||||
@@ -2513,7 +2516,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
|
||||
bool altPressed = me->modifiers() & Qt::AltModifier;
|
||||
// We iterate from last note in MIDI clip to the first,
|
||||
// chronologically
|
||||
auto it = notes.begin() + notes.size() - 1;
|
||||
auto it = notes.rbegin();
|
||||
for( int i = 0; i < notes.size(); ++i )
|
||||
{
|
||||
Note* n = *it;
|
||||
@@ -2556,7 +2559,7 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
|
||||
}
|
||||
|
||||
|
||||
--it;
|
||||
++it;
|
||||
}
|
||||
|
||||
// Emit MIDI clip has changed
|
||||
@@ -2575,10 +2578,10 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
|
||||
const NoteVector & notes = m_midiClip->notes();
|
||||
|
||||
// will be our iterator in the following loop
|
||||
auto it = notes.begin() + notes.size() - 1;
|
||||
auto it = notes.rbegin();
|
||||
|
||||
// loop through whole note-vector...
|
||||
for( int i = 0; i < notes.size(); ++i )
|
||||
while (it != notes.rend())
|
||||
{
|
||||
Note *note = *it;
|
||||
// and check whether the cursor is over an
|
||||
@@ -2591,12 +2594,12 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
|
||||
{
|
||||
break;
|
||||
}
|
||||
--it;
|
||||
++it;
|
||||
}
|
||||
|
||||
// did it reach end of vector because there's
|
||||
// no note??
|
||||
if( it != notes.begin()-1 )
|
||||
if (it != notes.rend())
|
||||
{
|
||||
Note *note = *it;
|
||||
// x coordinate of the right edge of the note
|
||||
|
||||
@@ -34,7 +34,6 @@ MidiPortMenu::MidiPortMenu( MidiPort::Mode _mode ) :
|
||||
ModelView( nullptr, this ),
|
||||
m_mode( _mode )
|
||||
{
|
||||
setFont( pointSize<9>( font() ) );
|
||||
connect( this, SIGNAL(triggered(QAction*)),
|
||||
this, SLOT(activatedPort(QAction*)));
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
AutoDetectMidiController( Model* parent ) :
|
||||
MidiController( parent ),
|
||||
m_detectedMidiChannel( 0 ),
|
||||
m_detectedMidiController( 0 )
|
||||
m_detectedMidiController(NONE)
|
||||
{
|
||||
updateName();
|
||||
}
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) )
|
||||
{
|
||||
m_detectedMidiChannel = event.channel() + 1;
|
||||
m_detectedMidiController = event.controllerNumber() + 1;
|
||||
m_detectedMidiController = event.controllerNumber();
|
||||
m_detectedMidiPort = Engine::audioEngine()->midiClient()->sourcePortName( event );
|
||||
|
||||
emit valueChanged();
|
||||
@@ -152,7 +152,7 @@ ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent,
|
||||
|
||||
m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox,
|
||||
tr( "Input controller" ) );
|
||||
m_midiControllerSpinBox->addTextForValue( 0, "---" );
|
||||
m_midiControllerSpinBox->addTextForValue(MidiController::NONE, "---" );
|
||||
m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) );
|
||||
m_midiControllerSpinBox->move( 68, 24 );
|
||||
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
#include <QUrl>
|
||||
#include <QListView>
|
||||
#include <QStandardPaths>
|
||||
#include <QStorageInfo>
|
||||
#include <QStringList>
|
||||
|
||||
#include "ConfigManager.h"
|
||||
#include "FileDialog.h"
|
||||
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
|
||||
@@ -45,19 +46,38 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption,
|
||||
|
||||
setOption( QFileDialog::DontUseNativeDialog );
|
||||
|
||||
// Add additional locations to the sidebar
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
QList<QUrl> urls;
|
||||
#else
|
||||
QList<QUrl> urls = sidebarUrls();
|
||||
urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::DesktopLocation ) );
|
||||
// Find downloads directory
|
||||
QDir downloadDir( QDir::homePath() + "/Downloads" );
|
||||
if ( ! downloadDir.exists() )
|
||||
downloadDir.setPath(QStandardPaths::writableLocation( QStandardPaths::DownloadLocation ));
|
||||
if ( downloadDir.exists() )
|
||||
urls << QUrl::fromLocalFile( downloadDir.absolutePath() );
|
||||
#endif
|
||||
|
||||
urls << QUrl::fromLocalFile( QStandardPaths::writableLocation( QStandardPaths::MusicLocation ) );
|
||||
urls << QUrl::fromLocalFile( ConfigManager::inst()->workingDir() );
|
||||
QDir desktopDir;
|
||||
desktopDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||
if (desktopDir.exists())
|
||||
{
|
||||
urls << QUrl::fromLocalFile(desktopDir.absolutePath());
|
||||
}
|
||||
|
||||
QDir downloadDir(QDir::homePath() + "/Downloads");
|
||||
if (!downloadDir.exists())
|
||||
{
|
||||
downloadDir.setPath(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
||||
}
|
||||
if (downloadDir.exists())
|
||||
{
|
||||
urls << QUrl::fromLocalFile(downloadDir.absolutePath());
|
||||
}
|
||||
|
||||
QDir musicDir;
|
||||
musicDir.setPath(QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
|
||||
if (musicDir.exists())
|
||||
{
|
||||
urls << QUrl::fromLocalFile(musicDir.absolutePath());
|
||||
}
|
||||
|
||||
urls << QUrl::fromLocalFile(ConfigManager::inst()->workingDir());
|
||||
|
||||
// Add `/Volumes` directory on OS X systems, this allows the user to browse
|
||||
// external disk drives.
|
||||
#ifdef LMMS_BUILD_APPLE
|
||||
@@ -66,6 +86,22 @@ FileDialog::FileDialog( QWidget *parent, const QString &caption,
|
||||
urls << QUrl::fromLocalFile( volumesDir.absolutePath() );
|
||||
#endif
|
||||
|
||||
#ifdef LMMS_BUILD_LINUX
|
||||
|
||||
// FileSystem types : https://www.javatpoint.com/linux-file-system
|
||||
QStringList usableFileSystems = {"ext", "ext2", "ext3", "ext4", "jfs", "reiserfs", "ntfs3", "fuse.sshfs", "fuseblk"};
|
||||
|
||||
for(QStorageInfo storage : QStorageInfo::mountedVolumes())
|
||||
{
|
||||
storage.refresh();
|
||||
|
||||
if (usableFileSystems.contains(QString(storage.fileSystemType()), Qt::CaseInsensitive) && storage.isValid() && storage.isReady())
|
||||
{
|
||||
urls << QUrl::fromLocalFile(storage.rootPath());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
setSidebarUrls(urls);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,6 @@ TrackOperationsWidget::TrackOperationsWidget( TrackView * parent ) :
|
||||
"to begin a new drag'n'drop action." ).arg(UI_CTRL_KEY) );
|
||||
|
||||
auto toMenu = new QMenu(this);
|
||||
toMenu->setFont( pointSize<9>( toMenu->font() ) );
|
||||
connect( toMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu()));
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ ComboBox::ComboBox( QWidget * _parent, const QString & _name ) :
|
||||
}
|
||||
|
||||
setFont( pointSize<9>( font() ) );
|
||||
m_menu.setFont( pointSize<8>( m_menu.font() ) );
|
||||
|
||||
connect( &m_menu, SIGNAL(triggered(QAction*)),
|
||||
this, SLOT(setItem(QAction*)));
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace lmms::gui
|
||||
|
||||
|
||||
LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this),
|
||||
m_wholeDisplay(numWhole, parent, name, false),
|
||||
m_fractionDisplay(numFrac, parent, name, true),
|
||||
@@ -62,6 +63,7 @@ LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& name,
|
||||
|
||||
|
||||
LcdFloatSpinBox::LcdFloatSpinBox(int numWhole, int numFrac, const QString& style, const QString& name, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
FloatModelView(new FloatModel(0, 0, 0, 0, nullptr, name, true), this),
|
||||
m_wholeDisplay(numWhole, style, parent, name, false),
|
||||
m_fractionDisplay(numFrac, style, parent, name, true),
|
||||
@@ -101,6 +103,7 @@ void LcdFloatSpinBox::layoutSetup(const QString &style)
|
||||
outerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
outerLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
this->setLayout(outerLayout);
|
||||
this->setFixedHeight(32);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,9 +243,9 @@ void LcdFloatSpinBox::paintEvent(QPaintEvent*)
|
||||
{
|
||||
p.setFont(pointSizeF(p.font(), 6.5));
|
||||
p.setPen(m_wholeDisplay.textShadowColor());
|
||||
p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2 + 1, height(), m_label);
|
||||
p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2 + 1, height(), m_label);
|
||||
p.setPen(m_wholeDisplay.textColor());
|
||||
p.drawText(width() / 2 - horizontalAdvance(p.fontMetrics(), m_label) / 2, height() - 1, m_label);
|
||||
p.drawText(width() / 2 - p.fontMetrics().boundingRect(m_label).width() / 2, height() - 1, m_label);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -570,6 +570,10 @@ f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const
|
||||
|
||||
void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer )
|
||||
{
|
||||
// Note: under certain circumstances the working buffer is a nullptr.
|
||||
// These cases are triggered in PlayHandle::doProcessing when the play method is called with a nullptr.
|
||||
// TODO: Find out if we can skip processing at a higher level if the buffer is nullptr.
|
||||
|
||||
// arpeggio- and chord-widget has to do its work -> adding sub-notes
|
||||
// for chords/arpeggios
|
||||
m_noteStacking.processNote( n );
|
||||
@@ -579,6 +583,15 @@ void InstrumentTrack::playNote( NotePlayHandle* n, sampleFrame* workingBuffer )
|
||||
{
|
||||
// all is done, so now lets play the note!
|
||||
m_instrument->playNote( n, workingBuffer );
|
||||
|
||||
// This is effectively the same as checking if workingBuffer is not a nullptr.
|
||||
// Calling processAudioBuffer with a nullptr leads to crashes. Hence the check.
|
||||
if (n->usesBuffer())
|
||||
{
|
||||
const fpp_t frames = n->framesLeftForCurrentPeriod();
|
||||
const f_cnt_t offset = n->noteOffset();
|
||||
processAudioBuffer(workingBuffer, frames + offset, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user