Merge branch 'master' into instr-sub-plugins

This commit is contained in:
Johannes Lorenz
2019-03-22 10:51:23 +01:00
144 changed files with 8061 additions and 3989 deletions

View File

@@ -45,7 +45,7 @@ INCLUDE(GenQrc)
ADD_GEN_QRC(LMMS_RCC_OUT lmms.qrc
"${CMAKE_SOURCE_DIR}/doc/AUTHORS"
"${CMAKE_SOURCE_DIR}/LICENSE.txt"
"${CMAKE_BINARY_DIR}/CONTRIBUTORS"
"${CONTRIBUTORS}"
)
# Paths relative to lmms executable

View File

@@ -89,16 +89,23 @@ bool AutomatableModel::isAutomated() const
}
bool AutomatableModel::mustQuoteName(const QString& name)
{
QRegExp reg("^[A-Za-z0-9._-]+$");
return !reg.exactMatch(name);
}
void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name )
{
bool mustQuote = mustQuoteName(name);
if( isAutomated() || m_scaleType != Linear )
{
// automation needs tuple of data (name, id, value)
// scale type also needs an extra value
// => it must be appended as a node
QRegExp reg("^[A-Za-z0-9._-]+$");
bool mustQuote = !reg.exactMatch(name);
QDomElement me = doc.createElement( mustQuote ? QString("automatablemodel") : name );
me.setAttribute( "id", ProjectJournal::idToSave( id() ) );
me.setAttribute( "value", m_value );
@@ -110,8 +117,18 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
}
else
{
// non automation, linear scale (default), can be saved as attribute
element.setAttribute( name, m_value );
if(mustQuote)
{
QDomElement me = doc.createElement( "automatablemodel" );
me.setAttribute( "nodename", name );
me.setAttribute( "value", m_value );
element.appendChild( me );
}
else
{
// non automation, linear scale (default), can be saved as attribute
element.setAttribute( name, m_value );
}
}
if( m_controllerConnection && m_controllerConnection->getController()->type()
@@ -131,7 +148,13 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co
element.appendChild( controllerElement );
}
QDomElement element = doc.createElement( name );
bool mustQuote = mustQuoteName(name);
QString elementName = mustQuote ? "controllerconnection"
: name;
QDomElement element = doc.createElement( elementName );
if(mustQuote)
element.setAttribute( "nodename", name );
m_controllerConnection->saveSettings( doc, element );
controllerElement.appendChild( element );
@@ -170,6 +193,17 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString&
if( connectionNode.isElement() )
{
QDomNode thisConnection = connectionNode.toElement().namedItem( name );
if( !thisConnection.isElement() )
{
thisConnection = connectionNode.toElement().namedItem( "controllerconnection" );
QDomElement tcElement = thisConnection.toElement();
// sanity check
if( tcElement.isNull() || tcElement.attribute( "nodename" ) != name )
{
// no, that wasn't it, act as if we never found one
thisConnection.clear();
}
}
if( thisConnection.isElement() )
{
setControllerConnection( new ControllerConnection( (Controller*)NULL ) );
@@ -432,7 +466,8 @@ void AutomatableModel::linkModel( AutomatableModel* model )
if( !model->hasLinkedModels() )
{
QObject::connect( this, SIGNAL( dataChanged() ), model, SIGNAL( dataChanged() ) );
QObject::connect( this, SIGNAL( dataChanged() ),
model, SIGNAL( dataChanged() ), Qt::DirectConnection );
}
}
}
@@ -488,7 +523,8 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c )
m_controllerConnection = c;
if( c )
{
QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ), this, SIGNAL( dataChanged() ) );
QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ),
this, SIGNAL( dataChanged() ), Qt::DirectConnection );
QObject::connect( m_controllerConnection, SIGNAL( destroyed() ), this, SLOT( unlinkControllerConnection() ) );
m_valueChanged = true;
emit dataChanged();

View File

@@ -772,6 +772,16 @@ void AutomationPattern::resolveAllIDs()
{
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
}
else
{
// FIXME: Remove this block once the automation system gets fixed
// This is a temporary fix for https://github.com/LMMS/lmms/issues/3781
o = Engine::projectJournal()->journallingObject(ProjectJournal::idFromSave(*k));
if( o && dynamic_cast<AutomatableModel *>( o ) )
{
a->addObject( dynamic_cast<AutomatableModel *>( o ), false );
}
}
}
a->m_idsToResolve.clear();
a->dataChanged();

View File

@@ -67,6 +67,7 @@ set(LMMS_SRCS
core/TrackContainer.cpp
core/ValueBuffer.cpp
core/VstSyncController.cpp
core/StepRecorder.cpp
core/audio/AudioAlsa.cpp
core/audio/AudioDevice.cpp

View File

@@ -117,7 +117,7 @@ void ControllerConnection::setController( Controller * _controller )
{
_controller->addConnection( this );
QObject::connect( _controller, SIGNAL( valueChanged() ),
this, SIGNAL( valueChanged() ) );
this, SIGNAL( valueChanged() ), Qt::DirectConnection );
}
m_ownsController =

View File

@@ -32,6 +32,7 @@
#include "Song.h"
#include "InstrumentTrack.h"
#include "SampleTrack.h"
#include "BBTrackContainer.h"
FxRoute::FxRoute( FxChannel * from, FxChannel * to, float amount ) :
@@ -305,6 +306,22 @@ void FxMixer::deleteChannel( int index )
inst->effectChannelModel()->setValue(val-1);
}
}
else if( t->type() == Track::SampleTrack )
{
SampleTrack* strk = dynamic_cast<SampleTrack *>( t );
int val = strk->effectChannelModel()->value(0);
if( val == index )
{
// we are deleting this track's fx send
// send to master
strk->effectChannelModel()->setValue(0);
}
else if( val > index )
{
// subtract 1 to make up for the missing channel
strk->effectChannelModel()->setValue(val-1);
}
}
}
FxChannel * ch = m_fxChannels[index];
@@ -379,6 +396,19 @@ void FxMixer::moveChannelLeft( int index )
inst->effectChannelModel()->setValue(a);
}
}
else if( trackList[i]->type() == Track::SampleTrack )
{
SampleTrack * strk = (SampleTrack *) trackList[i];
int val = strk->effectChannelModel()->value(0);
if( val == a )
{
strk->effectChannelModel()->setValue(b);
}
else if( val == b )
{
strk->effectChannelModel()->setValue(a);
}
}
}
}
@@ -780,4 +810,3 @@ void FxMixer::validateChannelName( int index, int oldIndex )
m_fxChannels[index]->m_name = tr( "FX %1" ).arg( index );
}
}

View File

@@ -26,6 +26,11 @@
#include "lmms_math.h"
#include "ValueBuffer.h"
#include <cstdio>
static bool s_NaNHandler;
namespace MixHelpers
{
@@ -68,10 +73,24 @@ bool isSilent( const sampleFrame* src, int frames )
return true;
}
bool useNaNHandler()
{
return s_NaNHandler;
}
void setNaNHandler( bool use )
{
s_NaNHandler = use;
}
/*! \brief Function for sanitizing a buffer of infs/nans - returns true if those are found */
bool sanitize( sampleFrame * src, int frames )
{
if( !useNaNHandler() )
{
return false;
}
bool found = false;
for( int f = 0; f < frames; ++f )
{
@@ -79,12 +98,23 @@ bool sanitize( sampleFrame * src, int frames )
{
if( isinf( src[f][c] ) || isnan( src[f][c] ) )
{
src[f][c] = 0.0f;
#ifdef LMMS_DEBUG
printf("Bad data, clearing buffer. frame: ");
printf("%d: value %f\n", f, src[f][c]);
#endif
for( int f = 0; f < frames; ++f )
{
for( int c = 0; c < 2; ++c )
{
src[f][c] = 0.0f;
}
}
found = true;
return found;
}
else
{
src[f][c] = qBound( -4.0f, src[f][c], 4.0f );
src[f][c] = qBound( -1000.0f, src[f][c], 1000.0f );
}
}
}
@@ -168,6 +198,13 @@ void addMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuff
void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames )
{
if ( !useNaNHandler() )
{
addMultipliedByBuffer( dst, src, coeffSrc, coeffSrcBuf,
frames );
return;
}
for( int f = 0; f < frames; ++f )
{
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f];
@@ -177,6 +214,13 @@ void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, f
void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames )
{
if ( !useNaNHandler() )
{
addMultipliedByBuffers( dst, src, coeffSrcBuf1, coeffSrcBuf2,
frames );
return;
}
for( int f = 0; f < frames; ++f )
{
dst[f][0] += ( isinf( src[f][0] ) || isnan( src[f][0] ) )
@@ -205,6 +249,12 @@ struct AddSanitizedMultipliedOp
void addSanitizedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames )
{
if ( !useNaNHandler() )
{
addMultiplied( dst, src, coeffSrc, frames );
return;
}
run<>( dst, src, frames, AddSanitizedMultipliedOp(coeffSrc) );
}

View File

@@ -177,11 +177,6 @@ void NotePlayHandle::setVolume( volume_t _volume )
void NotePlayHandle::setPanning( panning_t panning )
{
Note::setPanning( panning );
MidiEvent event( MidiMetaEvent, midiChannel(), midiKey(), panningToMidi( panning ) );
event.setMetaEvent( MidiNotePanning );
m_instrumentTrack->processOutEvent( event );
}

View File

@@ -218,6 +218,7 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
{
const PluginFactory::PluginInfo& pi = pluginFactory->pluginInfo(pluginName.toUtf8());
Plugin* inst;
if( pi.isNull() )
{
if( gui )
@@ -228,28 +229,31 @@ Plugin * Plugin::instantiate(const QString& pluginName, Model * parent,
arg( pluginName ).arg( pluginFactory->errorString(pluginName) ),
QMessageBox::Ok | QMessageBox::Default );
}
return new DummyPlugin();
}
Plugin* inst;
InstantiationHook instantiationHook;
if ((instantiationHook = ( InstantiationHook ) pi.library->resolve( "lmms_plugin_main" )))
{
inst = instantiationHook(parent, data);
inst = new DummyPlugin();
}
else
{
if( gui )
InstantiationHook instantiationHook;
if ((instantiationHook = ( InstantiationHook ) pi.library->resolve( "lmms_plugin_main" )))
{
QMessageBox::information( NULL,
tr( "Error while loading plugin" ),
tr( "Failed to load plugin \"%1\"!").arg( pluginName ),
QMessageBox::Ok | QMessageBox::Default );
inst = instantiationHook(parent, data);
if(!inst) {
inst = new DummyPlugin();
}
}
else
{
if( gui )
{
QMessageBox::information( NULL,
tr( "Error while loading plugin" ),
tr( "Failed to load plugin \"%1\"!").arg( pluginName ),
QMessageBox::Ok | QMessageBox::Default );
}
inst = new DummyPlugin();
}
return new DummyPlugin();
}
return inst;
}

View File

@@ -164,6 +164,11 @@ jo_id_t ProjectJournal::idToSave( jo_id_t id )
return id & ~EO_ID_MSB;
}
jo_id_t ProjectJournal::idFromSave( jo_id_t id )
{
return id | EO_ID_MSB;
}

View File

