Files
lmms/src/gui/song_editor.cpp
T
Tobias Doerffel c720c1f4c8 stable backport:
fixed various bugs regarding recording:
* once recording has been started, stop it as soon as playMode of song changes or similiar things happen (closes #2486341)
* fixed broken record-accompany in most use-cases other than using record-while-playing-song by also tracking NoteOn events and 
remember current play position to use
the correct note postitions on NoteOff events (play position might have wrapped around in the meantime due to looppoints or end 
of BB track) (closes #2486299, #2486203)



git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/branches/lmms/stable-0.4@2048 0778d3d1-df1d-0410-868b-ea421aaaa00d
2009-02-19 13:39:42 +00:00

716 lines
18 KiB
C++

#ifndef SINGLE_SOURCE_COMPILE
/*
* song_editor.cpp - basic window for song-editing
*
* Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
* 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 <QtGui/QAction>
#include <QtGui/QButtonGroup>
#include <QtGui/QKeyEvent>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#include <QtGui/QMdiArea>
#include <QtGui/QPainter>
#include <QtGui/QScrollBar>
#include <math.h>
#include "song_editor.h"
#include "automatable_slider.h"
#include "combobox.h"
#include "cpuload_widget.h"
#include "embed.h"
#include "lcd_spinbox.h"
#include "main_window.h"
#include "meter_dialog.h"
#include "mmp.h"
#include "text_float.h"
#include "timeline.h"
#include "tool_button.h"
#include "tooltip.h"
#include "visualization_widget.h"
#include "audio_device.h"
#include "piano_roll.h"
positionLine::positionLine( QWidget * _parent ) :
QWidget( _parent )
{
setFixedWidth( 1 );
setAttribute( Qt::WA_NoSystemBackground, true );
}
void positionLine::paintEvent( QPaintEvent * _pe )
{
QPainter p( this );
p.fillRect( rect(), QColor( 255, 255, 255, 153 ) );
}
songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) :
trackContainerView( _song ),
m_s( _song ),
m_scrollBack( FALSE )
{
_engine_ptr = this;
setWindowTitle( tr( "Song-Editor" ) );
setWindowIcon( embed::getIconPixmap( "songeditor" ) );
setFocusPolicy( Qt::StrongFocus );
setFocus();
// create time-line
m_timeLine = new timeLine( TRACK_OP_WIDTH +
DEFAULT_SETTINGS_WIDGET_WIDTH, 32,
pixelsPerTact(),
m_s->m_playPos[song::Mode_PlaySong],
m_currentPosition, this );
connect( this, SIGNAL( positionChanged( const midiTime & ) ),
m_s->m_playPos[song::Mode_PlaySong].m_timeLine,
SLOT( updatePosition( const midiTime & ) ) );
connect( m_timeLine, SIGNAL( positionChanged( const midiTime & ) ),
this, SLOT( updatePosition( const midiTime & ) ) );
m_positionLine = new positionLine( this );
// add some essential widgets to global tool-bar
QWidget * tb = engine::getMainWindow()->toolBar();
engine::getMainWindow()->addSpacingToToolBar( 10 );
m_tempoSpinBox = new lcdSpinBox( 3, tb, tr( "Tempo" ) );
m_tempoSpinBox->setModel( &m_s->m_tempoModel );
m_tempoSpinBox->setLabel( tr( "TEMPO/BPM" ) );
toolTip::add( m_tempoSpinBox, tr( "tempo of song" ) );
m_tempoSpinBox->setWhatsThis(
tr( "The tempo of a song is specified in beats per minute "
"(BPM). If you want to change the tempo of your "
"song, change this value. Every measure has four beats, "
"so the tempo in BPM specifies, how many measures / 4 "
"should be played within a minute (or how many measures "
"should be played within four minutes)." ) );
int col = engine::getMainWindow()->addWidgetToToolBar( m_tempoSpinBox,
0 );
toolButton * hq_btn = new toolButton( embed::getIconPixmap( "hq_mode" ),
tr( "High quality mode" ),
NULL, NULL, tb );
hq_btn->setCheckable( TRUE );
connect( hq_btn, SIGNAL( toggled( bool ) ),
this, SLOT( setHighQuality( bool ) ) );
hq_btn->setFixedWidth( 42 );
engine::getMainWindow()->addWidgetToToolBar( hq_btn, 1, col );
engine::getMainWindow()->addSpacingToToolBar( 10 );
m_timeSigDisplay = new meterDialog( this, TRUE );
m_timeSigDisplay->setModel( &m_s->m_timeSigModel );
engine::getMainWindow()->addWidgetToToolBar( m_timeSigDisplay );
engine::getMainWindow()->addSpacingToToolBar( 10 );
QLabel * master_vol_lbl = new QLabel( tb );
master_vol_lbl->setPixmap( embed::getIconPixmap( "master_volume" ) );
m_masterVolumeSlider = new automatableSlider( tb,
tr( "Master volume" ) );
m_masterVolumeSlider->setModel( &m_s->m_masterVolumeModel );
m_masterVolumeSlider->setOrientation( Qt::Vertical );
m_masterVolumeSlider->setPageStep( 1 );
m_masterVolumeSlider->setTickPosition( QSlider::TicksLeft );
m_masterVolumeSlider->setFixedSize( 26, 60 );
m_masterVolumeSlider->setTickInterval( 50 );
toolTip::add( m_masterVolumeSlider, tr( "master volume" ) );
connect( m_masterVolumeSlider, SIGNAL( logicValueChanged( int ) ), this,
SLOT( masterVolumeChanged( int ) ) );
connect( m_masterVolumeSlider, SIGNAL( sliderPressed() ), this,
SLOT( masterVolumePressed() ) );
connect( m_masterVolumeSlider, SIGNAL( logicSliderMoved( int ) ), this,
SLOT( masterVolumeMoved( int ) ) );
connect( m_masterVolumeSlider, SIGNAL( sliderReleased() ), this,
SLOT( masterVolumeReleased() ) );
m_mvsStatus = new textFloat;
m_mvsStatus->setTitle( tr( "Master volume" ) );
m_mvsStatus->setPixmap( embed::getIconPixmap( "master_volume" ) );
engine::getMainWindow()->addWidgetToToolBar( master_vol_lbl );
engine::getMainWindow()->addWidgetToToolBar( m_masterVolumeSlider );
engine::getMainWindow()->addSpacingToToolBar( 10 );
QLabel * master_pitch_lbl = new QLabel( tb );
master_pitch_lbl->setPixmap( embed::getIconPixmap( "master_pitch" ) );
master_pitch_lbl->setFixedHeight( 64 );
m_masterPitchSlider = new automatableSlider( tb, tr( "Master pitch" ) );
m_masterPitchSlider->setModel( &m_s->m_masterPitchModel );
m_masterPitchSlider->setOrientation( Qt::Vertical );
m_masterPitchSlider->setPageStep( 1 );
m_masterPitchSlider->setTickPosition( QSlider::TicksLeft );
m_masterPitchSlider->setFixedSize( 26, 60 );
m_masterPitchSlider->setTickInterval( 12 );
toolTip::add( m_masterPitchSlider, tr( "master pitch" ) );
connect( m_masterPitchSlider, SIGNAL( logicValueChanged( int ) ), this,
SLOT( masterPitchChanged( int ) ) );
connect( m_masterPitchSlider, SIGNAL( sliderPressed() ), this,
SLOT( masterPitchPressed() ) );
connect( m_masterPitchSlider, SIGNAL( logicSliderMoved( int ) ), this,
SLOT( masterPitchMoved( int ) ) );
connect( m_masterPitchSlider, SIGNAL( sliderReleased() ), this,
SLOT( masterPitchReleased() ) );
m_mpsStatus = new textFloat;
m_mpsStatus->setTitle( tr( "Master pitch" ) );
m_mpsStatus->setPixmap( embed::getIconPixmap( "master_pitch" ) );
engine::getMainWindow()->addWidgetToToolBar( master_pitch_lbl );
engine::getMainWindow()->addWidgetToToolBar( m_masterPitchSlider );
engine::getMainWindow()->addSpacingToToolBar( 10 );
// create widget for visualization- and cpu-load-widget
QWidget * vc_w = new QWidget( tb );
QVBoxLayout * vcw_layout = new QVBoxLayout( vc_w );
vcw_layout->setMargin( 0 );
vcw_layout->setSpacing( 0 );
//vcw_layout->addStretch();
vcw_layout->addWidget( new visualizationWidget(
embed::getIconPixmap( "output_graph" ), vc_w ) );
vcw_layout->addWidget( new cpuloadWidget( vc_w ) );
vcw_layout->addStretch();
engine::getMainWindow()->addWidgetToToolBar( vc_w );
// create own toolbar
m_toolBar = new QWidget( this );
m_toolBar->setFixedHeight( 32 );
m_toolBar->setAutoFillBackground( TRUE );
QPalette pal;
pal.setBrush( m_toolBar->backgroundRole(),
embed::getIconPixmap( "toolbar_bg" ) );
m_toolBar->setPalette( pal );
static_cast<QVBoxLayout *>( layout() )->insertWidget( 0, m_toolBar );
static_cast<QVBoxLayout *>( layout() )->insertWidget( 1, m_timeLine );
QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar );
tb_layout->setMargin( 0 );
tb_layout->setSpacing( 0 );
// fill own tool-bar
m_playButton = new toolButton( embed::getIconPixmap( "play", 24, 24 ),
tr( "Play song (Space)" ),
this, SLOT( play() ), m_toolBar );
m_recordButton = new toolButton( embed::getIconPixmap( "record" ),
tr( "Record samples from Audio-device" ),
this, SLOT( record() ), m_toolBar );
m_recordAccompanyButton = new toolButton(
embed::getIconPixmap( "record_accompany" ),
tr( "Record samples from Audio-device while playing "
"song or BB track" ),
this, SLOT( recordAccompany() ), m_toolBar );
// FIXME: disable record button while it is not implemented
m_recordButton->setDisabled( true );
// disable record buttons if capturing is not supported
if( !engine::getMixer()->audioDev()->supportsCapture() )
{
m_recordButton->setDisabled( true );
m_recordAccompanyButton->setDisabled( true );
}
m_stopButton = new toolButton( embed::getIconPixmap( "stop", 24, 24 ),
tr( "Stop song (Space)" ),
this, SLOT( stop() ), m_toolBar );
m_addBBTrackButton = new toolButton( embed::getIconPixmap(
"add_bb_track" ),
tr( "Add beat/bassline" ),
m_s, SLOT( addBBTrack() ),
m_toolBar );
m_addSampleTrackButton = new toolButton( embed::getIconPixmap(
"add_sample_track", 24, 24 ),
tr( "Add sample-track" ),
m_s, SLOT( addSampleTrack() ),
m_toolBar );
m_addAutomationTrackButton = new toolButton( embed::getIconPixmap(
"add_automation", 24, 24 ),
tr( "Add automation-track" ),
m_s, SLOT( addAutomationTrack() ),
m_toolBar );
m_drawModeButton = new toolButton( embed::getIconPixmap(
"edit_draw" ),
tr( "Draw mode" ),
NULL, NULL, m_toolBar );
m_drawModeButton->setCheckable( TRUE );
m_drawModeButton->setChecked( TRUE );
m_editModeButton = new toolButton( embed::getIconPixmap(
"edit_select" ),
tr( "Edit mode (select and move)" ),
NULL, NULL, m_toolBar );
m_editModeButton->setCheckable( TRUE );
QButtonGroup * tool_button_group = new QButtonGroup( this );
tool_button_group->addButton( m_drawModeButton );
tool_button_group->addButton( m_editModeButton );
tool_button_group->setExclusive( TRUE );
#if 0
#warning TODO
QWhatsThis::add( m_playButton, tr( "Click here, if you want to play "
"your whole song. Playing will "
"be started at the "
"song-position-marker (green). "
"You can also move it while "
"playing." ) );
QWhatsThis::add( m_stopButton, tr ( "Click here, if you want to stop "
"playing of your song. The "
"song-position-marker will be "
"set to the start of your song."
) );
/* QWhatsThis::add( m_insertBarButton, tr( "If you click here, a "
"bar will "
"be inserted at the "
"current bar." ) );
QWhatsThis::add( m_removeBarButton, tr( "If you click here, the "
"current bar will be "
"removed." ) );*/
#endif
QLabel * zoom_lbl = new QLabel( m_toolBar );
zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );
// setup zooming-stuff
m_zoomingComboBox = new comboBox( m_toolBar );
m_zoomingComboBox->setFixedSize( 80, 22 );
m_zoomingComboBox->move( 580, 4 );
for( int i = 0; i < 7; ++i )
{
m_zoomingComboBox->model()->addItem(
QString::number( 25 << i ) + "%" );
}
m_zoomingComboBox->model()->setInitValue(
m_zoomingComboBox->model()->findText( "100%" ) );
connect( m_zoomingComboBox->model(), SIGNAL( dataChanged() ),
this, SLOT( zoomingChanged( void ) ) );
tb_layout->addSpacing( 5 );
tb_layout->addWidget( m_playButton );
tb_layout->addWidget( m_recordButton );
tb_layout->addWidget( m_recordAccompanyButton );
tb_layout->addWidget( m_stopButton );
tb_layout->addSpacing( 10 );
tb_layout->addWidget( m_addBBTrackButton );
tb_layout->addWidget( m_addSampleTrackButton );
tb_layout->addWidget( m_addAutomationTrackButton );
tb_layout->addSpacing( 10 );
tb_layout->addWidget( m_drawModeButton );
tb_layout->addWidget( m_editModeButton );
tb_layout->addSpacing( 10 );
m_timeLine->addToolButtons( m_toolBar );
tb_layout->addSpacing( 15 );
tb_layout->addWidget( zoom_lbl );
tb_layout->addSpacing( 5 );
tb_layout->addWidget( m_zoomingComboBox );
tb_layout->addStretch();
m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
m_leftRightScroll->setMinimum( 0 );
m_leftRightScroll->setMaximum( 0 );
m_leftRightScroll->setSingleStep( 1 );
m_leftRightScroll->setPageStep( 20 );
static_cast<QVBoxLayout *>( layout() )->addWidget( m_leftRightScroll );
connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ),
this, SLOT( scrolled( int ) ) );
engine::getMainWindow()->workspace()->addSubWindow( this );
parentWidget()->setAttribute( Qt::WA_DeleteOnClose, FALSE );
parentWidget()->resize( 600, 300 );
parentWidget()->move( 5, 5 );
parentWidget()->show();
}
songEditor::~songEditor()
{
}
void songEditor::setHighQuality( bool _hq )
{
engine::getMixer()->changeQuality( mixer::qualitySettings(
_hq ? mixer::qualitySettings::Mode_HighQuality :
mixer::qualitySettings::Mode_Draft ) );
}
void songEditor::scrolled( int _new_pos )
{
update();
emit positionChanged( m_currentPosition = midiTime( _new_pos, 0 ) );
}
void songEditor::play( void )
{
m_s->play();
engine::getPianoRoll()->stopRecording();
if( m_s->playMode() == song::Mode_PlaySong )
{
m_playButton->setIcon( embed::getIconPixmap( "pause" ) );
}
else
{
m_playButton->setIcon( embed::getIconPixmap( "play" ) );
}
}
void songEditor::record( void )
{
m_s->record();
}
void songEditor::recordAccompany( void )
{
m_s->playAndRecord();
}
void songEditor::stop( void )
{
m_s->stop();
engine::getPianoRoll()->stopRecording();
m_playButton->setIcon( embed::getIconPixmap( "play" ) );
}
void songEditor::keyPressEvent( QKeyEvent * _ke )
{
if( /*_ke->modifiers() & Qt::ShiftModifier*/
engine::getMainWindow()->isShiftPressed() == TRUE &&
_ke->key() == Qt::Key_Insert )
{
m_s->insertBar();
}
else if(/* _ke->modifiers() & Qt::ShiftModifier &&*/
engine::getMainWindow()->isShiftPressed() == TRUE &&
_ke->key() == Qt::Key_Delete )
{
m_s->removeBar();
}
else if( _ke->key() == Qt::Key_Left )
{
tick t = m_s->currentTick() - midiTime::ticksPerTact();
if( t >= 0 )
{
m_s->setPlayPos( t, song::Mode_PlaySong );
}
}
else if( _ke->key() == Qt::Key_Right )
{
tick t = m_s->currentTick() + midiTime::ticksPerTact();
if( t < MaxSongLength )
{
m_s->setPlayPos( t, song::Mode_PlaySong );
}
}
else if( _ke->key() == Qt::Key_Space )
{
if( m_s->isPlaying() )
{
stop();
}
else
{
play();
}
}
else if( _ke->key() == Qt::Key_Home )
{
m_s->setPlayPos( 0, song::Mode_PlaySong );
}
else
{
QWidget::keyPressEvent( _ke );
}
}
void songEditor::paintEvent( QPaintEvent * _pe )
{
m_leftRightScroll->setMaximum( m_s->length() );
trackContainerView::paintEvent( _pe );
}
void songEditor::wheelEvent( QWheelEvent * _we )
{
if( engine::getMainWindow()->isCtrlPressed() == TRUE )
{
if( _we->delta() > 0 )
{
setPixelsPerTact( (int) qMin( pixelsPerTact() * 2,
256.0f ) );
}
else if( pixelsPerTact() >= 8 )
{
setPixelsPerTact( (int) pixelsPerTact() / 2 );
}
// update combobox with zooming-factor
m_zoomingComboBox->model()->setValue(
m_zoomingComboBox->model()->findText(
QString::number(
static_cast<int>( pixelsPerTact() *
100 / DEFAULT_PIXELS_PER_TACT ) ) +
"%" ) );
// update timeline
m_s->m_playPos[song::Mode_PlaySong].m_timeLine->
setPixelsPerTact( pixelsPerTact() );
// and make sure, all TCO's are resized and relocated
realignTracks();
}
else if( engine::getMainWindow()->isShiftPressed() == TRUE )
{
m_leftRightScroll->setValue( m_leftRightScroll->value() -
_we->delta() / 30 );
}
else
{
_we->ignore();
return;
}
_we->accept();
}
void songEditor::masterVolumeChanged( int _new_val )
{
masterVolumeMoved( _new_val );
if( m_mvsStatus->isVisible() == FALSE && m_s->m_loadingProject == FALSE
&& m_masterVolumeSlider->showStatus() )
{
m_mvsStatus->moveGlobal( m_masterVolumeSlider,
QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
m_mvsStatus->setVisibilityTimeOut( 1000 );
}
engine::getMixer()->setMasterGain( _new_val / 100.0f );
}
void songEditor::masterVolumePressed( void )
{
m_mvsStatus->moveGlobal( m_masterVolumeSlider,
QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
m_mvsStatus->show();
masterVolumeMoved( m_s->m_masterVolumeModel.value() );
}
void songEditor::masterVolumeMoved( int _new_val )
{
m_mvsStatus->setText( tr( "Value: %1%" ).arg( _new_val ) );
}
void songEditor::masterVolumeReleased( void )
{
m_mvsStatus->hide();
}
void songEditor::masterPitchChanged( int _new_val )
{
masterPitchMoved( _new_val );
if( m_mpsStatus->isVisible() == FALSE && m_s->m_loadingProject == FALSE
&& m_masterPitchSlider->showStatus() )
{
m_mpsStatus->moveGlobal( m_masterPitchSlider,
QPoint( m_masterPitchSlider->width() + 2, -2 ) );
m_mpsStatus->setVisibilityTimeOut( 1000 );
}
}
void songEditor::masterPitchPressed( void )
{
m_mpsStatus->moveGlobal( m_masterPitchSlider,
QPoint( m_masterPitchSlider->width() + 2, -2 ) );
m_mpsStatus->show();
masterPitchMoved( m_s->m_masterPitchModel.value() );
}
void songEditor::masterPitchMoved( int _new_val )
{
m_mpsStatus->setText( tr( "Value: %1 semitones").arg( _new_val ) );
}
void songEditor::masterPitchReleased( void )
{
m_mpsStatus->hide();
}
void songEditor::updatePosition( const midiTime & _t )
{
if( ( m_s->isPlaying() && m_s->m_playMode == song::Mode_PlaySong
&& m_timeLine->autoScroll() == timeLine::AutoScrollEnabled) ||
m_scrollBack == TRUE )
{
const int w = width() - DEFAULT_SETTINGS_WIDGET_WIDTH
- TRACK_OP_WIDTH;
if( _t > m_currentPosition + w * midiTime::ticksPerTact() /
pixelsPerTact() )
{
m_leftRightScroll->setValue( _t.getTact() );
}
else if( _t < m_currentPosition )
{
midiTime t = qMax(
(int)( _t - w * midiTime::ticksPerTact() /
pixelsPerTact() ),
0 );
m_leftRightScroll->setValue( t.getTact() );
}
m_scrollBack = FALSE;
}
const int x = m_s->m_playPos[song::Mode_PlaySong].m_timeLine->
markerX( _t ) + 8;
if( x >= TRACK_OP_WIDTH + DEFAULT_SETTINGS_WIDGET_WIDTH-1 )
{
m_positionLine->show();
m_positionLine->move( x, 50 );
}
else
{
m_positionLine->hide();
}
m_positionLine->setFixedHeight( height() );
}
void songEditor::zoomingChanged( void )
{
const QString & zfac = m_zoomingComboBox->model()->currentText();
setPixelsPerTact( zfac.left( zfac.length() - 1 ).toInt() *
DEFAULT_PIXELS_PER_TACT / 100 );
m_s->m_playPos[song::Mode_PlaySong].m_timeLine->
setPixelsPerTact( pixelsPerTact() );
realignTracks();
}
bool songEditor::allowRubberband( void ) const
{
return( m_editModeButton->isChecked() );
}
#include "moc_song_editor.cxx"
#endif