@@ -185,25 +185,17 @@ void ProjectRenderer::run()
// Skip first empty buffer.
Engine::mixer()->nextBuffer();
const Song::PlayPos & exportPos = Engine::getSong()->getPlayPos(
Song::Mode_PlaySong );
m_progress = 0;
std::pair<MidiTime, MidiTime> exportEndpoints = Engine::getSong()->getExportEndpoints();
tick_t startTick = exportEndpoints.first.getTicks();
tick_t endTick = exportEndpoints.second.getTicks();
tick_t lengthTicks = endTick - startTick;
// Now start processing
Engine::mixer()->startProcessing(false);
// Continually track and emit progress percentage to listeners.
while( exportPos.getTicks() < endTick &&
Engine::getSong()->isExporting() == true
&& !m_abort )
while (!Engine::getSong()->isExportDone() && !m_abort)
{
m_fileDev->processNextBuffer();
const int nprog = lengthTicks == 0 ? 100 : (exportPos.getTicks()-startTick) * 100 / lengthTicks;
if( m_progress != nprog )
const int nprog = Engine::getSong()->getExportProgress();
if (m_progress != nprog)
{
m_progress = nprog;
emit progressChanged( m_progress );

View File

@@ -55,7 +55,10 @@ ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) :
void ProcessWatcher::run()
{
while( !m_quit && (m_plugin->isRunning() || m_plugin->messagesLeft()) )
m_plugin->m_process.start( m_plugin->m_exec, m_plugin->m_args );
exec();
m_plugin->m_process.moveToThread( m_plugin->thread() );
while( !m_quit && m_plugin->messagesLeft() )
{
msleep( 200 );
}
@@ -123,9 +126,13 @@ RemotePlugin::RemotePlugin() :
#endif
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
this, SLOT( processFinished( int, QProcess::ExitStatus ) ) );
this, SLOT( processFinished( int, QProcess::ExitStatus ) ),
Qt::DirectConnection );
connect( &m_process, SIGNAL( errorOccurred( QProcess::ProcessError ) ),
this, SLOT( processErrored( QProcess::ProcessError ) ) );
this, SLOT( processErrored( QProcess::ProcessError ) ),
Qt::DirectConnection );
connect( &m_process, SIGNAL( finished( int, QProcess::ExitStatus ) ),
&m_watcher, SLOT( quit() ), Qt::DirectConnection );
}
@@ -133,7 +140,7 @@ RemotePlugin::RemotePlugin() :
RemotePlugin::~RemotePlugin()
{
m_watcher.quit();
m_watcher.stop();
m_watcher.wait();
if( m_failed == false )
@@ -207,6 +214,11 @@ bool RemotePlugin::init(const QString &pluginExecutable,
return failed();
}
// ensure the watcher is ready in case we're running again
// (e.g. 32-bit VST plugins on Windows)
m_watcher.wait();
m_watcher.reset();
QStringList args;
#ifdef SYNC_WITH_SHM_FIFO
// swap in and out for bidirectional communication
@@ -219,7 +231,10 @@ bool RemotePlugin::init(const QString &pluginExecutable,
#ifndef DEBUG_REMOTE_PLUGIN
m_process.setProcessChannelMode( QProcess::ForwardedChannels );
m_process.setWorkingDirectory( QCoreApplication::applicationDirPath() );
m_process.start( exec, args );
m_exec = exec;
m_args = args;
// we start the process on the watcher thread to work around QTBUG-8819
m_process.moveToThread( &m_watcher );
m_watcher.start( QThread::LowestPriority );
#else
qDebug() << exec << args;

View File

@@ -103,7 +103,7 @@ void RenderManager::renderTracks()
Track* tk = (*it);
Track::TrackTypes type = tk->type();
// Don't mute automation tracks
// Don't render automation tracks
if ( tk->isMuted() == false &&
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
{
@@ -115,7 +115,11 @@ void RenderManager::renderTracks()
for( auto it = t2.begin(); it != t2.end(); ++it )
{
Track* tk = (*it);
if ( tk->isMuted() == false )
Track::TrackTypes type = tk->type();
// Don't render automation tracks
if ( tk->isMuted() == false &&
( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
{
m_unmuted.push_back(tk);
}

View File

@@ -86,7 +86,9 @@ Song::Song() :
m_patternToPlay( NULL ),
m_loopPattern( false ),
m_elapsedTicks( 0 ),
m_elapsedTacts( 0 )
m_elapsedTacts( 0 ),
m_loopRenderCount(1),
m_loopRenderRemaining(1)
{
for(int i = 0; i < Mode_Count; ++i) m_elapsedMilliSeconds[i] = 0;
connect( &m_tempoModel, SIGNAL( dataChanged() ),
@@ -258,8 +260,6 @@ void Song::processNextBuffer()
m_playPos[m_playMode].setTicks(
tl->loopBegin().getTicks() );
m_vstSyncController.setAbsolutePosition(
tl->loopBegin().getTicks() );
m_vstSyncController.setPlaybackJumped( true );
emit updateSampleTracks();
@@ -286,8 +286,6 @@ void Song::processNextBuffer()
int ticks = m_playPos[m_playMode].getTicks() +
( int )( currentFrame / framesPerTick );
m_vstSyncController.setAbsolutePosition( ticks );
// did we play a whole tact?
if( ticks >= MidiTime::ticksPerTact() )
{
@@ -324,13 +322,12 @@ void Song::processNextBuffer()
// wrap milli second counter
setToTimeByTicks(ticks);
m_vstSyncController.setAbsolutePosition( ticks );
m_vstSyncController.setPlaybackJumped( true );
}
}
m_playPos[m_playMode].setTicks( ticks );
if( checkLoop )
if (checkLoop || m_loopRenderRemaining > 1)
{
m_vstSyncController.startCycle(
tl->loopBegin().getTicks(), tl->loopEnd().getTicks() );
@@ -340,11 +337,12 @@ void Song::processNextBuffer()
// beginning of the range
if( m_playPos[m_playMode] >= tl->loopEnd() )
{
if (m_loopRenderRemaining > 1)
m_loopRenderRemaining--;
ticks = tl->loopBegin().getTicks();
m_playPos[m_playMode].setTicks( ticks );
setToTime(tl->loopBegin());
m_vstSyncController.setAbsolutePosition( ticks );
m_vstSyncController.setPlaybackJumped( true );
emit updateSampleTracks();
@@ -359,7 +357,17 @@ void Song::processNextBuffer()
m_playPos[m_playMode].setCurrentFrame( currentFrame );
}
f_cnt_t framesToPlay =
if( framesPlayed == 0 )
{
// update VST sync position after we've corrected frame/
// tick count but before actually playing any frames
m_vstSyncController.setAbsolutePosition(
m_playPos[m_playMode].getTicks()
+ m_playPos[m_playMode].currentFrame()
/ (double) framesPerTick );
}
f_cnt_t framesToPlay =
Engine::mixer()->framesPerPeriod() - framesPlayed;
f_cnt_t framesLeft = ( f_cnt_t )framesPerTick -
@@ -476,29 +484,41 @@ void Song::setModified(bool value)
}
}
std::pair<MidiTime, MidiTime> Song::getExportEndpoints() const
bool Song::isExportDone() const
{
if ( m_renderBetweenMarkers )
return !isExporting() || m_playPos[m_playMode] >= m_exportSongEnd;
}
int Song::getExportProgress() const
{
MidiTime pos = m_playPos[m_playMode];
if (pos >= m_exportSongEnd)
{
return std::pair<MidiTime, MidiTime>(
m_playPos[Mode_PlaySong].m_timeLine->loopBegin(),
m_playPos[Mode_PlaySong].m_timeLine->loopEnd()
);
return 100;
}
else if ( m_exportLoop )
else if (pos <= m_exportSongBegin)
{
return std::pair<MidiTime, MidiTime>( MidiTime(0, 0), MidiTime(m_length, 0) );
return 0;
}
else if (pos >= m_exportLoopEnd)
{
pos = (m_exportLoopBegin-m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin) *
m_loopRenderCount + (pos - m_exportLoopEnd);
}
else if ( pos >= m_exportLoopBegin )
{
pos = (m_exportLoopBegin-m_exportSongBegin) + ((m_exportLoopEnd - m_exportLoopBegin) *
(m_loopRenderCount - m_loopRenderRemaining)) + (pos - m_exportLoopBegin);
}
else
{
// if not exporting as a loop, we leave one bar of padding at the end of the song to accomodate reverb, etc.
return std::pair<MidiTime, MidiTime>( MidiTime(0, 0), MidiTime(m_length+1, 0) );
pos = (pos - m_exportSongBegin);
}
return (float)pos/(float)m_exportEffectiveLength*100.0f;
}
void Song::playSong()
{
m_recording = false;
@@ -702,7 +722,10 @@ void Song::stop()
m_playPos[m_playMode].setCurrentFrame( 0 );
m_vstSyncController.setPlaybackState( m_exporting );
m_vstSyncController.setAbsolutePosition( m_playPos[m_playMode].getTicks() );
m_vstSyncController.setAbsolutePosition(
m_playPos[m_playMode].getTicks()
+ m_playPos[m_playMode].currentFrame()
/ (double) Engine::framesPerTick() );
// remove all note-play-handles that are active
Engine::mixer()->clear();
@@ -719,15 +742,41 @@ void Song::stop()
void Song::startExport()
{
stop();
if(m_renderBetweenMarkers)
if (m_renderBetweenMarkers)
{
m_exportSongBegin = m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin();
m_exportSongEnd = m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopEnd();
m_playPos[Mode_PlaySong].setTicks( m_playPos[Mode_PlaySong].m_timeLine->loopBegin().getTicks() );
}
else
{
m_exportSongEnd = MidiTime(m_length, 0);
// Handle potentially ridiculous loop points gracefully.
if (m_loopRenderCount > 1 && m_playPos[Mode_PlaySong].m_timeLine->loopEnd() > m_exportSongEnd)
{
m_exportSongEnd = m_playPos[Mode_PlaySong].m_timeLine->loopEnd();
}
if (!m_exportLoop)
m_exportSongEnd += MidiTime(1,0);
m_exportSongBegin = MidiTime(0,0);
m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd &&
m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ?
m_playPos[Mode_PlaySong].m_timeLine->loopBegin() : MidiTime(0,0);
m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd &&
m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ?
m_playPos[Mode_PlaySong].m_timeLine->loopEnd() : MidiTime(0,0);
m_playPos[Mode_PlaySong].setTicks( 0 );
}
m_exportEffectiveLength = (m_exportLoopBegin - m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin)
* m_loopRenderCount + (m_exportSongEnd - m_exportLoopEnd);
m_loopRenderRemaining = m_loopRenderCount;
playSong();
m_exporting = true;

366
src/core/StepRecorder.cpp Normal file
View File

@@ -0,0 +1,366 @@
/*
* 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.
*
*/
#include "StepRecorder.h"
#include "StepRecorderWidget.h"
#include "PianoRoll.h"
#include <QPainter>
#include <climits>
using std::min;
using std::max;
const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70;
StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget):
m_pianoRoll(pianoRoll),
m_stepRecorderWidget(stepRecorderWidget)
{
m_stepRecorderWidget.hide();
}
void StepRecorder::initialize()
{
connect(&m_updateReleasedTimer, SIGNAL(timeout()), this, SLOT(removeNotesReleasedForTooLong()));
}
void StepRecorder::start(const MidiTime& currentPosition, const MidiTime& stepLength)
{
m_isRecording = true;
setStepsLength(stepLength);
// quantize current position to get start recording position
const int q = m_pianoRoll.quantization();
const int curPosTicks = currentPosition.getTicks();
const int QuantizedPosTicks = (curPosTicks / q) * q;
const MidiTime& QuantizedPos = MidiTime(QuantizedPosTicks);
m_curStepStartPos = QuantizedPos;
m_curStepLength = 0;
m_stepRecorderWidget.show();
m_stepRecorderWidget.showHint();
prepareNewStep();
}
void StepRecorder::stop()
{
m_stepRecorderWidget.hide();
m_isRecording = false;
}
void StepRecorder::notePressed(const Note & n)
{
//if this is the first pressed note in step, advance position
if(!m_isStepInProgress)
{
m_isStepInProgress = true;
//move curser one step forwards
stepForwards();
}
StepNote* stepNote = findCurStepNote(n.key());
if(stepNote == nullptr)
{
m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning())));
m_pianoRoll.update();
}
else if (stepNote->isReleased())
{
stepNote->setPressed();
}
}
void StepRecorder::noteReleased(const Note & n)
{
StepNote* stepNote = findCurStepNote(n.key());
if(stepNote != nullptr && stepNote->isPressed())
{
stepNote->setReleased();
//if m_updateReleasedTimer is not already active, activate it
//(when activated, the timer will re-set itself as long as there are released notes)
if(!m_updateReleasedTimer.isActive())
{
m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS);
}
//check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step
if(allCurStepNotesReleased())
{
if(m_curStepLength > 0)
{
applyStep();
}
else
{
dismissStep();
}
}
}
}
bool StepRecorder::keyPressEvent(QKeyEvent* ke)
{
bool event_handled = false;
switch(ke->key())
{
case Qt::Key_Right:
{
if(!ke->isAutoRepeat())
{
stepForwards();
}
event_handled = true;
break;
}
case Qt::Key_Left:
{
if(!ke->isAutoRepeat())
{
stepBackwards();
}
event_handled = true;
break;
}
}
return event_handled;
}
void StepRecorder::setStepsLength(const MidiTime& newLength)
{
if(m_isStepInProgress)
{
//update current step length by the new amount : (number_of_steps * newLength)
m_curStepLength = (m_curStepLength / m_stepsLength) * newLength;
updateCurStepNotes();
}
m_stepsLength = newLength;
updateWidget();
}
QVector<Note*> StepRecorder::getCurStepNotes()
{
QVector<Note*> notes;
if(m_isStepInProgress)
{
for(StepNote* stepNote: m_curStepNotes)
{
notes.append(&stepNote->m_note);
}
}
return notes;
}
void StepRecorder::stepForwards()
{
if(m_isStepInProgress)
{
m_curStepLength += m_stepsLength;
updateCurStepNotes();
}
else
{
m_curStepStartPos += m_stepsLength;
}
updateWidget();
}
void StepRecorder::stepBackwards()
{
if(m_isStepInProgress)
{
if(m_curStepLength > 0)
{
m_curStepLength = max(m_curStepLength - m_stepsLength, 0);
}
else
{
//if length is already zero - move starting position backwards
m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0);
}
updateCurStepNotes();
}
else
{
m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0);
}
updateWidget();
}
void StepRecorder::applyStep()
{
m_pattern->addJournalCheckPoint();
for (const StepNote* stepNote : m_curStepNotes)
{
m_pattern->addNote(stepNote->m_note, false);
}
m_pattern->rearrangeAllNotes();
m_pattern->updateLength();
m_pattern->dataChanged();
Engine::getSong()->setModified();
prepareNewStep();
}
void StepRecorder::dismissStep()
{
if(!m_isStepInProgress)
{
return;
}
prepareNewStep();
}
void StepRecorder::prepareNewStep()
{
for(StepNote* stepNote : m_curStepNotes)
{
delete stepNote;
}
m_curStepNotes.clear();
m_isStepInProgress = false;
m_curStepStartPos = getCurStepEndPos();
m_curStepLength = 0;
updateWidget();
}
void StepRecorder::setCurrentPattern( Pattern* newPattern )
{
if(m_pattern != NULL && m_pattern != newPattern)
{
dismissStep();
}
m_pattern = newPattern;
}
void StepRecorder::removeNotesReleasedForTooLong()
{
int nextTimout = std::numeric_limits<int>::max();
bool notesRemoved = false;
QMutableVectorIterator<StepNote*> itr(m_curStepNotes);
while (itr.hasNext())
{
StepNote* stepNote = itr.next();
if(stepNote->isReleased())
{
const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout
if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS)
{
delete stepNote;
itr.remove();
notesRemoved = true;
}
else
{
nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased);
}
}
}
if(notesRemoved)
{
m_pianoRoll.update();
}
if(nextTimout != std::numeric_limits<int>::max())
{
m_updateReleasedTimer.start(nextTimout);
}
else
{
// no released note found for next timout, stop timer
m_updateReleasedTimer.stop();
}
}
MidiTime StepRecorder::getCurStepEndPos()
{
return m_curStepStartPos + m_curStepLength;
}
void StepRecorder::updateCurStepNotes()
{
for (StepNote* stepNote : m_curStepNotes)
{
stepNote->m_note.setLength(m_curStepLength);
stepNote->m_note.setPos(m_curStepStartPos);
}
}
void StepRecorder::updateWidget()
{
m_stepRecorderWidget.setStartPosition(m_curStepStartPos);
m_stepRecorderWidget.setEndPosition(getCurStepEndPos());
m_stepRecorderWidget.setStepsLength(m_stepsLength);
}
bool StepRecorder::allCurStepNotesReleased()
{
for (const StepNote* stepNote : m_curStepNotes)
{
if(stepNote->isPressed())
{
return false;
}
}
return true;
}
StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
{
for (StepNote* stepNote : m_curStepNotes)
{
if(stepNote->m_note.key() == key)
{
return stepNote;
}
}
return nullptr;
}

View File

@@ -909,10 +909,18 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
else if( m_action == MoveSelection )
{
const int dx = me->x() - m_initialMousePos.x();
const bool snap = !(me->modifiers() & Qt::ControlModifier) &&
me->button() == Qt::NoButton;
QVector<selectableObject *> so =
m_trackView->trackContainerView()->selectedObjects();
QVector<TrackContentObject *> tcos;
MidiTime smallest_pos, t;
int smallestPos = 0;
MidiTime dtick = MidiTime( static_cast<int>( dx *
MidiTime::ticksPerTact() / ppt ) );
if( snap )
{
dtick = dtick.toNearestTact();
}
// find out smallest position of all selected objects for not
// moving an object before zero
for( QVector<selectableObject *>::iterator it = so.begin();
@@ -926,23 +934,18 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me )
}
TrackContentObject * tco = tcov->m_tco;
tcos.push_back( tco );
smallest_pos = qMin<int>( smallest_pos,
(int)tco->startPosition() +
static_cast<int>( dx *
MidiTime::ticksPerTact() / ppt ) );
smallestPos = qMin<int>( smallestPos,
(int)tco->startPosition() + dtick );
}
dtick -= smallestPos;
if( snap )
{
dtick = dtick.toAbsoluteTact(); // round toward 0
}
for( QVector<TrackContentObject *>::iterator it = tcos.begin();
it != tcos.end(); ++it )
{
t = ( *it )->startPosition() +
static_cast<int>( dx *MidiTime::ticksPerTact() /
ppt )-smallest_pos;
if( ! ( me->modifiers() & Qt::ControlModifier )
&& me->button() == Qt::NoButton )
{
t = t.toNearestTact();
}
( *it )->movePosition( t );
( *it )->movePosition( ( *it )->startPosition() + dtick );
}
}
else if( m_action == Resize || m_action == ResizeLeft )
@@ -1921,13 +1924,15 @@ void TrackOperationsWidget::updateMenu()
{
toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) );
}
if( InstrumentTrackView * trackView = dynamic_cast<InstrumentTrackView *>( m_trackView ) )
if (QMenu *fxMenu = m_trackView->createFxMenu(tr("FX %1: %2"), tr("Assign to new FX Channel")))
{
QMenu *fxMenu = trackView->createFxMenu( tr( "FX %1: %2" ), tr( "Assign to new FX Channel" ));
toMenu->addMenu(fxMenu);
}
if (InstrumentTrackView * trackView = dynamic_cast<InstrumentTrackView *>(m_trackView))
{
toMenu->addSeparator();
toMenu->addMenu( trackView->midiMenu() );
toMenu->addMenu(trackView->midiMenu());
}
if( dynamic_cast<AutomationTrackView *>( m_trackView ) )
{
@@ -2674,6 +2679,19 @@ void TrackView::update()
/*! \brief Create a menu for assigning/creating channels for this track.
*
*/
QMenu * TrackView::createFxMenu(QString title, QString newFxLabel)
{
Q_UNUSED(title)
Q_UNUSED(newFxLabel)
return NULL;
}
/*! \brief Close this track View.
*
*/

View File

@@ -136,12 +136,12 @@ VstSyncController::~VstSyncController()
void VstSyncController::setAbsolutePosition( int ticks )
void VstSyncController::setAbsolutePosition( double ticks )
{
#ifdef VST_SNC_LATENCY
m_syncData->ppqPos = ( ( ticks + 0 ) / (float)48 ) - m_syncData->m_latency;
m_syncData->ppqPos = ( ( ticks + 0 ) / 48.0 ) - m_syncData->m_latency;
#else
m_syncData->ppqPos = ( ( ticks + 0 ) / (float)48 );
m_syncData->ppqPos = ( ( ticks + 0 ) / 48.0 );
#endif
}

View File

@@ -118,7 +118,9 @@ void AudioPort::doProcessing()
{
if( ph->buffer() )
{
if( ph->usesBuffer() )
if( ph->usesBuffer()
&& ( ph->type() == PlayHandle::TypeNotePlayHandle
|| !MixHelpers::isSilent( ph->buffer(), fpp ) ) )
{
m_bufferUsage = true;
MixHelpers::add( m_portBuffer, ph->buffer(), fpp );

View File

@@ -27,6 +27,10 @@
#include "AudioPortAudio.h"
#ifndef LMMS_HAVE_PORTAUDIO
void AudioPortAudioSetupUtil::updateBackends()
{
}
void AudioPortAudioSetupUtil::updateDevices()
{
}
@@ -328,6 +332,28 @@ int AudioPortAudio::_process_callback(
void AudioPortAudioSetupUtil::updateBackends()
{
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
const PaHostApiInfo * hi;
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
{
hi = Pa_GetHostApiInfo( i );
m_backendModel.addItem( hi->name );
}
Pa_Terminate();
}
void AudioPortAudioSetupUtil::updateDevices()
{
PaError err = Pa_Initialize();
@@ -409,37 +435,6 @@ AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) :
m_channels->setLabel( tr( "CHANNELS" ) );
m_channels->move( 308, 20 );*/
// Setup models
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
// todo: setup backend model
const PaHostApiInfo * hi;
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
{
hi = Pa_GetHostApiInfo( i );
m_setupUtil.m_backendModel.addItem( hi->name );
}
Pa_Terminate();
const QString& backend = ConfigManager::inst()->value( "audioportaudio",
"backend" );
const QString& device = ConfigManager::inst()->value( "audioportaudio",
"device" );
int i = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) );
m_setupUtil.m_backendModel.setValue( i );
m_setupUtil.updateDevices();
i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) );
m_setupUtil.m_deviceModel.setValue( i );
connect( &m_setupUtil.m_backendModel, SIGNAL( dataChanged() ),
&m_setupUtil, SLOT( updateDevices() ) );
@@ -478,6 +473,33 @@ void AudioPortAudio::setupWidget::saveSettings()
}
void AudioPortAudio::setupWidget::show()
{
if( m_setupUtil.m_backendModel.size() == 0 )
{
// populate the backend model the first time we are shown
m_setupUtil.updateBackends();
const QString& backend = ConfigManager::inst()->value(
"audioportaudio", "backend" );
const QString& device = ConfigManager::inst()->value(
"audioportaudio", "device" );
int i = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) );
m_setupUtil.m_backendModel.setValue( i );
m_setupUtil.updateDevices();
i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) );
m_setupUtil.m_deviceModel.setValue( i );
}
AudioDeviceSetupWidget::show();
}
#endif

View File

@@ -65,6 +65,7 @@
#include "GuiApplication.h"
#include "ImportFilter.h"
#include "MainWindow.h"
#include "MixHelpers.h"
#include "OutputSettings.h"
#include "ProjectRenderer.h"
#include "RenderManager.h"
@@ -105,6 +106,21 @@ static inline QString baseName( const QString & file )
}
#ifdef LMMS_BUILD_WIN32
// Workaround for old MinGW
#ifdef __MINGW32__
extern "C" _CRTIMP errno_t __cdecl freopen_s(FILE** _File,
const char *_Filename, const char *_Mode, FILE *_Stream);
#endif
// For qInstallMessageHandler
void consoleMessageHandler(QtMsgType type,
const QMessageLogContext &context, const QString &msg)
{
QByteArray localMsg = msg.toLocal8Bit();
fprintf(stderr, "%s\n", localMsg.constData());
}
#endif
inline void loadTranslation( const QString & tname,
@@ -243,6 +259,33 @@ int main( int argc, char * * argv )
signal(SIGFPE, signalHandler);
#endif
#ifdef LMMS_BUILD_WIN32
// Don't touch redirected streams here
// GetStdHandle should be called before AttachConsole
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);
FILE *fIn, *fOut, *fErr;
// Enable console output if available
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
if (!hStdIn)
{
freopen_s(&fIn, "CONIN$", "r", stdin);
}
if (!hStdOut)
{
freopen_s(&fOut, "CONOUT$", "w", stdout);
}
if (!hStdErr)
{
freopen_s(&fErr, "CONOUT$", "w", stderr);
}
}
// Make Qt's debug message handlers work
qInstallMessageHandler(consoleMessageHandler);
#endif
// initialize memory managers
NotePlayHandleManager::init();
@@ -301,7 +344,13 @@ int main( int argc, char * * argv )
return EXIT_FAILURE;
}
#endif
#ifdef LMMS_BUILD_LINUX
// don't let OS steal the menu bar. FIXME: only effective on Qt4
QCoreApplication::setAttribute( Qt::AA_DontUseNativeMenuBar );
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QCoreApplication * app = coreOnly ?
new QCoreApplication( argc, argv ) :
new MainApplication( argc, argv );
@@ -646,6 +695,10 @@ int main( int argc, char * * argv )
ConfigManager::inst()->loadConfigFile(configFile);
// Hidden settings
MixHelpers::setNaNHandler( ConfigManager::inst()->value( "app",
"nanhandler", "1" ).toInt() );
// set language
QString pos = ConfigManager::inst()->value( "app", "language" );
if( pos.isEmpty() )
@@ -930,5 +983,15 @@ int main( int argc, char * * argv )
printf( "\n" );
}
#ifdef LMMS_BUILD_WIN32
// Cleanup console
HWND hConsole = GetConsoleWindow();
if (hConsole)
{
SendMessage(hConsole, WM_CHAR, (WPARAM)VK_RETURN, (LPARAM)0);
FreeConsole();
}
#endif
return ret;
}

View File

@@ -56,6 +56,7 @@ SET(LMMS_SRCS
gui/widgets/FadeButton.cpp
gui/widgets/Fader.cpp
gui/widgets/FxLine.cpp
gui/widgets/FxLineLcdSpinBox.cpp
gui/widgets/Graph.cpp
gui/widgets/GroupBox.cpp
gui/widgets/InstrumentFunctionViews.cpp
@@ -87,6 +88,7 @@ SET(LMMS_SRCS
gui/widgets/TrackLabelButton.cpp
gui/widgets/TrackRenameLineEdit.cpp
gui/widgets/VisualizationWidget.cpp
gui/widgets/StepRecorderWidget.cpp
PARENT_SCOPE
)

View File

@@ -27,6 +27,7 @@
#include "ui_EffectSelectDialog.h"
#include "gui_templates.h"
#include "DummyEffect.h"
#include "embed.h"
#include "PluginFactory.h"
@@ -142,12 +143,17 @@ EffectSelectDialog::~EffectSelectDialog()
Effect * EffectSelectDialog::instantiateSelectedPlugin( EffectChain * _parent )
{
if( !m_currentSelection.name.isEmpty() && m_currentSelection.desc )
Effect* result = nullptr;
if(!m_currentSelection.name.isEmpty() && m_currentSelection.desc)
{
return Effect::instantiate( m_currentSelection.desc->name,
_parent, &m_currentSelection );
result = Effect::instantiate(m_currentSelection.desc->name,
_parent, &m_currentSelection);
}
return NULL;
if(!result)
{
result = new DummyEffect(_parent, QDomElement());
}
return result;
}

View File

@@ -128,6 +128,7 @@ void ExportProjectDialog::accept()
void ExportProjectDialog::closeEvent( QCloseEvent * _ce )
{
Engine::getSong()->setLoopRenderCount(1);
if( m_renderManager ) {
m_renderManager->abortProcessing();
}
@@ -187,6 +188,7 @@ void ExportProjectDialog::startExport()
Engine::getSong()->setExportLoop( exportLoopCB->isChecked() );
Engine::getSong()->setRenderBetweenMarkers( renderMarkersCB->isChecked() );
Engine::getSong()->setLoopRenderCount(loopCountSB->value());
connect( m_renderManager.get(), SIGNAL( progressChanged( int ) ),
progressBar, SLOT( setValue( int ) ) );

View File

@@ -47,6 +47,7 @@
#include "Mixer.h"
#include "gui_templates.h"
#include "InstrumentTrack.h"
#include "SampleTrack.h"
#include "Song.h"
#include "BBTrackContainer.h"
@@ -73,7 +74,7 @@ FxMixerView::FxMixerView() :
// Set margins
ml->setContentsMargins( 0, 4, 0, 0 );
// Channel area
m_channelAreaWidget = new QWidget;
chLayout = new QHBoxLayout( m_channelAreaWidget );
@@ -138,9 +139,9 @@ FxMixerView::FxMixerView() :
ml->addWidget( newChannelBtn, 0, Qt::AlignTop );
// add the stacked layout for the effect racks of fx channels
// add the stacked layout for the effect racks of fx channels
ml->addWidget( m_racksWidget, 0, Qt::AlignTop | Qt::AlignRight );
setCurrentFxLine( m_fxChannelViews[0]->m_fxLine );
setLayout( ml );
@@ -219,10 +220,10 @@ void FxMixerView::refreshDisplay()
chLayout->addWidget(m_fxChannelViews[i]->m_fxLine);
m_racksLayout->addWidget( m_fxChannelViews[i]->m_rackView );
}
// set selected fx line to 0
setCurrentFxLine( 0 );
// update all fx lines
for( int i = 0; i < m_fxChannelViews.size(); ++i )
{
@@ -251,6 +252,12 @@ void FxMixerView::updateMaxChannelSelector()
inst->effectChannelModel()->setRange(0,
m_fxChannelViews.size()-1,1);
}
else if( trackList[i]->type() == Track::SampleTrack )
{
SampleTrack * strk = (SampleTrack *) trackList[i];
strk->effectChannelModel()->setRange(0,
m_fxChannelViews.size()-1,1);
}
}
}
}
@@ -308,7 +315,7 @@ FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv,
connect(&fxChannel->m_soloModel, SIGNAL( dataChanged() ),
_mv, SLOT ( toggledSolo() ) );
ToolTip::add( m_soloBtn, tr( "Solo FX channel" ) );
// Create EffectRack for the channel
m_rackView = new EffectRackView( &fxChannel->m_fxChain, _mv->m_racksWidget );
m_rackView->setFixedSize( 245, FxLine::FxLineHeight );
@@ -354,6 +361,8 @@ void FxMixerView::updateFxLine(int index)
// does current channel send to this channel?
int selIndex = m_currentFxLine->channelIndex();
FxLine * thisLine = m_fxChannelViews[index]->m_fxLine;
thisLine->setToolTip( Engine::fxMixer()->effectChannel( index )->m_name );
FloatModel * sendModel = mix->channelSendModel(selIndex, index);
if( sendModel == NULL )
{

View File

@@ -55,11 +55,6 @@ GuiApplication* GuiApplication::instance()
GuiApplication::GuiApplication()
{
// enable HiDPI scaling before showing anything (Qt 5.6+ only)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
#endif
// prompt the user to create the LMMS working directory (e.g. ~/Documents/lmms) if it doesn't exist
if ( !ConfigManager::inst()->hasWorkingDir() &&
QMessageBox::question( NULL,

View File

@@ -66,6 +66,8 @@
#include "MidiApple.h"
#include "MidiDummy.h"
constexpr int BUFFERSIZE_RESOLUTION = 32;
inline void labelWidget( QWidget * _w, const QString & _txt )
{
QLabel * title = new QLabel( _txt, _w );
@@ -98,6 +100,8 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
"disablebackup" ).toInt() ),
m_openLastProject( ConfigManager::inst()->value( "app",
"openlastproject" ).toInt() ),
m_NaNHandler( ConfigManager::inst()->value( "app",
"nanhandler", "1" ).toInt() ),
m_hqAudioDev( ConfigManager::inst()->value( "mixer",
"hqaudio" ).toInt() ),
m_lang( ConfigManager::inst()->value( "app",
@@ -126,7 +130,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
m_compactTrackButtons( ConfigManager::inst()->value( "ui",
"compacttrackbuttons" ).toInt() ),
m_syncVSTPlugins( ConfigManager::inst()->value( "ui",
"syncvstplugins" ).toInt() ),
"syncvstplugins", "1" ).toInt() ),
m_animateAFP(ConfigManager::inst()->value( "ui",
"animateafp", "1" ).toInt() ),
m_printNoteLabels(ConfigManager::inst()->value( "ui",
@@ -134,7 +138,7 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
m_displayWaveform(ConfigManager::inst()->value( "ui",
"displaywaveform").toInt() ),
m_disableAutoQuit(ConfigManager::inst()->value( "ui",
"disableautoquit").toInt() ),
"disableautoquit", "1" ).toInt() ),
m_vstEmbedMethod( ConfigManager::inst()->vstEmbedMethod() )
{
setWindowIcon( embed::getIconPixmap( "setup_general" ) );
@@ -176,12 +180,12 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
bufsize_tw->setFixedHeight( 80 );
m_bufSizeSlider = new QSlider( Qt::Horizontal, bufsize_tw );
m_bufSizeSlider->setRange( 1, 256 );
m_bufSizeSlider->setRange( 1, 128 );
m_bufSizeSlider->setTickPosition( QSlider::TicksBelow );
m_bufSizeSlider->setPageStep( 8 );
m_bufSizeSlider->setTickInterval( 8 );
m_bufSizeSlider->setGeometry( 10, 16, 340, 18 );
m_bufSizeSlider->setValue( m_bufferSize / 64 );
m_bufSizeSlider->setValue( m_bufferSize / BUFFERSIZE_RESOLUTION );
connect( m_bufSizeSlider, SIGNAL( valueChanged( int ) ), this,
SLOT( setBufferSize( int ) ) );
@@ -245,6 +249,15 @@ SetupDialog::SetupDialog( ConfigTabs _tab_to_open ) :
misc_tw->setFixedHeight( YDelta*labelNumber + HeaderSize );
// Advanced setting, hidden for now
if( false )
{
LedCheckBox * useNaNHandler = new LedCheckBox(
tr( "Use built-in NaN handler" ),
misc_tw );
useNaNHandler->setChecked( m_NaNHandler );
}
TabWidget* embed_tw = new TabWidget( tr( "PLUGIN EMBEDDING" ), general);
embed_tw->setFixedHeight( 48 );
m_vstEmbedComboBox = new QComboBox( embed_tw );
@@ -813,6 +826,8 @@ void SetupDialog::accept()
QString::number( !m_disableBackup ) );
ConfigManager::inst()->setValue( "app", "openlastproject",
QString::number( m_openLastProject ) );
ConfigManager::inst()->setValue( "app", "nanhandler",
QString::number( m_NaNHandler ) );
ConfigManager::inst()->setValue( "mixer", "hqaudio",
QString::number( m_hqAudioDev ) );
ConfigManager::inst()->setValue( "ui", "smoothscroll",
@@ -877,7 +892,7 @@ void SetupDialog::accept()
void SetupDialog::setBufferSize( int _value )
{
const int step = DEFAULT_BUFFER_SIZE / 64;
const int step = DEFAULT_BUFFER_SIZE / BUFFERSIZE_RESOLUTION;
if( _value > step && _value % step )
{
int mod_value = _value % step;
@@ -897,7 +912,7 @@ void SetupDialog::setBufferSize( int _value )
m_bufSizeSlider->setValue( _value );
}
m_bufferSize = _value * 64;
m_bufferSize = _value * BUFFERSIZE_RESOLUTION;
m_bufSizeLbl->setText( tr( "Frames: %1\nLatency: %2 ms" ).arg(
m_bufferSize ).arg(
1000.0f * m_bufferSize /
@@ -910,7 +925,7 @@ void SetupDialog::setBufferSize( int _value )
void SetupDialog::resetBufSize()
{
setBufferSize( DEFAULT_BUFFER_SIZE / 64 );
setBufferSize( DEFAULT_BUFFER_SIZE / BUFFERSIZE_RESOLUTION );
}

View File

@@ -174,32 +174,6 @@ void SubWindow::elideText( QLabel *label, QString text )
/**
* @brief SubWindow::isMaximized
*
* This function checks if the subwindow is maximized.
* QMdiSubWindow::isMaximized() doesn't work on MacOS.
* Therefore we need our own implementation for checking this
* @return true if the subwindow is maximized at the moment.
* false if it's not.
*/
bool SubWindow::isMaximized()
{
#ifdef LMMS_BUILD_APPLE
// check if subwindow size is identical to the MdiArea size, accounting for scrollbars
int hScrollBarHeight = mdiArea()->horizontalScrollBar()->isVisible() ? mdiArea()->horizontalScrollBar()->size().height() : 0;
int vScrollBarWidth = mdiArea()->verticalScrollBar()->isVisible() ? mdiArea()->verticalScrollBar()->size().width() : 0;
QSize areaSize( this->mdiArea()->size().width() - vScrollBarWidth, this->mdiArea()->size().height() - hScrollBarHeight );
return areaSize == this->size();
#else
return QMdiSubWindow::isMaximized();
#endif
}
/**
* @brief SubWindow::getTrueNormalGeometry
*
@@ -388,8 +362,11 @@ void SubWindow::focusChanged( QMdiSubWindow *subWindow )
*/
void SubWindow::resizeEvent( QResizeEvent * event )
{
adjustTitleBar();
// When the parent QMdiArea gets resized, maximized subwindows also gets resized, if any.
// In that case, we should call QMdiSubWindow::resizeEvent first
// to ensure we get the correct window state.
QMdiSubWindow::resizeEvent( event );
adjustTitleBar();
// if the window was resized and ISN'T minimized/maximized/fullscreen,
// then save the current size

View File

@@ -89,7 +89,7 @@ TimeLineWidget::TimeLineWidget( const int xoff, const int yoff, const float ppt,
QTimer * updateTimer = new QTimer( this );
connect( updateTimer, SIGNAL( timeout() ),
this, SLOT( updatePosition() ) );
updateTimer->start( 50 );
updateTimer->start( 1000 / 60 ); // 60 fps
connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ),
this, SLOT( update() ) );
}

View File

@@ -7,19 +7,19 @@
<x>0</x>
<y>0</y>
<width>379</width>
<height>374</height>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>379</width>
<height>374</height>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>379</width>
<height>374</height>
<height>400</height>
</size>
</property>
<property name="windowTitle">
@@ -40,6 +40,47 @@
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="loopRepeatWidget" native="true">
<layout class="QHBoxLayout" name="loopRepeatHL">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="labelLoopRepeat">
<property name="text">
<string>Render Looped Section:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="loopCountSB">
<property name="suffix">
<string> time(s)</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>99</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout">
<item>

View File

@@ -73,11 +73,12 @@ void Editor::togglePlayStop()
play();
}
Editor::Editor(bool record) :
Editor::Editor(bool record, bool stepRecord) :
m_toolBar(new DropToolBar(this)),
m_playAction(nullptr),
m_recordAction(nullptr),
m_recordAccompanyAction(nullptr),
m_toggleStepRecordingAction(nullptr),
m_stopAction(nullptr)
{
m_toolBar = addDropToolBarToTop(tr("Transport controls"));
@@ -93,11 +94,13 @@ Editor::Editor(bool record) :
m_recordAction = new QAction(embed::getIconPixmap("record"), tr("Record"), this);
m_recordAccompanyAction = new QAction(embed::getIconPixmap("record_accompany"), tr("Record while playing"), this);
m_toggleStepRecordingAction = new QAction(embed::getIconPixmap("record_step_off"), tr("Toggle Step Recording"), this);
// Set up connections
connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
connect(m_recordAction, SIGNAL(triggered()), this, SLOT(record()));
connect(m_recordAccompanyAction, SIGNAL(triggered()), this, SLOT(recordAccompany()));
connect(m_toggleStepRecordingAction, SIGNAL(triggered()), this, SLOT(toggleStepRecording()));
connect(m_stopAction, SIGNAL(triggered()), this, SLOT(stop()));
new QShortcut(Qt::Key_Space, this, SLOT(togglePlayStop()));
@@ -108,6 +111,10 @@ Editor::Editor(bool record) :
addButton(m_recordAction, "recordButton");
addButton(m_recordAccompanyAction, "recordAccompanyButton");
}
if(stepRecord)
{
addButton(m_toggleStepRecordingAction, "stepRecordButton");
}
addButton(m_stopAction, "stopButton");
}

View File

@@ -62,6 +62,7 @@
#include "stdshims.h"
#include "TextFloat.h"
#include "TimeLineWidget.h"
#include "StepRecorderWidget.h"
using std::move;
@@ -177,11 +178,15 @@ PianoRoll::PianoRoll() :
m_ctrlMode( ModeDraw ),
m_mouseDownRight( false ),
m_scrollBack( false ),
m_stepRecorderWidget(this, DEFAULT_PR_PPT, PR_TOP_MARGIN, PR_BOTTOM_MARGIN + m_notesEditHeight, WHITE_KEY_WIDTH, 0),
m_stepRecorder(*this, m_stepRecorderWidget),
m_barLineColor( 0, 0, 0 ),
m_beatLineColor( 0, 0, 0 ),
m_lineColor( 0, 0, 0 ),
m_noteModeColor( 0, 0, 0 ),
m_noteColor( 0, 0, 0 ),
m_ghostNoteColor( 0, 0, 0 ),
m_ghostNoteTextColor( 0, 0, 0 ),
m_barColor( 0, 0, 0 ),
m_selectedNoteColor( 0, 0, 0 ),
m_textColor( 0, 0, 0 ),
@@ -189,7 +194,9 @@ PianoRoll::PianoRoll() :
m_textShadow( 0, 0, 0 ),
m_markedSemitoneColor( 0, 0, 0 ),
m_noteOpacity( 255 ),
m_ghostNoteOpacity( 255 ),
m_noteBorders( true ),
m_ghostNoteBorders( true ),
m_backgroundShade( 0, 0, 0 )
{
// gui names of edit modes
@@ -319,6 +326,10 @@ PianoRoll::PianoRoll() :
connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ),
this, SLOT( updatePosition( const MidiTime & ) ) );
//update timeline when in step-recording mode
connect( &m_stepRecorderWidget, SIGNAL( positionChanged( const MidiTime & ) ),
this, SLOT( updatePositionStepRecording( const MidiTime & ) ) );
// update timeline when in record-accompany mode
connect( Engine::getSong()->getPlayPos( Song::Mode_PlaySong ).m_timeLine,
SIGNAL( positionChanged( const MidiTime & ) ),
@@ -391,7 +402,7 @@ PianoRoll::PianoRoll() :
// Note length change can cause a redraw if Q is set to lock
connect( &m_noteLenModel, SIGNAL( dataChanged() ),
this, SLOT( quantizeChanged() ) );
this, SLOT( noteLengthChanged() ) );
// Set up scale model
const InstrumentFunctionNoteStacking::ChordTable& chord_table =
@@ -440,6 +451,8 @@ PianoRoll::PianoRoll() :
//connection for selecion from timeline
connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
this, SLOT( selectRegionFromPixels( int, int ) ) );
m_stepRecorder.initialize();
}
@@ -448,6 +461,7 @@ void PianoRoll::reset()
{
m_lastNoteVolume = DefaultVolume;
m_lastNotePanning = DefaultPanning;
clearGhostPattern();
}
void PianoRoll::showTextFloat(const QString &text, const QPoint &pos, int timeout)
@@ -599,6 +613,48 @@ PianoRoll::~PianoRoll()
}
void PianoRoll::setGhostPattern( Pattern* newPattern )
{
// Expects a pointer to a pattern or nullptr.
m_ghostNotes.clear();
if( newPattern != nullptr )
{
for( Note *note : newPattern->notes() )
{
Note * new_note = new Note( note->length(), note->pos(), note->key() );
m_ghostNotes.push_back( new_note );
}
emit ghostPatternSet( true );
}
}
void PianoRoll::loadGhostNotes( const QDomElement & de )
{
// Load ghost notes from DOM element.
if( de.isElement() )
{
QDomNode node = de.firstChild();
while( !node.isNull() )
{
Note * n = new Note;
n->restoreState( node.toElement() );
m_ghostNotes.push_back( n );
node = node.nextSibling();
}
emit ghostPatternSet( true );
}
}
void PianoRoll::clearGhostPattern()
{
setGhostPattern( nullptr );
emit ghostPatternSet( false );
update();
}
void PianoRoll::setCurrentPattern( Pattern* newPattern )
{
if( hasValidPattern() )
@@ -613,12 +669,19 @@ void PianoRoll::setCurrentPattern( Pattern* newPattern )
Engine::getSong()->playPattern( NULL );
}
if(m_stepRecorder.isRecording())
{
m_stepRecorder.stop();
}
// set new data
m_pattern = newPattern;
m_currentPosition = 0;
m_currentNote = NULL;
m_startKey = INITIAL_START_KEY;
m_stepRecorder.setCurrentPattern(newPattern);
if( ! hasValidPattern() )
{
//resizeEvent( NULL );
@@ -801,6 +864,30 @@ bool PianoRoll::noteBorders() const
void PianoRoll::setNoteBorders( const bool b )
{ m_noteBorders = b; }
QColor PianoRoll::ghostNoteColor() const
{ return m_ghostNoteColor; }
void PianoRoll::setGhostNoteColor( const QColor & c )
{ m_ghostNoteColor = c; }
QColor PianoRoll::ghostNoteTextColor() const
{ return m_ghostNoteTextColor; }
void PianoRoll::setGhostNoteTextColor( const QColor & c )
{ m_ghostNoteTextColor = c; }
int PianoRoll::ghostNoteOpacity() const
{ return m_ghostNoteOpacity; }
void PianoRoll::setGhostNoteOpacity( const int i )
{ m_ghostNoteOpacity = i; }
bool PianoRoll::ghostNoteBorders() const
{ return m_ghostNoteBorders; }
void PianoRoll::setGhostNoteBorders( const bool b )
{ m_ghostNoteBorders = b; }
QColor PianoRoll::backgroundShade() const
{ return m_backgroundShade; }
@@ -810,7 +897,6 @@ void PianoRoll::setBackgroundShade( const QColor & c )
void PianoRoll::drawNoteRect( QPainter & p, int x, int y,
int width, const Note * n, const QColor & noteCol, const QColor & noteTextColor,
const QColor & selCol, const int noteOpc, const bool borders, bool drawNoteName )
@@ -990,6 +1076,7 @@ void PianoRoll::shiftSemiTone( int amount ) // shift notes by amount semitones
{
if (!hasValidPattern()) {return;}
m_pattern->addJournalCheckPoint();
bool useAllNotes = ! isSelection();
for( Note *note : m_pattern->notes() )
{
@@ -1016,6 +1103,7 @@ void PianoRoll::shiftPos( int amount ) //shift notes pos by amount
{
if (!hasValidPattern()) {return;}
m_pattern->addJournalCheckPoint();
bool useAllNotes = ! isSelection();
bool first = true;
@@ -1083,8 +1171,19 @@ int PianoRoll::selectionCount() const // how many notes are selected?
void PianoRoll::keyPressEvent(QKeyEvent* ke )
void PianoRoll::keyPressEvent(QKeyEvent* ke)
{
if(m_stepRecorder.isRecording())
{
bool handled = m_stepRecorder.keyPressEvent(ke);
if(handled)
{
ke->accept();
update();
return;
}
}
if( hasValidPattern() && ke->modifiers() == Qt::NoModifier )
{
const int key_num = PianoView::getKeyFromKeyEvent( ke ) + ( DefaultOctave - 1 ) * KeysPerOctave;
@@ -1833,7 +1932,7 @@ void PianoRoll::testPlayNote( Note * n )
{
m_lastKey = n->key();
if( ! n->isPlaying() && ! m_recording )
if( ! n->isPlaying() && ! m_recording && ! m_stepRecorder.isRecording())
{
n->setIsPlaying( true );
@@ -2066,6 +2165,8 @@ void PianoRoll::mouseMoveEvent( QMouseEvent * me )
NOTE_EDIT_MIN_HEIGHT,
height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR -
PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT );
m_stepRecorderWidget.setBottomMargin(PR_BOTTOM_MARGIN + m_notesEditHeight);
repaint();
return;
}
@@ -3024,6 +3125,50 @@ void PianoRoll::paintEvent(QPaintEvent * pe )
QPolygonF editHandles;
// -- Begin ghost pattern
if( !m_ghostNotes.empty() )
{
for( const Note *note : m_ghostNotes )
{
int len_ticks = note->length();
if( len_ticks == 0 )
{
continue;
}
else if( len_ticks < 0 )
{
len_ticks = 4;
}
const int key = note->key() - m_startKey + 1;
int pos_ticks = note->pos();
int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact();
const int x = ( pos_ticks - m_currentPosition ) *
m_ppt / MidiTime::ticksPerTact();
// skip this note if not in visible area at all
if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) )
{
continue;
}
// is the note in visible area?
if( key > 0 && key <= visible_keys )
{
// we've done and checked all, let's draw the
// note
drawNoteRect( p, x + WHITE_KEY_WIDTH,
y_base - key * KEY_LINE_HEIGHT,
note_width, note, ghostNoteColor(), ghostNoteTextColor(), selectedNoteColor(),
ghostNoteOpacity(), ghostNoteBorders(), drawNoteNames );
}
}
}
// -- End ghost pattern
for( const Note *note : m_pattern->notes() )
{
int len_ticks = note->length();
@@ -3112,6 +3257,41 @@ void PianoRoll::paintEvent(QPaintEvent * pe )
}
}
//draw current step recording notes
for( const Note *note : m_stepRecorder.getCurStepNotes() )
{
int len_ticks = note->length();
if( len_ticks == 0 )
{
continue;
}
const int key = note->key() - m_startKey + 1;
int pos_ticks = note->pos();
int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact();
const int x = ( pos_ticks - m_currentPosition ) *
m_ppt / MidiTime::ticksPerTact();
// skip this note if not in visible area at all
if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) )
{
continue;
}
// is the note in visible area?
if( key > 0 && key <= visible_keys )
{
// we've done and checked all, let's draw the note
drawNoteRect( p, x + WHITE_KEY_WIDTH,
y_base - key * KEY_LINE_HEIGHT,
note_width, note, m_stepRecorder.curStepNoteColor(), noteTextColor(), selectedNoteColor(),
noteOpacity(), noteBorders(), drawNoteNames );
}
}
p.setPen( QPen( noteColor(), NOTE_EDIT_LINE_WIDTH + 2 ) );
p.drawPoints( editHandles );
@@ -3269,7 +3449,7 @@ void PianoRoll::wheelEvent(QWheelEvent * we )
}
if( nv.size() > 0 )
{
const int step = we->delta() > 0 ? 1.0 : -1.0;
const int step = we->delta() > 0 ? 1 : -1;
if( m_noteEditMode == NoteEditVolume )
{
for ( Note * n : nv )
@@ -3511,6 +3691,34 @@ void PianoRoll::recordAccompany()
bool PianoRoll::toggleStepRecording()
{
if(m_stepRecorder.isRecording())
{
m_stepRecorder.stop();
}
else
{
if(hasValidPattern())
{
if(Engine::getSong()->isPlaying())
{
m_stepRecorder.start(0, newNoteLen());
}
else
{
m_stepRecorder.start(
Engine::getSong()->getPlayPos(
Song::Mode_PlayPattern), newNoteLen());
}
}
}
return m_stepRecorder.isRecording();;
}
void PianoRoll::stop()
{
@@ -3524,22 +3732,29 @@ void PianoRoll::stop()
void PianoRoll::startRecordNote(const Note & n )
{
if( m_recording && hasValidPattern() &&
if(hasValidPattern())
{
if( m_recording &&
Engine::getSong()->isPlaying() &&
(Engine::getSong()->playMode() == desiredPlayModeForAccompany() ||
Engine::getSong()->playMode() == Song::Mode_PlayPattern ))
{
MidiTime sub;
if( Engine::getSong()->playMode() == Song::Mode_PlaySong )
Engine::getSong()->playMode() == Song::Mode_PlayPattern ))
{
sub = m_pattern->startPosition();
MidiTime sub;
if( Engine::getSong()->playMode() == Song::Mode_PlaySong )
{
sub = m_pattern->startPosition();
}
Note n1( 1, Engine::getSong()->getPlayPos(
Engine::getSong()->playMode() ) - sub,
n.key(), n.getVolume(), n.getPanning() );
if( n1.pos() >= 0 )
{
m_recordingNotes << n1;
}
}
Note n1( 1, Engine::getSong()->getPlayPos(
Engine::getSong()->playMode() ) - sub,
n.key(), n.getVolume(), n.getPanning() );
if( n1.pos() >= 0 )
else if (m_stepRecorder.isRecording())
{
m_recordingNotes << n1;
m_stepRecorder.notePressed(n);
}
}
}
@@ -3549,28 +3764,35 @@ void PianoRoll::startRecordNote(const Note & n )
void PianoRoll::finishRecordNote(const Note & n )
{
if( m_recording && hasValidPattern() &&
Engine::getSong()->isPlaying() &&
( Engine::getSong()->playMode() ==
desiredPlayModeForAccompany() ||
Engine::getSong()->playMode() ==
Song::Mode_PlayPattern ) )
if(hasValidPattern())
{
for( QList<Note>::Iterator it = m_recordingNotes.begin();
it != m_recordingNotes.end(); ++it )
if( m_recording &&
Engine::getSong()->isPlaying() &&
( Engine::getSong()->playMode() ==
desiredPlayModeForAccompany() ||
Engine::getSong()->playMode() ==
Song::Mode_PlayPattern ) )
{
if( it->key() == n.key() )
for( QList<Note>::Iterator it = m_recordingNotes.begin();
it != m_recordingNotes.end(); ++it )
{
Note n1( n.length(), it->pos(),
it->key(), it->getVolume(),
it->getPanning() );
n1.quantizeLength( quantization() );
m_pattern->addNote( n1 );
update();
m_recordingNotes.erase( it );
break;
if( it->key() == n.key() )
{
Note n1( n.length(), it->pos(),
it->key(), it->getVolume(),
it->getPanning() );
n1.quantizeLength( quantization() );
m_pattern->addNote( n1 );
update();
m_recordingNotes.erase( it );
break;
}
}
}
else if (m_stepRecorder.isRecording())
{
m_stepRecorder.noteReleased(n);
}
}
}
@@ -3580,6 +3802,7 @@ void PianoRoll::finishRecordNote(const Note & n )
void PianoRoll::horScrolled(int new_pos )
{
m_currentPosition = new_pos;
m_stepRecorderWidget.setCurrentPosition(m_currentPosition);
emit positionChanged( m_currentPosition );
update();
}
@@ -3950,6 +4173,13 @@ void PianoRoll::updatePositionAccompany( const MidiTime & t )
}
void PianoRoll::updatePositionStepRecording( const MidiTime & t )
{
if( m_stepRecorder.isRecording() )
{
autoScroll( t );
}
}
void PianoRoll::zoomingChanged()
@@ -3959,6 +4189,8 @@ void PianoRoll::zoomingChanged()
assert( m_ppt > 0 );
m_timeLine->setPixelsPerTact( m_ppt );
m_stepRecorderWidget.setPixelsPerTact( m_ppt );
update();
}
@@ -3970,7 +4202,11 @@ void PianoRoll::quantizeChanged()
update();
}
void PianoRoll::noteLengthChanged()
{
m_stepRecorder.setStepsLength(newNoteLen());
update();
}
int PianoRoll::quantization() const
@@ -4107,7 +4343,7 @@ Note * PianoRoll::noteUnderMouse()
PianoRollWindow::PianoRollWindow() :
Editor(true),
Editor(true, true),
m_editor(new PianoRoll())
{
setCentralWidget( m_editor );
@@ -4115,6 +4351,7 @@ PianoRollWindow::PianoRollWindow() :
m_playAction->setToolTip(tr( "Play/pause current pattern (Space)" ) );
m_recordAction->setToolTip(tr( "Record notes from MIDI-device/channel-piano" ) );
m_recordAccompanyAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ) );
m_toggleStepRecordingAction->setToolTip( tr( "Record notes from MIDI-device/channel-piano, one step at the time" ) );
m_stopAction->setToolTip( tr( "Stop playing of current pattern (Space)" ) );
DropToolBar *notesActionsToolBar = addDropToolBarToTop( tr( "Edit actions" ) );
@@ -4221,8 +4458,15 @@ PianoRollWindow::PianoRollWindow() :
m_chordComboBox = new ComboBox( m_toolBar );
m_chordComboBox->setModel( &m_editor->m_chordModel );
m_chordComboBox->setFixedSize( 105, 22 );
m_chordComboBox->setToolTip( tr( "Chord") );
m_chordComboBox->setToolTip( tr( "Chord" ) );
// -- Clear ghost pattern button
m_clearGhostButton = new QPushButton( m_toolBar );
m_clearGhostButton->setIcon( embed::getIconPixmap( "clear_ghost_note" ) );
m_clearGhostButton->setToolTip( tr( "Clear ghost notes" ) );
m_clearGhostButton->setEnabled( false );
connect( m_clearGhostButton, SIGNAL( clicked() ), m_editor, SLOT( clearGhostPattern() ) );
connect( m_editor, SIGNAL( ghostPatternSet( bool ) ), this, SLOT( ghostPatternSet( bool ) ) );
zoomAndNotesToolBar->addWidget( zoom_lbl );
zoomAndNotesToolBar->addWidget( m_zoomingComboBox );
@@ -4243,6 +4487,9 @@ PianoRollWindow::PianoRollWindow() :
zoomAndNotesToolBar->addWidget( chord_lbl );
zoomAndNotesToolBar->addWidget( m_chordComboBox );
zoomAndNotesToolBar->addSeparator();
zoomAndNotesToolBar->addWidget( m_clearGhostButton );
// setup our actual window
setFocusPolicy( Qt::StrongFocus );
setFocus();
@@ -4251,7 +4498,7 @@ PianoRollWindow::PianoRollWindow() :
// Connections
connect( m_editor, SIGNAL( currentPatternChanged() ), this, SIGNAL( currentPatternChanged() ) );
connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( patternRenamed() ) );
connect( m_editor, SIGNAL( currentPatternChanged() ), this, SLOT( updateAfterPatternChange() ) );
}
@@ -4265,6 +4512,14 @@ const Pattern* PianoRollWindow::currentPattern() const
void PianoRollWindow::setGhostPattern( Pattern* pattern )
{
m_editor->setGhostPattern( pattern );
}
void PianoRollWindow::setCurrentPattern( Pattern* pattern )
{
m_editor->setCurrentPattern( pattern );
@@ -4272,8 +4527,8 @@ void PianoRollWindow::setCurrentPattern( Pattern* pattern )
if ( pattern )
{
setWindowTitle( tr( "Piano-Roll - %1" ).arg( pattern->name() ) );
connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( patternRenamed()) );
connect( pattern, SIGNAL( dataChanged() ), this, SLOT( patternRenamed() ) );
connect( pattern->instrumentTrack(), SIGNAL( nameChanged() ), this, SLOT( updateAfterPatternChange()) );
connect( pattern, SIGNAL( dataChanged() ), this, SLOT( updateAfterPatternChange() ) );
}
else
{
@@ -4318,6 +4573,8 @@ void PianoRollWindow::stop()
void PianoRollWindow::record()
{
stopStepRecording(); //step recording mode is mutually exclusive with other record modes
m_editor->record();
}
@@ -4326,11 +4583,25 @@ void PianoRollWindow::record()
void PianoRollWindow::recordAccompany()
{
stopStepRecording(); //step recording mode is mutually exclusive with other record modes
m_editor->recordAccompany();
}
void PianoRollWindow::toggleStepRecording()
{
if(isRecording())
{
// step recording mode is mutually exclusive with other record modes
// stop them before starting step recording
stop();
}
m_editor->toggleStepRecording();
updateStepRecordingIcon();
}
void PianoRollWindow::stopRecording()
{
@@ -4350,6 +4621,21 @@ void PianoRollWindow::reset()
void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de )
{
if( !m_editor->ghostNotes().empty() )
{
QDomElement ghostNotesRoot = doc.createElement( "ghostnotes" );
for( Note *note : m_editor->ghostNotes() )
{
QDomElement ghostNoteNode = doc.createElement( "ghostnote" );
ghostNoteNode.setAttribute( "len", note->length() );
ghostNoteNode.setAttribute( "key", note->key() );
ghostNoteNode.setAttribute( "pos", note->pos() );
ghostNotesRoot.appendChild(ghostNoteNode);
}
de.appendChild( ghostNotesRoot );
}
MainWindow::saveWidgetState( this, de );
}
@@ -4358,6 +4644,8 @@ void PianoRollWindow::saveSettings( QDomDocument & doc, QDomElement & de )
void PianoRollWindow::loadSettings( const QDomElement & de )
{
m_editor->loadGhostNotes( de.firstChildElement("ghostnotes") );
MainWindow::restoreWidgetState( this, de );
}
@@ -4371,6 +4659,11 @@ QSize PianoRollWindow::sizeHint() const
void PianoRollWindow::updateAfterPatternChange()
{
patternRenamed();
updateStepRecordingIcon(); //pattern change turn step recording OFF - update icon accordingly
}
void PianoRollWindow::patternRenamed()
{
@@ -4387,8 +4680,37 @@ void PianoRollWindow::patternRenamed()
void PianoRollWindow::ghostPatternSet( bool state )
{
m_clearGhostButton->setEnabled( state );
}
void PianoRollWindow::focusInEvent( QFocusEvent * event )
{
// when the window is given focus, also give focus to the actual piano roll
m_editor->setFocus( event->reason() );
}
void PianoRollWindow::stopStepRecording()
{
if(m_editor->isStepRecording())
{
m_editor->toggleStepRecording();
updateStepRecordingIcon();
}
}
void PianoRollWindow::updateStepRecordingIcon()
{
if(m_editor->isStepRecording())
{
m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_on"));
}
else
{
m_toggleStepRecordingAction->setIcon(embed::getIconPixmap("record_step_off"));
}
}

View File

@@ -654,7 +654,7 @@ ComboBoxModel *SongEditor::zoomingModel() const
SongEditorWindow::SongEditorWindow(Song* song) :
Editor(Engine::mixer()->audioDev()->supportsCapture()),
Editor(Engine::mixer()->audioDev()->supportsCapture(), false),
m_editor(new SongEditor(song)),
m_crtlAction( NULL )
{
@@ -746,6 +746,16 @@ void SongEditorWindow::resizeEvent(QResizeEvent *event)
}
void SongEditorWindow::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if (event->type() == QEvent::WindowStateChange)
{
m_editor->realignTracks();
}
}
void SongEditorWindow::play()
{
emit playTriggered();

View File

@@ -70,14 +70,14 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) :
m_autoQuit = new TempoSyncKnob( knobBright_26, this );
m_autoQuit->setLabel( tr( "DECAY" ) );
m_autoQuit->move( 60, 5 );
m_autoQuit->setEnabled( isEnabled );
m_autoQuit->setEnabled( isEnabled && !effect()->m_autoQuitDisabled );
m_autoQuit->setHintText( tr( "Time:" ), "ms" );
m_gate = new Knob( knobBright_26, this );
m_gate->setLabel( tr( "GATE" ) );
m_gate->move( 93, 5 );
m_gate->setEnabled( isEnabled );
m_gate->setEnabled( isEnabled && !effect()->m_autoQuitDisabled );
m_gate->setHintText( tr( "Gate:" ), "" );

View File

@@ -419,9 +419,9 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * )
p.fillRect( x5, y_base - 1, 2, 2, end_points_color );
int LFO_GRAPH_W = s_lfoGraph->width() - 6; // substract border
int LFO_GRAPH_W = s_lfoGraph->width() - 3; // substract border
int LFO_GRAPH_H = s_lfoGraph->height() - 6; // substract border
int graph_x_base = LFO_GRAPH_X + 3;
int graph_x_base = LFO_GRAPH_X + 2;
int graph_y_base = LFO_GRAPH_Y + 3 + LFO_GRAPH_H / 2;
const float frames_for_graph = SECS_PER_LFO_OSCILLATION *

View File

@@ -0,0 +1,66 @@
/*
* FxLineLcdSpinBox.cpp - a specialization of LcdSpnBox for setting FX channels
*
* 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.
*
*/
#include "FxLineLcdSpinBox.h"
#include "CaptionMenu.h"
#include "FxMixerView.h"
#include "GuiApplication.h"
#include "Track.h"
void FxLineLcdSpinBox::setTrackView(TrackView * tv)
{
m_tv = tv;
}
void FxLineLcdSpinBox::mouseDoubleClickEvent(QMouseEvent* event)
{
gui->fxMixerView()->setCurrentFxLine(model()->value());
gui->fxMixerView()->parentWidget()->show();
gui->fxMixerView()->show();// show fxMixer window
gui->fxMixerView()->setFocus();// set focus to fxMixer window
//engine::getFxMixerView()->raise();
}
void FxLineLcdSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
// for the case, the user clicked right while pressing left mouse-
// button, the context-menu appears while mouse-cursor is still hidden
// and it isn't shown again until user does something which causes
// an QApplication::restoreOverrideCursor()-call...
mouseReleaseEvent(nullptr);
QPointer<CaptionMenu> contextMenu = new CaptionMenu(model()->displayName(), this);
if (QMenu *fxMenu = m_tv->createFxMenu(
tr("Assign to:"), tr("New FX Channel")))
{
contextMenu->addMenu(fxMenu);
contextMenu->addSeparator();
}
addDefaultActions(contextMenu);
contextMenu->exec(QCursor::pos());
}

View File

@@ -720,6 +720,15 @@ void graphModel::clear()
}
// Clear any part of the graph that isn't displayed
void graphModel::clearInvisible()
{
const int graph_length = length();
const int full_graph_length = m_samples.size();
for( int i = graph_length; i < full_graph_length; i++ )
m_samples[i] = 0;
emit samplesChanged( graph_length, full_graph_length - 1 );
}
void graphModel::drawSampleAt( int x, float val )
{

View File

@@ -0,0 +1,155 @@
/*
* StepRecoderWidget.cpp - widget that provide gui markers for step recording
*
* 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.
*
*/
#include "StepRecorderWidget.h"
#include "TextFloat.h"
#include "embed.h"
StepRecorderWidget::StepRecorderWidget(
QWidget * parent,
const int ppt,
const int marginTop,
const int marginBottom,
const int marginLeft,
const int marginRight) :
QWidget(parent),
m_marginTop(marginTop),
m_marginBottom(marginBottom),
m_marginLeft(marginLeft),
m_marginRight(marginRight)
{
const QColor baseColor = QColor(255, 0, 0);// QColor(204, 163, 0); // Orange
m_colorLineEnd = baseColor.lighter(150);
m_colorLineStart = baseColor.darker(120);
setAttribute(Qt::WA_NoSystemBackground, true);
setPixelsPerTact(ppt);
m_top = m_marginTop;
m_left = m_marginLeft;
}
void StepRecorderWidget::setPixelsPerTact(int ppt)
{
m_ppt = ppt;
}
void StepRecorderWidget::setCurrentPosition(MidiTime currentPosition)
{
m_currentPosition = currentPosition;
}
void StepRecorderWidget::setBottomMargin(const int marginBottom)
{
m_marginBottom = marginBottom;
}
void StepRecorderWidget::setStartPosition(MidiTime pos)
{
m_curStepStartPos = pos;
}
void StepRecorderWidget::setEndPosition(MidiTime pos)
{
m_curStepEndPos = pos;
emit positionChanged(m_curStepEndPos);
}
void StepRecorderWidget::showHint()
{
TextFloat::displayMessage(tr( "Hint" ), tr("Move recording curser using <Left/Right> arrows"),
embed::getIconPixmap("hint"));
}
void StepRecorderWidget::setStepsLength(MidiTime stepsLength)
{
m_stepsLength = stepsLength;
}
void StepRecorderWidget::paintEvent(QPaintEvent * pe)
{
QPainter painter(this);
updateBoundaries();
move(0, 0);
//draw steps ruler
painter.setPen(m_colorLineEnd);
MidiTime curPos = m_curStepEndPos;
int x = xCoordOfTick(curPos);
while(x <= m_right)
{
const int w = 2;
const int h = 4;
painter.drawRect(x - 1, m_top, w, h);
curPos += m_stepsLength;
x = xCoordOfTick(curPos);
}
//draw current step start/end position lines
if(m_curStepStartPos != m_curStepEndPos)
{
drawVerLine(&painter, m_curStepStartPos, m_colorLineStart, m_top, m_bottom);
}
drawVerLine(&painter, m_curStepEndPos, m_colorLineEnd, m_top, m_bottom);
//if the line is adjacent to the keyboard at the left - it cannot be seen.
//add another line to make it clearer
if(m_curStepEndPos == 0)
{
drawVerLine(&painter, xCoordOfTick(m_curStepEndPos) + 1, m_colorLineEnd, m_top, m_bottom);
}
}
int StepRecorderWidget::xCoordOfTick(int tick)
{
return m_marginLeft + ((tick - m_currentPosition) * m_ppt / MidiTime::ticksPerTact());
}
void StepRecorderWidget::drawVerLine(QPainter* painter, int x, const QColor& color, int top, int bottom)
{
if(x >= m_marginLeft && x <= (width() - m_marginRight))
{
painter->setPen(color);
painter->drawLine( x, top, x, bottom );
}
}
void StepRecorderWidget::drawVerLine(QPainter* painter, const MidiTime& pos, const QColor& color, int top, int bottom)
{
drawVerLine(painter, xCoordOfTick(pos), color, top, bottom);
}
void StepRecorderWidget::updateBoundaries()
{
setFixedSize(parentWidget()->size());
m_bottom = height() - m_marginBottom;
m_right = width() - m_marginTop;
//(no need to change top and left as they are static)
}

View File

@@ -47,6 +47,7 @@
#include "EffectRackView.h"
#include "embed.h"
#include "FileBrowser.h"
#include "FxLineLcdSpinBox.h"
#include "FxMixer.h"
#include "FxMixerView.h"
#include "GuiApplication.h"
@@ -126,10 +127,14 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) :
setName( tr( "Default preset" ) );
connect( &m_baseNoteModel, SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) );
connect( &m_pitchModel, SIGNAL( dataChanged() ), this, SLOT( updatePitch() ) );
connect( &m_pitchRangeModel, SIGNAL( dataChanged() ), this, SLOT( updatePitchRange() ) );
connect( &m_effectChannelModel, SIGNAL( dataChanged() ), this, SLOT( updateEffectChannel() ) );
connect( &m_baseNoteModel, SIGNAL( dataChanged() ),
this, SLOT( updateBaseNote() ), Qt::DirectConnection );
connect( &m_pitchModel, SIGNAL( dataChanged() ),
this, SLOT( updatePitch() ), Qt::DirectConnection );
connect( &m_pitchRangeModel, SIGNAL( dataChanged() ),
this, SLOT( updatePitchRange() ), Qt::DirectConnection );
connect( &m_effectChannelModel, SIGNAL( dataChanged() ),
this, SLOT( updateEffectChannel() ), Qt::DirectConnection );
}
@@ -1258,52 +1263,6 @@ QMenu * InstrumentTrackView::createFxMenu(QString title, QString newFxLabel)
class fxLineLcdSpinBox : public LcdSpinBox
{
Q_OBJECT
public:
fxLineLcdSpinBox( int _num_digits, QWidget * _parent,
const QString & _name ) :
LcdSpinBox( _num_digits, _parent, _name ) {}
protected:
virtual void mouseDoubleClickEvent ( QMouseEvent * _me )
{
gui->fxMixerView()->setCurrentFxLine( model()->value() );
gui->fxMixerView()->parentWidget()->show();
gui->fxMixerView()->show();// show fxMixer window
gui->fxMixerView()->setFocus();// set focus to fxMixer window
//engine::getFxMixerView()->raise();
}
virtual void contextMenuEvent( QContextMenuEvent* event )
{
// for the case, the user clicked right while pressing left mouse-
// button, the context-menu appears while mouse-cursor is still hidden
// and it isn't shown again until user does something which causes
// an QApplication::restoreOverrideCursor()-call...
mouseReleaseEvent( NULL );
QPointer<CaptionMenu> contextMenu = new CaptionMenu( model()->displayName(), this );
// This condition is here just as a safety check, fxLineLcdSpinBox is aways
// created inside a TabWidget inside an InstrumentTrackWindow
if ( InstrumentTrackWindow* window = dynamic_cast<InstrumentTrackWindow*>( (QWidget *)this->parent()->parent() ) )
{
QMenu *fxMenu = window->instrumentTrackView()->createFxMenu( tr( "Assign to:" ), tr( "New FX channel" ) );
contextMenu->addMenu( fxMenu );
contextMenu->addSeparator();
}
addDefaultActions( contextMenu );
contextMenu->exec( QCursor::pos() );
}
};
// #### ITW:
InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) :
QWidget(),
@@ -1424,7 +1383,7 @@ InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) :
// setup spinbox for selecting FX-channel
m_effectChannelNumber = new fxLineLcdSpinBox( 2, NULL, tr( "FX channel" ) );
m_effectChannelNumber = new FxLineLcdSpinBox( 2, NULL, tr( "FX channel" ), m_itv );
basicControlsLayout->addWidget( m_effectChannelNumber, 0, 6 );
basicControlsLayout->setAlignment( m_effectChannelNumber, widgetAlignment );
@@ -1545,6 +1504,7 @@ void InstrumentTrackWindow::setInstrumentTrackView( InstrumentTrackView* view )
}
m_itv = view;
m_effectChannelNumber->setTrackView(m_itv);
}
@@ -1663,6 +1623,8 @@ void InstrumentTrackWindow::updateInstrumentView()
modelChanged(); // Get the instrument window to refresh
m_track->dataChanged(); // Get the text on the trackButton to change
m_pianoView->setVisible(m_track->m_instrument->hasNoteInput());
}
}
@@ -1717,7 +1679,9 @@ void InstrumentTrackWindow::closeEvent( QCloseEvent* event )
void InstrumentTrackWindow::focusInEvent( QFocusEvent* )
{
m_pianoView->setFocus();
if(m_pianoView->isVisible()) {
m_pianoView->setFocus();
}
}
@@ -1851,6 +1815,9 @@ void InstrumentTrackWindow::viewInstrumentInDirection(int d)
// scroll the SongEditor/BB-editor to make sure the new trackview label is visible
bringToFront->trackContainerView()->scrollToTrackView(bringToFront);
// get the instrument window to refresh
modelChanged();
}
bringToFront->getInstrumentTrackWindow()->setFocus();
}

View File

@@ -637,6 +637,18 @@ void PatternView::openInPianoRoll()
void PatternView::setGhostInPianoRoll()
{
gui->pianoRoll()->setGhostPattern( m_pat );
gui->pianoRoll()->parentWidget()->show();
gui->pianoRoll()->show();
gui->pianoRoll()->setFocus();
}
void PatternView::resetName()
{
m_pat->setName( m_pat->m_instrumentTrack->name() );
@@ -663,8 +675,14 @@ void PatternView::constructContextMenu( QMenu * _cm )
_cm->insertAction( _cm->actions()[0], a );
connect( a, SIGNAL( triggered( bool ) ),
this, SLOT( openInPianoRoll() ) );
_cm->insertSeparator( _cm->actions()[1] );
QAction * b = new QAction( embed::getIconPixmap( "ghost_note" ),
tr( "Set as ghost in piano-roll" ), _cm );
if( m_pat->empty() ) { b->setEnabled( false ); }
_cm->insertAction( _cm->actions()[1], b );
connect( b, SIGNAL( triggered( bool ) ),
this, SLOT( setGhostInPianoRoll() ) );
_cm->insertSeparator( _cm->actions()[2] );
_cm->addSeparator();
_cm->addAction( embed::getIconPixmap( "edit_erase" ),

View File

@@ -28,6 +28,7 @@
#include <QFileInfo>
#include <QMenu>
#include <QLayout>
#include <QLineEdit>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPainter>
@@ -48,6 +49,8 @@
#include "MainWindow.h"
#include "Mixer.h"
#include "EffectRackView.h"
#include "FxMixerView.h"
#include "TabWidget.h"
#include "TrackLabelButton.h"
SampleTCO::SampleTCO( Track * _track ) :
@@ -471,7 +474,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted();
// state: selected, muted, normal
c = isSelected() ? selectedColor() : ( muted ? mutedBackgroundColor()
c = isSelected() ? selectedColor() : ( muted ? mutedBackgroundColor()
: painter.background().color() );
lingrad.setColorAt( 1, c.darker( 300 ) );
@@ -515,7 +518,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
// inner border
p.setPen( c.lighter( 160 ) );
p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH,
p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH,
rect().bottom() - TCO_BORDER_WIDTH );
// outer border
@@ -531,7 +534,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe )
embed::getIconPixmap( "muted", size, size ) );
}
// recording sample tracks is not possible at the moment
// recording sample tracks is not possible at the moment
/* if( m_tco->isRecord() )
{
@@ -562,10 +565,14 @@ SampleTrack::SampleTrack( TrackContainer* tc ) :
tr( "Volume" ) ),
m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f,
this, tr( "Panning" ) ),
m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ),
m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel )
{
setName( tr( "Sample track" ) );
m_panningModel.setCenterValue( DefaultPanning );
m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels()-1, 1);
connect( &m_effectChannelModel, SIGNAL( dataChanged() ), this, SLOT( updateEffectChannel() ) );
}
@@ -693,6 +700,7 @@ void SampleTrack::saveTrackSpecificSettings( QDomDocument & _doc,
#endif
m_volumeModel.saveSettings( _doc, _this, "vol" );
m_panningModel.saveSettings( _doc, _this, "pan" );
m_effectChannelModel.saveSettings( _doc, _this, "fxch" );
}
@@ -715,6 +723,8 @@ void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this )
}
m_volumeModel.loadSettings( _this, "vol" );
m_panningModel.loadSettings( _this, "pan" );
m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels() - 1 );
m_effectChannelModel.loadSettings( _this, "fxch" );
}
@@ -742,6 +752,14 @@ void SampleTrack::setPlayingTcos( bool isPlaying )
void SampleTrack::updateEffectChannel()
{
m_audioPort.setNextFxChannel( m_effectChannelModel.value() );
}
SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
@@ -749,13 +767,13 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
{
setFixedHeight( 32 );
TrackLabelButton * tlb = new TrackLabelButton( this,
getTrackSettingsWidget() );
connect( tlb, SIGNAL( clicked( bool ) ),
this, SLOT( showEffects() ) );
tlb->setIcon( embed::getIconPixmap( "sample_track" ) );
tlb->move( 3, 1 );
tlb->show();
m_tlb = new TrackLabelButton(this, getTrackSettingsWidget());
m_tlb->setCheckable(true);
connect(m_tlb, SIGNAL(clicked( bool )),
this, SLOT(showEffects()));
m_tlb->setIcon(embed::getIconPixmap("sample_track"));
m_tlb->move(3, 1);
m_tlb->show();
m_volumeKnob = new Knob( knobSmall_17, getTrackSettingsWidget(),
tr( "Track volume" ) );
@@ -779,16 +797,10 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
m_panningKnob->setLabel( tr( "PAN" ) );
m_panningKnob->show();
m_effectRack = new EffectRackView( _t->audioPort()->effects() );
m_effectRack->setFixedSize( 240, 242 );
m_effWindow = gui->mainWindow()->addWindowedWidget( m_effectRack );
m_effWindow->setAttribute( Qt::WA_DeleteOnClose, false );
m_effWindow->layout()->setSizeConstraint( QLayout::SetFixedSize );
m_effWindow->setWindowTitle( _t->name() );
m_effWindow->hide();
setModel( _t );
m_window = new SampleTrackWindow(this);
m_window->toggleVisibility(false);
}
@@ -796,7 +808,50 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) :
SampleTrackView::~SampleTrackView()
{
m_effWindow->deleteLater();
if(m_window != NULL)
{
m_window->setSampleTrackView(NULL);
m_window->parentWidget()->hide();
}
m_window = NULL;
}
QMenu * SampleTrackView::createFxMenu(QString title, QString newFxLabel)
{
int channelIndex = model()->effectChannelModel()->value();
FxChannel *fxChannel = Engine::fxMixer()->effectChannel(channelIndex);
// If title allows interpolation, pass channel index and name
if (title.contains("%2"))
{
title = title.arg(channelIndex).arg(fxChannel->m_name);
}
QMenu *fxMenu = new QMenu(title);
QSignalMapper * fxMenuSignalMapper = new QSignalMapper(fxMenu);
fxMenu->addAction(newFxLabel, this, SLOT(createFxLine()));
fxMenu->addSeparator();
for (int i = 0; i < Engine::fxMixer()->numChannels(); ++i)
{
FxChannel * currentChannel = Engine::fxMixer()->effectChannel(i);
if (currentChannel != fxChannel)
{
QString label = tr("FX %1: %2").arg(currentChannel->m_channelIndex).arg(currentChannel->m_name);
QAction * action = fxMenu->addAction(label, fxMenuSignalMapper, SLOT(map()));
fxMenuSignalMapper->setMapping(action, currentChannel->m_channelIndex);
}
}
connect(fxMenuSignalMapper, SIGNAL(mapped(int)), this, SLOT(assignFxLine(int)));
return fxMenu;
}
@@ -804,16 +859,7 @@ SampleTrackView::~SampleTrackView()
void SampleTrackView::showEffects()
{
if( m_effWindow->isHidden() )
{
m_effectRack->show();
m_effWindow->show();
m_effWindow->raise();
}
else
{
m_effWindow->hide();
}
m_window->toggleVisibility(m_window->parentWidget()->isHidden());
}
@@ -821,7 +867,261 @@ void SampleTrackView::showEffects()
void SampleTrackView::modelChanged()
{
SampleTrack * st = castModel<SampleTrack>();
m_volumeKnob->setModel( &st->m_volumeModel );
m_volumeKnob->setModel(&st->m_volumeModel);
TrackView::modelChanged();
}
SampleTrackWindow::SampleTrackWindow(SampleTrackView * tv) :
QWidget(),
ModelView(NULL, this),
m_track(tv->model()),
m_stv(tv)
{
// init own layout + widgets
setFocusPolicy(Qt::StrongFocus);
QVBoxLayout * vlayout = new QVBoxLayout(this);
vlayout->setMargin(0);
vlayout->setSpacing(0);
TabWidget* generalSettingsWidget = new TabWidget(tr("GENERAL SETTINGS"), this);
QVBoxLayout* generalSettingsLayout = new QVBoxLayout(generalSettingsWidget);
generalSettingsLayout->setContentsMargins(8, 18, 8, 8);
generalSettingsLayout->setSpacing(6);
QWidget* nameWidget = new QWidget(generalSettingsWidget);
QHBoxLayout* nameLayout = new QHBoxLayout(nameWidget);
nameLayout->setContentsMargins(0, 0, 0, 0);
nameLayout->setSpacing(2);
// 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 &)));
m_nameLineEdit->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred));
nameLayout->addWidget(m_nameLineEdit);
generalSettingsLayout->addWidget(nameWidget);
QGridLayout* basicControlsLayout = new QGridLayout;
basicControlsLayout->setHorizontalSpacing(3);
basicControlsLayout->setVerticalSpacing(0);
basicControlsLayout->setContentsMargins(0, 0, 0, 0);
QString labelStyleSheet = "font-size: 6pt;";
Qt::Alignment labelAlignment = Qt::AlignHCenter | Qt::AlignTop;
Qt::Alignment widgetAlignment = Qt::AlignHCenter | Qt::AlignCenter;
// set up volume knob
m_volumeKnob = new Knob(knobBright_26, NULL, tr("Sample volume"));
m_volumeKnob->setVolumeKnob(true);
m_volumeKnob->setHintText(tr("Volume:"), "%");
basicControlsLayout->addWidget(m_volumeKnob, 0, 0);
basicControlsLayout->setAlignment(m_volumeKnob, widgetAlignment);
QLabel *label = new QLabel(tr("VOL"), this);
label->setStyleSheet(labelStyleSheet);
basicControlsLayout->addWidget(label, 1, 0);
basicControlsLayout->setAlignment(label, labelAlignment);
// set up panning knob
m_panningKnob = new Knob(knobBright_26, NULL, tr("Panning"));
m_panningKnob->setHintText(tr("Panning:"), "");
basicControlsLayout->addWidget(m_panningKnob, 0, 1);
basicControlsLayout->setAlignment(m_panningKnob, widgetAlignment);
label = new QLabel(tr("PAN"),this);
label->setStyleSheet(labelStyleSheet);
basicControlsLayout->addWidget(label, 1, 1);
basicControlsLayout->setAlignment(label, labelAlignment);
basicControlsLayout->setColumnStretch(2, 1);
// setup spinbox for selecting FX-channel
m_effectChannelNumber = new FxLineLcdSpinBox(2, NULL, tr("FX channel"), m_stv);
basicControlsLayout->addWidget(m_effectChannelNumber, 0, 3);
basicControlsLayout->setAlignment(m_effectChannelNumber, widgetAlignment);
label = new QLabel(tr("FX"), this);
label->setStyleSheet(labelStyleSheet);
basicControlsLayout->addWidget(label, 1, 3);
basicControlsLayout->setAlignment(label, labelAlignment);
generalSettingsLayout->addLayout(basicControlsLayout);
m_effectRack = new EffectRackView(tv->model()->audioPort()->effects());
m_effectRack->setFixedSize(240, 242);
vlayout->addWidget(generalSettingsWidget);
vlayout->addWidget(m_effectRack);
setModel(tv->model());
QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget(this);
Qt::WindowFlags flags = subWin->windowFlags();
flags |= Qt::MSWindowsFixedSizeDialogHint;
flags &= ~Qt::WindowMaximizeButtonHint;
subWin->setWindowFlags(flags);
// Hide the Size and Maximize options from the system menu
// since the dialog size is fixed.
QMenu * systemMenu = subWin->systemMenu();
systemMenu->actions().at(2)->setVisible(false); // Size
systemMenu->actions().at(4)->setVisible(false); // Maximize
subWin->setWindowIcon(embed::getIconPixmap("sample_track"));
subWin->setFixedSize(subWin->size());
subWin->hide();
}
SampleTrackWindow::~SampleTrackWindow()
{
}
void SampleTrackWindow::setSampleTrackView(SampleTrackView* tv)
{
if(m_stv && tv)
{
m_stv->m_tlb->setChecked(false);
}
m_stv = tv;
}
void SampleTrackWindow::modelChanged()
{
m_track = castModel<SampleTrack>();
m_nameLineEdit->setText(m_track->name());
m_track->disconnect(SIGNAL(nameChanged()), this);
connect(m_track, SIGNAL(nameChanged()),
this, SLOT(updateName()));
m_volumeKnob->setModel(&m_track->m_volumeModel);
m_panningKnob->setModel(&m_track->m_panningModel);
m_effectChannelNumber->setModel(&m_track->m_effectChannelModel);
updateName();
}
/*! \brief Create and assign a new FX Channel for this track */
void SampleTrackView::createFxLine()
{
int channelIndex = gui->fxMixerView()->addNewChannel();
Engine::fxMixer()->effectChannel(channelIndex)->m_name = getTrack()->name();
assignFxLine(channelIndex);
}
/*! \brief Assign a specific FX Channel for this track */
void SampleTrackView::assignFxLine(int channelIndex)
{
model()->effectChannelModel()->setValue(channelIndex);
gui->fxMixerView()->setCurrentFxLine(channelIndex);
}
void SampleTrackWindow::updateName()
{
setWindowTitle(m_track->name().length() > 25 ? (m_track->name().left(24) + "...") : m_track->name());
if(m_nameLineEdit->text() != m_track->name())
{
m_nameLineEdit->setText(m_track->name());
}
}
void SampleTrackWindow::textChanged(const QString& new_name)
{
m_track->setName(new_name);
Engine::getSong()->setModified();
}
void SampleTrackWindow::toggleVisibility(bool on)
{
if(on)
{
show();
parentWidget()->show();
parentWidget()->raise();
}
else
{
parentWidget()->hide();
}
}
void SampleTrackWindow::closeEvent(QCloseEvent* ce)
{
ce->ignore();
if(gui->mainWindow()->workspace())
{
parentWidget()->hide();
}
else
{
hide();
}
m_stv->m_tlb->setFocus();
m_stv->m_tlb->setChecked(false);
}
void SampleTrackWindow::saveSettings(QDomDocument& doc, QDomElement & element)
{
MainWindow::saveWidgetState(this, element);
Q_UNUSED(element)
}
void SampleTrackWindow::loadSettings(const QDomElement& element)
{
MainWindow::restoreWidgetState(this, element);
if(isVisible())
{
m_stv->m_tlb->setChecked(true);
}
}