From 02db2fbd2f10c61f576f26935819fe0f707dbc84 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sun, 20 May 2012 18:50:06 +0200 Subject: [PATCH 001/133] Piano roll: fix scale marking algorithm and improve rendering A patch to fix the scale marking algorithm and possibly improve on the looks Closes #3493928. --- src/gui/piano_roll.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/gui/piano_roll.cpp b/src/gui/piano_roll.cpp index 27c54bffd..e94c02208 100644 --- a/src/gui/piano_roll.cpp +++ b/src/gui/piano_roll.cpp @@ -671,7 +671,8 @@ void pianoRoll::markSemiTone( int i ) for( int i = first; i <= last; i++ ) { - if( chord->hasSemiTone( std::abs( key - i ) % cap ) ) + //if( chord->hasSemiTone( std::abs( key - i ) % cap ) ) + if( chord->hasSemiTone( ( i + cap - ( key % cap ) ) % cap ) ) { m_markedSemiTones.push_back( i ); } @@ -2667,6 +2668,24 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) int key = m_startKey; + // display note marks before drawing other lines + for( int i = 0; i < m_markedSemiTones.size(); i++ ) + { + const int key_num = m_markedSemiTones.at( i ); + const int y = keyAreaBottom() + 5 + - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ); + + if( y > keyAreaBottom() ) + { + break; + } + + p.fillRect( WHITE_KEY_WIDTH+1, y-KEY_LINE_HEIGHT/2, + width() - 10, KEY_LINE_HEIGHT, + QColor( 0, 80 - ( key_num % KeysPerOctave ) * 3, 64 + key_num / 2) ); + } + + // draw all white keys... for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN; key_line_y -= KEY_LINE_HEIGHT, ++keys_processed ) @@ -2795,22 +2814,6 @@ void pianoRoll::paintEvent( QPaintEvent * _pe ) ++key; } - // display note marks - for( int i = 0; i < m_markedSemiTones.size(); i++ ) - { - const int key_num = m_markedSemiTones.at( i ); - const int y = keyAreaBottom() + 5 - - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ); - - if( y > keyAreaBottom() ) - { - break; - } - - p.fillRect( WHITE_KEY_WIDTH, y, - width() - 10, 1, - QColor( 64, 64 + ( key_num % KeysPerOctave ) * 7, 96 + key_num ) ); - } // erase the area below the piano, because there might be keys that // should be only half-visible From 8b66dfc860374555d7c85700a84f10fb86a20140 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 20 May 2012 18:55:30 +0200 Subject: [PATCH 002/133] ZynAddSubFX: fixed possible buffer overflow bankdir could be overflowed in the strncat function since the length limit is not correctly specified. Closes #3510466. --- plugins/zynaddsubfx/src/Misc/Bank.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/zynaddsubfx/src/Misc/Bank.cpp b/plugins/zynaddsubfx/src/Misc/Bank.cpp index 256184736..246a4098a 100644 --- a/plugins/zynaddsubfx/src/Misc/Bank.cpp +++ b/plugins/zynaddsubfx/src/Misc/Bank.cpp @@ -337,9 +337,9 @@ int Bank::newbank(const char *newbankdirname) if(((bankdir[strlen(bankdir) - 1]) != '/') && ((bankdir[strlen(bankdir) - 1]) != '\\')) - strncat(bankdir, "/", MAX_STRING_SIZE); + strncat(bankdir, "/", MAX_STRING_SIZE - strlen(bankdir) - 1 ); ; - strncat(bankdir, newbankdirname, MAX_STRING_SIZE); + strncat(bankdir, newbankdirname, MAX_STRING_SIZE - strlen(bankdir) - 1); #ifdef OS_WINDOWS result = mkdir(bankdir); #else From 04c1a0906f629d9f7336dd6fa23b659d518609c0 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sun, 20 May 2012 19:00:29 +0200 Subject: [PATCH 003/133] AutomationEditor: show tooltip with exact value Added tooltip telling the exact value under the cursor when editing automation. Signed-off-by: Tobias Doerffel --- src/gui/AutomationEditor.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/AutomationEditor.cpp b/src/gui/AutomationEditor.cpp index 3a597e784..1593d1c82 100644 --- a/src/gui/AutomationEditor.cpp +++ b/src/gui/AutomationEditor.cpp @@ -36,6 +36,8 @@ #include #include #include +#include + #ifndef __USE_XOPEN @@ -1234,6 +1236,10 @@ inline void AutomationEditor::drawCross( QPainter & _p ) _p.drawLine( VALUES_WIDTH, (int) cross_y, width(), (int) cross_y ); _p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), height() - SCROLLBAR_SIZE ); + QPoint tt_pos = QCursor::pos(); + tt_pos.ry() -= 64; + tt_pos.rx() += 32; + QToolTip::showText( tt_pos,QString::number( level ),this); } From 370ff26c2bf63f48e5cc12368002a4b8ddd1fe4d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 27 Oct 2012 22:01:08 +0200 Subject: [PATCH 004/133] LocalZynAddSubFx: added missing header include ZASF fails to build with GCC 4.7 due to missing header include. Thanks to Alexander Liebendorfer for pointing out the issue. --- plugins/zynaddsubfx/LocalZynAddSubFx.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/zynaddsubfx/LocalZynAddSubFx.cpp b/plugins/zynaddsubfx/LocalZynAddSubFx.cpp index 34663c2c3..bd270816f 100644 --- a/plugins/zynaddsubfx/LocalZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/LocalZynAddSubFx.cpp @@ -24,6 +24,8 @@ #include +#include + #include "LocalZynAddSubFx.h" #include "src/Input/NULLMidiIn.h" From 4ba4d8b4e0c4215f5161877939bc4bdfcc2d9842 Mon Sep 17 00:00:00 2001 From: Jesse Dubay Date: Sat, 27 Oct 2012 22:16:45 +0200 Subject: [PATCH 005/133] ControllerConnectionDialog: fix crash on autodetect when using raw MIDI client --- src/gui/ControllerConnectionDialog.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/ControllerConnectionDialog.cpp b/src/gui/ControllerConnectionDialog.cpp index 9c0db85ab..53ffc89e9 100644 --- a/src/gui/ControllerConnectionDialog.cpp +++ b/src/gui/ControllerConnectionDialog.cpp @@ -427,7 +427,10 @@ void ControllerConnectionDialog::midiValueChanged() if( m_midiAutoDetect.value() ) { m_midiController->useDetected(); - m_readablePorts->updateMenu(); + if( m_readablePorts ) + { + m_readablePorts->updateMenu(); + } } } From ad3af9779825178ee5ab4fe8394d800692f3754d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 27 Oct 2012 22:20:12 +0200 Subject: [PATCH 006/133] MidiClientRaw: fixed parsing of MIDI events A break was missing in the function where MIDI events are generated from raw MIDI data. This always has been broken as of commit d1880566830e2962 but was not observed as the code for all switch-cases has been identical. --- src/core/midi/MidiClient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/midi/MidiClient.cpp b/src/core/midi/MidiClient.cpp index 2572d78da..744a280f7 100644 --- a/src/core/midi/MidiClient.cpp +++ b/src/core/midi/MidiClient.cpp @@ -222,11 +222,11 @@ void MidiClientRaw::parseData( const Uint8 _c ) m_midiParseData.m_buffer[0] - KeysPerOctave; m_midiParseData.m_midiEvent.m_data.m_param[1] = m_midiParseData.m_buffer[1]; + break; + case MidiControlChange: - m_midiParseData.m_midiEvent.m_data.m_param[0] = - m_midiParseData.m_buffer[0] - KeysPerOctave; - m_midiParseData.m_midiEvent.m_data.m_param[1] = - m_midiParseData.m_buffer[1]; + m_midiParseData.m_midiEvent.m_data.m_param[0] = m_midiParseData.m_buffer[0]; + m_midiParseData.m_midiEvent.m_data.m_param[1] = m_midiParseData.m_buffer[1]; break; case MidiPitchBend: From d448e6743da7610a8572f17251bb7d559d3e1b73 Mon Sep 17 00:00:00 2001 From: NoiseByNorthwest Date: Tue, 21 Feb 2012 00:32:47 +0100 Subject: [PATCH 007/133] Ergonomic enhancements for AudioFileProcessor plugin (interactive wave view). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch includes: * sampleBuffer::visualise(): add possibility to specified a range to visualize instead of the whole sample * add sampleBuffer::sampleRate() and sampleBuffer::sampleLength() getters * definition of AudioFileProcessorWaveView and AudioFileProcessorWaveView::knob classes for AudioFileProcessor plugin * knob::getValue() specified “virtual” to allow redefinition in child class AudioFileProcessorWaveView::knob * delete audioFileKnob class (made obsolete by AudioFileProcessorWaveView::knob) * add audioFileProcessor::isPlaying() signal, which is emitted in audioFileProcessor::playNote * change type of AudioFileProcessorView::m_startKnob and AudioFileProcessorView::m_endKnob (AudioFileProcessorWaveView::knob instead of audioFileKnob) * replace AudioFileProcessorView::m_graph (QPixmap) by AudioFileProcessorView::m_waveView (AudioFileProcessorWaveView) Signed-off-by: Tobias Doerffel --- include/knob.h | 3 +- include/sample_buffer.h | 16 +- .../audio_file_processor.cpp | 557 ++++++++++++++++-- .../audio_file_processor.h | 138 ++++- src/core/sample_buffer.cpp | 19 +- 5 files changed, 676 insertions(+), 57 deletions(-) diff --git a/include/knob.h b/include/knob.h index aa2767cb4..a224d097d 100644 --- a/include/knob.h +++ b/include/knob.h @@ -112,6 +112,8 @@ protected: virtual void paintEvent( QPaintEvent * _me ); virtual void wheelEvent( QWheelEvent * _me ); + virtual float getValue( const QPoint & _p ); + private slots: virtual void enterValue(); void displayHelp(); @@ -128,7 +130,6 @@ private: void drawKnob( QPainter * _p ); void setPosition( const QPoint & _p ); - float getValue( const QPoint & _p ); bool updateAngle(); inline float pageSize() const diff --git a/include/sample_buffer.h b/include/sample_buffer.h index 43c36a6bc..7993a23c7 100644 --- a/include/sample_buffer.h +++ b/include/sample_buffer.h @@ -77,10 +77,10 @@ public: const float _freq, const bool _looped = false ); - void visualize( QPainter & _p, const QRect & _dr, const QRect & _clip ); - inline void visualize( QPainter & _p, const QRect & _dr ) + void visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 ); + inline void visualize( QPainter & _p, const QRect & _dr, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 ) { - visualize( _p, _dr, _dr ); + visualize( _p, _dr, _dr, _from_frame, _to_frame ); } inline const QString & audioFile() const @@ -132,6 +132,16 @@ public: return m_frequency; } + sample_rate_t sampleRate() const + { + return m_sampleRate; + } + + int sampleLength() const + { + return double( m_endFrame - m_startFrame ) / m_sampleRate * 1000; + } + inline void setFrequency( float _freq ) { m_varLock.lock(); diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 8e2041e97..c97aa2c7a 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -115,6 +115,11 @@ void audioFileProcessor::playNote( notePlayHandle * _n, applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, frames,_n ); + emit isPlaying( _n->totalFramesPlayed() * _n->frequency() / m_sampleBuffer.frequency() ); + } + else + { + emit isPlaying( 0 ); } } @@ -260,22 +265,6 @@ void audioFileProcessor::loopPointChanged( void ) - - - -class audioFileKnob : public knob -{ -public: - audioFileKnob( QWidget * _parent ) : - knob( knobStyled, _parent ) - { - setFixedSize( 37, 47 ); - } -}; - - - - QPixmap * AudioFileProcessorView::s_artwork = NULL; @@ -347,7 +336,7 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, "Otherwise it will be amplified up or down (your " "actual sample-file isn't touched!)" ) ); - m_startKnob = new audioFileKnob( this ); + m_startKnob = new AudioFileProcessorWaveView::knob( this ); m_startKnob->move( 68, 108 ); m_startKnob->setHintText( tr( "Startpoint:" )+" ", "" ); m_startKnob->setWhatsThis( @@ -357,7 +346,7 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, "which AudioFileProcessor returns if a note is longer " "than the sample between the start and end-points." ) ); - m_endKnob = new audioFileKnob( this ); + m_endKnob = new AudioFileProcessorWaveView::knob( this ); m_endKnob->move( 119, 108 ); m_endKnob->setHintText( tr( "Endpoint:" )+" ", "" ); m_endKnob->setWhatsThis( @@ -367,6 +356,18 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument, "AudioFileProcessor returns if a note is longer than " "the sample between the start and end-points." ) ); + m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer ); + m_waveView->move( 2, 172 ); + m_waveView->setKnobs( + dynamic_cast( m_startKnob ), + dynamic_cast( m_endKnob ) + ); + + connect( castModel(), SIGNAL( isPlaying( f_cnt_t ) ), + m_waveView, SLOT( isPlaying( f_cnt_t ) ) ); + + qRegisterMetaType( "f_cnt_t" ); + setAcceptDrops( TRUE ); } @@ -464,26 +465,6 @@ void AudioFileProcessorView::paintEvent( QPaintEvent * ) p.setPen( QColor( 255, 255, 255 ) ); p.drawText( 8, 99, file_name ); - - p.drawPixmap( 2, 172, m_graph ); - - - p.setPen( QColor( 0xFF, 0xAA, 0x00 ) ); - const QRect graph_rect( 4, 174, 241, 70 ); - const f_cnt_t frames = qMax( a->m_sampleBuffer.frames(), - static_cast( 1 ) ); - const int start_frame_x = a->m_sampleBuffer.startFrame() * - graph_rect.width() / frames; - const int end_frame_x = a->m_sampleBuffer.endFrame() * - graph_rect.width() / frames; - - p.drawLine( start_frame_x + graph_rect.x(), graph_rect.y(), - start_frame_x + graph_rect.x(), - graph_rect.height() + graph_rect.y() ); - p.drawLine( end_frame_x + graph_rect.x(), graph_rect.y(), - end_frame_x + graph_rect.x(), - graph_rect.height() + graph_rect.y() ); - } @@ -491,13 +472,7 @@ void AudioFileProcessorView::paintEvent( QPaintEvent * ) void AudioFileProcessorView::sampleUpdated( void ) { - m_graph = QPixmap( 245, 75 ); - m_graph.fill( Qt::transparent ); - QPainter p( &m_graph ); - p.setPen( QColor( 64, 255, 160 ) ); - castModel()->m_sampleBuffer. - visualize( p, QRect( 2, 2, m_graph.width() - 4, - m_graph.height() - 4 ) ); + m_waveView->update(); update(); } @@ -535,6 +510,498 @@ void AudioFileProcessorView::modelChanged( void ) +AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, sampleBuffer & _buf ) : + QWidget( _parent ), + m_sampleBuffer( _buf ), + m_graph( QPixmap( _w - 2 * s_padding, _h - 2 * s_padding ) ), + m_from( 0 ), + m_to( m_sampleBuffer.frames() ), + m_last_from( 0 ), + m_last_to( 0 ), + m_startKnob( 0 ), + m_endKnob( 0 ), + m_isDragging( false ), + m_reversed( false ), + m_framesPlayed( 0 ) +{ + setFixedSize( _w, _h ); + setMouseTracking( true ); + + if( m_sampleBuffer.frames() > 1 ) + { + const f_cnt_t marging = ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * 0.1; + m_from = qMax( 0, m_sampleBuffer.startFrame() - marging ); + m_to = qMin( m_sampleBuffer.endFrame() + marging, m_sampleBuffer.frames() ); + } + + update(); +} + + + + +void AudioFileProcessorWaveView::isPlaying( f_cnt_t _frames_played ) +{ + m_framesPlayed = _frames_played % ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ); + update(); +} + + + + +void AudioFileProcessorWaveView::enterEvent( QEvent * _e ) +{ + QApplication::setOverrideCursor( Qt::OpenHandCursor ); +} + + + + +void AudioFileProcessorWaveView::leaveEvent( QEvent * _e ) +{ + while( QApplication::overrideCursor() ) + { + QApplication::restoreOverrideCursor(); + } +} + + + + +void AudioFileProcessorWaveView::mousePressEvent( QMouseEvent * _me ) +{ + m_isDragging = true; + m_draggingLastPoint = _me->pos(); + + if( isCloseTo( _me->x(), m_startFrameX ) ) + { + m_draggingType = sample_start; + } + else if( isCloseTo( _me->x(), m_endFrameX ) ) + { + m_draggingType = sample_end; + } + else + { + m_draggingType = wave; + QApplication::setOverrideCursor( Qt::ClosedHandCursor ); + } +} + + + + +void AudioFileProcessorWaveView::mouseReleaseEvent( QMouseEvent * _me ) +{ + m_isDragging = false; + if( m_draggingType == wave ) + { + QApplication::restoreOverrideCursor(); + } +} + + + + +void AudioFileProcessorWaveView::mouseMoveEvent( QMouseEvent * _me ) +{ + if( ! m_isDragging ) + { + const bool is_size_cursor = + QApplication::overrideCursor()->shape() == Qt::SizeHorCursor; + + if( isCloseTo( _me->x(), m_startFrameX ) || + isCloseTo( _me->x(), m_endFrameX ) ) + { + if( ! is_size_cursor ) + { + QApplication::setOverrideCursor( Qt::SizeHorCursor ); + } + } + else if( is_size_cursor ) + { + QApplication::restoreOverrideCursor(); + } + return; + } + + const int step = _me->x() - m_draggingLastPoint.x(); + switch( m_draggingType ) + { + case sample_start: + slideSamplePointByPx( start, step ); + break; + case sample_end: + slideSamplePointByPx( end, step ); + break; + default: + if( qAbs( _me->y() - m_draggingLastPoint.y() ) + < 2 * qAbs( _me->x() - m_draggingLastPoint.x() ) ) + { + slide( step ); + } + else + { + zoom( _me->y() < m_draggingLastPoint.y() ); + } + } + + m_draggingLastPoint = _me->pos(); + update(); +} + + + + +void AudioFileProcessorWaveView::wheelEvent( QWheelEvent * _we ) +{ + zoom( _we->delta() > 0 ); + update(); +} + + + + +void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) +{ + QPainter p( this ); + + p.drawPixmap( s_padding, s_padding, m_graph ); + + p.setPen( QColor( 0xFF, 0xFF, 0x00 ) ); + const QRect graph_rect( s_padding, s_padding, width() - 2 * s_padding, height() - 2 * s_padding ); + const f_cnt_t frames = m_to - m_from; + m_startFrameX = graph_rect.x() + ( m_sampleBuffer.startFrame() - m_from ) * + double( graph_rect.width() ) / frames; + m_endFrameX = graph_rect.x() + ( m_sampleBuffer.endFrame() - m_from ) * + double( graph_rect.width() ) / frames; + + p.drawLine( m_startFrameX, graph_rect.y(), + m_startFrameX, + graph_rect.height() + graph_rect.y() ); + p.drawLine( m_endFrameX, graph_rect.y(), + m_endFrameX, + graph_rect.height() + graph_rect.y() ); + + if( m_endFrameX - m_startFrameX > 2 ) + { + p.fillRect( + m_startFrameX + 1, + graph_rect.y(), + m_endFrameX - m_startFrameX - 1, + graph_rect.height() + graph_rect.y(), + QColor( 255, 255, 0, 70 ) + ); + + if( m_framesPlayed ) + { + const int played_width_px = m_framesPlayed + / double( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) + * ( m_endFrameX - m_startFrameX ); + QLinearGradient g( m_startFrameX + 1, 0, m_startFrameX + 1 + played_width_px, 0 ); + const QColor c( 0, 120, 255, 180 ); + g.setColorAt( 0, Qt::transparent ); + g.setColorAt( 0.8, c ); + g.setColorAt( 1, c ); + p.fillRect( + m_startFrameX + 1, + graph_rect.y(), + played_width_px, + graph_rect.height() + graph_rect.y(), + g + ); + p.setPen( QColor( 0, 255, 255 ) ); + p.drawLine( + m_startFrameX + 1 + played_width_px, + graph_rect.y(), + m_startFrameX + 1 + played_width_px, + graph_rect.height() + graph_rect.y() + ); + m_framesPlayed = 0; + } + } + + QLinearGradient g( 0, 0, width() * 0.7, 0 ); + const QColor c( 0, 0, 150, 180 ); + g.setColorAt( 0, c ); + g.setColorAt( 0.4, c ); + g.setColorAt( 1, Qt::transparent ); + p.fillRect( s_padding, s_padding, m_graph.width(), 14, g ); + + p.setPen( QColor( 255, 255, 20 ) ); + p.setFont( pointSize<8>( font() ) ); + + QString length_text; + const int length = m_sampleBuffer.sampleLength(); + + if( length > 20000 ) + { + length_text = QString::number( length / 1000 ) + "s"; + } + else if( length > 2000 ) + { + length_text = QString::number( ( length / 100 ) / 10.0 ) + "s"; + } + else + { + length_text = QString::number( length ) + "ms"; + } + + p.drawText( + s_padding + 2, + s_padding + 10, + tr( "Sample length:" ) + " " + length_text + ); +} + + + + +void AudioFileProcessorWaveView::updateGraph() +{ + if( m_to == 1 ) + { + m_to = m_sampleBuffer.frames() * 0.7; + slideSamplePointToFrames( end, m_to * 0.7 ); + } + + if( m_from > m_sampleBuffer.startFrame() ) + { + m_from = m_sampleBuffer.startFrame(); + } + + if( m_to < m_sampleBuffer.endFrame() ) + { + m_to = m_sampleBuffer.endFrame(); + } + + if( m_sampleBuffer.reversed() != m_reversed ) + { + reverse(); + } + else if( m_last_from == m_from && m_last_to == m_to ) + { + return; + } + + m_last_from = m_from; + m_last_to = m_to; + + m_graph.fill( Qt::transparent ); + QPainter p( &m_graph ); + p.setPen( QColor( 64, 255, 160 ) ); + m_sampleBuffer.visualize( + p, + QRect( 0, 0, m_graph.width(), m_graph.height() ), + m_from, m_to + ); +} + + + + +void AudioFileProcessorWaveView::zoom( const bool _out ) +{ + const f_cnt_t start = m_sampleBuffer.startFrame(); + const f_cnt_t end = m_sampleBuffer.endFrame(); + const f_cnt_t frames = m_sampleBuffer.frames(); + const f_cnt_t d_from = start - m_from; + const f_cnt_t d_to = m_to - end; + + const f_cnt_t step = qMax( 1, qMax( d_from, d_to ) / 10 ); + const f_cnt_t step_from = ( _out ? - step : step ); + const f_cnt_t step_to = ( _out ? step : - step ); + + const double comp_ratio = double( qMin( d_from, d_to ) ) + / qMax( 1, qMax( d_from, d_to ) ); + + f_cnt_t new_from; + f_cnt_t new_to; + + if( ( _out && d_from < d_to ) || ( ! _out && d_to < d_from ) ) + { + new_from = qBound( 0, m_from + step_from, start ); + new_to = qBound( + end, + m_to + f_cnt_t( step_to * ( new_from == m_from ? 1 : comp_ratio ) ), + frames + ); + } + else + { + new_to = qBound( end, m_to + step_to, frames ); + new_from = qBound( + 0, + m_from + f_cnt_t( step_from * ( new_to == m_to ? 1 : comp_ratio ) ), + start + ); + } + + if( double( new_to - new_from ) / m_sampleBuffer.sampleRate() > 0.05 ) + { + m_from = new_from; + m_to = new_to; + } +} + + + + +void AudioFileProcessorWaveView::slide( int _px ) +{ + const double fact = qAbs( double( _px ) / width() ); + f_cnt_t step = ( m_to - m_from ) * fact; + if( _px > 0 ) + { + step = -step; + } + + f_cnt_t step_from = qBound( 0, m_from + step, m_sampleBuffer.frames() ) - m_from; + f_cnt_t step_to = qBound( m_from + 1, m_to + step, m_sampleBuffer.frames() ) - m_to; + + step = qAbs( step_from ) < qAbs( step_to ) ? step_from : step_to; + + m_from += step; + m_to += step; + slideSampleByFrames( step ); +} + + + + +void AudioFileProcessorWaveView::setKnobs( knob * _start, knob * _end ) +{ + m_startKnob = _start; + m_endKnob = _end; + + m_startKnob->setWaveView( this ); + m_startKnob->setRelatedKnob( m_endKnob ); + + m_endKnob->setWaveView( this ); + m_endKnob->setRelatedKnob( m_startKnob ); +} + + + + +void AudioFileProcessorWaveView::slideSamplePointByPx( knobType _point, int _px ) +{ + slideSamplePointByFrames( + _point, + f_cnt_t( ( double( _px ) / width() ) * ( m_to - m_from ) ) + ); +} + + + + +void AudioFileProcessorWaveView::slideSamplePointByFrames( knobType _point, f_cnt_t _frames, bool _slide_to ) +{ + knob * knob = _point == start ? m_startKnob : m_endKnob; + if( ! knob ) + { + return; + } + + const double v = double( _frames ) / m_sampleBuffer.frames(); + if( _slide_to ) + { + knob->slideTo( v ); + } + else + { + knob->slideBy( v ); + } +} + + + + +void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) +{ + const double v = double( _frames ) / m_sampleBuffer.frames(); + m_startKnob->slideBy( v, false ); + m_endKnob->slideBy( v, false ); +} + + + + +void AudioFileProcessorWaveView::reverse() +{ + slideSampleByFrames( + m_sampleBuffer.frames() + - m_sampleBuffer.endFrame() + - m_sampleBuffer.startFrame() + ); + + const f_cnt_t from = m_from; + m_from = m_sampleBuffer.frames() - m_to; + m_to = m_sampleBuffer.frames() - from; + + m_reversed = ! m_reversed; +} + + + + +void AudioFileProcessorWaveView::knob::slideTo( double _v, bool _check_bound ) +{ + if( _check_bound && ! checkBound( _v ) ) + { + return; + } + model()->setValue( _v ); + emit sliderMoved( model()->value() ); +} + + + + +float AudioFileProcessorWaveView::knob::getValue( const QPoint & _p ) +{ + const double dec_fact = ! m_waveView ? 1 : + double( m_waveView->m_to - m_waveView->m_from ) + / m_waveView->m_sampleBuffer.frames(); + const float inc = ::knob::getValue( _p ) * dec_fact; + const float next = model()->value() - inc; + + if( ! checkBound( next ) ) + { + return 0; + } + return inc; +} + + + + +bool AudioFileProcessorWaveView::knob::checkBound( double _v ) const +{ + if( ! m_relatedKnob || ! m_waveView ) + { + return true; + } + + if( ( m_relatedKnob->model()->value() - _v > 0 ) != + ( m_relatedKnob->model()->value() - model()->value() > 0 ) ) + return false; + + const double d1 = qAbs( m_relatedKnob->model()->value() - model()->value() ) + * ( m_waveView->m_sampleBuffer.frames() ) + / m_waveView->m_sampleBuffer.sampleRate(); + + const double d2 = qAbs( m_relatedKnob->model()->value() - _v ) + * ( m_waveView->m_sampleBuffer.frames() ) + / m_waveView->m_sampleBuffer.sampleRate(); + + return d1 < d2 || d2 > 0.02; +} + + + + + extern "C" { diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index ae7f75b39..1617e745b 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -76,6 +76,10 @@ private slots: void loopPointChanged(); +signals: + void isPlaying( f_cnt_t _frames_played ); + + private: typedef sampleBuffer::handleState handleState; @@ -94,6 +98,9 @@ private: +class AudioFileProcessorWaveView; + + class AudioFileProcessorView : public InstrumentView { Q_OBJECT @@ -118,7 +125,7 @@ private: static QPixmap * s_artwork; - QPixmap m_graph; + AudioFileProcessorWaveView * m_waveView; knob * m_ampKnob; knob * m_startKnob; knob * m_endKnob; @@ -130,5 +137,134 @@ private: +class AudioFileProcessorWaveView : public QWidget +{ + Q_OBJECT +protected: + virtual void enterEvent( QEvent * _e ); + virtual void leaveEvent( QEvent * _e ); + virtual void mousePressEvent( QMouseEvent * _me ); + virtual void mouseReleaseEvent( QMouseEvent * _me ); + virtual void mouseMoveEvent( QMouseEvent * _me ); + virtual void wheelEvent( QWheelEvent * _we ); + virtual void paintEvent( QPaintEvent * _pe ); + + +public: + enum knobType + { + start, + end, + } ; + + class knob : public ::knob + { + const AudioFileProcessorWaveView * m_waveView; + const knob * m_relatedKnob; + + + public: + knob( QWidget * _parent ) : + ::knob( knobStyled, _parent ), + m_waveView( 0 ), + m_relatedKnob( 0 ) + { + setFixedSize( 37, 47 ); + } + + void setWaveView( const AudioFileProcessorWaveView * _wv ) + { + m_waveView = _wv; + } + + void setRelatedKnob( const knob * _knob ) + { + m_relatedKnob = _knob; + } + + void slideBy( double _v, bool _check_bound = true ) + { + slideTo( model()->value() + _v, _check_bound ); + } + + void slideTo( double _v, bool _check_bound = true ); + + + protected: + float getValue( const QPoint & _p ); + + + private: + bool checkBound( double _v ) const; + + } ; + + +public slots: + void update() + { + updateGraph(); + QWidget::update(); + } + + void isPlaying( f_cnt_t _frames_played ); + + +private: + static const int s_padding = 2; + + enum draggingType + { + wave, + sample_start, + sample_end, + } ; + + sampleBuffer & m_sampleBuffer; + QPixmap m_graph; + f_cnt_t m_from; + f_cnt_t m_to; + f_cnt_t m_last_from; + f_cnt_t m_last_to; + knob * m_startKnob; + knob * m_endKnob; + f_cnt_t m_startFrameX; + f_cnt_t m_endFrameX; + bool m_isDragging; + QPoint m_draggingLastPoint; + draggingType m_draggingType; + bool m_reversed; + f_cnt_t m_framesPlayed; + + +public: + AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, sampleBuffer & _buf ); + void setKnobs( knob * _start, knob * _end ); + + +private: + void zoom( const bool _out = false ); + void slide( int _px ); + void slideSamplePointByPx( knobType _point, int _px ); + void slideSamplePointByFrames( knobType _point, f_cnt_t _frames, bool _slide_to = false ); + void slideSampleByFrames( f_cnt_t _frames ); + + void slideSamplePointToFrames( knobType _point, f_cnt_t _frames ) + { + slideSamplePointByFrames( _point, _frames, true ); + } + + void updateGraph(); + void reverse(); + + static bool isCloseTo( int _a, int _b ) + { + return qAbs( _a - _b ) < 3; + } + +} ; + + + #endif diff --git a/src/core/sample_buffer.cpp b/src/core/sample_buffer.cpp index df5c56c48..40b0bc036 100644 --- a/src/core/sample_buffer.cpp +++ b/src/core/sample_buffer.cpp @@ -699,8 +699,10 @@ f_cnt_t sampleBuffer::getLoopedIndex( f_cnt_t _index ) const void sampleBuffer::visualize( QPainter & _p, const QRect & _dr, - const QRect & _clip ) + const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) { + const bool focus_on_range = _to_frame <= m_frames + && 0 <= _from_frame && _from_frame < _to_frame; // _p.setClipRect( _clip ); // _p.setPen( QColor( 0x22, 0xFF, 0x44 ) ); //_p.setPen( QColor( 64, 224, 160 ) ); @@ -709,25 +711,28 @@ void sampleBuffer::visualize( QPainter & _p, const QRect & _dr, const int yb = h / 2 + _dr.y(); const float y_space = h*0.25f; + const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_frames; - if( m_frames < 60000 ) + if( nb_frames < 60000 ) { _p.setRenderHint( QPainter::Antialiasing ); QColor c = _p.pen().color(); _p.setPen( QPen( c, 0.7 ) ); } - const int fpp = tLimit( m_frames / w, 1, 20 ); - QPoint * l = new QPoint[m_frames / fpp + 1]; + const int fpp = tLimit( nb_frames / w, 1, 20 ); + QPoint * l = new QPoint[nb_frames / fpp + 1]; int n = 0; const int xb = _dr.x(); - for( int frame = 0; frame < m_frames; frame += fpp ) + const int first = focus_on_range ? _from_frame : 0; + const int last = focus_on_range ? _to_frame : m_frames; + for( int frame = first; frame < last; frame += fpp ) { - l[n] = QPoint( xb + ( frame * w / m_frames ), + l[n] = QPoint( xb + ( (frame - first) * double( w ) / nb_frames ), (int)( yb - ( ( m_data[frame][0]+m_data[frame][1] ) * y_space ) ) ); ++n; } - _p.drawPolyline( l, m_frames / fpp ); + _p.drawPolyline( l, nb_frames / fpp ); delete[] l; } From c1368ddb1ad09a52927031442a89a90620631f39 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 27 Oct 2012 22:45:59 +0200 Subject: [PATCH 008/133] SetupDialog: added option for disabling auto save feature As requested by various users, it should be configurable whether the auto save feature is active or not. --- include/setup_dialog.h | 2 ++ src/gui/MainWindow.cpp | 9 ++++++--- src/gui/setup_dialog.cpp | 21 ++++++++++++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/include/setup_dialog.h b/include/setup_dialog.h index 83ca075f2..35c1c13f3 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -105,6 +105,7 @@ private slots: void toggleDisableChActInd( bool _disabled ); void toggleManualChPiano( bool _enabled ); void toggleSmoothScroll( bool _enabled ); + void toggleAutoSave( bool _enabled ); void toggleOneInstrumentTrackWindow( bool _enabled ); void toggleCompactTrackButtons( bool _enabled ); @@ -152,6 +153,7 @@ private: bool m_disableChActInd; bool m_manualChPiano; bool m_smoothScroll; + bool m_disableAutoSave; bool m_oneInstrumentTrackWindow; bool m_compactTrackButtons; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4966ff1b4..ed7397c89 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -184,9 +184,12 @@ MainWindow::MainWindow( void ) : m_updateTimer.start( 1000 / 20, this ); // 20 fps - // connect auto save - connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); - m_autoSaveTimer.start(1000 * 60); // 1 minute + if( !configManager::inst()->value( "ui", "disableautosave" ).toInt() ) + { + // connect auto save + connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); + m_autoSaveTimer.start(1000 * 60); // 1 minute + } } diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 108b3a5e3..6f2691693 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -113,6 +113,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_manualChPiano( configManager::inst()->value( "ui", "manualchannelpiano" ).toInt() ), m_smoothScroll( configManager::inst()->value( "ui", "smoothscroll" ).toInt() ), + m_disableAutoSave( configManager::inst()->value( "ui", "disableautosave" ).toInt() ), m_oneInstrumentTrackWindow( configManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() ), m_compactTrackButtons( configManager::inst()->value( "ui", @@ -464,7 +465,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : tabWidget * ui_fx_tw = new tabWidget( tr( "UI effects vs. " "performance" ).toUpper(), performance ); - ui_fx_tw->setFixedHeight( 90 ); + ui_fx_tw->setFixedHeight( 100 ); ledCheckBox * disable_ch_act_ind = new ledCheckBox( tr( "Disable channel activity indicators" ), @@ -491,6 +492,14 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : this, SLOT( toggleSmoothScroll( bool ) ) ); + ledCheckBox * autoSave = new ledCheckBox( + tr( "Disable auto save feature" ), ui_fx_tw ); + autoSave->move( 10, 80 ); + autoSave->setChecked( m_disableAutoSave ); + connect( autoSave, SIGNAL( toggled( bool ) ), + this, SLOT( toggleAutoSave( bool ) ) ); + + perf_layout->addWidget( ui_fx_tw ); perf_layout->addStretch(); @@ -759,6 +768,8 @@ void setupDialog::accept() QString::number( m_manualChPiano ) ); configManager::inst()->setValue( "ui", "smoothscroll", QString::number( m_smoothScroll ) ); + configManager::inst()->setValue( "ui", "disableautosave", + QString::number( m_disableAutoSave ) ); configManager::inst()->setValue( "ui", "oneinstrumenttrackwindow", QString::number( m_oneInstrumentTrackWindow ) ); configManager::inst()->setValue( "ui", "compacttrackbuttons", @@ -926,6 +937,14 @@ void setupDialog::toggleSmoothScroll( bool _enabled ) +void setupDialog::toggleAutoSave( bool _enabled ) +{ + m_disableAutoSave = _enabled; +} + + + + void setupDialog::toggleCompactTrackButtons( bool _enabled ) From 95e1ef1c78ad95661ae12752b30889f13608c392 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 27 Oct 2012 22:50:16 +0200 Subject: [PATCH 009/133] InstrumentTrack: show FX mixer when double clicking FX line LCD spinbox As proposed by Danil on 2012-03-12. --- src/tracks/InstrumentTrack.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 74980c103..73adbbde8 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1193,6 +1193,9 @@ class fxLineLcdSpinBox : public lcdSpinBox virtual void mouseDoubleClickEvent ( QMouseEvent * _me ) { engine::fxMixerView()->setCurrentFxLine( model()->value() ); + + engine::fxMixerView()->show();// show fxMixer window + engine::fxMixerView()->setFocus();// set focus to fxMixer window //engine::getFxMixerView()->raise(); } }; From e7605ea04508cd26f5f6bb524e7aaedf1e278a94 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 27 Oct 2012 23:01:37 +0200 Subject: [PATCH 010/133] Localizations: renamed ir.* to fa.* for ISO 639 conformance As proposed by Octosquidopus renamed translation files containing Farsi translations from ir.* to fa.* --- data/locale/{ir.qm => fa.qm} | Bin data/locale/{ir.ts => fa.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename data/locale/{ir.qm => fa.qm} (100%) rename data/locale/{ir.ts => fa.ts} (100%) diff --git a/data/locale/ir.qm b/data/locale/fa.qm similarity index 100% rename from data/locale/ir.qm rename to data/locale/fa.qm diff --git a/data/locale/ir.ts b/data/locale/fa.ts similarity index 100% rename from data/locale/ir.ts rename to data/locale/fa.ts From 69947a624b43a5de257217b3af9e1383312ba305 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Thu, 1 Nov 2012 21:35:17 +0100 Subject: [PATCH 011/133] Track: allow smaller height Here's one way to cram more stuff onto small screens, or otherwise help reducing visual clutter: Allow tracks to be shift-dragged all the way down to 8 px height. Signed-off-by: Tobias Doerffel --- include/track.h | 15 ++++++++++++ src/core/track.cpp | 45 ++++++++++++++++++++++------------ src/tracks/AutomationTrack.cpp | 2 +- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/include/track.h b/include/track.h index 256cf31d1..1011c245d 100644 --- a/include/track.h +++ b/include/track.h @@ -59,6 +59,13 @@ const int TRACK_OP_WIDTH = 78; const int DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT = 96; const int TRACK_OP_WIDTH_COMPACT = 60; +/*! The minimum track height in pixels + * + * Tracks can be resized by shift-dragging anywhere inside the track + * display. This sets the minimum size in pixels for a track. + */ +const Uint16 MINIMAL_TRACK_HEIGHT = 8; +const Uint16 DEFAULT_TRACK_HEIGHT = 32; const int TCO_BORDER_WIDTH = 1; @@ -435,6 +442,13 @@ public: using Model::dataChanged; + inline int getHeight() { + return ( m_height >= MINIMAL_TRACK_HEIGHT ? m_height : DEFAULT_TRACK_HEIGHT ); + } + inline void setHeight( int _height ) { + m_height = _height; + } + public slots: virtual void setName( const QString & _new_name ) @@ -450,6 +464,7 @@ private: trackContainer * m_trackContainer; TrackTypes m_type; QString m_name; + int m_height; BoolModel m_mutedModel; BoolModel m_soloModel; diff --git a/src/core/track.cpp b/src/core/track.cpp index 7f2cc096d..b9b925f34 100644 --- a/src/core/track.cpp +++ b/src/core/track.cpp @@ -80,13 +80,6 @@ const Sint16 RESIZE_GRIP_WIDTH = 4; const Uint16 TRACK_OP_BTN_WIDTH = 20; const Uint16 TRACK_OP_BTN_HEIGHT = 14; -/*! The minimum track height in pixels - * - * Tracks can be resized by shift-dragging anywhere inside the track - * display. This sets the minimum size in pixels for a track. - */ -const Uint16 MINIMAL_TRACK_HEIGHT = 32; - /*! A pointer for that text bubble used when moving segments, etc. * @@ -1563,6 +1556,7 @@ track::track( TrackTypes _type, trackContainer * _tc ) : m_trackContentObjects() /*!< The track content objects (segments) */ { m_trackContainer->addTrack( this ); + m_height = -1; } @@ -1679,8 +1673,10 @@ void track::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "type", type() ); _this.setAttribute( "name", name() ); _this.setAttribute( "muted", isMuted() ); -// ### TODO -// _this.setAttribute( "height", m_trackView->height() ); + if( m_height >= MINIMAL_TRACK_HEIGHT ) + { + _this.setAttribute( "height", m_height ); + } QDomElement ts_de = _doc.createElement( nodeName() ); // let actual track (InstrumentTrack, bbTrack, sampleTrack etc.) save @@ -1773,12 +1769,11 @@ void track::loadSettings( const QDomElement & _this ) } node = node.nextSibling(); } -/* + if( _this.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT ) { - m_trackView->setFixedHeight( - _this.attribute( "height" ).toInt() ); - }*/ + m_height = _this.attribute( "height" ).toInt(); + } } @@ -2122,6 +2117,7 @@ trackView::trackView( track * _track, trackContainerView * _tcv ) : layout->addWidget( &m_trackOperationsWidget ); layout->addWidget( &m_trackSettingsWidget ); layout->addWidget( &m_trackContentWidget, 1 ); + setFixedHeight( m_track->getHeight() ); resizeEvent( NULL ); @@ -2225,7 +2221,8 @@ void trackView::modelChanged() connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); m_trackOperationsWidget.m_muteBtn->setModel( &m_track->m_mutedModel ); m_trackOperationsWidget.m_soloBtn->setModel( &m_track->m_soloModel ); - ModelView::modelChanged(); + ModelView::modelChanged(); + setFixedHeight( m_track->getHeight() ); } @@ -2256,6 +2253,10 @@ void trackView::undoStep( JournalEntry & _je ) MINIMAL_TRACK_HEIGHT ) ); m_trackContainerView->realignTracks(); break; + /*case RestoreTrack: + setFixedHeight( DEFAULT_TRACK_HEIGHT ); + m_trackContainerView->realignTracks(); + break; */ } restoreJournallingState(); } @@ -2331,6 +2332,16 @@ void trackView::dropEvent( QDropEvent * _de ) */ void trackView::mousePressEvent( QMouseEvent * _me ) { + // If previously dragged too small, restore on shift-leftclick + if( height() < DEFAULT_TRACK_HEIGHT && + _me->modifiers() & Qt::ShiftModifier && + _me->button() == Qt::LeftButton ) + { + setFixedHeight( DEFAULT_TRACK_HEIGHT ); + m_track->setHeight( DEFAULT_TRACK_HEIGHT ); + } + + if( m_trackContainerView->allowRubberband() == true ) { QWidget::mousePressEvent( _me ); @@ -2385,6 +2396,7 @@ void trackView::mousePressEvent( QMouseEvent * _me ) */ void trackView::mouseMoveEvent( QMouseEvent * _me ) { + if( m_trackContainerView->allowRubberband() == true ) { QWidget::mouseMoveEvent( _me ); @@ -2415,12 +2427,15 @@ void trackView::mouseMoveEvent( QMouseEvent * _me ) { setFixedHeight( qMax( _me->y(), MINIMAL_TRACK_HEIGHT ) ); m_trackContainerView->realignTracks(); + m_track->setHeight( height() ); + } + if( height() < DEFAULT_TRACK_HEIGHT ) { + toolTip::add( this, m_track->m_name ); } } - /*! \brief Handle a mouse release event on this track View. * * \param _me the MouseEvent to handle. diff --git a/src/tracks/AutomationTrack.cpp b/src/tracks/AutomationTrack.cpp index 67ca77379..1f92533dd 100644 --- a/src/tracks/AutomationTrack.cpp +++ b/src/tracks/AutomationTrack.cpp @@ -131,7 +131,7 @@ AutomationTrackView::AutomationTrackView( AutomationTrack * _at, trackContainerView * _tcv ) : trackView( _at, _tcv ) { - setFixedHeight( 32 ); + setFixedHeight( 32 ); trackLabelButton * tlb = new trackLabelButton( this, getTrackSettingsWidget() ); tlb->setIcon( embed::getIconPixmap( "automation_track" ) ); From eb60d9e06ebf36e9dc0366b54bb50b192f69e29d Mon Sep 17 00:00:00 2001 From: Devin Venable Date: Mon, 29 Oct 2012 17:26:08 -0500 Subject: [PATCH 012/133] Added multiple track export feature. Here is the patchset for my multiple track export feature. It works now the way I originally envisioned. For example, after I export tracks on my little song, I see this in the directory I created: devin@devin-studio:~/lmms/projects/fff$ ls 0_Defaultpreset.wav 3_Defaultpreset.wav 6_csidSouwav.wav 1_Defaultpreset.wav 4_spacenoiseswavwav.wav 7_HHOPENwav.wav 2_Defaultpreset.wav 5_csidkickwav.wav 8_HHOPENwav.wav Each instrument or sample track is exported individually, regardless of whether in its own song track or playing as part of a BB track. The name is taken from either the song track name or from the BB track name. My goal was to get the tracks individually exported, so that I could combine them with other tracks in Ardour. Signed-off-by: Tobias Doerffel --- include/export_project_dialog.h | 20 ++-- include/song.h | 3 +- src/core/ProjectRenderer.cpp | 6 +- src/core/song.cpp | 65 +++++++----- src/gui/MainWindow.cpp | 6 ++ src/gui/export_project_dialog.cpp | 166 +++++++++++++++++++++++------- 6 files changed, 193 insertions(+), 73 deletions(-) diff --git a/include/export_project_dialog.h b/include/export_project_dialog.h index 266b9f93d..944e5fe18 100644 --- a/include/export_project_dialog.h +++ b/include/export_project_dialog.h @@ -28,17 +28,17 @@ #define _EXPORT_PROJECT_DIALOG_H #include - +#include #include "ui_export_project.h" -class ProjectRenderer; +#include "ProjectRenderer.h" class exportProjectDialog : public QDialog, public Ui::ExportProjectDialog { Q_OBJECT public: - exportProjectDialog( const QString & _file_name, QWidget * _parent ); + exportProjectDialog( const QString & _file_name, QWidget * _parent, bool multi_export ); virtual ~exportProjectDialog(); @@ -50,12 +50,20 @@ protected: private slots: void startBtnClicked( void ); void updateTitleBar( int ); - + void render(ProjectRenderer* renderer); + void multi_render(); + ProjectRenderer* prep_render(); + void pop_render(); + void accept(); private: QString m_fileName; - ProjectRenderer * m_renderer; - + QString m_dirName; + std::vector m_renderers; + bool m_multi_export; + std::vector m_unmuted; + ProjectRenderer::ExportFileFormats m_ft; + std::vector m_to_render_vec; } ; #endif diff --git a/include/song.h b/include/song.h index 3b129714e..6eafefa90 100644 --- a/include/song.h +++ b/include/song.h @@ -207,7 +207,8 @@ public slots: void resumeFromPause(); void importProject(); - void exportProject(); + void exportProject(bool multiExport=false); + void exportProjectTracks(); void startExport(); void stopExport(); diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 37de20e76..89f3f46d3 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -35,7 +35,7 @@ #ifdef LMMS_HAVE_SCHED_H #include #endif - +#include FileEncodeDevice __fileEncodeDevices[] = { @@ -126,6 +126,7 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( void ProjectRenderer::startProcessing() { + if( isReady() ) { // have to do mixer stuff with GUI-thread-affinity in order to @@ -139,11 +140,11 @@ void ProjectRenderer::startProcessing() QThread::HighPriority #endif ); + } } - void ProjectRenderer::run() { #if 0 @@ -157,6 +158,7 @@ void ProjectRenderer::run() #endif #endif + engine::getSong()->startExport(); song::playPos & pp = engine::getSong()->getPlayPos( diff --git a/src/core/song.cpp b/src/core/song.cpp index 8962e73e7..75f4f2ef4 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -60,7 +60,8 @@ #include "templates.h" #include "text_float.h" #include "timeline.h" - +#include +using namespace std; tick_t midiTime::s_ticksPerTact = DefaultTicksPerTact; @@ -1136,9 +1137,12 @@ void song::restoreControllerStates( const QDomElement & _this ) } +void song::exportProjectTracks() +{ + exportProject(true); +} - -void song::exportProject() +void song::exportProject(bool multiExport) { if( isEmpty() ) { @@ -1151,38 +1155,51 @@ void song::exportProject() } QFileDialog efd( engine::mainWindow() ); - efd.setFileMode( QFileDialog::AnyFile ); - efd.setAcceptMode( QFileDialog::AcceptSave ); - int idx = 0; - QStringList types; - while( __fileEncodeDevices[idx].m_fileFormat != - ProjectRenderer::NumFileFormats ) + if (multiExport) { - types << tr( __fileEncodeDevices[idx].m_description ); - ++idx; - } - efd.setFilters( types ); - - QString base_filename; - if( !m_fileName.isEmpty() ) - { - efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); - base_filename = QFileInfo( m_fileName ).completeBaseName(); + efd.setFileMode( QFileDialog::Directory); + efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) ); + if( !m_fileName.isEmpty() ) + { + efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); + } } else { - efd.setDirectory( configManager::inst()->userProjectsDir() ); - base_filename = tr( "untitled" ); + efd.setFileMode( QFileDialog::AnyFile ); + int idx = 0; + QStringList types; + while( __fileEncodeDevices[idx].m_fileFormat != + ProjectRenderer::NumFileFormats ) + { + types << tr( __fileEncodeDevices[idx].m_description ); + ++idx; + } + efd.setFilters( types ); + QString base_filename; + if( !m_fileName.isEmpty() ) + { + efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); + base_filename = QFileInfo( m_fileName ).completeBaseName(); + } + else + { + efd.setDirectory( configManager::inst()->userProjectsDir() ); + base_filename = tr( "untitled" ); + } + efd.selectFile( base_filename + __fileEncodeDevices[0].m_extension ); + efd.setWindowTitle( tr( "Select file for project-export..." ) ); } - efd.selectFile( base_filename + __fileEncodeDevices[0].m_extension ); - efd.setWindowTitle( tr( "Select file for project-export..." ) ); + + efd.setAcceptMode( QFileDialog::AcceptSave ); + if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() ) { const QString export_file_name = efd.selectedFiles()[0]; exportProjectDialog epd( export_file_name, - engine::mainWindow() ); + engine::mainWindow(), multiExport ); epd.exec(); } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ed7397c89..c725e9499 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -258,6 +258,12 @@ void MainWindow::finalize( void ) engine::getSong(), SLOT( exportProject() ), Qt::CTRL + Qt::Key_E ); + project_menu->addAction( embed::getIconPixmap( "project_export" ), + tr( "E&xport tracks..." ), + engine::getSong(), + SLOT( exportProjectTracks() ), + Qt::CTRL + Qt::Key_E ); + project_menu->addSeparator(); project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), qApp, SLOT( closeAllWindows() ), diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index c3804eaa3..a0bb87633 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -23,20 +23,21 @@ */ #include +#include #include #include "export_project_dialog.h" +#include "song.h" #include "engine.h" #include "MainWindow.h" -#include "ProjectRenderer.h" exportProjectDialog::exportProjectDialog( const QString & _file_name, - QWidget * _parent ) : + QWidget * _parent, bool multi_export=false ) : QDialog( _parent ), Ui::ExportProjectDialog(), m_fileName( _file_name ), - m_renderer( NULL ) + m_multi_export(multi_export) { setupUi( this ); setWindowTitle( tr( "Export project to %1" ).arg( @@ -83,7 +84,12 @@ exportProjectDialog::exportProjectDialog( const QString & _file_name, exportProjectDialog::~exportProjectDialog() { - delete m_renderer; + + for( std::vector::const_iterator it = m_renderers.begin(); + it != m_renderers.end(); ++it ) + { + delete (*it); + } } @@ -91,13 +97,31 @@ exportProjectDialog::~exportProjectDialog() void exportProjectDialog::reject() { - if( m_renderer == NULL ) + for( std::vector::const_iterator it = m_renderers.begin(); + it != m_renderers.end(); ++it ) { - accept(); + (*it)->abortProcessing(); + } +} +void exportProjectDialog::accept() +{ + // If more to render, kick off next render job + if (m_renderers.size() > 0) + { + pop_render( ); } else { - m_renderer->abortProcessing(); + // If done, then reset mute states + while(!m_unmuted.empty()) + { + track* restore_track = m_unmuted.back(); + m_unmuted.pop_back(); + restore_track->setMuted(false); + } + + QDialog::accept(); + } } @@ -106,19 +130,106 @@ void exportProjectDialog::reject() void exportProjectDialog::closeEvent( QCloseEvent * _ce ) { - if( m_renderer != NULL && m_renderer->isRunning() ) + for( std::vector::const_iterator it = m_renderers.begin(); + it != m_renderers.end(); ++it ) { - m_renderer->abortProcessing(); + if( (*it)->isRunning() ) + { + (*it)->abortProcessing(); + } } QDialog::closeEvent( _ce ); } +void exportProjectDialog::pop_render() { + track* render_track = m_to_render_vec.back(); + m_to_render_vec.pop_back(); + for (std::vector::const_iterator it = m_unmuted.begin(); + it != m_unmuted.end(); ++it) { + if ((*it) == render_track) { + (*it)->setMuted(false); + } else { + (*it)->setMuted(true); + } + } + + // Pop next render job and start + ProjectRenderer* r = m_renderers.back(); + m_renderers.pop_back(); + render(r); +} + +void exportProjectDialog::multi_render() +{ + m_dirName = m_fileName; + QString path = QDir(m_fileName).filePath("text.txt"); + std::string strTest = path.toStdString(); + + const trackContainer::trackList & tl = engine::getSong()->tracks(); + + // Check for all unmuted tracks. Remember list. + int x = 0; + for( trackContainer::trackList::const_iterator it = tl.begin(); + it != tl.end(); ++it ) + { + // Don't mute automation tracks + if (! (*it)->isMuted() && (*it)->nodeName() != "automationtrack") + { + m_unmuted.push_back((*it)); + QString nextName = (*it)->name(); + nextName = nextName.remove(QRegExp("[^a-zA-Z]")); + QString name = QString("%1_%2.wav").arg(x++).arg(nextName); + m_fileName = QDir(m_dirName).filePath(name); + std::string strTest = m_fileName.toStdString(); + prep_render(); + } + } + + m_to_render_vec = m_unmuted; + + pop_render( ); +} + +ProjectRenderer* exportProjectDialog::prep_render( + ) { + mixer::qualitySettings qs = + mixer::qualitySettings( + static_cast(interpolationCB->currentIndex()), + static_cast(oversamplingCB->currentIndex()), + sampleExactControllersCB->isChecked(), + aliasFreeOscillatorsCB->isChecked()); + ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( + samplerateCB->currentText().section(" ", 0, 0).toUInt(), false, + bitrateCB->currentText().section(" ", 0, 0).toUInt(), + static_cast(depthCB->currentIndex())); + ProjectRenderer* renderer = new ProjectRenderer(qs, os, m_ft, m_fileName); + m_renderers.push_back(renderer); + return renderer; +} + +void exportProjectDialog::render(ProjectRenderer* renderer) +{ + + if (renderer->isReady()) { + connect(renderer, SIGNAL( progressChanged( int ) ), progressBar, + SLOT( setValue( int ) )); + connect(renderer, SIGNAL( progressChanged( int ) ), this, + SLOT( updateTitleBar( int ) )); + connect(renderer, SIGNAL( finished() ), this, SLOT( accept() )); + connect(renderer, SIGNAL( finished() ), engine::mainWindow(), + SLOT( resetWindowTitle() )); + + renderer->startProcessing(); + } else { + accept(); + } +} void exportProjectDialog::startBtnClicked() { - ProjectRenderer::ExportFileFormats ft = ProjectRenderer::NumFileFormats; + m_ft = ProjectRenderer::NumFileFormats; for( int i = 0; i < ProjectRenderer::NumFileFormats; ++i ) { @@ -126,12 +237,12 @@ void exportProjectDialog::startBtnClicked() ProjectRenderer::tr( __fileEncodeDevices[i].m_description ) ) { - ft = __fileEncodeDevices[i].m_fileFormat; + m_ft = __fileEncodeDevices[i].m_fileFormat; break; } } - if( ft == ProjectRenderer::NumFileFormats ) + if( m_ft == ProjectRenderer::NumFileFormats ) { QMessageBox::information( this, tr( "Error" ), tr( "Error while determining file-encoder device. " @@ -146,38 +257,13 @@ void exportProjectDialog::startBtnClicked() updateTitleBar( 0 ); - mixer::qualitySettings qs = mixer::qualitySettings( - static_cast( - interpolationCB->currentIndex() ), - static_cast( - oversamplingCB->currentIndex() ), - sampleExactControllersCB->isChecked(), - aliasFreeOscillatorsCB->isChecked() ); - - ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( - samplerateCB->currentText().section( " ", 0, 0 ).toUInt(), - false, - bitrateCB->currentText().section( " ", 0, 0 ).toUInt(), - static_cast( - depthCB->currentIndex() ) ); - - m_renderer = new ProjectRenderer( qs, os, ft, m_fileName ); - if( m_renderer->isReady() ) + if (m_multi_export==true) { - connect( m_renderer, SIGNAL( progressChanged( int ) ), - progressBar, SLOT( setValue( int ) ) ); - connect( m_renderer, SIGNAL( progressChanged( int ) ), - this, SLOT( updateTitleBar( int ) ) ); - connect( m_renderer, SIGNAL( finished() ), - this, SLOT( accept() ) ); - connect( m_renderer, SIGNAL( finished() ), - engine::mainWindow(), SLOT( resetWindowTitle() ) ); - - m_renderer->startProcessing(); + multi_render(); } else { - accept(); + render(prep_render()); } } From 929b44f14b6e28d04eee317f5bc2885bb687f32a Mon Sep 17 00:00:00 2001 From: devin Date: Fri, 2 Nov 2012 14:31:55 -0500 Subject: [PATCH 013/133] Added individual BB tracks to multi export Signed-off-by: Tobias Doerffel --- include/export_project_dialog.h | 12 ++++-- src/core/song.cpp | 2 - src/gui/export_project_dialog.cpp | 66 +++++++++++++++++++++++-------- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/include/export_project_dialog.h b/include/export_project_dialog.h index 944e5fe18..ce2dbdaab 100644 --- a/include/export_project_dialog.h +++ b/include/export_project_dialog.h @@ -59,11 +59,15 @@ private slots: private: QString m_fileName; QString m_dirName; - std::vector m_renderers; - bool m_multi_export; - std::vector m_unmuted; + typedef QVector RenderVector; + RenderVector m_renderers; + bool m_multiExport; + + typedef QVector TrackVector; + TrackVector m_unmuted; + TrackVector m_unmutedBB; ProjectRenderer::ExportFileFormats m_ft; - std::vector m_to_render_vec; + TrackVector m_tracksToRender; } ; #endif diff --git a/src/core/song.cpp b/src/core/song.cpp index 75f4f2ef4..d76f0383a 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -60,8 +60,6 @@ #include "templates.h" #include "text_float.h" #include "timeline.h" -#include -using namespace std; tick_t midiTime::s_ticksPerTact = DefaultTicksPerTact; diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index a0bb87633..0eb2cc15c 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -30,14 +30,17 @@ #include "song.h" #include "engine.h" #include "MainWindow.h" +#include "bb_track_container.h" +#include "bb_track.h" +#include exportProjectDialog::exportProjectDialog( const QString & _file_name, QWidget * _parent, bool multi_export=false ) : QDialog( _parent ), Ui::ExportProjectDialog(), m_fileName( _file_name ), - m_multi_export(multi_export) + m_multiExport(multi_export) { setupUi( this ); setWindowTitle( tr( "Export project to %1" ).arg( @@ -85,7 +88,7 @@ exportProjectDialog::exportProjectDialog( const QString & _file_name, exportProjectDialog::~exportProjectDialog() { - for( std::vector::const_iterator it = m_renderers.begin(); + for( RenderVector::const_iterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { delete (*it); @@ -97,7 +100,7 @@ exportProjectDialog::~exportProjectDialog() void exportProjectDialog::reject() { - for( std::vector::const_iterator it = m_renderers.begin(); + for( RenderVector::const_iterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { (*it)->abortProcessing(); @@ -130,7 +133,7 @@ void exportProjectDialog::accept() void exportProjectDialog::closeEvent( QCloseEvent * _ce ) { - for( std::vector::const_iterator it = m_renderers.begin(); + for( RenderVector::const_iterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { if( (*it)->isRunning() ) @@ -143,18 +146,22 @@ void exportProjectDialog::closeEvent( QCloseEvent * _ce ) void exportProjectDialog::pop_render() { - track* render_track = m_to_render_vec.back(); - m_to_render_vec.pop_back(); + track* render_track = m_tracksToRender.back(); + m_tracksToRender.pop_back(); - for (std::vector::const_iterator it = m_unmuted.begin(); + // Set must states for song tracks + for (TrackVector::const_iterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it) { - if ((*it) == render_track) { + if ((*it) == render_track) + { (*it)->setMuted(false); - } else { + } else + { (*it)->setMuted(true); } } + // Pop next render job and start ProjectRenderer* r = m_renderers.back(); m_renderers.pop_back(); @@ -165,29 +172,54 @@ void exportProjectDialog::multi_render() { m_dirName = m_fileName; QString path = QDir(m_fileName).filePath("text.txt"); - std::string strTest = path.toStdString(); + + int x = 1; const trackContainer::trackList & tl = engine::getSong()->tracks(); // Check for all unmuted tracks. Remember list. - int x = 0; for( trackContainer::trackList::const_iterator it = tl.begin(); it != tl.end(); ++it ) { + track* tk = (*it); + track::TrackTypes type = tk->type(); // Don't mute automation tracks - if (! (*it)->isMuted() && (*it)->nodeName() != "automationtrack") + if (! tk->isMuted() && ( + type == track::InstrumentTrack || + type == track::SampleTrack )) { - m_unmuted.push_back((*it)); - QString nextName = (*it)->name(); + m_unmuted.push_back(tk); + QString nextName = tk->name(); + nextName = nextName.remove(QRegExp("[^a-zA-Z]")); + QString name = QString("%1_%2.wav").arg(x++).arg(nextName); + m_fileName = QDir(m_dirName).filePath(name); + prep_render(); + } else if (! tk->isMuted() && type == track::BBTrack) + { + m_unmutedBB.push_back(tk); + } + + + } + + const trackContainer::trackList t2 = engine::getBBTrackContainer()->tracks(); + for( trackContainer::trackList::const_iterator it = t2.begin(); + it != t2.end(); ++it ) + { + track* tk = (*it); + if (! tk->isMuted()) + { + m_unmuted.push_back(tk); + QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); QString name = QString("%1_%2.wav").arg(x++).arg(nextName); m_fileName = QDir(m_dirName).filePath(name); - std::string strTest = m_fileName.toStdString(); prep_render(); } } - m_to_render_vec = m_unmuted; + + m_tracksToRender = m_unmuted; pop_render( ); } @@ -257,7 +289,7 @@ void exportProjectDialog::startBtnClicked() updateTitleBar( 0 ); - if (m_multi_export==true) + if (m_multiExport==true) { multi_render(); } From 2c3e7483aa0b78229c1dbe1f0f936979b2ade459 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 3 Nov 2012 19:37:28 +0100 Subject: [PATCH 014/133] MainWindow: fixed ambiguous hotkey for export operations The new export operation had the same hotkey as the classical one. This is not a good idea and is now fixed. --- src/gui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c725e9499..1a0602240 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -262,7 +262,7 @@ void MainWindow::finalize( void ) tr( "E&xport tracks..." ), engine::getSong(), SLOT( exportProjectTracks() ), - Qt::CTRL + Qt::Key_E ); + Qt::CTRL + Qt::SHIFT + Qt::Key_E ); project_menu->addSeparator(); project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), From 075b7b354ac8a688a64e4e8cfe8da47fefef8e1a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 3 Nov 2012 19:38:50 +0100 Subject: [PATCH 015/133] ExportProjectDialog: remove obsolete header No need for iostream header in LMMS. --- src/gui/export_project_dialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index 0eb2cc15c..5f5190456 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -33,7 +33,6 @@ #include "bb_track_container.h" #include "bb_track.h" -#include exportProjectDialog::exportProjectDialog( const QString & _file_name, QWidget * _parent, bool multi_export=false ) : From 3a59fdea586177d72f52af81f0dc8860faeaf3a2 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sat, 10 Nov 2012 12:16:32 +0100 Subject: [PATCH 016/133] InstrumentMidiIOView: fix number of digits for MIDI channel spinboxes MIDI channels are in range 1 to 16, so there's no need for 3 digits here. Signed-off-by: Tobias Doerffel --- src/gui/widgets/InstrumentMidiIOView.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/widgets/InstrumentMidiIOView.cpp b/src/gui/widgets/InstrumentMidiIOView.cpp index 7db26499b..8969db162 100644 --- a/src/gui/widgets/InstrumentMidiIOView.cpp +++ b/src/gui/widgets/InstrumentMidiIOView.cpp @@ -47,8 +47,8 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget * _parent ) : m_midiInputGroupBox = new groupBox( tr( "ENABLE MIDI INPUT" ), this ); m_midiInputGroupBox->setGeometry( 4, 5, 242, 80 ); - m_inputChannelSpinBox = new lcdSpinBox( 3, m_midiInputGroupBox ); - m_inputChannelSpinBox->addTextForValue( 0, "---" ); + m_inputChannelSpinBox = new lcdSpinBox( 2, m_midiInputGroupBox ); + m_inputChannelSpinBox->addTextForValue( 0, "--" ); m_inputChannelSpinBox->setLabel( tr( "CHANNEL" ) ); m_inputChannelSpinBox->move( 16, 32 ); m_inputChannelSpinBox->setEnabled( false ); @@ -69,7 +69,7 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget * _parent ) : m_midiOutputGroupBox = new groupBox( tr( "ENABLE MIDI OUTPUT" ), this ); m_midiOutputGroupBox->setGeometry( 4, 90, 242, 80 ); - m_outputChannelSpinBox = new lcdSpinBox( 3, m_midiOutputGroupBox ); + m_outputChannelSpinBox = new lcdSpinBox( 2, m_midiOutputGroupBox ); m_outputChannelSpinBox->setLabel( tr( "CHANNEL" ) ); m_outputChannelSpinBox->move( 16, 32 ); m_outputChannelSpinBox->setEnabled( false ); From 6f7572b949b26dfa5afe91c422b77b21f3c82baa Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 10 Nov 2012 12:28:36 +0100 Subject: [PATCH 017/133] PianoView: do not include Xlib.h header file anymore This file is a relict from times when we evaluated X11 events for the virtual keyboard functionality. --- src/gui/PianoView.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gui/PianoView.cpp b/src/gui/PianoView.cpp index d5a96f850..89e0174b3 100644 --- a/src/gui/PianoView.cpp +++ b/src/gui/PianoView.cpp @@ -57,11 +57,6 @@ #include "templates.h" #include "update_event.h" -#ifdef Q_WS_X11 - -#include - -#endif /*! The black / white order of keys as they appear on the keyboard. */ From 3aa03da5af6090ac58769c97d9ddee3340dec5ca Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Sat, 10 Nov 2012 12:49:37 +0100 Subject: [PATCH 018/133] InstrumentMidiIOView: added support for fixed output notes It came to me that having LMMS output one fixed note from a track could be useful for controlling drum machines or something like that, so here's a new spinbox for the MIDI tab. Signed-off-by: Tobias Doerffel --- include/InstrumentMidiIOView.h | 1 + include/MidiPort.h | 3 +++ include/midi.h | 1 + src/core/midi/MidiPort.cpp | 13 ++++++++++++- src/gui/widgets/InstrumentMidiIOView.cpp | 16 +++++++++++++--- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/include/InstrumentMidiIOView.h b/include/InstrumentMidiIOView.h index 4747cd13d..047613062 100644 --- a/include/InstrumentMidiIOView.h +++ b/include/InstrumentMidiIOView.h @@ -55,6 +55,7 @@ private: lcdSpinBox * m_outputChannelSpinBox; lcdSpinBox * m_fixedOutputVelocitySpinBox; lcdSpinBox * m_outputProgramSpinBox; + lcdSpinBox * m_fixedOutputNoteSpinBox; QToolButton * m_wpBtn; } ; diff --git a/include/MidiPort.h b/include/MidiPort.h index 50e8db3c6..a5b1aee0e 100644 --- a/include/MidiPort.h +++ b/include/MidiPort.h @@ -56,6 +56,8 @@ class MidiPort : public Model, public SerializingObject m_fixedInputVelocityModel); mapPropertyFromModel(int,fixedOutputVelocity,setFixedOutputVelocity, m_fixedOutputVelocityModel); + mapPropertyFromModel(int,fixedOutputNote,setFixedOutputNote, + m_fixedOutputNoteModel); mapPropertyFromModel(int,outputProgram,setOutputProgram, m_outputProgramModel); mapPropertyFromModel(bool,isReadable,setReadable,m_readableModel); @@ -155,6 +157,7 @@ private: IntModel m_outputControllerModel; IntModel m_fixedInputVelocityModel; IntModel m_fixedOutputVelocityModel; + IntModel m_fixedOutputNoteModel; IntModel m_outputProgramModel; BoolModel m_readableModel; BoolModel m_writableModel; diff --git a/include/midi.h b/include/midi.h index c7f048838..5d8b91da6 100644 --- a/include/midi.h +++ b/include/midi.h @@ -85,6 +85,7 @@ const int MidiChannelCount = 16; const int MidiControllerCount = 128; const int MidiProgramCount = 128; const int MidiMaxVelocity = 127; +const int MidiMaxNote = 127; const int MidiMaxPanning = 127; const int MidiMinPanning = -128; diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index 4cbc78ef9..49c61e674 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -52,6 +52,8 @@ MidiPort::MidiPort( const QString & _name, MidiClient * _mc, tr( "Fixed input velocity" ) ), m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), + m_fixedOutputNoteModel( -1, -1, MidiMaxNote, this, + tr( "Fixed output note" ) ), m_outputProgramModel( 1, 1, MidiProgramCount, this, tr( "Output MIDI program" ) ), m_readableModel( false, this, tr( "Receive MIDI-events" ) ), @@ -156,9 +158,16 @@ void MidiPort::processOutEvent( const midiEvent & _me, const midiTime & _time ) midiEvent ev = _me; // we use/display MIDI channels 1...16 but we need 0...15 for // the outside world - if( ev.m_channel > 0 ) + // We are already in "real" MIDI channel space here + /* if( ev.m_channel > 0 ) { --ev.m_channel; + } */ + if( ( _me.m_type == MidiNoteOn || _me.m_type == MidiNoteOff ) && + fixedOutputNote() >=0 ) { + // Convert MIDI note number (from spinbox) -> LMMS note number + // that will be converted back when outputted. + ev.key() = fixedOutputNote() - KeysPerOctave; } if( fixedOutputVelocity() >= 0 && _me.velocity() > 0 && ( _me.m_type == MidiNoteOn || @@ -183,6 +192,8 @@ void MidiPort::saveSettings( QDomDocument & _doc, QDomElement & _this ) "fixedinputvelocity" ); m_fixedOutputVelocityModel.saveSettings( _doc, _this, "fixedoutputvelocity" ); + m_fixedOutputNoteModel.saveSettings( _doc, _this, + "fixedoutputnote" ); m_outputProgramModel.saveSettings( _doc, _this, "outputprogram" ); m_readableModel.saveSettings( _doc, _this, "readable" ); m_writableModel.saveSettings( _doc, _this, "writable" ); diff --git a/src/gui/widgets/InstrumentMidiIOView.cpp b/src/gui/widgets/InstrumentMidiIOView.cpp index 8969db162..bfa08e95c 100644 --- a/src/gui/widgets/InstrumentMidiIOView.cpp +++ b/src/gui/widgets/InstrumentMidiIOView.cpp @@ -85,26 +85,34 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget * _parent ) : m_outputProgramSpinBox->move( 112, 32 ); m_outputProgramSpinBox->setEnabled( false ); + m_fixedOutputNoteSpinBox = new lcdSpinBox( 3, m_midiOutputGroupBox ); + m_fixedOutputNoteSpinBox->addTextForValue( -1, "---" ); + m_fixedOutputNoteSpinBox->setLabel( tr( "NOTE" ) ); + m_fixedOutputNoteSpinBox->move( 160, 32 ); + m_fixedOutputNoteSpinBox->setEnabled( false ); + + connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_outputChannelSpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_fixedOutputVelocitySpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_outputProgramSpinBox, SLOT( setEnabled( bool ) ) ); - + connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), + m_fixedOutputNoteSpinBox, SLOT( setEnabled( bool ) ) ); if( !engine::getMixer()->midiClient()->isRaw() ) { m_rpBtn = new QToolButton( m_midiInputGroupBox ); m_rpBtn->setText( tr( "MIDI devices to receive MIDI events from" ) ); m_rpBtn->setIcon( embed::getIconPixmap( "piano" ) ); - m_rpBtn->setGeometry( 186, 24, 32, 32 ); + m_rpBtn->setGeometry( 208, 24, 32, 32 ); m_rpBtn->setPopupMode( QToolButton::InstantPopup ); m_wpBtn = new QToolButton( m_midiOutputGroupBox ); m_wpBtn->setText( tr( "MIDI devices to send MIDI events to" ) ); m_wpBtn->setIcon( embed::getIconPixmap( "piano" ) ); - m_wpBtn->setGeometry( 186, 24, 32, 32 ); + m_wpBtn->setGeometry( 208, 24, 32, 32 ); m_wpBtn->setPopupMode( QToolButton::InstantPopup ); } } @@ -131,6 +139,8 @@ void InstrumentMidiIOView::modelChanged() m_outputChannelSpinBox->setModel( &mp->m_outputChannelModel ); m_fixedOutputVelocitySpinBox->setModel( &mp->m_fixedOutputVelocityModel ); + m_fixedOutputNoteSpinBox->setModel( + &mp->m_fixedOutputNoteModel ); m_outputProgramSpinBox->setModel( &mp->m_outputProgramModel ); if( m_rpBtn ) From b998ef6e131febb15008ef57daea793213cccd27 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 19:38:11 +0100 Subject: [PATCH 019/133] Midi: added more controller related constants --- include/midi.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/midi.h b/include/midi.h index 5d8b91da6..c19eeb919 100644 --- a/include/midi.h +++ b/include/midi.h @@ -81,10 +81,30 @@ enum MidiMetaEvents } ; +enum MidiStandardControllers +{ + MidiControllerBankSelect = 0, + MidiControllerModulationWheel = 1, + MidiControllerBreathController = 2, + MidiControllerFootController = 4, + MidiControllerPortamentoTime = 5, + MidiControllerMainVolume = 7, + MidiControllerBalance = 8, + MidiControllerPan = 10, + MidiControllerEffectControl1 = 12, + MidiControllerEffectControl2 = 13, + MidiControllerSustain = 64, + MidiControllerPortamento = 65, + MidiControllerSostenuto = 66, + MidiControllerSoftPedal = 67, + MidiControllerLegatoFootswitch = 68, +}; + const int MidiChannelCount = 16; const int MidiControllerCount = 128; const int MidiProgramCount = 128; const int MidiMaxVelocity = 127; +const int MidiMaxControllerValue = 127; const int MidiMaxNote = 127; const int MidiMaxPanning = 127; From aae89e186c77c98a52d128673b0ed086794f9825 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 19:38:32 +0100 Subject: [PATCH 020/133] MidiEvent: added controllerNumber() and controllerValue() --- include/midi.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/include/midi.h b/include/midi.h index c19eeb919..61a9e8ebf 100644 --- a/include/midi.h +++ b/include/midi.h @@ -1,7 +1,7 @@ /* * midi.h - constants, structs etc. concerning MIDI * - * Copyright (c) 2005-2010 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -169,6 +169,16 @@ struct midiEvent return m_data.m_param[0]; } + inline uint8_t controllerNumber() const + { + return m_data.m_param[0]; + } + + inline uint8_t controllerValue() const + { + return m_data.m_param[1]; + } + inline Sint16 velocity() const { return m_data.m_param[1]; From c39512403b2e51b008ebd6f238495f5a0f5d1f69 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 19:39:27 +0100 Subject: [PATCH 021/133] InstrumentTrack, NotePlayHandled: added initial sustain pedal support --- include/InstrumentTrack.h | 8 +++++++- src/core/note_play_handle.cpp | 7 ++++++- src/tracks/InstrumentTrack.cpp | 13 +++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 57e8ffb60..1dc4383c6 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -2,7 +2,7 @@ * InstrumentTrack.h - declaration of class InstrumentTrack, a track + window * which holds an instrument-plugin * - * Copyright (c) 2004-2010 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -80,6 +80,11 @@ public: // silence all running notes played by this track void silenceAllNotes(); + bool isSustainPedalPressed() const + { + return m_sustainPedalPressed; + } + f_cnt_t beatLen( notePlayHandle * _n ) const; @@ -215,6 +220,7 @@ private: notePlayHandle * m_notes[NumKeys]; int m_runningMidiNotes[NumKeys]; + bool m_sustainPedalPressed; IntModel m_baseNoteModel; diff --git a/src/core/note_play_handle.cpp b/src/core/note_play_handle.cpp index a94a9eca4..90bf54d01 100644 --- a/src/core/note_play_handle.cpp +++ b/src/core/note_play_handle.cpp @@ -190,6 +190,7 @@ void notePlayHandle::play( sampleFrame * _working_buffer ) } if( m_released == false && + instrumentTrack()->isSustainPedalPressed() == false && m_totalFramesPlayed + engine::getMixer()->framesPerPeriod() >= m_frames ) { @@ -293,7 +294,11 @@ void notePlayHandle::play( sampleFrame * _working_buffer ) f_cnt_t notePlayHandle::framesLeft() const { - if( m_released && actualReleaseFramesToDo() == 0 ) + if( instrumentTrack()->isSustainPedalPressed() ) + { + return 4*engine::getMixer()->framesPerPeriod(); + } + else if( m_released && actualReleaseFramesToDo() == 0 ) { return m_framesBeforeRelease; } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 73adbbde8..267af765b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -98,6 +98,7 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) : m_midiPort( tr( "unnamed_track" ), engine::getMixer()->midiClient(), this, this ), m_notes(), + m_sustainPedalPressed( false ), m_baseNoteModel( 0, 0, KeysPerOctave * NumOctaves - 1, this, tr( "Base note" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, @@ -303,6 +304,18 @@ void InstrumentTrack::processInEvent( const midiEvent & _me, break; case MidiControlChange: + if( _me.controllerNumber() == MidiControllerSustain ) + { + if( _me.controllerValue() > MidiMaxControllerValue/2 ) + { + m_sustainPedalPressed = true; + } + else + { + m_sustainPedalPressed = false; + } + } + case MidiProgramChange: m_instrument->handleMidiEvent( _me, _time ); break; From 37651dad20b7d6fd79a170073baba0d7f594a85d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 19:43:34 +0100 Subject: [PATCH 022/133] AudioPort: made destructor virtual It is recommended to make destructors virtual if the class is polymorphic. --- include/AudioPort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/AudioPort.h b/include/AudioPort.h index 5cfcbef53..82e61fb1e 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -37,7 +37,7 @@ class AudioPort { public: AudioPort( const QString & _name, bool _has_effect_chain = true ); - ~AudioPort(); + virtual ~AudioPort(); inline sampleFrame * firstBuffer() { From c9d5c1dd06cef0fca39c7c7b2de3752d100c64ab Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 22:11:49 +0100 Subject: [PATCH 023/133] Track: coding style fixes --- src/core/track.cpp | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/core/track.cpp b/src/core/track.cpp index b9b925f34..06692d20a 100644 --- a/src/core/track.cpp +++ b/src/core/track.cpp @@ -2,7 +2,7 @@ * track.cpp - implementation of classes concerning tracks -> necessary for * all track-like objects (beat/bassline, sample-track...) * - * Copyright (c) 2004-2010 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -31,7 +31,7 @@ * \mainpage Track classes * * \section introduction Introduction - * + * * \todo fill this out */ @@ -637,7 +637,7 @@ void trackContentObjectView::mousePressEvent( QMouseEvent * _me ) * * If in move mode, move ourselves in the track, * * or if in move-selection mode, move the entire selection, * * or if in resize mode, resize ourselves, - * * otherwise ??? + * * otherwise ??? * * \param _me The QMouseEvent to handle. * \todo what does the final else case do here? @@ -1192,7 +1192,7 @@ void trackContentWidget::paintEvent( QPaintEvent * _pe ) // Don't draw background on BB-Editor if( m_trackView->getTrackContainerView() != engine::getBBEditor() ) { - p.drawTiledPixmap( rect(), m_background, QPoint( + p.drawTiledPixmap( rect(), m_background, QPoint( tcv->currentPosition().getTact() * ppt, 0 ) ); } } @@ -1673,9 +1673,9 @@ void track::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( "type", type() ); _this.setAttribute( "name", name() ); _this.setAttribute( "muted", isMuted() ); - if( m_height >= MINIMAL_TRACK_HEIGHT ) + if( m_height >= MINIMAL_TRACK_HEIGHT ) { - _this.setAttribute( "height", m_height ); + _this.setAttribute( "height", m_height ); } QDomElement ts_de = _doc.createElement( nodeName() ); @@ -1768,11 +1768,11 @@ void track::loadSettings( const QDomElement & _this ) } } node = node.nextSibling(); - } - + } + if( _this.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT ) { - m_height = _this.attribute( "height" ).toInt(); + m_height = _this.attribute( "height" ).toInt(); } } @@ -2332,13 +2332,13 @@ void trackView::dropEvent( QDropEvent * _de ) */ void trackView::mousePressEvent( QMouseEvent * _me ) { - // If previously dragged too small, restore on shift-leftclick - if( height() < DEFAULT_TRACK_HEIGHT && - _me->modifiers() & Qt::ShiftModifier && - _me->button() == Qt::LeftButton ) + // If previously dragged too small, restore on shift-leftclick + if( height() < DEFAULT_TRACK_HEIGHT && + _me->modifiers() & Qt::ShiftModifier && + _me->button() == Qt::LeftButton ) { - setFixedHeight( DEFAULT_TRACK_HEIGHT ); - m_track->setHeight( DEFAULT_TRACK_HEIGHT ); + setFixedHeight( DEFAULT_TRACK_HEIGHT ); + m_track->setHeight( DEFAULT_TRACK_HEIGHT ); } @@ -2429,8 +2429,10 @@ void trackView::mouseMoveEvent( QMouseEvent * _me ) m_trackContainerView->realignTracks(); m_track->setHeight( height() ); } - if( height() < DEFAULT_TRACK_HEIGHT ) { - toolTip::add( this, m_track->m_name ); + + if( height() < DEFAULT_TRACK_HEIGHT ) + { + toolTip::add( this, m_track->m_name ); } } From d3d6d658368ed547087f308e9a9c16c3b3b72c2c Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 22:17:11 +0100 Subject: [PATCH 024/133] Track: do not load height information if greater than default height This is a workaround for issue #3585927. Once we found the source of this issue, we can revert this commit. --- src/core/track.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/track.cpp b/src/core/track.cpp index 06692d20a..8f47dee16 100644 --- a/src/core/track.cpp +++ b/src/core/track.cpp @@ -1770,7 +1770,8 @@ void track::loadSettings( const QDomElement & _this ) node = node.nextSibling(); } - if( _this.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT ) + if( _this.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT && + _this.attribute( "height" ).toInt() <= DEFAULT_TRACK_HEIGHT ) // workaround for #3585927, tobydox/2012-11-11 { m_height = _this.attribute( "height" ).toInt(); } From 3a1c117a378184de6e3b945640febe2f9d3cbb5f Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 22:42:04 +0100 Subject: [PATCH 025/133] CMakeLists: make fftw3 a requirement and not optional anymore We have too many components in LMMS relying on FFTW3. Building LMMS without them cripples LMMS' functionality substantially, so simply make fftw3 a requirement. Closes #3495736. --- CMakeLists.txt | 14 +------- include/fft_helpers.h | 6 +--- lmmsconfig.h.in | 1 - plugins/ladspa_effect/swh/CMakeLists.txt | 42 +++++++++++------------- plugins/spectrum_analyzer/CMakeLists.txt | 13 +++----- plugins/zynaddsubfx/CMakeLists.txt | 4 --- src/core/fft_helpers.cpp | 6 +--- 7 files changed, 27 insertions(+), 59 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 07e18716b..1b5ff44b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,6 @@ OPTION(WANT_ALSA "Include ALSA (Advanced Linux Sound Architecture) support" ON) OPTION(WANT_CALF "Include CALF LADSPA plugins" ON) OPTION(WANT_CAPS "Include C* Audio Plugin Suite (LADSPA plugins)" ON) OPTION(WANT_CMT "Include Computer Music Toolkit LADSPA plugins" ON) -OPTION(WANT_FFTW3F "Include SpectrumAnalyzer and ZynAddSubFX plugin" ON) OPTION(WANT_JACK "Include JACK (Jack Audio Connection Kit) support" ON) OPTION(WANT_OGGVORBIS "Include OGG/Vorbis support" ON) OPTION(WANT_PULSEAUDIO "Include PulseAudio support" ON) @@ -261,16 +260,7 @@ ENDIF(WANT_JACK) # check for FFTW3F-library -IF(WANT_FFTW3F) - PKG_CHECK_MODULES(FFTW3F fftw3f>=3.0.0) - IF(FFTW3F_FOUND) - SET(LMMS_HAVE_FFTW3F TRUE) - SET(STATUS_FFTW3F "OK") - ELSE(FFTW3F_FOUND) - SET(STATUS_FFTW3F "not found, libfftw3-dev (or similiar) " - "is highly recommended") - ENDIF(FFTW3F_FOUND) -ENDIF(WANT_FFTW3F) +PKG_CHECK_MODULES(FFTW3F REQUIRED fftw3f>=3.0.0) # check for Fluidsynth @@ -615,13 +605,11 @@ MESSAGE( "* Stk Mallets : ${STATUS_STK}\n" "* VST-instrument hoster : ${STATUS_VST}\n" "* VST-effect hoster : ${STATUS_VST}\n" -"* SpectrumAnalyzer : ${STATUS_FFTW3F}\n" "* CALF LADSPA plugins : ${STATUS_CALF}\n" "* CAPS LADSPA plugins : ${STATUS_CAPS}\n" "* CMT LADSPA plugins : ${STATUS_CMT}\n" "* TAP LADSPA plugins : ${STATUS_TAP}\n" "* SWH LADSPA plugins : ${STATUS_SWH}\n" -"* ZynAddSubFX : ${STATUS_FFTW3F}\n" ) MESSAGE( diff --git a/include/fft_helpers.h b/include/fft_helpers.h index c6c941c43..44fe7b738 100644 --- a/include/fft_helpers.h +++ b/include/fft_helpers.h @@ -1,7 +1,7 @@ /* * fft_helpers.h - some functions around FFT analysis * - * Copyright (c) 2008 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -29,8 +29,6 @@ #include "lmmsconfig.h" #include "export.h" -#ifdef LMMS_HAVE_FFTW3F - #include const int FFT_BUFFER_SIZE = 2048; @@ -85,5 +83,3 @@ int EXPORT calc13octaveband31( float * _absspec_buffer, float * _subbands, float EXPORT signalpower(float *timesignal, int num_values); #endif - -#endif diff --git a/lmmsconfig.h.in b/lmmsconfig.h.in index 4965d2433..d645c5025 100644 --- a/lmmsconfig.h.in +++ b/lmmsconfig.h.in @@ -7,7 +7,6 @@ #cmakedefine LMMS_HOST_X86_64 #cmakedefine LMMS_HAVE_ALSA -#cmakedefine LMMS_HAVE_FFTW3F #cmakedefine LMMS_HAVE_FLUIDSYNTH #cmakedefine LMMS_HAVE_JACK #cmakedefine LMMS_HAVE_OGGVORBIS diff --git a/plugins/ladspa_effect/swh/CMakeLists.txt b/plugins/ladspa_effect/swh/CMakeLists.txt index 675d12caa..85621c5dc 100644 --- a/plugins/ladspa_effect/swh/CMakeLists.txt +++ b/plugins/ladspa_effect/swh/CMakeLists.txt @@ -1,35 +1,31 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) ADD_DEFINITIONS(-DFFTW3) -IF(LMMS_HAVE_FFTW3F) -INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS} ${CMAKE_BINARY_DIR}) LINK_DIRECTORIES(${FFTW3F_LIBRARY_DIRS}) LINK_LIBRARIES(-lfftw3f) -ENDIF(LMMS_HAVE_FFTW3F) -INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}) FILE(GLOB PLUGIN_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.c) FOREACH(_item ${PLUGIN_SOURCES}) GET_FILENAME_COMPONENT(_plugin "${_item}" NAME_WE) - IF(NOT "${_plugin}" STREQUAL "mbeq_1197" OR LMMS_HAVE_FFTW3F) - ADD_LIBRARY(${_plugin} MODULE ${_item}) - INSTALL(TARGETS ${_plugin} LIBRARY DESTINATION ${PLUGIN_DIR}/ladspa) - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES PREFIX "") - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES COMPILE_FLAGS "-O3 -Wall -fomit-frame-pointer -fstrength-reduce -funroll-loops -ffast-math -c -fno-strict-aliasing") - IF(LMMS_BUILD_WIN32) - ADD_CUSTOM_COMMAND(TARGET ${_plugin} POST_BUILD COMMAND ${STRIP} ${CMAKE_CURRENT_BINARY_DIR}/${_plugin}.dll) - ELSE(LMMS_BUILD_WIN32) - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -fPIC -DPIC") - ENDIF(LMMS_BUILD_WIN32) - IF(LMMS_BUILD_APPLE) - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Bsymbolic -lm") - ELSE(LMMS_BUILD_APPLE) - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined -Wl,-Bsymbolic -lm") - ENDIF(LMMS_BUILD_APPLE) - IF(LMMS_BUILD_LINUX) - SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -nostartfiles") - ENDIF(LMMS_BUILD_LINUX) - ENDIF(NOT "${_plugin}" STREQUAL "mbeq_1197" OR LMMS_HAVE_FFTW3F) + + ADD_LIBRARY(${_plugin} MODULE ${_item}) + INSTALL(TARGETS ${_plugin} LIBRARY DESTINATION ${PLUGIN_DIR}/ladspa) + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES PREFIX "") + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES COMPILE_FLAGS "-O3 -Wall -fomit-frame-pointer -fstrength-reduce -funroll-loops -ffast-math -c -fno-strict-aliasing") + IF(LMMS_BUILD_WIN32) + ADD_CUSTOM_COMMAND(TARGET ${_plugin} POST_BUILD COMMAND ${STRIP} ${CMAKE_CURRENT_BINARY_DIR}/${_plugin}.dll) + ELSE(LMMS_BUILD_WIN32) + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -fPIC -DPIC") + ENDIF(LMMS_BUILD_WIN32) + IF(LMMS_BUILD_APPLE) + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Bsymbolic -lm") + ELSE(LMMS_BUILD_APPLE) + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined -Wl,-Bsymbolic -lm") + ENDIF(LMMS_BUILD_APPLE) + IF(LMMS_BUILD_LINUX) + SET_TARGET_PROPERTIES(${_plugin} PROPERTIES LINK_FLAGS "${LINK_FLAGS} -nostartfiles") + ENDIF(LMMS_BUILD_LINUX) ENDFOREACH(_item ${PLUGIN_SOURCES}) diff --git a/plugins/spectrum_analyzer/CMakeLists.txt b/plugins/spectrum_analyzer/CMakeLists.txt index 9b01558da..0863c481e 100644 --- a/plugins/spectrum_analyzer/CMakeLists.txt +++ b/plugins/spectrum_analyzer/CMakeLists.txt @@ -1,8 +1,5 @@ -IF(LMMS_HAVE_FFTW3F) - INCLUDE(BuildPlugin) - INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) - LINK_DIRECTORIES(${FFTW3F_LIBRARY_DIRS}) - LINK_LIBRARIES(${FFTW3F_LIBRARIES}) - BUILD_PLUGIN(spectrumanalyzer spectrum_analyzer.cpp spectrumanalyzer_controls.cpp spectrumanalyzer_control_dialog.cpp spectrum_analyzer.h spectrumanalyzer_controls.h spectrumanalyzer_control_dialog.h MOCFILES spectrumanalyzer_controls.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png) -ENDIF(LMMS_HAVE_FFTW3F) - +INCLUDE(BuildPlugin) +INCLUDE_DIRECTORIES(${FFTW3F_INCLUDE_DIRS}) +LINK_DIRECTORIES(${FFTW3F_LIBRARY_DIRS}) +LINK_LIBRARIES(${FFTW3F_LIBRARIES}) +BUILD_PLUGIN(spectrumanalyzer spectrum_analyzer.cpp spectrumanalyzer_controls.cpp spectrumanalyzer_control_dialog.cpp spectrum_analyzer.h spectrumanalyzer_controls.h spectrumanalyzer_control_dialog.h MOCFILES spectrumanalyzer_controls.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png) diff --git a/plugins/zynaddsubfx/CMakeLists.txt b/plugins/zynaddsubfx/CMakeLists.txt index 9eee0f82c..2f268856d 100644 --- a/plugins/zynaddsubfx/CMakeLists.txt +++ b/plugins/zynaddsubfx/CMakeLists.txt @@ -1,5 +1,3 @@ -IF(LMMS_HAVE_FFTW3F) - INCLUDE(BuildPlugin) SET(ZYN_SRC_GUI @@ -130,5 +128,3 @@ ENDIF(LMMS_BUILD_WIN64) ADD_CUSTOM_TARGET(libfltk COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/fltk && cd ${CMAKE_CURRENT_BINARY_DIR}/fltk && ${CMAKE_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR}/fltk ${FLTK_EXTRA_FLAGS} -DCMAKE_MODULE_PATH=${CMAKE_SOURCE_DIR}/cmake/modules/ -DFLTK_USE_SYSTEM_ZLIB:BOOL=ON -DFLTK_USE_SYSTEM_JPEG:BOOL=ON -DFLTK_USE_SYSTEM_PNG:BOOL=ON -DOPTION_BUILD_EXAMPLES:BOOL=OFF -DCMAKE_BUILD_TYPE=release && ${CMAKE_BUILD_TOOL}) ADD_DEPENDENCIES(RemoteZynAddSubFx libfltk) -ENDIF(LMMS_HAVE_FFTW3F) - diff --git a/src/core/fft_helpers.cpp b/src/core/fft_helpers.cpp index eb80025e0..ebc95a4f9 100644 --- a/src/core/fft_helpers.cpp +++ b/src/core/fft_helpers.cpp @@ -1,7 +1,7 @@ /* * fft_helpers.cpp - some functions around FFT analysis * - * Copyright (c) 2008 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -25,8 +25,6 @@ #include "fft_helpers.h" -#ifdef LMMS_HAVE_FFTW3F - #include @@ -243,5 +241,3 @@ float signalpower(float *timesignal, int num_values) return power; } -#endif - From 295dd63b0e502b9133cbdde53b8b435ddc28ab40 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 22:55:38 +0100 Subject: [PATCH 026/133] SetupDialog: turn off auto save per default Due to various bug reports, I think we should turn off auto save per default. Users who want this feature can turn it on explicitely (and live with problems when they have big projects). --- include/setup_dialog.h | 2 +- src/gui/MainWindow.cpp | 2 +- src/gui/setup_dialog.cpp | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/setup_dialog.h b/include/setup_dialog.h index 35c1c13f3..e899cd6e5 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -153,7 +153,7 @@ private: bool m_disableChActInd; bool m_manualChPiano; bool m_smoothScroll; - bool m_disableAutoSave; + bool m_enableAutoSave; bool m_oneInstrumentTrackWindow; bool m_compactTrackButtons; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 1a0602240..8a70355fd 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -184,7 +184,7 @@ MainWindow::MainWindow( void ) : m_updateTimer.start( 1000 / 20, this ); // 20 fps - if( !configManager::inst()->value( "ui", "disableautosave" ).toInt() ) + if( configManager::inst()->value( "ui", "enableautosave" ).toInt() ) { // connect auto save connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 6f2691693..26566d19b 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -113,7 +113,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_manualChPiano( configManager::inst()->value( "ui", "manualchannelpiano" ).toInt() ), m_smoothScroll( configManager::inst()->value( "ui", "smoothscroll" ).toInt() ), - m_disableAutoSave( configManager::inst()->value( "ui", "disableautosave" ).toInt() ), + m_enableAutoSave( configManager::inst()->value( "ui", "enableautosave" ).toInt() ), m_oneInstrumentTrackWindow( configManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() ), m_compactTrackButtons( configManager::inst()->value( "ui", @@ -493,9 +493,9 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : ledCheckBox * autoSave = new ledCheckBox( - tr( "Disable auto save feature" ), ui_fx_tw ); + tr( "Enable auto save feature" ), ui_fx_tw ); autoSave->move( 10, 80 ); - autoSave->setChecked( m_disableAutoSave ); + autoSave->setChecked( m_enableAutoSave ); connect( autoSave, SIGNAL( toggled( bool ) ), this, SLOT( toggleAutoSave( bool ) ) ); @@ -768,8 +768,8 @@ void setupDialog::accept() QString::number( m_manualChPiano ) ); configManager::inst()->setValue( "ui", "smoothscroll", QString::number( m_smoothScroll ) ); - configManager::inst()->setValue( "ui", "disableautosave", - QString::number( m_disableAutoSave ) ); + configManager::inst()->setValue( "ui", "enableautosave", + QString::number( m_enableAutoSave ) ); configManager::inst()->setValue( "ui", "oneinstrumenttrackwindow", QString::number( m_oneInstrumentTrackWindow ) ); configManager::inst()->setValue( "ui", "compacttrackbuttons", @@ -939,7 +939,7 @@ void setupDialog::toggleSmoothScroll( bool _enabled ) void setupDialog::toggleAutoSave( bool _enabled ) { - m_disableAutoSave = _enabled; + m_enableAutoSave = _enabled; } From 3842cb3d61c628b1d62e05d64a9bf263ffd10366 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 23:13:01 +0100 Subject: [PATCH 027/133] RemoteVstPlugin: fixed too short arrays for preset names The dimension of preset name arrays was too small. Fixed this by raising it from 30 to 64 characters. --- plugins/vst_base/RemoteVstPlugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 63f441c0b..81e24468b 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -883,7 +883,7 @@ void RemoteVstPlugin::rotateProgram( int _len ) currProgram = _len - 1; } - char presName[30]; + char presName[64]; sprintf( presName, " %d/%d: %s", currProgram, m_plugin->numPrograms, presetName() ); sendMessage( message( IdVstPluginPresetString ).addString( presName ) ); @@ -1106,7 +1106,7 @@ void RemoteVstPlugin::loadChunkFromPresetFile( const std::string & _file ) fclose( stream ); } } - char presName[30]; + char presName[64]; int currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0) + 1; sprintf( presName, " %d/%d: %s", currProgram, m_plugin->numPrograms, presetName() ); From 3be675ac7b2e1208019814fa2764dda6ef20d3b4 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 11 Nov 2012 23:40:48 +0100 Subject: [PATCH 028/133] Vestige: send NoteOff events for all possible MIDI keys In VestigeInstrumentView::noteOffAll() do not only send MIDI NoteOff events for all LMMS keys but for the whole range of MIDI keys. --- plugins/vestige/vestige.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 425cbecdd..8bcbf4803 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -711,7 +711,7 @@ void VestigeInstrumentView::noteOffAll( void ) m_vi->m_pluginMutex.lock(); if( m_vi->m_plugin != NULL ) { - for( int key = 0; key < NumKeys; ++key ) + for( int key = 0; key <= MidiMaxNote; ++key ) { m_vi->m_plugin->processMidiEvent( midiEvent( MidiNoteOff, 0, key, 0 ), 0 ); From 4a962c58d98a1d648f745fc76b10109adfe68dba Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 25 Nov 2012 11:45:54 +0100 Subject: [PATCH 029/133] ExportProjectDialog: coding style fixes --- include/export_project_dialog.h | 10 +-- src/gui/export_project_dialog.cpp | 114 +++++++++++++++++------------- 2 files changed, 69 insertions(+), 55 deletions(-) diff --git a/include/export_project_dialog.h b/include/export_project_dialog.h index ce2dbdaab..eb0ba35ec 100644 --- a/include/export_project_dialog.h +++ b/include/export_project_dialog.h @@ -2,7 +2,7 @@ * export_project_dialog.h - declaration of class exportProjectDialog which is * responsible for exporting project * - * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -51,10 +51,10 @@ private slots: void startBtnClicked( void ); void updateTitleBar( int ); void render(ProjectRenderer* renderer); - void multi_render(); - ProjectRenderer* prep_render(); - void pop_render(); - void accept(); + void multiRender(); + ProjectRenderer* prepRender(); + void popRender(); + void accept(); private: QString m_fileName; diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index 5f5190456..702d47ad0 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -1,7 +1,7 @@ /* * export_project_dialog.cpp - implementation of dialog for exporting project * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -87,7 +87,7 @@ exportProjectDialog::exportProjectDialog( const QString & _file_name, exportProjectDialog::~exportProjectDialog() { - for( RenderVector::const_iterator it = m_renderers.begin(); + for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { delete (*it); @@ -99,27 +99,29 @@ exportProjectDialog::~exportProjectDialog() void exportProjectDialog::reject() { - for( RenderVector::const_iterator it = m_renderers.begin(); - it != m_renderers.end(); ++it ) + for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { (*it)->abortProcessing(); } } + + + void exportProjectDialog::accept() { // If more to render, kick off next render job - if (m_renderers.size() > 0) + if( m_renderers.isEmpty() == false ) { - pop_render( ); + popRender(); } else { // If done, then reset mute states - while(!m_unmuted.empty()) + while( m_unmuted.isEmpty() == false ) { - track* restore_track = m_unmuted.back(); + track* restoreTrack = m_unmuted.back(); m_unmuted.pop_back(); - restore_track->setMuted(false); + restoreTrack->setMuted( false ); } QDialog::accept(); @@ -132,8 +134,7 @@ void exportProjectDialog::accept() void exportProjectDialog::closeEvent( QCloseEvent * _ce ) { - for( RenderVector::const_iterator it = m_renderers.begin(); - it != m_renderers.end(); ++it ) + for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { if( (*it)->isRunning() ) { @@ -143,20 +144,23 @@ void exportProjectDialog::closeEvent( QCloseEvent * _ce ) QDialog::closeEvent( _ce ); } -void exportProjectDialog::pop_render() { + + +void exportProjectDialog::popRender() { track* render_track = m_tracksToRender.back(); m_tracksToRender.pop_back(); // Set must states for song tracks - for (TrackVector::const_iterator it = m_unmuted.begin(); - it != m_unmuted.end(); ++it) { - if ((*it) == render_track) + for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) + { + if( (*it) == render_track ) { - (*it)->setMuted(false); - } else + (*it)->setMuted( false ); + } + else { - (*it)->setMuted(true); + (*it)->setMuted( true ); } } @@ -164,10 +168,10 @@ void exportProjectDialog::pop_render() { // Pop next render job and start ProjectRenderer* r = m_renderers.back(); m_renderers.pop_back(); - render(r); + render( r ); } -void exportProjectDialog::multi_render() +void exportProjectDialog::multiRender() { m_dirName = m_fileName; QString path = QDir(m_fileName).filePath("text.txt"); @@ -176,24 +180,24 @@ void exportProjectDialog::multi_render() const trackContainer::trackList & tl = engine::getSong()->tracks(); - // Check for all unmuted tracks. Remember list. - for( trackContainer::trackList::const_iterator it = tl.begin(); + // Check for all unmuted tracks. Remember list. + for( trackContainer::trackList::ConstIterator it = tl.begin(); it != tl.end(); ++it ) { track* tk = (*it); track::TrackTypes type = tk->type(); // Don't mute automation tracks - if (! tk->isMuted() && ( - type == track::InstrumentTrack || - type == track::SampleTrack )) + if ( tk->isMuted() == false && + ( type == track::InstrumentTrack || type == track::SampleTrack ) ) { m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); QString name = QString("%1_%2.wav").arg(x++).arg(nextName); m_fileName = QDir(m_dirName).filePath(name); - prep_render(); - } else if (! tk->isMuted() && type == track::BBTrack) + prepRender(); + } + else if (! tk->isMuted() && type == track::BBTrack ) { m_unmutedBB.push_back(tk); } @@ -202,62 +206,72 @@ void exportProjectDialog::multi_render() } const trackContainer::trackList t2 = engine::getBBTrackContainer()->tracks(); - for( trackContainer::trackList::const_iterator it = t2.begin(); - it != t2.end(); ++it ) + for( trackContainer::trackList::ConstIterator it = t2.begin(); it != t2.end(); ++it ) { track* tk = (*it); - if (! tk->isMuted()) + if ( tk->isMuted() == false ) { m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); QString name = QString("%1_%2.wav").arg(x++).arg(nextName); m_fileName = QDir(m_dirName).filePath(name); - prep_render(); + prepRender(); } } m_tracksToRender = m_unmuted; - pop_render( ); + popRender(); } -ProjectRenderer* exportProjectDialog::prep_render( - ) { + + +ProjectRenderer* exportProjectDialog::prepRender() +{ mixer::qualitySettings qs = mixer::qualitySettings( static_cast(interpolationCB->currentIndex()), static_cast(oversamplingCB->currentIndex()), sampleExactControllersCB->isChecked(), - aliasFreeOscillatorsCB->isChecked()); + aliasFreeOscillatorsCB->isChecked() ); + ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( - samplerateCB->currentText().section(" ", 0, 0).toUInt(), false, + samplerateCB->currentText().section(" ", 0, 0).toUInt(), + false, bitrateCB->currentText().section(" ", 0, 0).toUInt(), - static_cast(depthCB->currentIndex())); - ProjectRenderer* renderer = new ProjectRenderer(qs, os, m_ft, m_fileName); + static_cast( depthCB->currentIndex() ) ); + + ProjectRenderer* renderer = new ProjectRenderer( qs, os, m_ft, m_fileName ); + m_renderers.push_back(renderer); + return renderer; } -void exportProjectDialog::render(ProjectRenderer* renderer) + + +void exportProjectDialog::render( ProjectRenderer* renderer ) { - if (renderer->isReady()) { - connect(renderer, SIGNAL( progressChanged( int ) ), progressBar, - SLOT( setValue( int ) )); - connect(renderer, SIGNAL( progressChanged( int ) ), this, - SLOT( updateTitleBar( int ) )); - connect(renderer, SIGNAL( finished() ), this, SLOT( accept() )); - connect(renderer, SIGNAL( finished() ), engine::mainWindow(), - SLOT( resetWindowTitle() )); + if( renderer->isReady() ) + { + connect( renderer, SIGNAL( progressChanged( int ) ), progressBar, SLOT( setValue( int ) ) ); + connect( renderer, SIGNAL( progressChanged( int ) ), this, SLOT( updateTitleBar( int ) )) ; + connect( renderer, SIGNAL( finished() ), this, SLOT( accept() ) ); + connect( renderer, SIGNAL( finished() ), engine::mainWindow(), SLOT( resetWindowTitle() ) ); renderer->startProcessing(); - } else { + } + else + { accept(); } } + + void exportProjectDialog::startBtnClicked() { m_ft = ProjectRenderer::NumFileFormats; @@ -290,11 +304,11 @@ void exportProjectDialog::startBtnClicked() if (m_multiExport==true) { - multi_render(); + multiRender(); } else { - render(prep_render()); + render(prepRender()); } } From 9e08849d6380e4b0d5125bc96ff213e759efe3d7 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 25 Nov 2012 11:47:18 +0100 Subject: [PATCH 030/133] ExportProjectDialog: fixed crash when exporting whole project as one track We have to use the new semantics of the ProjectRenderer management when exporting the whole project as one track as well. Otherwise the program crashed as it tried to utilize the ProjectRenderer instance twice. --- src/gui/export_project_dialog.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index 702d47ad0..fd1a02bc5 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -308,7 +308,8 @@ void exportProjectDialog::startBtnClicked() } else { - render(prepRender()); + prepRender(); + popRender(); } } From 59732b05ed9641a85bfeba19531f78d7967dcee8 Mon Sep 17 00:00:00 2001 From: Kristi Date: Mon, 13 Jun 2011 23:14:19 +0200 Subject: [PATCH 031/133] ExportProjectDialog: added option for exporting song as loop (backport) This patch adds the option to remove the extra silence at the end, so that the exported song can be seamlessly looped. This is a backport of commit 8f1657164a2d59d720affa5e811b69f8db969e32. Closes #3588890. Signed-off-by: Tobias Doerffel --- include/song.h | 20 +++++++++++++++++--- src/core/song.cpp | 4 +++- src/gui/dialogs/export_project.ui | 7 +++++++ src/gui/export_project_dialog.cpp | 2 ++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/include/song.h b/include/song.h index 6eafefa90..45c0a6f69 100644 --- a/include/song.h +++ b/include/song.h @@ -1,7 +1,7 @@ /* * song.h - class song - the root of the model-tree * - * Copyright (c) 2004-2011 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -111,6 +111,11 @@ public: return m_exporting; } + inline void setExportLoop( bool exportLoop ) + { + m_exportLoop = exportLoop; + } + inline bool isRecording() const { return m_recording; @@ -120,8 +125,16 @@ public: inline bool isExportDone() const { - return m_exporting == true && - m_playPos[Mode_PlaySong].getTact() >= length() + 1; + if ( m_exportLoop ) + { + return m_exporting == true && + m_playPos[Mode_PlaySong].getTact() >= length(); + } + else + { + return m_exporting == true && + m_playPos[Mode_PlaySong].getTact() >= length() + 1; + } } inline PlayModes playMode() const @@ -283,6 +296,7 @@ private: volatile bool m_recording; volatile bool m_exporting; + volatile bool m_exportLoop; volatile bool m_playing; volatile bool m_paused; diff --git a/src/core/song.cpp b/src/core/song.cpp index d76f0383a..58a1e31a7 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1,7 +1,7 @@ /* * song.cpp - root of the model tree * - * Copyright (c) 2004-2011 Tobias Doerffel + * Copyright (c) 2004-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -80,6 +80,7 @@ song::song() : m_modified( false ), m_recording( false ), m_exporting( false ), + m_exportLoop( false ), m_playing( false ), m_paused( false ), m_loadingProject( false ), @@ -645,6 +646,7 @@ void song::stopExport() { stop(); m_exporting = false; + m_exportLoop = false; } diff --git a/src/gui/dialogs/export_project.ui b/src/gui/dialogs/export_project.ui index b0544ebc8..dae06e684 100644 --- a/src/gui/dialogs/export_project.ui +++ b/src/gui/dialogs/export_project.ui @@ -279,6 +279,13 @@ + + + + Export as loop (remove end silence) + + + diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index fd1a02bc5..1167df8f2 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -243,6 +243,8 @@ ProjectRenderer* exportProjectDialog::prepRender() bitrateCB->currentText().section(" ", 0, 0).toUInt(), static_cast( depthCB->currentIndex() ) ); + engine::getSong()->setExportLoop( exportLoopCB->isChecked() ); + ProjectRenderer* renderer = new ProjectRenderer( qs, os, m_ft, m_fileName ); m_renderers.push_back(renderer); From 1b7ae1f7a9e106e3dc2fc96e90ad7b891c6e4815 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 19:15:29 +0100 Subject: [PATCH 032/133] RemotePlugin, VstPlugin: reorganized and partly rewrote program/preset related functions The code for managing programs/presets of RemotePlugin instances was very confusing, mainly within the VstPlugin and RemoteVstPlugin class. I therefore started to reorganize and rewrite functions. --- include/RemotePlugin.h | 10 +- plugins/vestige/vestige.cpp | 38 ++++--- plugins/vestige/vestige.h | 6 +- plugins/vst_base/RemoteVstPlugin.cpp | 128 ++++++++++++++-------- plugins/vst_base/VstPlugin.cpp | 53 +++++---- plugins/vst_base/VstPlugin.h | 21 ++-- plugins/vst_base/communication.h | 6 +- plugins/vst_effect/VstEffectControls.cpp | 21 ++-- plugins/zynaddsubfx/RemoteZynAddSubFx.cpp | 8 +- plugins/zynaddsubfx/ZynAddSubFx.cpp | 4 +- 10 files changed, 176 insertions(+), 119 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index da253d511..40c8fee4a 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -1,7 +1,7 @@ /* * RemotePlugin.h - base class providing RPC like mechanisms * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -509,12 +509,8 @@ enum RemoteMessageIDs IdSaveSettingsToFile, IdLoadSettingsFromString, IdLoadSettingsFromFile, - IdLoadChunkFromPresetFile, - IdRotateProgram, - IdLoadPrograms, - IdSavePreset, - IdSetParameter, - IdLoadPresetFromFile, + IdSavePresetFile, + IdLoadPresetFile, IdDebugMessage, IdUserBase = 64 } ; diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 8bcbf4803..46630d740 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -1,7 +1,7 @@ /* * vestige.cpp - instrument-plugin for hosting VST-instruments * - * Copyright (c) 2005-2011 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -398,7 +398,7 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, m_rolLPresetButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "stepper-left" ) ); connect( m_rolLPresetButton, SIGNAL( clicked() ), this, - SLOT( rolrPreset() ) ); + SLOT( previousProgram() ) ); toolTip::add( m_rolLPresetButton, tr( "Previous (-)" ) ); m_rolLPresetButton->setShortcut( Qt::Key_Minus ); @@ -432,7 +432,7 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, m_rolRPresetButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "stepper-right" ) ); connect( m_rolRPresetButton, SIGNAL( clicked() ), this, - SLOT( rollPreset() ) ); + SLOT( nextProgram() ) ); toolTip::add( m_rolRPresetButton, tr( "Next (+)" ) ); m_rolRPresetButton->setShortcut( Qt::Key_Plus ); @@ -501,11 +501,12 @@ void VestigeInstrumentView::updateMenu( void ) { // get all presets - - if ( m_vi->m_plugin != NULL ) { - m_vi->m_plugin->loadPrograms( 1 ); + if ( m_vi->m_plugin != NULL ) + { + m_vi->m_plugin->loadProgramNames(); QWidget::update(); - QString str = m_vi->m_plugin->presetsString(); + QString str = m_vi->m_plugin->allProgramNames(); QStringList list1 = str.split("|"); @@ -600,13 +601,13 @@ void VestigeInstrumentView::openPlugin() -void VestigeInstrumentView::openPreset( void ) +void VestigeInstrumentView::openPreset() { if ( m_vi->m_plugin != NULL ) { m_vi->m_plugin->openPreset( ); bool converted; - QString str = m_vi->m_plugin->presetString().section("/", 0, 0); + QString str = m_vi->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; QWidget::update(); @@ -617,10 +618,11 @@ void VestigeInstrumentView::openPreset( void ) -void VestigeInstrumentView::savePreset( void ) +void VestigeInstrumentView::savePreset() { - if ( m_vi->m_plugin != NULL ) { + if ( m_vi->m_plugin != NULL ) + { m_vi->m_plugin->savePreset( ); /* bool converted; QString str = m_vi->m_plugin->presetString().section("/", 0, 0); @@ -634,13 +636,13 @@ void VestigeInstrumentView::savePreset( void ) -void VestigeInstrumentView::rollPreset( void ) +void VestigeInstrumentView::nextProgram() { if ( m_vi->m_plugin != NULL ) { - m_vi->m_plugin->rollPreset( 1 ); + m_vi->m_plugin->rotateProgram( 1 ); bool converted; - QString str = m_vi->m_plugin->presetString().section("/", 0, 0); + QString str = m_vi->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; QWidget::update(); @@ -650,13 +652,13 @@ void VestigeInstrumentView::rollPreset( void ) -void VestigeInstrumentView::rolrPreset( void ) +void VestigeInstrumentView::previousProgram() { if ( m_vi->m_plugin != NULL ) { - m_vi->m_plugin->rollPreset( -1 ); + m_vi->m_plugin->rotateProgram( -1 ); bool converted; - QString str = m_vi->m_plugin->presetString().section("/", 0, 0); + QString str = m_vi->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; QWidget::update(); @@ -673,7 +675,7 @@ void VestigeInstrumentView::selPreset( void ) if (action) if ( m_vi->m_plugin != NULL ) { lastPosInMenu = action->data().toInt(); - m_vi->m_plugin->rollPreset( action->data().toInt() + 2 ); + m_vi->m_plugin->setProgram( action->data().toInt() ); QWidget::update(); } } @@ -790,7 +792,7 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) p.setFont( pointSize<8>( f ) ); p.drawText( 10, 114, tr( "by" ) + " " + m_vi->m_plugin->vendorString() ); - p.drawText( 10, 225, m_vi->m_plugin->presetString() ); + p.drawText( 10, 225, m_vi->m_plugin->currentProgramName() ); } // m_pluginMutex.unlock(); } diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 25786fe0f..632b6dee6 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -1,7 +1,7 @@ /* * vestige.h - instrument VeSTige for hosting VST-plugins * - * Copyright (c) 2005-2011 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -145,8 +145,8 @@ protected slots: void managePlugin( void ); void openPreset( void ); void savePreset( void ); - void rollPreset( void ); - void rolrPreset( void ); + void nextProgram(); + void previousProgram(); void selPreset( void ); void toggleGUI( void ); void noteOffAll( void ); diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 81e24468b..975d86a65 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -163,9 +163,12 @@ public: // determine product-string of plugin const char * pluginProductString(); - // determine name of plugipn preset + // determine name of plugin preset const char * presetName(); + // send name of current program back to host + void sendCurrentProgramName(); + // do a complete parameter-dump and post it void getParameterDump(); @@ -182,13 +185,16 @@ public: void loadChunkFromFile( const std::string & _file, int _len ); // restore settings chunk of plugin from file - void loadChunkFromPresetFile( const std::string & _file ); + void loadPresetFile( const std::string & _file ); - // restore settings chunk of plugin from file - void rotateProgram( int _len ); + // sets given program index + void setProgram( int index ); - // Load names VST of presets/programs - void loadPrograms( int _len ); + // rotate current program by given offset + void rotateProgram( int offset ); + + // Load names of presets/programs + void getProgramNames(); // Save presets/programs void savePreset( const std::string & _file ); @@ -401,29 +407,33 @@ bool RemoteVstPlugin::processMessage( const message & _m ) sendMessage( IdLoadSettingsFromFile ); break; - case IdLoadChunkFromPresetFile: - loadChunkFromPresetFile( _m.getString( 0 ) ); - sendMessage( IdLoadChunkFromPresetFile ); + case IdLoadPresetFile: + loadPresetFile( _m.getString( 0 ) ); + sendMessage( IdLoadPresetFile ); break; - case IdRotateProgram: + case IdVstSetProgram: + setProgram( _m.getInt( 0 ) ); + sendMessage( IdVstSetProgram ); + break; + + case IdVstRotateProgram: rotateProgram( _m.getInt( 0 ) ); - sendMessage( IdRotateProgram ); + sendMessage( IdVstRotateProgram ); break; - case IdLoadPrograms: - loadPrograms( _m.getInt( 0 ) ); - sendMessage( IdLoadPrograms ); + case IdVstProgramNames: + getProgramNames(); break; - case IdSavePreset: + case IdSavePresetFile: savePreset( _m.getString( 0 ) ); - sendMessage( IdSavePreset ); + sendMessage( IdSavePresetFile ); break; - case IdSetParameter: + case IdVstSetParameter: m_plugin->setParameter( m_plugin, _m.getInt( 0 ), _m.getFloat( 1 ) ); - sendMessage( IdSetParameter ); + sendMessage( IdVstSetParameter ); break; @@ -691,7 +701,6 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) #endif m_currentSamplePos += bufferSize(); - } @@ -771,9 +780,10 @@ const char * RemoteVstPlugin::pluginProductString() const char * RemoteVstPlugin::presetName() { static char buf[32]; - buf[0] = 0; - m_plugin->dispatcher(m_plugin, effGetProgramName, 0, 0, buf, 0); + memset( buf, 0, sizeof( buf ) ); + + pluginDispatch( effGetProgramName, 0, 0, buf ); buf[31] = 0; return buf; @@ -781,6 +791,16 @@ const char * RemoteVstPlugin::presetName() +void RemoteVstPlugin::sendCurrentProgramName() +{ + char presName[64]; + sprintf( presName, " %d/%d: %s", pluginDispatch( effGetProgram ) + 1, m_plugin->numPrograms, presetName() ); + + sendMessage( message( IdVstCurrentProgramName ).addString( presName ) ); +} + + + void RemoteVstPlugin::getParameterDump() { char curPresName[30]; @@ -869,30 +889,55 @@ void RemoteVstPlugin::saveChunkToFile( const std::string & _file ) -void RemoteVstPlugin::rotateProgram( int _len ) +void RemoteVstPlugin::setProgram( int program ) { - int currProgram; - if (isInitialized() == false) return; - if (_len <= 1) { - currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0) + _len; - if (currProgram >= m_plugin->numPrograms) currProgram = m_plugin->numPrograms - 1; - if (currProgram < 0) currProgram = 0; - m_plugin->dispatcher(m_plugin, effSetProgram, 0, currProgram++, 0, 0); - } else { - m_plugin->dispatcher(m_plugin, effSetProgram, 0, _len - 2, 0, 0); - currProgram = _len - 1; + if( isInitialized() == false ) + { + return; } - char presName[64]; - sprintf( presName, " %d/%d: %s", currProgram, m_plugin->numPrograms, presetName() ); + if( program < 0 ) + { + program = 0; + } + else if( program >= m_plugin->numPrograms ) + { + program = m_plugin->numPrograms - 1; + } + pluginDispatch( effSetProgram, 0, program ); - sendMessage( message( IdVstPluginPresetString ).addString( presName ) ); + sendCurrentProgramName(); } -void RemoteVstPlugin::loadPrograms( int _len ) +void RemoteVstPlugin::rotateProgram( int offset ) +{ + if( isInitialized() == false ) + { + return; + } + + int newProgram = pluginDispatch( effGetProgram ) + offset; + + if( newProgram < 0 ) + { + newProgram = 0; + } + else if( newProgram >= m_plugin->numPrograms ) + { + newProgram = m_plugin->numPrograms - 1; + } + pluginDispatch( effSetProgram, 0, newProgram ); + + sendCurrentProgramName(); +} + + + + +void RemoteVstPlugin::getProgramNames() { char presName[1024+256*30]; char curProgName[30]; @@ -917,7 +962,7 @@ void RemoteVstPlugin::loadPrograms( int _len ) } } else sprintf( presName, "%s", presetName() ); - sendMessage( message( IdVstPluginPresetsString ).addString( presName ) ); + sendMessage( message( IdVstProgramNames ).addString( presName ) ); } @@ -1034,7 +1079,7 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) -void RemoteVstPlugin::loadChunkFromPresetFile( const std::string & _file ) +void RemoteVstPlugin::loadPresetFile( const std::string & _file ) { void * chunk = NULL; unsigned int * pLen = new unsigned int[ 1 ]; @@ -1048,7 +1093,7 @@ void RemoteVstPlugin::loadChunkFromPresetFile( const std::string & _file ) float * pFloat; if (m_plugin->uniqueID != pBank->fxID) { - sendMessage( message( IdVstPluginPresetString ). + sendMessage( message( IdVstCurrentProgramName ). addString( "Error: Plugin UniqID not match" ) ); fclose( stream ); delete[] (unsigned int*)pLen; @@ -1106,11 +1151,8 @@ void RemoteVstPlugin::loadChunkFromPresetFile( const std::string & _file ) fclose( stream ); } } - char presName[64]; - int currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0) + 1; - sprintf( presName, " %d/%d: %s", currProgram, m_plugin->numPrograms, presetName() ); - sendMessage( message( IdVstPluginPresetString ).addString( presName ) ); + sendCurrentProgramName(); delete[] (unsigned int*)pLen; delete[] (sBank*)pBank; diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 79d618f10..ad9e1cdde 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -1,7 +1,7 @@ /* * VstPlugin.cpp - implementation of VstPlugin class * - * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -88,8 +88,8 @@ VstPlugin::VstPlugin( const QString & _plugin ) : m_version( 0 ), m_vendorString(), m_productString(), - m_presetString(), - m_presetsString(), + m_currentProgramName(), + m_allProgramNames(), p_name() { setSplittedChannels( true ); @@ -406,12 +406,12 @@ bool VstPlugin::processMessage( const message & _m ) m_productString = _m.getQString(); break; - case IdVstPluginPresetString: - m_presetString = _m.getQString(); + case IdVstCurrentProgramName: + m_currentProgramName = _m.getQString(); break; - case IdVstPluginPresetsString: - m_presetsString = _m.getQString(); + case IdVstProgramNames: + m_allProgramNames = _m.getQString(); break; case IdVstPluginUniqueID: @@ -457,12 +457,12 @@ void VstPlugin::openPreset( ) !ofd.selectedFiles().isEmpty() ) { lock(); - sendMessage( message( IdLoadChunkFromPresetFile ). + sendMessage( message( IdLoadPresetFile ). addString( QSTR_TO_STDSTR( QDir::toNativeSeparators( ofd.selectedFiles()[0] ) ) ) ); - waitForMessage( IdLoadChunkFromPresetFile ); + waitForMessage( IdLoadPresetFile ); unlock(); } } @@ -470,24 +470,33 @@ void VstPlugin::openPreset( ) -void VstPlugin::rollPreset( int step ) +void VstPlugin::setProgram( int index ) { lock(); - sendMessage( message( IdRotateProgram ). - addInt( step ) ); - waitForMessage( IdRotateProgram ); + sendMessage( message( IdVstSetProgram ).addInt( index ) ); + waitForMessage( IdVstSetProgram ); unlock(); } -void VstPlugin::loadPrograms( int step ) +void VstPlugin::rotateProgram( int offset ) { lock(); - sendMessage( message( IdLoadPrograms ). - addInt( step ) ); - waitForMessage( IdLoadPrograms ); + sendMessage( message( IdVstRotateProgram ).addInt( offset ) ); + waitForMessage( IdVstRotateProgram ); + unlock(); +} + + + + +void VstPlugin::loadProgramNames() +{ + lock(); + sendMessage( message( IdVstProgramNames ) ); + waitForMessage( IdVstProgramNames ); unlock(); } @@ -496,7 +505,7 @@ void VstPlugin::loadPrograms( int step ) void VstPlugin::savePreset( ) { - QString presName = this->presetString() == "" ? tr(": default"): this->presetString(); + QString presName = currentProgramName().isEmpty() ? tr(": default") : currentProgramName(); presName.replace(tr("\""), tr("'")); // QFileDialog unable to handle double quotes properly QFileDialog sfd( NULL, tr( "Save Preset" ), presName.section(": ", 1, 1) + tr(".fxp"), @@ -519,12 +528,12 @@ void VstPlugin::savePreset( ) fns = fns + tr(".fxb"); else fns = fns.left(fns.length() - 4) + (fns.right( 4 )).toLower(); lock(); - sendMessage( message( IdSavePreset ). + sendMessage( message( IdSavePresetFile ). addString( QSTR_TO_STDSTR( QDir::toNativeSeparators( fns ) ) ) ); - waitForMessage( IdSavePreset ); + waitForMessage( IdSavePresetFile ); unlock(); } } @@ -535,8 +544,8 @@ void VstPlugin::savePreset( ) void VstPlugin::setParam( int i, float f ) { lock(); - sendMessage( message( IdSetParameter ).addInt( i ).addFloat( f ) ); - waitForMessage( IdSetParameter ); + sendMessage( message( IdVstSetParameter ).addInt( i ).addFloat( f ) ); + waitForMessage( IdVstSetParameter ); unlock(); } diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 03155e844..2dcbbf34a 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -1,7 +1,7 @@ /* * VstPlugin.h - declaration of VstPlugin class * - * Copyright (c) 2005-2010 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -75,16 +75,18 @@ public: return m_productString; } - inline const QString & presetString() const + inline const QString& currentProgramName() const { - return m_presetString; + return m_currentProgramName; } - inline const QString & presetsString() const + inline const QString& allProgramNames() const { - return m_presetsString; + return m_allProgramNames; } + int currentProgram(); + const QMap & parameterDump(); void setParameterDump( const QMap & _pdump ); @@ -114,8 +116,9 @@ public slots: void setTempo( bpm_t _bpm ); void updateSampleRate(); void openPreset( void ); - void rollPreset( int step ); - void loadPrograms( int step ); + void setProgram( int index ); + void rotateProgram( int offset ); + void loadProgramNames(); void savePreset( void ); void setParam( int i, float f ); @@ -135,8 +138,8 @@ private: Sint32 m_version; QString m_vendorString; QString m_productString; - QString m_presetString; - QString m_presetsString; + QString m_currentProgramName; + QString m_allProgramNames; QString p_name; diff --git a/plugins/vst_base/communication.h b/plugins/vst_base/communication.h index 542a4d142..bc46a8e65 100644 --- a/plugins/vst_base/communication.h +++ b/plugins/vst_base/communication.h @@ -64,6 +64,10 @@ enum VstRemoteMessageIDs IdVstGetParameterDump, IdVstSetParameterDump, IdVstGetParameterProperties, + IdVstProgramNames, + IdVstCurrentProgramName, + IdVstSetProgram, + IdVstRotateProgram, // remoteVstPlugin -> vstPlugin IdVstFailedLoadingPlugin, @@ -74,9 +78,9 @@ enum VstRemoteMessageIDs IdVstPluginVersion, IdVstPluginVendorString, IdVstPluginProductString, - IdVstPluginPresetString, IdVstPluginPresetsString, IdVstPluginUniqueID, + IdVstSetParameter, IdVstParameterCount, IdVstParameterDump, IdVstParameterProperties diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index b25d9578e..866c8c4ed 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -170,7 +170,7 @@ void VstEffectControls::savePreset( void ) if ( m_effect->m_plugin != NULL ) { m_effect->m_plugin->savePreset( ); /* bool converted; - QString str = m_vi->m_plugin->presetString().section("/", 0, 0); + QString str = m_vi->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; QWidget::update();*/ @@ -185,11 +185,12 @@ void VstEffectControls::updateMenu( void ) { // get all presets - - if ( m_effect->m_plugin != NULL ) { - m_effect->m_plugin->loadPrograms( 1 ); + if ( m_effect->m_plugin != NULL ) + { + m_effect->m_plugin->loadProgramNames(); ///QWidget::update(); - QString str = m_effect->m_plugin->presetsString(); + QString str = m_effect->m_plugin->allProgramNames(); QStringList list1 = str.split("|"); @@ -223,7 +224,7 @@ void VstEffectControls::openPreset( void ) if ( m_effect->m_plugin != NULL ) { m_effect->m_plugin->openPreset( ); bool converted; - QString str = m_effect->m_plugin->presetString().section("/", 0, 0); + QString str = m_effect->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; //QWidget::update(); @@ -238,9 +239,9 @@ void VstEffectControls::rollPreset( void ) { if ( m_effect->m_plugin != NULL ) { - m_effect->m_plugin->rollPreset( 1 ); + m_effect->m_plugin->rotateProgram( 1 ); bool converted; - QString str = m_effect->m_plugin->presetString().section("/", 0, 0); + QString str = m_effect->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; //QWidget::update(); @@ -254,9 +255,9 @@ void VstEffectControls::rolrPreset( void ) { if ( m_effect->m_plugin != NULL ) { - m_effect->m_plugin->rollPreset( -1 ); + m_effect->m_plugin->rotateProgram( -1 ); bool converted; - QString str = m_effect->m_plugin->presetString().section("/", 0, 0); + QString str = m_effect->m_plugin->currentProgramName().section("/", 0, 0); if (str != "") lastPosInMenu = str.toInt(&converted, 10) - 1; //QWidget::update(); @@ -273,7 +274,7 @@ void VstEffectControls::selPreset( void ) if (action) if ( m_effect->m_plugin != NULL ) { lastPosInMenu = action->data().toInt(); - m_effect->m_plugin->rollPreset( lastPosInMenu + 2 ); + m_effect->m_plugin->setProgram( lastPosInMenu ); //QWidget::update(); } } diff --git a/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp b/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp index 91cd5f490..29300bf75 100644 --- a/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/RemoteZynAddSubFx.cpp @@ -1,7 +1,7 @@ /* * RemoteZynAddSubFx.cpp - ZynAddSubFx-embedding plugin * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -98,7 +98,7 @@ public: case IdShowUI: case IdHideUI: case IdLoadSettingsFromFile: - case IdLoadPresetFromFile: + case IdLoadPresetFile: pthread_mutex_lock( &m_guiMutex ); m_guiMessages.push( _m ); pthread_mutex_unlock( &m_guiMutex ); @@ -221,7 +221,7 @@ void RemoteZynAddSubFx::guiThread() break; } - case IdLoadPresetFromFile: + case IdLoadPresetFile: { LocalZynAddSubFx::loadPreset( m.getString(), ui ? ui->npartcounter->value()-1 : 0 ); @@ -232,7 +232,7 @@ void RemoteZynAddSubFx::guiThread() ui->refresh_master_ui(); } pthread_mutex_lock( &m_master->mutex ); - sendMessage( IdLoadPresetFromFile ); + sendMessage( IdLoadPresetFile ); pthread_mutex_unlock( &m_master->mutex ); break; } diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index de3335ce5..78733faed 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -290,8 +290,8 @@ void ZynAddSubFxInstrument::loadFile( const QString & _file ) { m_remotePlugin->lock(); m_remotePlugin->sendMessage( - RemotePlugin::message( IdLoadPresetFromFile ).addString( fn ) ); - m_remotePlugin->waitForMessage( IdLoadPresetFromFile ); + RemotePlugin::message( IdLoadPresetFile ).addString( fn ) ); + m_remotePlugin->waitForMessage( IdLoadPresetFile ); m_remotePlugin->unlock(); } else From 56b07e29c29478fc1727d864233aed7e8a1c714e Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 19:18:20 +0100 Subject: [PATCH 033/133] RemoteVstPlugin: send updates of current program name if changed Send current program name back to host if current program has changed. --- plugins/vst_base/RemoteVstPlugin.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 975d86a65..17405d655 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -291,6 +291,7 @@ private: bpm_t m_bpm; double m_currentSamplePos; + int m_currentProgram; } ; @@ -312,7 +313,8 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : m_outputs( NULL ), m_midiEvents(), m_bpm( 0 ), - m_currentSamplePos( 0 ) + m_currentSamplePos( 0 ), + m_currentProgram( -1 ) { pthread_mutex_init( &m_pluginLock, NULL ); @@ -701,6 +703,13 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) #endif m_currentSamplePos += bufferSize(); + + int newCurrentProgram = pluginDispatch( effGetProgram ); + if( newCurrentProgram != m_currentProgram ) + { + m_currentProgram = newCurrentProgram; + sendCurrentProgramName(); + } } From 0d80f0c569cd72e748c6e19395e603596dfeaea8 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 19:30:26 +0100 Subject: [PATCH 034/133] VstPlugin, RemoteVstPlugin: save/restore current program of VST plugin Not all plugins save current program in their chunk (or do not restore it properly). We therefore have to save and restore the current program manually. Closes #3581879. --- plugins/vst_base/RemoteVstPlugin.cpp | 4 ++++ plugins/vst_base/VstPlugin.cpp | 25 ++++++++++++++++++++++++- plugins/vst_base/VstPlugin.h | 2 ++ plugins/vst_base/communication.h | 1 + 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 17405d655..dcffaf996 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -419,6 +419,10 @@ bool RemoteVstPlugin::processMessage( const message & _m ) sendMessage( IdVstSetProgram ); break; + case IdVstCurrentProgram: + sendMessage( message( IdVstCurrentProgram ).addInt( m_currentProgram ) ); + break; + case IdVstRotateProgram: rotateProgram( _m.getInt( 0 ) ); sendMessage( IdVstRotateProgram ); diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index ad9e1cdde..7c828fcc0 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -90,7 +90,8 @@ VstPlugin::VstPlugin( const QString & _plugin ) : m_productString(), m_currentProgramName(), m_allProgramNames(), - p_name() + p_name(), + m_currentProgram() { setSplittedChannels( true ); @@ -279,6 +280,10 @@ void VstPlugin::loadSettings( const QDomElement & _this ) setParameterDump( dump ); } + if( _this.hasAttribute( "program" ) ) + { + setProgram( _this.attribute( "program" ).toInt() ); + } } @@ -309,6 +314,8 @@ void VstPlugin::saveSettings( QDomDocument & _doc, QDomElement & _this ) _this.setAttribute( it.key(), it.value() ); } } + + _this.setAttribute( "program", currentProgram() ); } @@ -335,6 +342,18 @@ void VstPlugin::updateSampleRate() +int VstPlugin::currentProgram() +{ + lock(); + sendMessage( message( IdVstCurrentProgram ) ); + waitForMessage( IdVstCurrentProgram ); + unlock(); + + return m_currentProgram; +} + + + const QMap & VstPlugin::parameterDump() { lock(); @@ -406,6 +425,10 @@ bool VstPlugin::processMessage( const message & _m ) m_productString = _m.getQString(); break; + case IdVstCurrentProgram: + m_currentProgram = _m.getInt(); + break; + case IdVstCurrentProgramName: m_currentProgramName = _m.getQString(); break; diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 2dcbbf34a..157788f83 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -145,6 +145,8 @@ private: QMap m_parameterDump; + int m_currentProgram; + } ; diff --git a/plugins/vst_base/communication.h b/plugins/vst_base/communication.h index bc46a8e65..cc6133b68 100644 --- a/plugins/vst_base/communication.h +++ b/plugins/vst_base/communication.h @@ -65,6 +65,7 @@ enum VstRemoteMessageIDs IdVstSetParameterDump, IdVstGetParameterProperties, IdVstProgramNames, + IdVstCurrentProgram, IdVstCurrentProgramName, IdVstSetProgram, IdVstRotateProgram, From 533f086600d4bb1c02fd252f0fdc7e124c4be025 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 19:45:27 +0100 Subject: [PATCH 035/133] AutomatableModel: initialize m_value and m_initValue in constructor Even though we call setInitValue() in AutomatableModel constructor, initialize according class members before. --- src/core/AutomatableModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 2207c3a3b..034be3bb5 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -1,7 +1,7 @@ /* * AutomatableModel.cpp - some implementations of AutomatableModel-class * - * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -44,6 +44,8 @@ AutomatableModel::AutomatableModel( DataType _type, bool _default_constructed ) : Model( _parent, _display_name, _default_constructed ), m_dataType( _type ), + m_value( _val ), + m_initValue( _val ), m_minValue( _min ), m_maxValue( _max ), m_step( _step ), From 9d610e5108824b66af590fd611e3240ebb1a7e60 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 20:00:55 +0100 Subject: [PATCH 036/133] RemoteVstPlugin: use pluginDispatch() everywhere for thread safety Calling plugin dispatcher without lock is potentially dangerous. --- plugins/vst_base/RemoteVstPlugin.cpp | 55 ++++++++++++++++------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index dcffaf996..39124ed04 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -823,7 +823,7 @@ void RemoteVstPlugin::getParameterDump() for( int i = 0; i < m_plugin->numParams; ++i ) { //pluginDispatch( effGetParameterProperties, i, 0, &vst_props ); - m_plugin->dispatcher(m_plugin, effGetParamName, i, 0, curPresName, 0); + pluginDispatch( effGetParamName, i, 0, curPresName ); m.addInt( i ); m.addString( /*vst_props.shortLabel*/curPresName ); m.addFloat( m_plugin->getParameter( m_plugin, i ) ); @@ -955,26 +955,32 @@ void RemoteVstPlugin::getProgramNames() char presName[1024+256*30]; char curProgName[30]; if (isInitialized() == false) return; - bool progNameIndexed = (m_plugin->dispatcher(m_plugin, 29, 0, -1, curProgName, 0) == 1); + bool progNameIndexed = ( pluginDispatch( 29, 0, -1, curProgName ) == 1 ); if (m_plugin->numPrograms > 1) { if (progNameIndexed) { - for (int i = 0; i< (m_plugin->numPrograms >= 256?256:m_plugin->numPrograms); i++) { - m_plugin->dispatcher(m_plugin, 29, i, -1, curProgName, 0); + for (int i = 0; i< (m_plugin->numPrograms >= 256?256:m_plugin->numPrograms); i++) + { + pluginDispatch( 29, i, -1, curProgName ); if (i == 0) sprintf( presName, "%s", curProgName ); else sprintf( presName + strlen(presName), "|%s", curProgName ); } - } else { - int currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0); - for (int i = 0; i< (m_plugin->numPrograms >= 256?256:m_plugin->numPrograms); i++) { - m_plugin->dispatcher(m_plugin, effSetProgram, 0, i, 0, 0); + } + else + { + int currProgram = pluginDispatch( effGetProgram ); + for (int i = 0; i< (m_plugin->numPrograms >= 256?256:m_plugin->numPrograms); i++) + { + pluginDispatch( effSetProgram, 0, i ); if (i == 0) sprintf( presName, "%s", presetName() ); else sprintf( presName + strlen(presName), "|%s", presetName() ); } - m_plugin->dispatcher(m_plugin, effSetProgram, 0, currProgram, 0, 0); + pluginDispatch( effSetProgram, 0, currProgram ); } } else sprintf( presName, "%s", presetName() ); + presName[sizeof(presName)-1] = 0; + sendMessage( message( IdVstProgramNames ).addString( presName ) ); } @@ -1008,13 +1014,14 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) bool isPreset = _file.substr( _file.find_last_of( "." ) + 1 ) == "fxp"; int presNameLen = _file.find_last_of( "/" ) + _file.find_last_of( "\\" ) + 2; - if (isPreset) { + if (isPreset) + { for (int i = 0; i < _file.length() - 4 - presNameLen; i++) progName[i] = i < 23 ? _file[presNameLen + i] : 0; - m_plugin->dispatcher(m_plugin, 4, 0, 0, progName, 0); - } // m_plugin->dispatcher( m_plugin, effGetProgramName, 0, 0, progName, 0.0f ); + pluginDispatch( 4, 0, 0, progName ); + } if ( chunky ) - chunk_size = m_plugin->dispatcher( m_plugin, 23, isPreset, 0, &data, false ); + chunk_size = pluginDispatch( 23, isPreset, 0, &data ); else { if (isPreset) { chunk_size = m_plugin->numParams * sizeof( float ); @@ -1057,7 +1064,7 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) fwrite ( data, 1, chunk_size, stream ); else { int numPrograms = m_plugin->numPrograms; - int currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0); + int currProgram = pluginDispatch( effGetProgram ); chunk_size = (m_plugin->numParams * sizeof( float )); pBank->byteSize = chunk_size + 48; pBank->byteSize = endian_swap( pBank->byteSize ); @@ -1068,8 +1075,8 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) unsigned int* pValue,* toUIntArray = reinterpret_cast( data ); float value; for (int j = 0; j < numPrograms; j++) { - m_plugin->dispatcher(m_plugin, effSetProgram, 0, j, 0, 0); - m_plugin->dispatcher(m_plugin, effGetProgramName, 0, 0, pBank->prgName, 0); + pluginDispatch( effSetProgram, 0, j ); + pluginDispatch( effGetProgramName, 0, 0, pBank->prgName ); fwrite ( pBank, 1, 56, stream ); for ( int i = 0; i < m_plugin->numParams; i++ ) { @@ -1079,7 +1086,7 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) } fwrite ( data, 1, chunk_size, stream ); } - m_plugin->dispatcher(m_plugin, effSetProgram, 0, currProgram, 0, 0); + pluginDispatch( effSetProgram, 0, currProgram ); } fclose( stream ); @@ -1128,9 +1135,9 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) if(_file.substr(_file.find_last_of(".") + 1) == "fxp") { pBank->prgName[23] = 0; - m_plugin->dispatcher(m_plugin, 4, 0, 0, pBank->prgName, 0); + pluginDispatch( 4, 0, 0, pBank->prgName ); if(pBank->fxMagic != 0x6B437846) - m_plugin->dispatcher(m_plugin, 24, 1, len, chunk, 0); + pluginDispatch( 24, 1, len, chunk ); else { unsigned int* toUIntArray = reinterpret_cast( chunk ); for (int i = 0; i < pBank->numPrograms; i++ ) { @@ -1141,26 +1148,26 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) } } else { if(pBank->fxMagic != 0x6B427846) { - m_plugin->dispatcher(m_plugin, 24, 0, len, chunk, 0); + pluginDispatch( 24, 0, len, chunk ); } else { int numPrograms = pBank->numPrograms; unsigned int * toUIntArray; - int currProgram = m_plugin->dispatcher(m_plugin, effGetProgram, 0, 0, 0, 0); + int currProgram = pluginDispatch( effGetProgram ); chunk = new char[ len = sizeof(float)*m_plugin->numParams ]; toUIntArray = reinterpret_cast( chunk ); for (int i =0; i < numPrograms; i++) { fread (pBank, 1, 56, stream); fread (chunk, len, 1, stream); - m_plugin->dispatcher(m_plugin, effSetProgram, 0, i, 0, 0); + pluginDispatch( effSetProgram, 0, i ); pBank->prgName[23] = 0; - m_plugin->dispatcher(m_plugin, 4, 0, 0, pBank->prgName, 0); + pluginDispatch( 4, 0, 0, pBank->prgName ); for (int j = 0; j < m_plugin->numParams; j++ ) { toUInt = endian_swap( toUIntArray[ j ] ); pFloat = ( float* ) &toUInt; m_plugin->setParameter( m_plugin, j, *pFloat ); } } - m_plugin->dispatcher(m_plugin, effSetProgram, 0, currProgram, 0, 0); + pluginDispatch( effSetProgram, 0, currProgram ); fclose( stream ); } } From bdaedcdf86400a566cbd4a7020b70781b9cef21b Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 20:16:06 +0100 Subject: [PATCH 037/133] VstPlugin, RemoteVstPlugin: update program name via new idle update messages Introduced new idle update messages for exchanging data periodically but not inferring audio processing. --- plugins/vst_base/RemoteVstPlugin.cpp | 26 ++++++++++++++++++-------- plugins/vst_base/VstPlugin.cpp | 16 +++++++++++++++- plugins/vst_base/VstPlugin.h | 4 ++++ plugins/vst_base/communication.h | 1 + 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 39124ed04..c6203440e 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -438,11 +438,24 @@ bool RemoteVstPlugin::processMessage( const message & _m ) break; case IdVstSetParameter: + lock(); m_plugin->setParameter( m_plugin, _m.getInt( 0 ), _m.getFloat( 1 ) ); + unlock(); sendMessage( IdVstSetParameter ); break; + case IdVstIdleUpdate: + { + int newCurrentProgram = pluginDispatch( effGetProgram ); + if( newCurrentProgram != m_currentProgram ) + { + m_currentProgram = newCurrentProgram; + sendCurrentProgramName(); + } + + break; + } default: return RemotePluginClient::processMessage( _m ); @@ -691,6 +704,8 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) memset( m_outputs[i], 0, bufferSize() * sizeof( float ) ); } + lock(); + #ifdef OLD_VST_SDK if( m_plugin->flags & effFlagsCanReplacing ) { @@ -706,14 +721,9 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) } #endif - m_currentSamplePos += bufferSize(); + unlock(); - int newCurrentProgram = pluginDispatch( effGetProgram ); - if( newCurrentProgram != m_currentProgram ) - { - m_currentProgram = newCurrentProgram; - sendCurrentProgramName(); - } + m_currentSamplePos += bufferSize(); } @@ -807,7 +817,7 @@ const char * RemoteVstPlugin::presetName() void RemoteVstPlugin::sendCurrentProgramName() { char presName[64]; - sprintf( presName, " %d/%d: %s", pluginDispatch( effGetProgram ) + 1, m_plugin->numPrograms, presetName() ); + sprintf( presName, "%d/%d: %s", pluginDispatch( effGetProgram ) + 1, m_plugin->numPrograms, presetName() ); sendMessage( message( IdVstCurrentProgramName ).addString( presName ) ); } diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 7c828fcc0..59d75fb53 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -91,7 +91,8 @@ VstPlugin::VstPlugin( const QString & _plugin ) : m_currentProgramName(), m_allProgramNames(), p_name(), - m_currentProgram() + m_currentProgram(), + m_idleTimer() { setSplittedChannels( true ); @@ -110,6 +111,11 @@ VstPlugin::VstPlugin( const QString & _plugin ) : this, SLOT( setTempo( bpm_t ) ) ); connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); + + // update once per second + m_idleTimer.start( 1000 ); + connect( &m_idleTimer, SIGNAL( timeout() ), + this, SLOT( idleUpdate() ) ); } @@ -574,6 +580,14 @@ void VstPlugin::setParam( int i, float f ) +void VstPlugin::idleUpdate() +{ + lock(); + sendMessage( message( IdVstIdleUpdate ) ); + unlock(); +} + + void VstPlugin::loadChunk( const QByteArray & _chunk ) { diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index 157788f83..c932d50aa 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "mixer.h" @@ -121,6 +122,7 @@ public slots: void loadProgramNames(); void savePreset( void ); void setParam( int i, float f ); + void idleUpdate(); private: @@ -147,6 +149,8 @@ private: int m_currentProgram; + QTimer m_idleTimer; + } ; diff --git a/plugins/vst_base/communication.h b/plugins/vst_base/communication.h index cc6133b68..652af1c83 100644 --- a/plugins/vst_base/communication.h +++ b/plugins/vst_base/communication.h @@ -69,6 +69,7 @@ enum VstRemoteMessageIDs IdVstCurrentProgramName, IdVstSetProgram, IdVstRotateProgram, + IdVstIdleUpdate, // remoteVstPlugin -> vstPlugin IdVstFailedLoadingPlugin, From e43f34914f45da28c0f790e28d7f70678bd92007 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 20:23:41 +0100 Subject: [PATCH 038/133] RemoteVstPlugin: renamed presetName() to programName() --- plugins/vst_base/RemoteVstPlugin.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index c6203440e..2f6959613 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -163,8 +163,8 @@ public: // determine product-string of plugin const char * pluginProductString(); - // determine name of plugin preset - const char * presetName(); + // determine name of current program + const char * programName(); // send name of current program back to host void sendCurrentProgramName(); @@ -800,7 +800,7 @@ const char * RemoteVstPlugin::pluginProductString() -const char * RemoteVstPlugin::presetName() +const char * RemoteVstPlugin::programName() { static char buf[32]; @@ -817,7 +817,7 @@ const char * RemoteVstPlugin::presetName() void RemoteVstPlugin::sendCurrentProgramName() { char presName[64]; - sprintf( presName, "%d/%d: %s", pluginDispatch( effGetProgram ) + 1, m_plugin->numPrograms, presetName() ); + sprintf( presName, "%d/%d: %s", pluginDispatch( effGetProgram ) + 1, m_plugin->numPrograms, programName() ); sendMessage( message( IdVstCurrentProgramName ).addString( presName ) ); } @@ -982,12 +982,12 @@ void RemoteVstPlugin::getProgramNames() for (int i = 0; i< (m_plugin->numPrograms >= 256?256:m_plugin->numPrograms); i++) { pluginDispatch( effSetProgram, 0, i ); - if (i == 0) sprintf( presName, "%s", presetName() ); - else sprintf( presName + strlen(presName), "|%s", presetName() ); + if (i == 0) sprintf( presName, "%s", programName() ); + else sprintf( presName + strlen(presName), "|%s", programName() ); } pluginDispatch( effSetProgram, 0, currProgram ); } - } else sprintf( presName, "%s", presetName() ); + } else sprintf( presName, "%s", programName() ); presName[sizeof(presName)-1] = 0; From 6817631c91c8ea403e13ad02b83e25f3702d8de6 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 26 Nov 2012 20:49:31 +0100 Subject: [PATCH 039/133] RemoteVstPlugin: more locking, range checking etc. --- plugins/vst_base/RemoteVstPlugin.cpp | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 2f6959613..a3efdbaf6 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -802,13 +802,14 @@ const char * RemoteVstPlugin::pluginProductString() const char * RemoteVstPlugin::programName() { - static char buf[32]; + static char buf[24]; memset( buf, 0, sizeof( buf ) ); pluginDispatch( effGetProgramName, 0, 0, buf ); - buf[31] = 0; + buf[24] = 0; + return buf; } @@ -826,17 +827,19 @@ void RemoteVstPlugin::sendCurrentProgramName() void RemoteVstPlugin::getParameterDump() { - char curPresName[30]; //VstParameterProperties vst_props; message m( IdVstParameterDump ); m.addInt( m_plugin->numParams ); for( int i = 0; i < m_plugin->numParams; ++i ) { - //pluginDispatch( effGetParameterProperties, i, 0, &vst_props ); - pluginDispatch( effGetParamName, i, 0, curPresName ); + char paramName[32]; + pluginDispatch( effGetParamName, i, 0, paramName ); + paramName[31] = 0; m.addInt( i ); - m.addString( /*vst_props.shortLabel*/curPresName ); + m.addString( paramName ); + lock(); m.addFloat( m_plugin->getParameter( m_plugin, i ) ); + unlock(); } sendMessage( m ); } @@ -846,6 +849,7 @@ void RemoteVstPlugin::getParameterDump() void RemoteVstPlugin::setParameterDump( const message & _m ) { + lock(); const int n = _m.getInt( 0 ); const int params = ( n > m_plugin->numParams ) ? m_plugin->numParams : n; @@ -858,6 +862,7 @@ void RemoteVstPlugin::setParameterDump( const message & _m ) item.value = _m.getFloat( ++p ); m_plugin->setParameter( m_plugin, item.index, item.value ); } + unlock(); } @@ -1037,12 +1042,14 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) chunk_size = m_plugin->numParams * sizeof( float ); data = new char[ chunk_size ]; unsigned int* toUIntArray = reinterpret_cast( data ); + lock(); for ( int i = 0; i < m_plugin->numParams; i++ ) { float value = m_plugin->getParameter( m_plugin, i ); unsigned int * pValue = ( unsigned int * ) &value; toUIntArray[ i ] = endian_swap( *pValue ); } + unlock(); } else chunk_size = (((m_plugin->numParams * sizeof( float )) + 56)*m_plugin->numPrograms); } @@ -1088,12 +1095,14 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) pluginDispatch( effSetProgram, 0, j ); pluginDispatch( effGetProgramName, 0, 0, pBank->prgName ); fwrite ( pBank, 1, 56, stream ); + lock(); for ( int i = 0; i < m_plugin->numParams; i++ ) { value = m_plugin->getParameter( m_plugin, i ); pValue = ( unsigned int * ) &value; toUIntArray[ i ] = endian_swap( *pValue ); } + unlock(); fwrite ( data, 1, chunk_size, stream ); } pluginDispatch( effSetProgram, 0, currProgram ); @@ -1148,13 +1157,17 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) pluginDispatch( 4, 0, 0, pBank->prgName ); if(pBank->fxMagic != 0x6B437846) pluginDispatch( 24, 1, len, chunk ); - else { + else + { + lock(); unsigned int* toUIntArray = reinterpret_cast( chunk ); - for (int i = 0; i < pBank->numPrograms; i++ ) { + for (int i = 0; i < pBank->numPrograms; i++ ) + { toUInt = endian_swap( toUIntArray[ i ] ); pFloat = ( float* ) &toUInt; m_plugin->setParameter( m_plugin, i, *pFloat ); } + unlock(); } } else { if(pBank->fxMagic != 0x6B427846) { @@ -1165,6 +1178,7 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) int currProgram = pluginDispatch( effGetProgram ); chunk = new char[ len = sizeof(float)*m_plugin->numParams ]; toUIntArray = reinterpret_cast( chunk ); + lock(); for (int i =0; i < numPrograms; i++) { fread (pBank, 1, 56, stream); fread (chunk, len, 1, stream); @@ -1177,6 +1191,7 @@ void RemoteVstPlugin::loadPresetFile( const std::string & _file ) m_plugin->setParameter( m_plugin, j, *pFloat ); } } + unlock(); pluginDispatch( effSetProgram, 0, currProgram ); fclose( stream ); } From da87fd4e551268c9f32a9ba67d5b5da80f698960 Mon Sep 17 00:00:00 2001 From: Jens Lang Date: Mon, 26 Nov 2012 22:58:27 +0100 Subject: [PATCH 040/133] RemotePlugin: use atomic operation for lock Use GCC's builtin atomic add/subtract operation for incrementing/ decrementing the recursive lock variable. This is needed to avoid race conditions and is much faster than using mutexes etc. Signed-off-by: Tobias Doerffel --- include/RemotePlugin.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 40c8fee4a..347536b4b 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -284,7 +284,7 @@ public: // recursive lock inline void lock() { - if( !isInvalid() && ++m_lockDepth == 1 ) + if( !isInvalid() && __sync_add_and_fetch( &m_lockDepth, 1 ) == 1 ) { #ifdef USE_QT_SEMAPHORES m_dataSem.acquire(); @@ -297,16 +297,13 @@ public: // recursive unlock inline void unlock() { - if( m_lockDepth > 0 ) + if( __sync_sub_and_fetch( &m_lockDepth, 1) <= 0 ) { - if( --m_lockDepth == 0 ) - { #ifdef USE_QT_SEMAPHORES - m_dataSem.release(); + m_dataSem.release(); #else - sem_post( m_dataSem ); + sem_post( m_dataSem ); #endif - } } } @@ -484,7 +481,7 @@ private: sem_t * m_dataSem; sem_t * m_messageSem; #endif - int m_lockDepth; + volatile int m_lockDepth; } ; From 0dc2060ea2407b864c67849d4ac4637ce4bfdc11 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 27 Nov 2012 23:29:55 +0100 Subject: [PATCH 041/133] ExportProjectDialog: fixed non-multi-track export once more There was another bug causing LMMS to crash when using regular export feature. Thanks to Mikobuntu for reporting this bug. --- src/gui/export_project_dialog.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index 1167df8f2..93527c657 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -146,21 +146,24 @@ void exportProjectDialog::closeEvent( QCloseEvent * _ce ) -void exportProjectDialog::popRender() { - - track* render_track = m_tracksToRender.back(); - m_tracksToRender.pop_back(); - - // Set must states for song tracks - for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) +void exportProjectDialog::popRender() +{ + if( m_multiExport && m_tracksToRender.isEmpty() == false ) { - if( (*it) == render_track ) + track* renderTrack = m_tracksToRender.back(); + m_tracksToRender.pop_back(); + + // Set must states for song tracks + for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) { - (*it)->setMuted( false ); - } - else - { - (*it)->setMuted( true ); + if( (*it) == renderTrack ) + { + (*it)->setMuted( false ); + } + else + { + (*it)->setMuted( true ); + } } } @@ -171,6 +174,8 @@ void exportProjectDialog::popRender() { render( r ); } + + void exportProjectDialog::multiRender() { m_dirName = m_fileName; From 3b72f1e9d94fbae88b004b7623a45ae90094bf98 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 27 Nov 2012 23:36:42 +0100 Subject: [PATCH 042/133] Widgets/Fader: increment/decrement by 1 via scroll wheel As per popular demand, incrementing/decrement fader value by 1 instead of 5 when using the scroll wheel. --- src/gui/widgets/fader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/widgets/fader.cpp b/src/gui/widgets/fader.cpp index e94a26d94..ae088dd38 100644 --- a/src/gui/widgets/fader.cpp +++ b/src/gui/widgets/fader.cpp @@ -1,8 +1,8 @@ /* * fader.cpp - fader-widget used in mixer - partly taken from Hydrogen * - * Copyright (c) 2008-2011 Tobias Doerffel - * + * Copyright (c) 2008-2012 Tobias Doerffel + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -153,11 +153,11 @@ void fader::wheelEvent ( QWheelEvent *ev ) if ( ev->delta() > 0 ) { - m_model->incValue( 5 ); + m_model->incValue( 1 ); } else { - m_model->incValue( -5 ); + m_model->incValue( -1 ); } updateTextFloat(); s_textFloat->setVisibilityTimeOut( 1000 ); From 3739ef82c618808623b367b5dbe89c4a175c5245 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 27 Nov 2012 23:56:37 +0100 Subject: [PATCH 043/133] Widgets/Fader: generic knob drawing code Instead of hard-coding pixmap sizes, calculate everything dynamically. --- include/fader.h | 6 +++--- src/gui/widgets/fader.cpp | 15 ++------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/include/fader.h b/include/fader.h index 61b3fd407..db5fbd878 100644 --- a/include/fader.h +++ b/include/fader.h @@ -79,12 +79,12 @@ private: virtual void wheelEvent( QWheelEvent *ev ); virtual void paintEvent( QPaintEvent *ev ); - inline uint knob_y() const + int knobPosY() const { float fRange = m_model->maxValue() - m_model->minValue(); float realVal = m_model->value() - m_model->minValue(); -// uint knob_y = (uint)( 116.0 - ( 86.0 * ( m_model->value() / fRange ) ) ); - return (uint)( 116.0 - ( 86.0 * ( realVal / fRange ) ) ); + + return height() - ( ( height() - m_knob.height() ) * ( realVal / fRange ) ); } FloatModel * m_model; diff --git a/src/gui/widgets/fader.cpp b/src/gui/widgets/fader.cpp index ae088dd38..b101c2340 100644 --- a/src/gui/widgets/fader.cpp +++ b/src/gui/widgets/fader.cpp @@ -225,7 +225,7 @@ void fader::updateTextFloat() { s_textFloat->setText( QString("Volume: %1 %").arg( m_model->value() * 100 ) ); } - s_textFloat->moveGlobal( this, QPoint( width() - m_knob.width() - 5, knob_y() - 46 ) ); + s_textFloat->moveGlobal( this, QPoint( width() - m_knob.width() - 5, knobPosY() - 46 ) ); } @@ -268,18 +268,7 @@ void fader::paintEvent( QPaintEvent * ev) } // knob - static const uint knob_height = 29; - static const uint knob_width = 15; - - float fRange = m_model->maxValue() - m_model->minValue(); - - float realVal = m_model->value() - m_model->minValue(); - -// uint knob_y = (uint)( 116.0 - ( 86.0 * ( m_model->value() / fRange ) ) ); - uint knob_y = (uint)( 116.0 - ( 86.0 * ( realVal / fRange ) ) ); - - - painter.drawPixmap( QRect( 4, knob_y - knob_height, knob_width, knob_height), m_knob, QRect( 0, 0, knob_width, knob_height ) ); + painter.drawPixmap( 4, knobPosY() - m_knob.height(), m_knob ); } From 8db0d0b0fa82a33ed368c1c3cd909aa68d650dda Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 28 Nov 2012 00:07:56 +0100 Subject: [PATCH 044/133] Widgets/Fader: improved usability by not jumping to mouse click position The previous behaviour was very annoying because starting to drag the knob almost always resulted in a value change. This has been changed by a rewritten logic for mouse click and move behaviour. Closes #3588157. --- include/fader.h | 6 +++++- src/gui/widgets/fader.cpp | 43 +++++++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/include/fader.h b/include/fader.h index db5fbd878..842b30cb6 100644 --- a/include/fader.h +++ b/include/fader.h @@ -1,7 +1,7 @@ /* * fader.h - fader-widget used in FX-mixer - partly taken from Hydrogen * - * Copyright (c) 2008-2011 Tobias Doerffel + * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -91,6 +91,7 @@ private: void setPeak( float fPeak, float &targetPeak, float &persistentPeak, QTime &lastPeakTime ); int calculateDisplayPeak( float fPeak ); + float m_fPeakValue_L; float m_fPeakValue_R; float m_persistentPeak_L; @@ -105,6 +106,9 @@ private: QPixmap m_leds; QPixmap m_knob; + int m_moveStartPoint; + float m_startValue; + static textFloat * s_textFloat; void updateTextFloat(); diff --git a/src/gui/widgets/fader.cpp b/src/gui/widgets/fader.cpp index b101c2340..84c4bf31b 100644 --- a/src/gui/widgets/fader.cpp +++ b/src/gui/widgets/fader.cpp @@ -73,7 +73,9 @@ fader::fader( FloatModel * _model, const QString & _name, QWidget * _parent ) : m_fMaxPeak( 1.1 ), m_back( embed::getIconPixmap( "fader_background" ) ), m_leds( embed::getIconPixmap( "fader_leds" ) ), - m_knob( embed::getIconPixmap( "fader_knob" ) ) + m_knob( embed::getIconPixmap( "fader_knob" ) ), + m_moveStartPoint( -1 ), + m_startValue( 0 ) { if( s_textFloat == NULL ) { @@ -107,35 +109,46 @@ void fader::contextMenuEvent( QContextMenuEvent * _ev ) -void fader::mouseMoveEvent( QMouseEvent *ev ) +void fader::mouseMoveEvent( QMouseEvent *mouseEvent ) { - float fVal = (float)( height() - ev->y() ) / (float)height(); - fVal = fVal * ( m_model->maxValue() - m_model->minValue() ); + if( m_moveStartPoint >= 0 ) + { + int dy = m_moveStartPoint - mouseEvent->globalY(); - fVal = fVal + m_model->minValue(); + float delta = dy * ( m_model->maxValue() - m_model->minValue() ) / (float) ( height() - m_knob.height() ); - m_model->setValue( fVal ); + model()->setValue( m_startValue + delta ); - updateTextFloat(); + updateTextFloat(); + } } -void fader::mousePressEvent( QMouseEvent * _me ) +void fader::mousePressEvent( QMouseEvent* mouseEvent ) { - if( _me->button() == Qt::LeftButton && - ! ( _me->modifiers() & Qt::ControlModifier ) ) + if( mouseEvent->button() == Qt::LeftButton && + ! ( mouseEvent->modifiers() & Qt::ControlModifier ) ) { - updateTextFloat(); - s_textFloat->show(); + if( mouseEvent->y() >= knobPosY() - m_knob.height() && mouseEvent->y() < knobPosY() ) + { + updateTextFloat(); + s_textFloat->show(); - mouseMoveEvent( _me ); - _me->accept(); + m_moveStartPoint = mouseEvent->globalY(); + m_startValue = model()->value(); + + mouseEvent->accept(); + } + else + { + m_moveStartPoint = -1; + } } else { - AutomatableModelView::mousePressEvent( _me ); + AutomatableModelView::mousePressEvent( mouseEvent ); } } From 8a75c40fd6c14f6889cb305b6c36cfd987d137f2 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 28 Nov 2012 00:10:11 +0100 Subject: [PATCH 045/133] Widgets/Fader: open input dialog on double click There have been requests to have an input dialog when double clicking the fader (like we have it for knobs for ages already). Closes #3588157. --- include/fader.h | 1 + src/gui/widgets/fader.cpp | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/fader.h b/include/fader.h index 842b30cb6..02bc475c2 100644 --- a/include/fader.h +++ b/include/fader.h @@ -74,6 +74,7 @@ public: private: virtual void contextMenuEvent( QContextMenuEvent * _me ); virtual void mousePressEvent( QMouseEvent *ev ); + virtual void mouseDoubleClickEvent( QMouseEvent* mouseEvent ); virtual void mouseMoveEvent( QMouseEvent *ev ); virtual void mouseReleaseEvent( QMouseEvent * _me ); virtual void wheelEvent( QWheelEvent *ev ); diff --git a/src/gui/widgets/fader.cpp b/src/gui/widgets/fader.cpp index 84c4bf31b..e9e14b2c4 100644 --- a/src/gui/widgets/fader.cpp +++ b/src/gui/widgets/fader.cpp @@ -45,6 +45,7 @@ */ +#include #include #include #include @@ -154,6 +155,27 @@ void fader::mousePressEvent( QMouseEvent* mouseEvent ) +void fader::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) +{ + bool ok; + + // TODO: dbV handling + int newValue = QInputDialog::getInteger( this, windowTitle(), + tr( "Please enter a new value between %1 and %2:" ). + arg( model()->minValue()*100 ). + arg( model()->maxValue()*100 ), + model()->value()*100, + model()->minValue()*100, + model()->maxValue()*100, 1, &ok ); + + if( ok ) + { + model()->setValue( newValue / 100.0f ); + } +} + + + void fader::mouseReleaseEvent( QMouseEvent * _me ) { s_textFloat->hide(); From 40a92d84179ec4256549c6a35e6dee5bad452df2 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 19:00:06 +0100 Subject: [PATCH 046/133] FlpImport/unrtf: renamed malloc.h to ur_malloc.h for not conflicting with system headers --- plugins/flp_import/unrtf.cpp | 2 +- plugins/flp_import/unrtf/attr.c | 2 +- plugins/flp_import/unrtf/convert.c | 2 +- plugins/flp_import/unrtf/hash.c | 2 +- plugins/flp_import/unrtf/html.c | 2 +- plugins/flp_import/unrtf/output.c | 2 +- plugins/flp_import/unrtf/parse.c | 2 +- plugins/flp_import/unrtf/{malloc.c => ur_malloc.c} | 2 +- plugins/flp_import/unrtf/{malloc.h => ur_malloc.h} | 0 plugins/flp_import/unrtf/word.c | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename plugins/flp_import/unrtf/{malloc.c => ur_malloc.c} (99%) rename plugins/flp_import/unrtf/{malloc.h => ur_malloc.h} (100%) diff --git a/plugins/flp_import/unrtf.cpp b/plugins/flp_import/unrtf.cpp index 91b7a3c24..7f3edaacf 100644 --- a/plugins/flp_import/unrtf.cpp +++ b/plugins/flp_import/unrtf.cpp @@ -62,7 +62,7 @@ OutputPersonality * op = NULL; #include "error.c" #include "hash.c" #include "html.c" -#include "malloc.c" +#include "ur_malloc.c" #include "output.c" #include "parse.c" #include "util.c" diff --git a/plugins/flp_import/unrtf/attr.c b/plugins/flp_import/unrtf/attr.c index 1b8001ac9..b127b429d 100644 --- a/plugins/flp_import/unrtf/attr.c +++ b/plugins/flp_import/unrtf/attr.c @@ -53,7 +53,7 @@ #include #endif -#include "malloc.h" +#include "ur_malloc.h" #include "defs.h" #include "error.h" #include "attr.h" diff --git a/plugins/flp_import/unrtf/convert.c b/plugins/flp_import/unrtf/convert.c index 92c3f0061..e56598a60 100644 --- a/plugins/flp_import/unrtf/convert.c +++ b/plugins/flp_import/unrtf/convert.c @@ -90,7 +90,7 @@ #include "defs.h" #include "parse.h" #include "util.h" -#include "malloc.h" +#include "ur_malloc.h" #include "main.h" #include "error.h" #include "word.h" diff --git a/plugins/flp_import/unrtf/hash.c b/plugins/flp_import/unrtf/hash.c index a51cc745f..b9dafd7f5 100644 --- a/plugins/flp_import/unrtf/hash.c +++ b/plugins/flp_import/unrtf/hash.c @@ -55,7 +55,7 @@ #include "error.h" #include "main.h" -#include "malloc.h" +#include "ur_malloc.h" typedef struct _hi { diff --git a/plugins/flp_import/unrtf/html.c b/plugins/flp_import/unrtf/html.c index 0ab1e4afd..32fa187ed 100644 --- a/plugins/flp_import/unrtf/html.c +++ b/plugins/flp_import/unrtf/html.c @@ -55,7 +55,7 @@ #include #endif -#include "malloc.h" +#include "ur_malloc.h" #include "defs.h" #include "error.h" #include "main.h" diff --git a/plugins/flp_import/unrtf/output.c b/plugins/flp_import/unrtf/output.c index d30620b36..85a80d10e 100644 --- a/plugins/flp_import/unrtf/output.c +++ b/plugins/flp_import/unrtf/output.c @@ -56,7 +56,7 @@ #include #endif -#include "malloc.h" +#include "ur_malloc.h" #include "defs.h" #include "error.h" #include "output.h" diff --git a/plugins/flp_import/unrtf/parse.c b/plugins/flp_import/unrtf/parse.c index 153999f41..353ad41de 100644 --- a/plugins/flp_import/unrtf/parse.c +++ b/plugins/flp_import/unrtf/parse.c @@ -61,7 +61,7 @@ #include "defs.h" #include "parse.h" -#include "malloc.h" +#include "ur_malloc.h" #include "main.h" #include "error.h" #include "word.h" diff --git a/plugins/flp_import/unrtf/malloc.c b/plugins/flp_import/unrtf/ur_malloc.c similarity index 99% rename from plugins/flp_import/unrtf/malloc.c rename to plugins/flp_import/unrtf/ur_malloc.c index 693218285..a72ade733 100644 --- a/plugins/flp_import/unrtf/malloc.c +++ b/plugins/flp_import/unrtf/ur_malloc.c @@ -55,7 +55,7 @@ #endif #include "error.h" -#include "malloc.h" +#include "ur_malloc.h" static unsigned long count=0; diff --git a/plugins/flp_import/unrtf/malloc.h b/plugins/flp_import/unrtf/ur_malloc.h similarity index 100% rename from plugins/flp_import/unrtf/malloc.h rename to plugins/flp_import/unrtf/ur_malloc.h diff --git a/plugins/flp_import/unrtf/word.c b/plugins/flp_import/unrtf/word.c index d863cdf71..b639d94f5 100644 --- a/plugins/flp_import/unrtf/word.c +++ b/plugins/flp_import/unrtf/word.c @@ -71,7 +71,7 @@ #include "defs.h" #include "parse.h" -#include "malloc.h" +#include "ur_malloc.h" #include "main.h" #include "error.h" #include "word.h" From c4e29da4129edd6e2d98309e1216c74a47cc288e Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 19:01:09 +0100 Subject: [PATCH 047/133] RemoteVstPlugin: fixed out-of-bound array access --- plugins/vst_base/RemoteVstPlugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index a3efdbaf6..d6087aebb 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1,7 +1,7 @@ /* * RemoteVstPlugin.cpp - LMMS VST Support Layer (RemotePlugin client) * - * Copyright (c) 2005-2010 Tobias Doerffel + * Copyright (c) 2005-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -808,7 +808,7 @@ const char * RemoteVstPlugin::programName() pluginDispatch( effGetProgramName, 0, 0, buf ); - buf[24] = 0; + buf[23] = 0; return buf; } From 2cac30f495bc1565d572af0327099e869ee2f81f Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 19:01:37 +0100 Subject: [PATCH 048/133] RemoteVstPlugin: use size_t for loop index to fix compiler warning --- plugins/vst_base/RemoteVstPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index d6087aebb..4dad16c69 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -1031,7 +1031,7 @@ void RemoteVstPlugin::savePreset( const std::string & _file ) if (isPreset) { - for (int i = 0; i < _file.length() - 4 - presNameLen; i++) + for (size_t i = 0; i < _file.length() - 4 - presNameLen; i++) progName[i] = i < 23 ? _file[presNameLen + i] : 0; pluginDispatch( 4, 0, 0, progName ); } From e864c8057a5023b8f571ea61c6d1d7f2282a57c0 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 19:04:45 +0100 Subject: [PATCH 049/133] DrumSynth: removed unused variable --- src/core/drumsynth.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/drumsynth.cpp b/src/core/drumsynth.cpp index f5a56498e..b24283718 100644 --- a/src/core/drumsynth.cpp +++ b/src/core/drumsynth.cpp @@ -270,7 +270,7 @@ int DrumSynth::GetDSFileSamples(const char *dsfile, int16_t *&wave, int channels int MainFilter, HighPass; long NON, NT, TON, DiON, TDroop=0, DStep; - float a, b=0.f, c=0.f, d=0.f, g, TT=0.f, TL, NL, F1, F2, Fsync; + float a, b=0.f, c=0.f, d=0.f, g, TT=0.f, TL, NL, F1, F2; float TphiStart=0.f, Tphi, TDroopRate, ddF, DAtten, DGain; long BON, BON2, BFStep, BFStep2, botmp; @@ -354,7 +354,6 @@ int DrumSynth::GetDSFileSamples(const char *dsfile, int16_t *&wave, int channels F1 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F1",200.0,dsfile) / Fs; if(fabs(F1)<0.001f) F1=0.001f; //to prevent overtone ratio div0 F2 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F2",120.0,dsfile) / Fs; - Fsync = F2; TDroopRate = GetPrivateProfileFloat(sec,"Droop",0.f,dsfile); if(TDroopRate>0.f) { From ef68156c7161df831d6a4d2ca230bfebe7d1f4a2 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 22:07:11 +0100 Subject: [PATCH 050/133] RemoteVstPlugin: compile with -O3 as otherwise incorrect code is generated There seems to be a bug in current MinGW GCC which leads to incorrect code (crash for no obvious reason) in RemoteVstPlugin process. --- build_mingw32 | 3 ++- build_mingw64 | 2 +- plugins/vst_base/CMakeLists.txt | 1 + plugins/vst_base/Win64/CMakeLists.txt | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build_mingw32 b/build_mingw32 index 347550416..abd09ff07 100755 --- a/build_mingw32 +++ b/build_mingw32 @@ -1,6 +1,7 @@ MINGW=/opt/mingw32 export PATH=$PATH:$MINGW/bin -export CFLAGS="-march=pentium3 -mtune=generic -mpreferred-stack-boundary=5 -fno-tree-vectorize" +#export CFLAGS="-march=pentium3 -mtune=generic -mpreferred-stack-boundary=5 -fno-tree-vectorize" +export CFLAGS="-march=pentium3 -mtune=generic -mpreferred-stack-boundary=5" export CXXFLAGS="$CFLAGS" cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/modules/Win32Toolchain.cmake -DCMAKE_MODULE_PATH=`pwd`/../cmake/modules/ diff --git a/build_mingw64 b/build_mingw64 index 3d74cad37..32c86d42e 100755 --- a/build_mingw64 +++ b/build_mingw64 @@ -1,6 +1,6 @@ MINGW=/opt/mingw64 export PATH=$PATH:$MINGW/bin -export CFLAGS="-fno-tree-vectorize" +#export CFLAGS="-fno-tree-vectorize" export CXXFLAGS="$CFLAGS" cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/modules/Win64Toolchain.cmake -DCMAKE_MODULE_PATH=`pwd`/../cmake/modules/ diff --git a/plugins/vst_base/CMakeLists.txt b/plugins/vst_base/CMakeLists.txt index d3d3d5c41..6412121a0 100644 --- a/plugins/vst_base/CMakeLists.txt +++ b/plugins/vst_base/CMakeLists.txt @@ -6,6 +6,7 @@ IF(LMMS_BUILD_WIN32) ADD_DEFINITIONS(-DPTW32_STATIC_LIB) ADD_EXECUTABLE(RemoteVstPlugin ${CMAKE_CURRENT_SOURCE_DIR}/RemoteVstPlugin.cpp) TARGET_LINK_LIBRARIES(RemoteVstPlugin -lQtCore4 -lpthread -lgdi32 -lws2_32) + SET_TARGET_PROPERTIES(RemoteVstPlugin PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -O3") ADD_CUSTOM_COMMAND(TARGET RemoteVstPlugin POST_BUILD COMMAND ${STRIP} ${CMAKE_CURRENT_BINARY_DIR}/RemoteVstPlugin.exe) INSTALL(TARGETS RemoteVstPlugin RUNTIME DESTINATION ${PLUGIN_DIR}) diff --git a/plugins/vst_base/Win64/CMakeLists.txt b/plugins/vst_base/Win64/CMakeLists.txt index af96b3a28..3f151e363 100644 --- a/plugins/vst_base/Win64/CMakeLists.txt +++ b/plugins/vst_base/Win64/CMakeLists.txt @@ -5,6 +5,7 @@ ADD_EXECUTABLE(RemoteVstPlugin32 ${CMAKE_CURRENT_SOURCE_DIR}/../RemoteVstPlugin. TARGET_LINK_LIBRARIES(RemoteVstPlugin32 -lQtCore4 -lpthread -lgdi32 -lws2_32) ADD_CUSTOM_COMMAND(TARGET RemoteVstPlugin32 POST_BUILD COMMAND ${STRIP} ${CMAKE_CURRENT_BINARY_DIR}/RemoteVstPlugin32.exe) +SET_TARGET_PROPERTIES(RemoteVstPlugin32 PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -O3") INSTALL(TARGETS RemoteVstPlugin32 RUNTIME DESTINATION ${PLUGIN_DIR}/32) From 5f298c0c48974b29c35ba5cc1eb01d1041faa165 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 22:09:15 +0100 Subject: [PATCH 051/133] RemoteVstPlugin: coding style fixes --- plugins/vst_base/RemoteVstPlugin.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 4dad16c69..855ffc9dc 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -261,8 +261,7 @@ private: lock(); if( m_plugin ) { - ret = m_plugin->dispatcher( m_plugin, cmd, param1, - param2, p, f ); + ret = m_plugin->dispatcher( m_plugin, cmd, param1, param2, p, f ); } unlock(); return ret; @@ -322,7 +321,7 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : // process until we have loaded the plugin while( 1 ) - { + { message m = receiveMessage(); processMessage( m ); if( m.id == IdVstLoadPlugin || m.id == IdQuit ) From f28601db5dcbb567a167310b767cb168d257f94c Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 2 Dec 2012 22:09:28 +0100 Subject: [PATCH 052/133] RemoteVstPlugin: less verbose debug message --- plugins/vst_base/RemoteVstPlugin.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 855ffc9dc..f48d67bf1 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -637,10 +637,7 @@ bool RemoteVstPlugin::load( const std::string & _plugin_file ) if( m_plugin->magic != kEffectMagic ) { - char buf[256]; - sprintf( buf, "%s is not a VST plugin\n", - _plugin_file.c_str() ); - debugMessage( buf ); + debugMessage( "File is not a VST plugin\n" ); return false; } From fdea64c0d6add8535a1e3fc18dfa105907144cfc Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:05:06 +0100 Subject: [PATCH 053/133] RemotePlugin: more sanity checks --- include/RemotePlugin.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 347536b4b..5e03a724a 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -415,7 +415,8 @@ private: return; } lock(); - while( _len > m_data->endPtr - m_data->startPtr ) + while( isInvalid() == false && + _len > m_data->endPtr - m_data->startPtr ) { unlock(); #ifndef LMMS_BUILD_WIN32 @@ -436,7 +437,7 @@ private: void write( const void * _buf, int _len ) { - if( isInvalid() ) + if( isInvalid() || _len > SHM_FIFO_SIZE ) { return; } From a00a922119898c70337a0a6d1233ce274ea91e27 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:05:36 +0100 Subject: [PATCH 054/133] RemotePlugin: smaller buffers for printing numbers --- include/RemotePlugin.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 5e03a724a..58a7d0465 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -546,8 +546,7 @@ public: message & addInt( int _i ) { - char buf[128]; - buf[0] = 0; + char buf[32]; sprintf( buf, "%d", _i ); data.push_back( std::string( buf ) ); return *this; @@ -555,8 +554,7 @@ public: message & addFloat( float _f ) { - char buf[128]; - buf[0] = 0; + char buf[32]; sprintf( buf, "%f", _f ); data.push_back( std::string( buf ) ); return *this; From efe3b4781072a011d84e07a63db694ca5c6f601f Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:05:57 +0100 Subject: [PATCH 055/133] RemotePlugin: return number of bytes sent in sendMessage() --- include/RemotePlugin.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 58a7d0465..b7cc5b8ef 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -607,7 +607,7 @@ public: m_out = out; } - void sendMessage( const message & _m ); + int sendMessage( const message & _m ); message receiveMessage(); inline bool isInvalid() const @@ -926,19 +926,21 @@ RemotePluginBase::~RemotePluginBase() -void RemotePluginBase::sendMessage( const message & _m ) +int RemotePluginBase::sendMessage( const message & _m ) { m_out->lock(); m_out->writeInt( _m.id ); m_out->writeInt( _m.data.size() ); - int j = 0; + int j = 8; for( unsigned int i = 0; i < _m.data.size(); ++i ) { m_out->writeString( _m.data[i] ); - j += _m.data[i].size(); + j += 4 + _m.data[i].size(); } m_out->unlock(); m_out->messageSent(); + + return j; } From 3bad03d56eb3116c1ed63dfa3f127f390e0f952e Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:06:25 +0100 Subject: [PATCH 056/133] RemotePlugin: improved management of shared memory keys --- include/RemotePlugin.h | 6 ++++-- src/core/RemotePlugin.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index b7cc5b8ef..87bb3a28b 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -1101,13 +1101,15 @@ void RemotePluginClient::setShmKey( key_t _key, int _size ) { #ifdef USE_QT_SHMEM m_shmObj.setKey( QString::number( _key ) ); - if( m_shmObj.attach() ) + if( m_shmObj.attach() || m_shmObj.error() == QSharedMemory::NoError ) { m_shm = (float *) m_shmObj.data(); } else { - debugMessage( "failed getting shared memory\n" ); + char buf[64]; + sprintf( buf, "failed getting shared memory: %d\n", m_shmObj.error() ); + debugMessage( buf ); } #else if( m_shm != NULL ) diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index 55fe5309e..dd6a237dd 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -314,7 +314,7 @@ void RemotePlugin::resizeSharedProcessingMemory() #endif } - int shm_key = 0; + static int shm_key = 0; #ifdef USE_QT_SHMEM do { From 890a8a4ba9597e9bcb5fcd3f3c7d04e4c1b560fd Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:07:32 +0100 Subject: [PATCH 057/133] RemoteVstPlugin: new method pluginDispatchNoLocking() --- plugins/vst_base/RemoteVstPlugin.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index f48d67bf1..f88448e2f 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -267,6 +267,17 @@ private: return ret; } + // thread-safe dispatching of plugin + int pluginDispatchNoLocking( int cmd, int param1 = 0, int param2 = 0, void * p = NULL, float f = 0 ) + { + if( m_plugin ) + { + return m_plugin->dispatcher( m_plugin, cmd, param1, param2, p, f ); + } + return 0; + } + + std::string m_shortName; HINSTANCE m_libInst; From 60017ae6e3eb0f76e808db73f8f1449f991debdb Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 3 Dec 2012 00:07:56 +0100 Subject: [PATCH 058/133] RemoteVstPlugin: decreased locking overhead in getParameterDump() --- plugins/vst_base/RemoteVstPlugin.cpp | 31 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index f88448e2f..aaae0c270 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -834,21 +834,26 @@ void RemoteVstPlugin::sendCurrentProgramName() void RemoteVstPlugin::getParameterDump() { - //VstParameterProperties vst_props; - message m( IdVstParameterDump ); - m.addInt( m_plugin->numParams ); - for( int i = 0; i < m_plugin->numParams; ++i ) - { + lock(); + + message m( IdVstParameterDump ); + m.addInt( m_plugin->numParams ); + + for( int i = 0; i < m_plugin->numParams; ++i ) + { char paramName[32]; - pluginDispatch( effGetParamName, i, 0, paramName ); - paramName[31] = 0; - m.addInt( i ); + memset( paramName, 0, sizeof( paramName ) ); + pluginDispatchNoLocking( effGetParamName, i, 0, paramName ); + paramName[sizeof(paramName)-1] = 0; + + m.addInt( i ); m.addString( paramName ); - lock(); - m.addFloat( m_plugin->getParameter( m_plugin, i ) ); - unlock(); - } - sendMessage( m ); + m.addFloat( m_plugin->getParameter( m_plugin, i ) ); + } + + unlock(); + + sendMessage( m ); } From c27c321778d0447caca49bd20b66238abdf83aec Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 9 Dec 2012 17:25:59 +0100 Subject: [PATCH 059/133] RemotePlugin: increased SHM_FIFO_SIZE again As there are plugins whose parameter dumps are bigger than 64 KB increased SHM_FIFO_SIZE to 512 KB. --- include/RemotePlugin.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 87bb3a28b..5ac33e766 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -115,7 +115,7 @@ typedef int32_t key_t; // sometimes we need to exchange bigger messages (e.g. for VST parameter dumps) // so set a usable value here -const int SHM_FIFO_SIZE = 64000; +const int SHM_FIFO_SIZE = 512*1024; // implements a FIFO inside a shared memory segment From 639763dbb2621702fee623a7dc1da16743c9233b Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 9 Dec 2012 17:29:14 +0100 Subject: [PATCH 060/133] FxMixerView: do not set size constraint on parent MDI window As reported by Mikobuntu on 2012-12-06 there's a problem when minimizing the FX mixer window. The problem disappears when not setting a size constraint on the mixer's MDI window. --- src/gui/FxMixerView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index 14c9d9ebd..ce3abb2f1 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -238,7 +238,7 @@ FxMixerView::FxMixerView() : flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); - subWin->layout()->setSizeConstraint(QLayout::SetFixedSize); + //subWin->layout()->setSizeConstraint(QLayout::SetFixedSize); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); From 70304f98003a1346f68b10beb8a2c337a4c94260 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 10 Dec 2012 21:28:25 +0100 Subject: [PATCH 061/133] Release candidate 1 for 0.4.14 Bumped version number to 0.4.14-rc1 in CMakeLists.txt. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b5ff44b9..9f37ef069 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,8 +15,8 @@ INCLUDE(FindPkgConfig) SET(VERSION_MAJOR "0") SET(VERSION_MINOR "4") -SET(VERSION_PATCH "13") -#SET(VERSION_SUFFIX "") +SET(VERSION_PATCH "14") +SET(VERSION_SUFFIX "rc1") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") IF(VERSION_SUFFIX) SET(VERSION "${VERSION}-${VERSION_SUFFIX}") From dcf7245fe8223d3d13dbdd7839b18c53ba2e15fc Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 6 Jan 2013 22:40:08 +0100 Subject: [PATCH 062/133] Presets: fixed track names to reflect actual preset names Thanks to Tres Finocchiaro for pointing out this issue and many thanks to Mike Choi for providing a helper script for automatically fixing the preset files. Signed-off-by: Tobias Doerffel --- data/presets/BitInvader/alien_strings.xpf | 2 +- data/presets/BitInvader/beehive.xpf | 2 +- data/presets/BitInvader/bell.xpf | 2 +- data/presets/BitInvader/cello.xpf | 2 +- data/presets/BitInvader/drama.xpf | 2 +- data/presets/BitInvader/epiano.xpf | 2 +- data/presets/BitInvader/soft_pad.xpf | 2 +- data/presets/BitInvader/spacefx.xpf | 2 +- data/presets/BitInvader/subbass.xpf | 2 +- data/presets/BitInvader/sweep_pad.xpf | 2 +- data/presets/BitInvader/toy_piano.xpf | 2 +- data/presets/BitInvader/wah_synth.xpf | 2 +- data/presets/LB302/Oh Synth.xpf | 2 +- data/presets/LB302/STrash.xpf | 2 +- data/presets/Organic/organ_blues.xpf | 2 +- data/presets/Organic/organ_risingsun.xpf | 2 +- data/presets/Organic/organ_swish.xpf | 2 +- data/presets/Organic/pad_ethereal.xpf | 2 +- data/presets/Organic/pad_rich.xpf | 2 +- data/presets/Organic/pad_sweep.xpf | 2 +- data/presets/Organic/puresine.xpf | 2 +- data/presets/Organic/sequencer_64.xpf | 2 +- data/presets/TripleOscillator/SBass.xpf | 2 +- data/presets/TripleOscillator/SBass2.xpf | 2 +- data/presets/TripleOscillator/SEGuitar.xpf | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/data/presets/BitInvader/alien_strings.xpf b/data/presets/BitInvader/alien_strings.xpf index 1e9f9fa50..62c5163f8 100644 --- a/data/presets/BitInvader/alien_strings.xpf +++ b/data/presets/BitInvader/alien_strings.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/beehive.xpf b/data/presets/BitInvader/beehive.xpf index 69295aca0..a4c5840e4 100644 --- a/data/presets/BitInvader/beehive.xpf +++ b/data/presets/BitInvader/beehive.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/bell.xpf b/data/presets/BitInvader/bell.xpf index 53626aa36..ce2abec88 100644 --- a/data/presets/BitInvader/bell.xpf +++ b/data/presets/BitInvader/bell.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/cello.xpf b/data/presets/BitInvader/cello.xpf index 6e958d044..44990bb68 100644 --- a/data/presets/BitInvader/cello.xpf +++ b/data/presets/BitInvader/cello.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/drama.xpf b/data/presets/BitInvader/drama.xpf index d2241d879..7ed801dff 100644 --- a/data/presets/BitInvader/drama.xpf +++ b/data/presets/BitInvader/drama.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/epiano.xpf b/data/presets/BitInvader/epiano.xpf index 37daa83f7..c6e6e22e0 100644 --- a/data/presets/BitInvader/epiano.xpf +++ b/data/presets/BitInvader/epiano.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/soft_pad.xpf b/data/presets/BitInvader/soft_pad.xpf index 3123be10e..c82e0510f 100644 --- a/data/presets/BitInvader/soft_pad.xpf +++ b/data/presets/BitInvader/soft_pad.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/spacefx.xpf b/data/presets/BitInvader/spacefx.xpf index 5a1e3c3d9..8e7b93fcb 100644 --- a/data/presets/BitInvader/spacefx.xpf +++ b/data/presets/BitInvader/spacefx.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/subbass.xpf b/data/presets/BitInvader/subbass.xpf index 16bb9547d..d4d93734f 100644 --- a/data/presets/BitInvader/subbass.xpf +++ b/data/presets/BitInvader/subbass.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/sweep_pad.xpf b/data/presets/BitInvader/sweep_pad.xpf index eb0cfe04a..e4f4ee48c 100644 --- a/data/presets/BitInvader/sweep_pad.xpf +++ b/data/presets/BitInvader/sweep_pad.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/toy_piano.xpf b/data/presets/BitInvader/toy_piano.xpf index 706d0ae40..3976affe3 100644 --- a/data/presets/BitInvader/toy_piano.xpf +++ b/data/presets/BitInvader/toy_piano.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/BitInvader/wah_synth.xpf b/data/presets/BitInvader/wah_synth.xpf index 51f7cf0f6..ee8877b7c 100644 --- a/data/presets/BitInvader/wah_synth.xpf +++ b/data/presets/BitInvader/wah_synth.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/LB302/Oh Synth.xpf b/data/presets/LB302/Oh Synth.xpf index 3b9027fee..5b548c16c 100644 --- a/data/presets/LB302/Oh Synth.xpf +++ b/data/presets/LB302/Oh Synth.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/LB302/STrash.xpf b/data/presets/LB302/STrash.xpf index b03fd228b..f001b9b55 100644 --- a/data/presets/LB302/STrash.xpf +++ b/data/presets/LB302/STrash.xpf @@ -2,7 +2,7 @@ - + diff --git a/data/presets/Organic/organ_blues.xpf b/data/presets/Organic/organ_blues.xpf index 7f57a805c..4f102cc04 100644 --- a/data/presets/Organic/organ_blues.xpf +++ b/data/presets/Organic/organ_blues.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/organ_risingsun.xpf b/data/presets/Organic/organ_risingsun.xpf index c69ee05d3..346b59562 100644 --- a/data/presets/Organic/organ_risingsun.xpf +++ b/data/presets/Organic/organ_risingsun.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/organ_swish.xpf b/data/presets/Organic/organ_swish.xpf index bc5022f98..3917962bc 100644 --- a/data/presets/Organic/organ_swish.xpf +++ b/data/presets/Organic/organ_swish.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/pad_ethereal.xpf b/data/presets/Organic/pad_ethereal.xpf index 4ca87e676..e0aa2854d 100644 --- a/data/presets/Organic/pad_ethereal.xpf +++ b/data/presets/Organic/pad_ethereal.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/pad_rich.xpf b/data/presets/Organic/pad_rich.xpf index 9aa11c2e4..62d8de950 100644 --- a/data/presets/Organic/pad_rich.xpf +++ b/data/presets/Organic/pad_rich.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/pad_sweep.xpf b/data/presets/Organic/pad_sweep.xpf index 7c63b6608..ed2c7f87e 100644 --- a/data/presets/Organic/pad_sweep.xpf +++ b/data/presets/Organic/pad_sweep.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/puresine.xpf b/data/presets/Organic/puresine.xpf index 06fb0bcab..3e0d6c0f6 100644 --- a/data/presets/Organic/puresine.xpf +++ b/data/presets/Organic/puresine.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/Organic/sequencer_64.xpf b/data/presets/Organic/sequencer_64.xpf index 4be35bd5f..3d3fab9fe 100644 --- a/data/presets/Organic/sequencer_64.xpf +++ b/data/presets/Organic/sequencer_64.xpf @@ -3,7 +3,7 @@ - + diff --git a/data/presets/TripleOscillator/SBass.xpf b/data/presets/TripleOscillator/SBass.xpf index 982bf5498..ae45def69 100644 --- a/data/presets/TripleOscillator/SBass.xpf +++ b/data/presets/TripleOscillator/SBass.xpf @@ -2,7 +2,7 @@ - + diff --git a/data/presets/TripleOscillator/SBass2.xpf b/data/presets/TripleOscillator/SBass2.xpf index 62cc71bf0..b0df645d7 100644 --- a/data/presets/TripleOscillator/SBass2.xpf +++ b/data/presets/TripleOscillator/SBass2.xpf @@ -2,7 +2,7 @@ - + diff --git a/data/presets/TripleOscillator/SEGuitar.xpf b/data/presets/TripleOscillator/SEGuitar.xpf index 4778f50b4..bd6ec8df8 100644 --- a/data/presets/TripleOscillator/SEGuitar.xpf +++ b/data/presets/TripleOscillator/SEGuitar.xpf @@ -2,7 +2,7 @@ - + From 716146848c6d6b220d59100ea77a8cc16e6677c3 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 6 Jan 2013 22:48:41 +0100 Subject: [PATCH 063/133] ZynAddSubFx: when loading a preset set preset name as track name Based upon the suggestion of Tres Finocchiaro we use the filename of a ZASF preset for determining the preset name and set it as track name. --- plugins/zynaddsubfx/ZynAddSubFx.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index 78733faed..f1483c19b 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -301,6 +301,9 @@ void ZynAddSubFxInstrument::loadFile( const QString & _file ) m_pluginMutex.unlock(); } + instrumentTrack()->setName( QFileInfo( _file ).baseName(). + replace( QRegExp( "^[0-9]{4}-" ), QString() ) ); + m_modifiedControllers.clear(); emit settingsChanged(); From 2960f67bebf3c530793983ee6835c416481fe408 Mon Sep 17 00:00:00 2001 From: NoiseByNorthwest Date: Wed, 2 Jan 2013 22:10:46 +0100 Subject: [PATCH 064/133] AudioFileProcessor: fixed crash for samples with zero length This is a fix for #3598536. Closes #3598536. Signed-off-by: Tobias Doerffel --- plugins/audio_file_processor/audio_file_processor.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index c97aa2c7a..208a03d9d 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -542,7 +542,15 @@ AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _ void AudioFileProcessorWaveView::isPlaying( f_cnt_t _frames_played ) { - m_framesPlayed = _frames_played % ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ); + const f_cnt_t nb_frames = m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame(); + if( nb_frames < 1 ) + { + m_framesPlayed = 0; + } + else + { + m_framesPlayed = _frames_played % nb_frames; + } update(); } From 3cc01560be6cd62122b1a853638e2bf3395e432a Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 17:22:26 +0100 Subject: [PATCH 065/133] VST Automation: lock prevention src/core/RemotePlugin.cpp @ RemotePlugin::process Above function should be now more thread safe, but functionality remains. This prevent lmms locks, when automation is connected to lmms VST plugin wrappers controler / knob on Linux. On win32 build whenever is lmms wrapper parameter controler / knob amended. plugins/vst_base/VstPlugin.cpp @ VstPlugin::setParam plugins/vst_base/RemoteVstPlugin.cpp @ RemoteVstPlugin::processMessage In above functions we dont wait for message confirmation when parameter setter function finish, and dont send such confirmations. This workaround prevent locks on Linux, whenever there is automation connected and if you try to move with VST plugin (not via lmms plugins wrapper, but with VSTs internal window) than it freezes, on win32 build it will freeze whenever you connect automation and than if you move your mouse above this VST plugin window. Signed-off-by: Tobias Doerffel --- plugins/vst_base/RemoteVstPlugin.cpp | 2 +- plugins/vst_base/VstPlugin.cpp | 2 +- src/core/RemotePlugin.cpp | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index aaae0c270..40d02cacb 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -451,7 +451,7 @@ bool RemoteVstPlugin::processMessage( const message & _m ) lock(); m_plugin->setParameter( m_plugin, _m.getInt( 0 ), _m.getFloat( 1 ) ); unlock(); - sendMessage( IdVstSetParameter ); + //sendMessage( IdVstSetParameter ); break; diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index 59d75fb53..c986de2d8 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -574,7 +574,7 @@ void VstPlugin::setParam( int i, float f ) { lock(); sendMessage( message( IdVstSetParameter ).addInt( i ).addFloat( f ) ); - waitForMessage( IdVstSetParameter ); + //waitForMessage( IdVstSetParameter ); unlock(); } diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index dd6a237dd..bb755bf40 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -230,14 +230,13 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, lock(); sendMessage( IdStartProcessing ); - unlock(); if( m_failed || _out_buf == NULL || m_outputCount == 0 ) { + unlock(); return false; } - lock(); waitForMessage( IdProcessingDone ); unlock(); From c2e9918c8ab402c04b29c54ae72ad2f88e48e635 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 19:28:47 +0100 Subject: [PATCH 066/133] VST Automation: crash prevention Fixed destuctors for various types of VST automation panel removal. Automated signals correct disconection on panel removal. When you load new VST plugin in the place of old one, while automation is already connected and active there, this should not result in LMMS crash or reuse of that old automation connections and automation window destructor should not leave zombie VST plugins on application exit. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 102 +++++++++++++++++++---- plugins/vestige/vestige.h | 1 + plugins/vst_effect/VstEffectControls.cpp | 78 +++++++++++++---- plugins/vst_effect/VstEffectControls.h | 1 + 4 files changed, 151 insertions(+), 31 deletions(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 46630d740..514d93f1e 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -117,13 +117,14 @@ void vestigeInstrument::loadSettings( const QDomElement & _this ) m_plugin->loadSettings( _this ); const QMap & dump = m_plugin->parameterDump(); - int paramCount = (dump).size(); + paramCount = dump.size(); char paramStr[35]; - vstKnobs = new knob *[paramCount]; - knobFModel = new FloatModel *[paramCount]; + vstKnobs = new knob *[ paramCount ]; + knobFModel = new FloatModel *[ paramCount ]; QStringList list1; QWidget * widget = new QWidget(); - for (int i = 0; i < paramCount; i++) { + for( int i = 0; i < paramCount; i++ ) + { sprintf( paramStr, "param%d", i); list1 = dump[paramStr].split(":"); @@ -167,9 +168,10 @@ void vestigeInstrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_plugin->saveSettings( _doc, _this ); if (knobFModel != NULL) { const QMap & dump = m_plugin->parameterDump(); - int paramCount = (dump).size(); + paramCount = dump.size(); char paramStr[35]; - for (int i = 0; i < paramCount; i++) { + for( int i = 0; i < paramCount; i++ ) + { if (knobFModel[i]->isAutomated() || knobFModel[i]->getControllerConnection()) { sprintf( paramStr, "param%d", i); knobFModel[i]->saveSettings( _doc, _this, paramStr ); @@ -223,7 +225,10 @@ void vestigeInstrument::loadFile( const QString & _file ) InstrumentTrack::tr( "Default preset" ); m_pluginMutex.unlock(); - closePlugin(); + if ( m_plugin != NULL ) + { + closePlugin(); + } m_pluginDLL = _file; textFloat * tf = textFloat::displayMessage( @@ -308,6 +313,51 @@ bool vestigeInstrument::handleMidiEvent( const midiEvent & _me, void vestigeInstrument::closePlugin( void ) { + // disconnect all signals + if( knobFModel != NULL ) + { + for( int i = 0; i < paramCount; i++ ) + { + delete knobFModel[ i ]; + delete vstKnobs[ i ]; + } + } + + if( vstKnobs != NULL ) + { + delete [] vstKnobs; + vstKnobs = NULL; + } + + if( knobFModel != NULL ) + { + delete [] knobFModel; + knobFModel = NULL; + } + + if( m_scrollArea != NULL ) + { +// delete m_scrollArea; + m_scrollArea = NULL; + } + + if( m_subWindow != NULL ) + { + m_subWindow->setAttribute( Qt::WA_DeleteOnClose ); + m_subWindow->close(); + + if( m_subWindow != NULL ) + { + delete m_subWindow; + } + m_subWindow = NULL; + } + + if( p_subWindow != NULL ) + { + p_subWindow = NULL; + } + m_pluginMutex.lock(); if( m_plugin ) { @@ -832,23 +882,24 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume l->addWidget( m_syncButton, 0, 0, 1, 2, Qt::AlignLeft ); const QMap & dump = m_vi->m_plugin->parameterDump(); - int paramCount = (dump).size(); + m_vi->paramCount = dump.size(); bool isVstKnobs = true; if (m_vi->vstKnobs == NULL) { - m_vi->vstKnobs = new knob *[paramCount]; + m_vi->vstKnobs = new knob *[ m_vi->paramCount ]; isVstKnobs = false; } if (m_vi->knobFModel == NULL) { - m_vi->knobFModel = new FloatModel *[paramCount]; + m_vi->knobFModel = new FloatModel *[ m_vi->paramCount ]; } char paramStr[35]; QStringList list1; if (isVstKnobs == false) { - for (int i = 0; i < paramCount; i++) { + for( int i = 0; i < m_vi->paramCount; i++ ) + { sprintf( paramStr, "param%d", i); list1 = dump[paramStr].split(":"); @@ -865,15 +916,19 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume } int i = 0; - for (int lrow = 0+1; lrow < (int(paramCount / 10) + 1)+1; lrow++) { - for (int lcolumn = 0; lcolumn < 10; lcolumn++) { - if (i < paramCount) + for( int lrow = 1; lrow < ( int( m_vi->paramCount / 10 ) + 1 ) + 1; lrow++ ) + { + for( int lcolumn = 0; lcolumn < 10; lcolumn++ ) + { + if( i < m_vi->paramCount ) + { l->addWidget( m_vi->vstKnobs[i], lrow, lcolumn, Qt::AlignCenter ); + } i++; } } - l->setRowStretch( (int(paramCount / 10) + 1), 1 ); + l->setRowStretch( ( int( m_vi->paramCount / 10) + 1), 1 ); l->setColumnStretch( 10, 1 ); widget->setLayout(l); @@ -911,10 +966,25 @@ void manageVestigeInstrumentView::syncPlugin( void ) manageVestigeInstrumentView::~manageVestigeInstrumentView() { + if( m_vi->knobFModel != NULL ) + { + for( int i = 0; i < m_vi->paramCount; i++ ) + { + delete m_vi->knobFModel[ i ]; + delete m_vi->vstKnobs[ i ]; + } + } + if (m_vi->vstKnobs != NULL) { delete []m_vi->vstKnobs; m_vi->vstKnobs = NULL; } + + if( m_vi->knobFModel != NULL ) + { + delete [] m_vi->knobFModel; + m_vi->knobFModel = NULL; + } if (m_vi->m_scrollArea != NULL) { delete m_vi->m_scrollArea; @@ -929,6 +999,8 @@ manageVestigeInstrumentView::~manageVestigeInstrumentView() delete m_vi->m_subWindow; m_vi->m_subWindow = NULL; } + + m_vi->p_subWindow = NULL; } diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 632b6dee6..0320fe4b6 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -92,6 +92,7 @@ private: knob ** vstKnobs; FloatModel ** knobFModel; QObject * p_subWindow; + int paramCount; friend class VestigeInstrumentView; diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index 866c8c4ed..abd22c5ee 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -69,13 +69,14 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) m_effect->m_plugin->loadSettings( _this ); const QMap & dump = m_effect->m_plugin->parameterDump(); - int paramCount = (dump).size(); + paramCount = dump.size(); char paramStr[35]; - vstKnobs = new knob *[paramCount]; - knobFModel = new FloatModel *[paramCount]; + vstKnobs = new knob *[ paramCount ]; + knobFModel = new FloatModel *[ paramCount ]; QStringList list1; QWidget * widget = new QWidget(); - for (int i = 0; i < paramCount; i++) { + for( int i = 0; i < paramCount; i++ ) + { sprintf( paramStr, "param%d", i); list1 = dump[paramStr].split(":"); @@ -120,13 +121,15 @@ void VstEffectControls::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_effect->m_plugin->saveSettings( _doc, _this ); if (knobFModel != NULL) { const QMap & dump = m_effect->m_plugin->parameterDump(); - int paramCount = (dump).size(); + paramCount = dump.size(); char paramStr[35]; - for (int i = 0; i < paramCount; i++) + for( int i = 0; i < paramCount; i++ ) + { if (knobFModel[i]->isAutomated() || knobFModel[i]->getControllerConnection()) { sprintf( paramStr, "param%d", i); knobFModel[i]->saveSettings( _doc, _this, paramStr ); } + } } } m_effect->m_pluginMutex.unlock(); @@ -321,17 +324,17 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * l->addWidget( m_syncButton, 0, 0, 1, 2, Qt::AlignLeft ); const QMap & dump = m_effect->m_plugin->parameterDump(); - int paramCount = (dump).size(); + m_vi->paramCount = dump.size(); bool isVstKnobs = true, isKnobFModel = true; if (m_vi->vstKnobs == NULL) { - m_vi->vstKnobs = new knob *[paramCount]; + m_vi->vstKnobs = new knob *[ m_vi->paramCount ]; isVstKnobs = false; } if (m_vi->knobFModel == NULL) { - m_vi->knobFModel = new FloatModel *[paramCount]; + m_vi->knobFModel = new FloatModel *[ m_vi->paramCount ]; isKnobFModel = false; } @@ -339,7 +342,8 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * QStringList list1; if (isVstKnobs == false) { - for (int i = 0; i < paramCount; i++) { + for( int i = 0; i < m_vi->paramCount; i++ ) + { sprintf( paramStr, "param%d", i); list1 = dump[paramStr].split(":"); @@ -356,15 +360,19 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * } int i = 0; - for (int lrow = 0+1; lrow < (int(paramCount / 10) + 1)+1; lrow++) { - for (int lcolumn = 0; lcolumn < 10; lcolumn++) { - if (i < paramCount) + for( int lrow = 1; lrow < ( int( m_vi->paramCount / 10 ) + 1 ) + 1; lrow++ ) + { + for( int lcolumn = 0; lcolumn < 10; lcolumn++ ) + { + if( i < m_vi->paramCount ) + { l->addWidget( m_vi->vstKnobs[i], lrow, lcolumn, Qt::AlignCenter ); + } i++; } } - l->setRowStretch( (int(paramCount / 10) + 1), 1 ); + l->setRowStretch( ( int( m_vi->paramCount / 10 ) + 1 ), 1 ); l->setColumnStretch( 10, 1 ); widget->setLayout(l); @@ -418,8 +426,46 @@ void manageVSTEffectView::setParameter( void ) manageVSTEffectView::~manageVSTEffectView() { - delete m_vi2->m_subWindow; - m_vi2->m_subWindow = NULL; + if( m_vi2->knobFModel != NULL ) + { + for( int i = 0; i < m_vi2->paramCount; i++ ) + { + delete m_vi2->knobFModel[ i ]; + delete m_vi2->vstKnobs[ i ]; + } + } + + if( m_vi2->vstKnobs != NULL ) + { + delete [] m_vi2->vstKnobs; + m_vi2->vstKnobs = NULL; + } + + if( m_vi2->knobFModel != NULL ) + { + delete [] m_vi2->knobFModel; + m_vi2->knobFModel = NULL; + } + + if( m_vi2->m_scrollArea != NULL ) + { + delete m_vi2->m_scrollArea; + m_vi2->m_scrollArea = NULL; + } + + if( m_vi2->m_subWindow != NULL ) + { + m_vi2->m_subWindow->setAttribute( Qt::WA_DeleteOnClose ); + m_vi2->m_subWindow->close(); + + if( m_vi2->m_subWindow != NULL ) + { + delete m_vi2->m_subWindow; + } + m_vi2->m_subWindow = NULL; + } + //delete m_vi2->m_subWindow; + //m_vi2->m_subWindow = NULL; } diff --git a/plugins/vst_effect/VstEffectControls.h b/plugins/vst_effect/VstEffectControls.h index a56c54fcc..1c92cfefe 100644 --- a/plugins/vst_effect/VstEffectControls.h +++ b/plugins/vst_effect/VstEffectControls.h @@ -88,6 +88,7 @@ private: QScrollArea * m_scrollArea; FloatModel ** knobFModel; knob ** vstKnobs; + int paramCount; QObject * ctrHandle; From e8bdc7b3c144fa3e3189396c3883dcadcf521eee Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 20:13:59 +0100 Subject: [PATCH 067/133] VST Automation: Sync button fix Only not automated values are synced from VST plugin, no need to sync already automated values. Synced values are now not trackable with undo / redo, before all changes were tracked individualy, but user could lose ability to undo easily changes before sync buton was pressed. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 23 +++++++++++++++-------- plugins/vst_effect/VstEffectControls.cpp | 23 +++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 514d93f1e..2f7914fd6 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -950,16 +950,23 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume void manageVestigeInstrumentView::syncPlugin( void ) { char paramStr[35]; - QStringList list1; + QStringList s_dumpValues; const QMap & dump = m_vi->m_plugin->parameterDump(); - float f; + float f_value; - for (int i = 0; i<(dump).size(); i++) { - sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); - f = (list1.at(2)).toFloat(); - m_vi->knobFModel[i]->setValue(f); - m_vi->knobFModel[i]->setInitValue(f); + for( int i = 0; i < m_vi->paramCount; i++ ) + { + // only not automated knobs are synced from VST + // those auto-setted values are not jurnaled, tracked for undo / redo + if( !( m_vi->knobFModel[ i ]->isAutomated() || + m_vi->knobFModel[ i ]->getControllerConnection() ) ) + { + sprintf( paramStr, "param%d", i ); + s_dumpValues = dump[ paramStr ].split( ":" ); + f_value = ( s_dumpValues.at( 2 ) ).toFloat(); + m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); + m_vi->knobFModel[ i ]->setInitValue( f_value ); + } } } diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index abd22c5ee..968aed2a4 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -394,16 +394,23 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * void manageVSTEffectView::syncPlugin( void ) { char paramStr[35]; - QStringList list1; + QStringList s_dumpValues; const QMap & dump = m_effect->m_plugin->parameterDump(); - float f; + float f_value; - for (int i = 0; i<(dump).size(); i++) { - sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); - f = (list1.at(2)).toFloat(); - m_vi2->knobFModel[i]->setValue(f); - m_vi2->knobFModel[i]->setInitValue(f); + for( int i = 0; i < m_vi2->paramCount; i++ ) + { + // only not automated knobs are synced from VST + // those auto-setted values are not jurnaled, tracked for undo / redo + if( !( m_vi2->knobFModel[ i ]->isAutomated() || + m_vi2->knobFModel[ i ]->getControllerConnection() ) ) + { + sprintf( paramStr, "param%d", i ); + s_dumpValues = dump[ paramStr ].split( ":" ); + f_value = ( s_dumpValues.at( 2 ) ).toFloat(); + m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); + m_vi2->knobFModel[ i ]->setInitValue( f_value ); + } } } From ba03b7045737108722a061c11d03cd40e5c1e094 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 20:50:25 +0100 Subject: [PATCH 068/133] State of VST controls in LMMS control wrapper is reloaded after Save/Load, from plugins state After project save/load unsaved VST control parameters in LMMS VST control wrapper are not set to zero now, but reloaded according plugins saved state. No need to sync values manualy again, after project save, load. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 20 ++++++++++++++------ plugins/vst_effect/VstEffectControls.cpp | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 2f7914fd6..ee54629b3 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -121,19 +121,27 @@ void vestigeInstrument::loadSettings( const QDomElement & _this ) char paramStr[35]; vstKnobs = new knob *[ paramCount ]; knobFModel = new FloatModel *[ paramCount ]; - QStringList list1; + QStringList s_dumpValues; QWidget * widget = new QWidget(); for( int i = 0; i < paramCount; i++ ) { - sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); + sprintf( paramStr, "param%d", i ); + s_dumpValues = dump[ paramStr ].split( ":" ); vstKnobs[i] = new knob( knobBright_26, widget ); - vstKnobs[i]->setHintText( list1.at(1) + ":", ""); - vstKnobs[i]->setLabel( list1.at(1).left(15) ); + vstKnobs[i]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); + vstKnobs[i]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); - knobFModel[i] = new FloatModel( (list1.at(2)).toFloat(), 0.0f, 1.0f, 0.01f, this, QString::number(i) ); + knobFModel[i] = new FloatModel( 0.0f, 0.0f, 1.0f, 0.01f, this, QString::number(i) ); knobFModel[i]->loadSettings( _this, paramStr ); + + if( !( knobFModel[ i ]->isAutomated() || + knobFModel[ i ]->getControllerConnection() ) ) + { + knobFModel[ i ]->setValue( ( s_dumpValues.at( 2 )).toFloat() ); + knobFModel[ i ]->setInitValue( ( s_dumpValues.at( 2 )).toFloat() ); + } + connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); vstKnobs[i]->setModel( knobFModel[i] ); diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index 968aed2a4..ec74eea6a 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -73,19 +73,27 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) char paramStr[35]; vstKnobs = new knob *[ paramCount ]; knobFModel = new FloatModel *[ paramCount ]; - QStringList list1; + QStringList s_dumpValues; QWidget * widget = new QWidget(); for( int i = 0; i < paramCount; i++ ) { - sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); + sprintf( paramStr, "param%d", i ); + s_dumpValues = dump[ paramStr ].split( ":" ); vstKnobs[i] = new knob( knobBright_26, widget ); - vstKnobs[i]->setHintText( list1.at(1) + ":", ""); - vstKnobs[i]->setLabel( list1.at(1).left(15) ); + vstKnobs[i]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); + vstKnobs[i]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); - knobFModel[i] = new FloatModel( (list1.at(2)).toFloat(), 0.0f, 1.0f, 0.01f, this, QString::number(i) ); + knobFModel[i] = new FloatModel( 0.0f, 0.0f, 1.0f, 0.01f, this, QString::number(i) ); knobFModel[i]->loadSettings( _this, paramStr ); + + if( !( knobFModel[ i ]->isAutomated() || + knobFModel[ i ]->getControllerConnection() ) ) + { + knobFModel[ i ]->setValue( (s_dumpValues.at( 2 ) ).toFloat() ); + knobFModel[ i ]->setInitValue( (s_dumpValues.at( 2 ) ).toFloat() ); + } + connect( knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); vstKnobs[i]->setModel( knobFModel[i] ); From 5b6fa164e74a2b7e27f9e3f242d3c4157b04885e Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 22:22:30 +0100 Subject: [PATCH 069/133] VST Automation: Filter for automated parameters Filter to display only automated / all knobs (new button) on LMMS VST parametr control window. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 38 ++++++++++++++++++++++++ plugins/vestige/vestige.h | 2 ++ plugins/vst_effect/VstEffectControls.cpp | 37 +++++++++++++++++++++++ plugins/vst_effect/VstEffectControls.h | 2 ++ 4 files changed, 79 insertions(+) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index ee54629b3..192d58d33 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -889,6 +889,19 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume l->addWidget( m_syncButton, 0, 0, 1, 2, Qt::AlignLeft ); + m_displayAutomatedOnly = new QPushButton( tr( "Automated" ), this ); + connect( m_displayAutomatedOnly, SIGNAL( clicked() ), this, + SLOT( displayAutomatedOnly() ) ); + m_displayAutomatedOnly->setWhatsThis( + tr( "Click here if you want to display automated parameters only." ) ); + + l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); + + for( int i = 0; i < 10; i++ ) + { + l->addItem( new QSpacerItem( 68, 45, QSizePolicy::Fixed, QSizePolicy::Fixed ), 0, i ); + } + const QMap & dump = m_vi->m_plugin->parameterDump(); m_vi->paramCount = dump.size(); @@ -979,6 +992,31 @@ void manageVestigeInstrumentView::syncPlugin( void ) } + + +void manageVestigeInstrumentView::displayAutomatedOnly( void ) +{ + bool isAuto = QString::compare( m_displayAutomatedOnly->text(), tr( "Automated" ) ) == 0; + + for( int i = 0; i< m_vi->paramCount; i++ ) + { + + if( !( m_vi->knobFModel[ i ]->isAutomated() || + m_vi->knobFModel[ i ]->getControllerConnection() ) ) + { + if( m_vi->vstKnobs[ i ]->isVisible() == true && isAuto ) + { + m_vi->vstKnobs[ i ]->hide(); + m_displayAutomatedOnly->setText( "All" ); + } else { + m_vi->vstKnobs[ i ]->show(); + m_displayAutomatedOnly->setText( "Automated" ); + } + } + } +} + + manageVestigeInstrumentView::~manageVestigeInstrumentView() { if( m_vi->knobFModel != NULL ) diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 0320fe4b6..299cdda3e 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -111,6 +111,7 @@ public: protected slots: void syncPlugin( void ); + void displayAutomatedOnly( void ); void setParameter( void ); @@ -128,6 +129,7 @@ private: QWidget *widget; QGridLayout * l; QPushButton * m_syncButton; + QPushButton * m_displayAutomatedOnly; } ; diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index ec74eea6a..c8b0766cc 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -331,6 +331,19 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * l->addWidget( m_syncButton, 0, 0, 1, 2, Qt::AlignLeft ); + m_displayAutomatedOnly = new QPushButton( tr( "Automated" ), widget ); + connect( m_displayAutomatedOnly, SIGNAL( clicked() ), this, + SLOT( displayAutomatedOnly() ) ); + m_displayAutomatedOnly->setWhatsThis( + tr( "Click here if you want to display automated parameters only." ) ); + + l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); + + for( int i = 0; i < 10; i++ ) + { + l->addItem( new QSpacerItem( 68, 45, QSizePolicy::Fixed, QSizePolicy::Fixed ), 0, i ); + } + const QMap & dump = m_effect->m_plugin->parameterDump(); m_vi->paramCount = dump.size(); @@ -424,6 +437,30 @@ void manageVSTEffectView::syncPlugin( void ) +void manageVSTEffectView::displayAutomatedOnly( void ) +{ + bool isAuto = QString::compare( m_displayAutomatedOnly->text(), tr( "Automated" ) ) == 0; + + for( int i = 0; i< m_vi2->paramCount; i++ ) + { + + if( !( m_vi2->knobFModel[ i ]->isAutomated() || + m_vi2->knobFModel[ i ]->getControllerConnection() ) ) + { + if( m_vi2->vstKnobs[ i ]->isVisible() == true && isAuto ) + { + m_vi2->vstKnobs[ i ]->hide(); + m_displayAutomatedOnly->setText( "All" ); + } else { + m_vi2->vstKnobs[ i ]->show(); + m_displayAutomatedOnly->setText( "Automated" ); + } + } + } +} + + + void manageVSTEffectView::setParameter( void ) { diff --git a/plugins/vst_effect/VstEffectControls.h b/plugins/vst_effect/VstEffectControls.h index 1c92cfefe..67622196f 100644 --- a/plugins/vst_effect/VstEffectControls.h +++ b/plugins/vst_effect/VstEffectControls.h @@ -113,6 +113,7 @@ public: protected slots: void syncPlugin( void ); + void displayAutomatedOnly( void ); void setParameter( void ); private: @@ -129,6 +130,7 @@ private: QGridLayout * l; QPushButton * m_syncButton; + QPushButton * m_displayAutomatedOnly; } ; From f4cc0373210038b3534316cdf62a772817c5f2ad Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 21:57:56 +0100 Subject: [PATCH 070/133] VST Control: Right window title for parameter manual edit When you double-click on some knob to change its VST parameter value manually, new dialog window now has same title as what was that knobs name. (instead of "lmms" title string) Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 16 ++++++++-------- plugins/vst_effect/VstEffectControls.cpp | 21 +++++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 192d58d33..32e82c252 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -128,7 +128,7 @@ void vestigeInstrument::loadSettings( const QDomElement & _this ) sprintf( paramStr, "param%d", i ); s_dumpValues = dump[ paramStr ].split( ":" ); - vstKnobs[i] = new knob( knobBright_26, widget ); + vstKnobs[i] = new knob( knobBright_26, widget, s_dumpValues.at( 1 ) ); vstKnobs[i]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); vstKnobs[i]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); @@ -916,21 +916,21 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume } char paramStr[35]; - QStringList list1; + QStringList s_dumpValues; if (isVstKnobs == false) { for( int i = 0; i < m_vi->paramCount; i++ ) { sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); + s_dumpValues = dump[ paramStr ].split( ":" ); - m_vi->vstKnobs[i] = new knob( knobBright_26, this ); - m_vi->vstKnobs[i]->setHintText( list1.at(1) + ":", ""); - m_vi->vstKnobs[i]->setLabel( list1.at(1).left(15) ); + m_vi->vstKnobs[ i ] = new knob( knobBright_26, this, s_dumpValues.at( 1 ) ); + m_vi->vstKnobs[ i ]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); + m_vi->vstKnobs[ i ]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); sprintf( paramStr, "%d", i); - m_vi->knobFModel[i] = new FloatModel( (list1.at(2)).toFloat(), 0.0f, 1.0f, 0.01f, - castModel(), tr( paramStr ) ); + m_vi->knobFModel[ i ] = new FloatModel( (s_dumpValues.at( 2 )).toFloat(), + 0.0f, 1.0f, 0.01f, castModel(), tr( paramStr ) ); connect( m_vi->knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); m_vi->vstKnobs[i] ->setModel( m_vi->knobFModel[i] ); } diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index c8b0766cc..3b1fd16ce 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -80,7 +80,7 @@ void VstEffectControls::loadSettings( const QDomElement & _this ) sprintf( paramStr, "param%d", i ); s_dumpValues = dump[ paramStr ].split( ":" ); - vstKnobs[i] = new knob( knobBright_26, widget ); + vstKnobs[i] = new knob( knobBright_26, widget, s_dumpValues.at( 1 ) ); vstKnobs[i]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); vstKnobs[i]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); @@ -360,23 +360,24 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * } char paramStr[35]; - QStringList list1; + QStringList s_dumpValues; if (isVstKnobs == false) { for( int i = 0; i < m_vi->paramCount; i++ ) { sprintf( paramStr, "param%d", i); - list1 = dump[paramStr].split(":"); + s_dumpValues = dump[ paramStr ].split( ":" ); - m_vi->vstKnobs[i] = new knob( knobBright_26, widget); - m_vi->vstKnobs[i]->setHintText( list1.at(1) + ":", ""); - m_vi->vstKnobs[i]->setLabel( list1.at(1).left(15) ); + m_vi->vstKnobs[ i ] = new knob( knobBright_26, widget, s_dumpValues.at( 1 ) ); + m_vi->vstKnobs[ i ]->setHintText( s_dumpValues.at( 1 ) + ":", "" ); + m_vi->vstKnobs[ i ]->setLabel( s_dumpValues.at( 1 ).left( 15 ) ); sprintf( paramStr, "%d", i); - m_vi->knobFModel[i] = new FloatModel( (list1.at(2)).toFloat(), 0.0f, 1.0f, 0.01f, - _eff, tr( paramStr ) ); - connect( m_vi->knobFModel[i], SIGNAL( dataChanged() ), this, SLOT( setParameter() ) ); - m_vi->vstKnobs[i] ->setModel( m_vi->knobFModel[i] ); + m_vi->knobFModel[ i ] = new FloatModel( ( s_dumpValues.at( 2 ) ).toFloat(), + 0.0f, 1.0f, 0.01f, _eff, tr( paramStr ) ); + connect( m_vi->knobFModel[ i ], SIGNAL( dataChanged() ), this, + SLOT( setParameter() ) ); + m_vi->vstKnobs[ i ] ->setModel( m_vi->knobFModel[ i ] ); } } From b9c926dabd887c0e90db265e6c3642d72bc4416e Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 3 Jan 2013 22:09:24 +0100 Subject: [PATCH 071/133] VST parametr control window: window title as trackname Window title for VSTi parameter controlling window should be set according to actual track name, not from VST plugin name as its now, it is hard to get to know which window belongs to where when same plugin is e.g. opened several times, so with the same name. Now this is handled in paint event, but TBD whenever is that track name changed. tbd, temporary solution. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 32e82c252..4a5a3d456 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -852,6 +852,11 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) m_vi->m_plugin->vendorString() ); p.drawText( 10, 225, m_vi->m_plugin->currentProgramName() ); } + + if( m_vi->m_subWindow != NULL ) + { + m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() ); + } // m_pluginMutex.unlock(); } @@ -872,7 +877,7 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume m_vi->m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); m_vi->m_subWindow->setFixedSize( 960, 300); m_vi->m_subWindow->setWidget(m_vi->m_scrollArea); - m_vi->m_subWindow->setWindowTitle(m_vi->m_plugin->name()); + m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() ); m_vi->m_subWindow->setWindowIcon( PLUGIN_NAME::getIconPixmap( "logo" ) ); //m_vi->m_subWindow->setAttribute(Qt::WA_DeleteOnClose); @@ -1114,6 +1119,7 @@ void manageVestigeInstrumentView::dropEvent( QDropEvent * _de ) void manageVestigeInstrumentView::paintEvent( QPaintEvent * ) { + m_vi->m_subWindow->setWindowTitle(m_vi->instrumentTrack()->name()); } From 1612a253496b34ff8b8a784c0bfe82c83e713c85 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 6 Jan 2013 23:13:59 +0100 Subject: [PATCH 072/133] ExportProjectDialog: fixed broken cancel button The cancel button functionality was lost during introduction of the multi track export functionality. Closes #3598342. --- src/gui/export_project_dialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index 93527c657..eba9123ee 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -1,7 +1,7 @@ /* * export_project_dialog.cpp - implementation of dialog for exporting project * - * Copyright (c) 2004-2012 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -103,6 +103,8 @@ void exportProjectDialog::reject() { (*it)->abortProcessing(); } + + QDialog::reject(); } From 99091e9ce36cc2b3034951b4648d22cad43315b5 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 6 Jan 2013 23:40:48 +0100 Subject: [PATCH 073/133] AutomatableModelView: added context menu action for removing song-global automation Up to now there was no possibility to remove song-global automation from a control once created. Overcome this issue by adding an according action in the context menu of all AutomatableModelView instances. --- include/AutomatableModelView.h | 3 ++- src/gui/AutomatableModelView.cpp | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/include/AutomatableModelView.h b/include/AutomatableModelView.h index b7c3579e7..6c59d8f61 100644 --- a/include/AutomatableModelView.h +++ b/include/AutomatableModelView.h @@ -1,7 +1,7 @@ /* * AutomatableModelView.h - class AutomatableModelView * - * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2008-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -101,6 +101,7 @@ public slots: void execConnectionDialog(); void removeConnection(); void editSongGlobalAutomation(); + void removeSongGlobalAutomation(); protected: diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index 744ba26f5..3d1995636 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -1,7 +1,7 @@ /* * AutomatableModelView.cpp - implementation of AutomatableModelView * - * Copyright (c) 2011 Tobias Doerffel + * Copyright (c) 2011-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -88,6 +88,12 @@ void AutomatableModelView::addDefaultActions( QMenu * _menu ) AutomatableModel::tr( "Edit song-global automation" ), amvSlots, SLOT( editSongGlobalAutomation() ) ); + + _menu->addAction( QPixmap(), + AutomatableModel::tr( "Remove song-global automation" ), + amvSlots, + SLOT( removeSongGlobalAutomation() ) ); + _menu->addSeparator(); QString controllerTxt; @@ -241,4 +247,11 @@ void AutomatableModelViewSlots::editSongGlobalAutomation() +void AutomatableModelViewSlots::removeSongGlobalAutomation() +{ + delete AutomationPattern::globalAutomationPattern( amv->modelUntyped() ); +} + + + #include "moc_AutomatableModelView.cxx" From 80106138c8bad310d5cefd3ecc7980253a627f4a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 7 Jan 2013 00:29:25 +0100 Subject: [PATCH 074/133] AutomationPattern: removed magic value at position zero In automation patterns there always had to be a value at position zero which also had a special semantic concerning the initial values of connected objects. However that logic was buggy and confusing. I therefore completely removed the neccessity for a value at position zero (automated value will be 0 until the first set point). --- include/AutomationPattern.h | 4 +- src/core/AutomationPattern.cpp | 145 ++++++++++++--------------------- 2 files changed, 54 insertions(+), 95 deletions(-) diff --git a/include/AutomationPattern.h b/include/AutomationPattern.h index 391283bf2..62bf92d06 100644 --- a/include/AutomationPattern.h +++ b/include/AutomationPattern.h @@ -2,7 +2,7 @@ * AutomationPattern.h - declaration of class AutomationPattern, which contains * all information about an automation pattern * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2013 Tobias Doerffel * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net @@ -109,6 +109,8 @@ public slots: private: + void cleanObjects(); + AutomationTrack * m_autoTrack; QVector m_idsToResolve; objectVector m_objects; diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index 003f75856..6ec1ce319 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -2,7 +2,7 @@ * AutomationPattern.cpp - implementation of class AutomationPattern which * holds dynamic values * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2013 Tobias Doerffel * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net @@ -45,7 +45,6 @@ AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) : m_hasAutomation( false ) { changeLength( midiTime( 1, 0 ) ); - m_timeMap[0] = 0; } @@ -101,12 +100,6 @@ void AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup ) if( addIt ) { m_objects += _obj; - // been empty before? - if( m_objects.size() == 1 && !hasAutomation() ) - { - // then initialize default-value - putValue( 0, _obj->value(), false ); - } connect( _obj, SIGNAL( destroyed( jo_id_t ) ), this, SLOT( objectDestroyed( jo_id_t ) ), Qt::DirectConnection ); @@ -152,6 +145,8 @@ midiTime AutomationPattern::putValue( const midiTime & _time, const float _value, const bool _quant_pos ) { + cleanObjects(); + midiTime newTime = _quant_pos && engine::automationEditor() ? note::quantized( _time, engine::automationEditor()->quantization() ) : @@ -159,43 +154,7 @@ midiTime AutomationPattern::putValue( const midiTime & _time, m_timeMap[newTime] = _value; - if( newTime == 0 ) - { - for( objectVector::iterator it = m_objects.begin(); - it != m_objects.end(); ) - { - if( *it ) - { - ( *it )->setValue( _value ); - ++it; - } - else - { - it = m_objects.erase( it ); - } - } - } - - // just one automation value? - if( m_timeMap.size() == 1 ) - { - m_hasAutomation = m_objects.isEmpty(); // usually false - for( objectVector::iterator it = m_objects.begin(); - it != m_objects.end(); ++it ) - { - // default value differs from current value? - if( *it && _value != ( *it )->initValue() ) - { - // then enable automating this object - m_hasAutomation = true; - } - } - } - else - { - // in all other cases assume we have automation - m_hasAutomation = true; - } + m_hasAutomation = true; // we need to maximize our length in case we're part of a hidden // automation track as the user can't resize this pattern @@ -214,41 +173,22 @@ midiTime AutomationPattern::putValue( const midiTime & _time, void AutomationPattern::removeValue( const midiTime & _time ) { - if( _time != 0 ) + cleanObjects(); + + m_timeMap.remove( _time ); + + if( getTrack() && + getTrack()->type() == track::HiddenAutomationTrack ) { - m_timeMap.remove( _time ); - - if( m_timeMap.size() == 1 ) - { - const float val = m_timeMap[0]; - m_hasAutomation = false; - for( objectVector::iterator it = m_objects.begin(); - it != m_objects.end(); ) - { - if( *it ) - { - ( *it )->setValue( val ); - if( ( *it )->initValue() != val ) - { - m_hasAutomation = true; - } - ++it; - } - else - { - it = m_objects.erase( it ); - } - } - } - - if( getTrack() && - getTrack()->type() == track::HiddenAutomationTrack ) - { - changeLength( length() ); - } - - emit dataChanged(); + changeLength( length() ); } + + if( m_timeMap.isEmpty() ) + { + m_hasAutomation = false; + } + + emit dataChanged(); } @@ -260,10 +200,22 @@ float AutomationPattern::valueAt( const midiTime & _time ) const { return 0; } - timeMap::const_iterator v = m_timeMap.lowerBound( _time ); + + if( m_timeMap.contains( _time ) ) + { + return m_timeMap[_time]; + } + // lowerBound returns next value with greater key, therefore we take // the previous element to get the current value - return ( v != m_timeMap.begin() ) ? (v-1).value() : v.value(); + timeMap::ConstIterator v = m_timeMap.lowerBound( _time ); + + if( v == m_timeMap.begin() ) + { + return 0; + } + + return (v-1).value(); } @@ -326,17 +278,7 @@ void AutomationPattern::loadSettings( const QDomElement & _this ) } m_hasAutomation = m_timeMap.size() > 0; - if( m_hasAutomation == false ) - { - for( objectVector::iterator it = m_objects.begin(); - it != m_objects.end(); ++it ) - { - if( *it ) - { - ( *it )->setValue( m_timeMap[0] ); - } - } - } + int len = _this.attribute( "len" ).toInt(); if( len <= 0 ) { @@ -366,7 +308,7 @@ const QString AutomationPattern::name() const void AutomationPattern::processMidiTime( const midiTime & _time ) { - if( _time >= 0 && m_hasAutomation ) + if( _time >= 0 && hasAutomation() ) { const float val = valueAt( _time ); for( objectVector::iterator it = m_objects.begin(); @@ -502,9 +444,9 @@ void AutomationPattern::resolveAllIDs() void AutomationPattern::clear() { - const float val = firstObject()->value(); m_timeMap.clear(); - putValue( 0, val ); + + emit dataChanged(); if( engine::automationEditor() && engine::automationEditor()->currentPattern() == this ) @@ -537,6 +479,21 @@ void AutomationPattern::objectDestroyed( jo_id_t _id ) +void AutomationPattern::cleanObjects() +{ + for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); ) + { + if( *it ) + { + ++it; + } + else + { + it = m_objects.erase( it ); + } + } +} + #include "moc_AutomationPattern.cxx" From 59300906d7ea4f1fc8f6856eca17a6305452384a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 7 Jan 2013 00:36:43 +0100 Subject: [PATCH 075/133] AutomationEditor: fixed drawing of empty automation patterns Due to recent changes to AutomationPattern the inner draw loop needs to be adjusted. --- src/gui/AutomationEditor.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/AutomationEditor.cpp b/src/gui/AutomationEditor.cpp index 1593d1c82..d87b7bf09 100644 --- a/src/gui/AutomationEditor.cpp +++ b/src/gui/AutomationEditor.cpp @@ -2,7 +2,7 @@ * AutomationEditor.cpp - implementation of AutomationEditor which is used for * actual setting of dynamic values * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2013 Tobias Doerffel * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net @@ -1424,7 +1424,8 @@ void AutomationEditor::paintEvent( QPaintEvent * _pe ) timeMap & time_map = m_pattern->getTimeMap(); timeMap::iterator it = time_map.begin(); p.setPen( QColor( 0xFF, 0xDF, 0x20 ) ); - do + + while( it != time_map.end() ) { Sint32 len_ticks = 4; @@ -1516,7 +1517,7 @@ void AutomationEditor::paintEvent( QPaintEvent * _pe ) } else printf("not in range\n"); ++it; - } while( it != time_map.end() ); + } } else { From 174037c31a1911d8d15cd834758a896d3872e016 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Mon, 7 Jan 2013 20:21:57 +0100 Subject: [PATCH 076/133] ExportProjectDialog: fix static file extension on multitrack export When using the multitrack export feature the output files always had the extension ".wav", even if exported as OGG. This patch fixes this issue. Closes #3595157. Signed-off-by: Tobias Doerffel --- include/export_project_dialog.h | 1 + src/gui/export_project_dialog.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/export_project_dialog.h b/include/export_project_dialog.h index eb0ba35ec..7f068e4b2 100644 --- a/include/export_project_dialog.h +++ b/include/export_project_dialog.h @@ -59,6 +59,7 @@ private slots: private: QString m_fileName; QString m_dirName; + QString m_fileExtension; typedef QVector RenderVector; RenderVector m_renderers; bool m_multiExport; diff --git a/src/gui/export_project_dialog.cpp b/src/gui/export_project_dialog.cpp index eba9123ee..2a385093c 100644 --- a/src/gui/export_project_dialog.cpp +++ b/src/gui/export_project_dialog.cpp @@ -39,6 +39,7 @@ exportProjectDialog::exportProjectDialog( const QString & _file_name, QDialog( _parent ), Ui::ExportProjectDialog(), m_fileName( _file_name ), + m_fileExtension(), m_multiExport(multi_export) { setupUi( this ); @@ -200,7 +201,7 @@ void exportProjectDialog::multiRender() m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); - QString name = QString("%1_%2.wav").arg(x++).arg(nextName); + QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); m_fileName = QDir(m_dirName).filePath(name); prepRender(); } @@ -221,7 +222,7 @@ void exportProjectDialog::multiRender() m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); - QString name = QString("%1_%2.wav").arg(x++).arg(nextName); + QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); m_fileName = QDir(m_dirName).filePath(name); prepRender(); } @@ -292,6 +293,7 @@ void exportProjectDialog::startBtnClicked() __fileEncodeDevices[i].m_description ) ) { m_ft = __fileEncodeDevices[i].m_fileFormat; + m_fileExtension = QString( QLatin1String( __fileEncodeDevices[i].m_extension ) ); break; } } From 9ec76136781097d0c2346515b594d2eceb2460b9 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Mon, 7 Jan 2013 21:14:04 +0100 Subject: [PATCH 077/133] InstrumentTrack: Add support for more MIDI commands MIDI commands All Notes Off, All Sound Off and Omni/Mono/Poly mode will now silence all playing notes, as they should. Signed-off-by: Tobias Doerffel --- include/midi.h | 10 ++++++++++ src/tracks/InstrumentTrack.cpp | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/midi.h b/include/midi.h index 61a9e8ebf..71a1d0f57 100644 --- a/include/midi.h +++ b/include/midi.h @@ -98,6 +98,16 @@ enum MidiStandardControllers MidiControllerSostenuto = 66, MidiControllerSoftPedal = 67, MidiControllerLegatoFootswitch = 68, + // Channel Mode Messages are controllers too... + MidiControllerAllSoundOff = 120, + MidiControllerResetAllControllers = 121, + MidiControllerLocalControl = 122, + MidiControllerAllNotesOff = 123, + MidiControllerOmniOn = 124, + MidiControllerOmniOff = 125, + MidiControllerMonoOn = 126, + MidiControllerPolyOn = 127, + }; const int MidiChannelCount = 16; diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 267af765b..016c54d3d 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -315,6 +315,17 @@ void InstrumentTrack::processInEvent( const midiEvent & _me, m_sustainPedalPressed = false; } } + if( _me.controllerNumber() == MidiControllerAllSoundOff || + _me.controllerNumber() == MidiControllerAllNotesOff || + _me.controllerNumber() == MidiControllerOmniOn || + _me.controllerNumber() == MidiControllerOmniOff || + _me.controllerNumber() == MidiControllerMonoOn || + _me.controllerNumber() == MidiControllerPolyOn ) + { + silenceAllNotes(); + } + m_instrument->handleMidiEvent( _me, _time ); + break; case MidiProgramChange: m_instrument->handleMidiEvent( _me, _time ); From e3e2e48b71a608bdbdb13fde50e1c826608a24f0 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 7 Jan 2013 22:06:37 +0100 Subject: [PATCH 078/133] MainWindow: pass optional parameter to toggleWindow() to force showing window There are use cases where we want to force to show a certain window. --- include/MainWindow.h | 6 +++--- src/gui/MainWindow.cpp | 23 ++++++++++------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/MainWindow.h b/include/MainWindow.h index a76fc320c..6335c6401 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -1,7 +1,7 @@ /* * main_window.h - declaration of class MainWindow, the main window of LMMS * - * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -106,7 +106,7 @@ public slots: void aboutLMMS( void ); void help( void ); void toggleAutomationEditorWin( void ); - void toggleBBEditorWin( void ); + void toggleBBEditorWin( bool forceShow = false ); void toggleSongEditorWin( void ); void toggleProjectNotesWin( void ); void toggleFxMixerWin( void ); @@ -132,7 +132,7 @@ private: void finalize( void ); - void toggleWindow( QWidget * _w ); + void toggleWindow( QWidget *window, bool forceShow = false ); QMdiArea * m_workspace; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 8a70355fd..98bf7e408 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1,9 +1,7 @@ -#ifndef SINGLE_SOURCE_COMPILE - /* * main_window.cpp - implementation of LMMS-main-window * - * Copyright (c) 2004-2011 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -810,16 +808,17 @@ void MainWindow::help( void ) -void MainWindow::toggleWindow( QWidget * _w ) +void MainWindow::toggleWindow( QWidget *window, bool forceShow ) { - QWidget * parent = _w->parentWidget(); + QWidget *parent = window->parentWidget(); - if( m_workspace->activeSubWindow() != parent - || parent->isHidden() ) + if( forceShow || + m_workspace->activeSubWindow() != parent || + parent->isHidden() ) { parent->show(); - _w->show(); - _w->setFocus(); + window->show(); + window->setFocus(); } else { @@ -836,9 +835,9 @@ void MainWindow::toggleWindow( QWidget * _w ) -void MainWindow::toggleBBEditorWin( void ) +void MainWindow::toggleBBEditorWin( bool forceShow ) { - toggleWindow( engine::getBBEditor() ); + toggleWindow( engine::getBBEditor(), forceShow ); } @@ -1072,5 +1071,3 @@ void MainWindow::autoSave() #include "moc_MainWindow.cxx" - -#endif From 27e2b5e4ea557b24762aa045271081231c698d32 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 7 Jan 2013 22:07:52 +0100 Subject: [PATCH 079/133] BbTrack: fixed openInBBEditor() to reliably show the BB Editor Use the function provided by MainWindow to reliably show the BB Editor. Thanks to Tres Finocchiaro for pointing out this issue. --- src/tracks/bb_track.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tracks/bb_track.cpp b/src/tracks/bb_track.cpp index db258a7f8..f4ba75dbc 100644 --- a/src/tracks/bb_track.cpp +++ b/src/tracks/bb_track.cpp @@ -1,7 +1,7 @@ /* * bb_track.cpp - implementation of class bbTrack and bbTCO * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -33,6 +33,7 @@ #include "embed.h" #include "engine.h" #include "gui_templates.h" +#include "MainWindow.h" #include "mixer.h" #include "rename_dialog.h" #include "song.h" @@ -223,10 +224,9 @@ void bbTCOView::paintEvent( QPaintEvent * ) void bbTCOView::openInBBEditor() { - engine::getBBTrackContainer()->setCurrentBB( bbTrack::numOfBBTrack( - m_bbTCO->getTrack() ) ); - engine::getBBEditor()->show(); - engine::getBBEditor()->setFocus(); + engine::getBBTrackContainer()->setCurrentBB( bbTrack::numOfBBTrack( m_bbTCO->getTrack() ) ); + + engine::mainWindow()->toggleBBEditorWin( true ); } From 128d94b261708f3f0859b3010eeb8b416440fde2 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 16:56:49 +0100 Subject: [PATCH 080/133] German localization: fixed wrong chord name translation The chord name 6add9 should stay 6add9. --- data/locale/de.qm | Bin 191705 -> 191705 bytes data/locale/de.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data/locale/de.qm b/data/locale/de.qm index 57adb48786c123df98d171a13d94f9d3a9661295..0d300fb4e46ff3368201799955f0875081c24e15 100644 GIT binary patch delta 21 dcmcb4ll$gP?hXGN7|og*+Zq|SH8S1I1^|393TXfU delta 26 icmcb4ll$gP?hXGNCUZ^{ZWd~67iwhOF4V|$JR1P8nhTi# diff --git a/data/locale/de.ts b/data/locale/de.ts index 6c22a9ae8..cdeb4810d 100644 --- a/data/locale/de.ts +++ b/data/locale/de.ts @@ -539,7 +539,7 @@ If you're interested in translating LMMS in another language or want to imp 6add9 - madd9 + 6add9 m6 From 1a981f50c85e68987df1f77a841c2393edf608d7 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 16:57:35 +0100 Subject: [PATCH 081/133] InstrumentFunctions/ChordCreator: fixed wrong 6add9 chord The 6add9 played a normal major chord which is wrong and has been fixed. Thanks to Mike804 for reporting this bug. Closes #3600618. --- src/core/InstrumentFunctions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 30c3aef6b..5b663b401 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -1,7 +1,7 @@ /* * InstrumentFunctions.cpp - models for instrument-function-tab * - * Copyright (c) 2004-2010 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -48,7 +48,7 @@ ChordCreator::ChordTable::Init ChordCreator::ChordTable::s_initTable[] = { QT_TRANSLATE_NOOP( "ChordCreator", "6" ), { 0, 4, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "6sus4" ), { 0, 5, 7, 9, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "6add9" ), { 0, 4, 7, 12, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "6add9" ), { 0, 4, 7, 9, 14, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "m6" ), { 0, 3, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "m6add9" ), { 0, 3, 7, 9, 14, -1 } }, From 4a1642abc7d3369fb0cab5a2f641287c873d1d20 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 17:45:51 +0100 Subject: [PATCH 082/133] InstrumentFunctions/ChordCreator: fixed wrong 6add9 chord Various seventh chords were wrong and have been fixed. Thanks to Mike804 for pointing out issues with some chords. Closes #3600618. --- src/core/InstrumentFunctions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 5b663b401..05804d4c6 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -56,11 +56,11 @@ ChordCreator::ChordTable::Init ChordCreator::ChordTable::s_initTable[] = { QT_TRANSLATE_NOOP( "ChordCreator", "7sus4" ), { 0, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "7#5" ), { 0, 4, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "7b5" ), { 0, 4, 6, 10, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "7#9" ), { 0, 4, 7, 10, 13, 18, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "7b9" ), { 0, 4, 7, 10, 13, 16, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "7#5#9" ), { 0, 4, 8, 12, 14, 19, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "7#5b9" ), { 0, 4, 8, 12, 14, 17, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "7b5b9" ), { 0, 4, 6, 10, 12, 15, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "7#9" ), { 0, 4, 7, 10, 15, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "7b9" ), { 0, 4, 7, 10, 13, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "7#5#9" ), { 0, 4, 8, 10, 15, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "7#5b9" ), { 0, 4, 8, 10, 13, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "7b5b9" ), { 0, 4, 6, 10, 13, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "7add11" ), { 0, 4, 7, 10, 17, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "7add13" ), { 0, 4, 7, 10, 21, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "7#11" ), { 0, 4, 7, 10, 18, -1 } }, From 6940ceca44e99975c09fb6a680d00974dd693755 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 18:13:18 +0100 Subject: [PATCH 083/133] InstrumentFunctions: added natural minor scale A natural minor scale was missing which lead to some irritations about the harmonic and melodic minor scales. Had to add the new scale at the end of the chord table in order to not break existing projects and presets in which simple chord table indices are saved. Closes #3594824. --- src/core/InstrumentFunctions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 05804d4c6..5868d19b4 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -130,6 +130,7 @@ ChordCreator::ChordTable::Init ChordCreator::ChordTable::s_initTable[] = { QT_TRANSLATE_NOOP( "ChordCreator", "Mixolydian" ), { 0, 2, 4, 5, 7, 9, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Aeolian" ), { 0, 2, 3, 5, 7, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Locrian" ), { 0, 1, 3, 5, 6, 8, 10, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "Minor" ), { 0, 2, 3, 5, 7, 8, 10, -1 } }, } ; From 99efe8aef32dc8033efb4e78110582ecf6d67da9 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 18:48:35 +0100 Subject: [PATCH 084/133] LocalZynAddSubFx: include/forward MIDI channel information When sending MIDI events to the ZynAddSubFX engine do not statically send them on channel 0 but on the MIDI channel which is set as output MIDI channel of the instrument track. This adds some flexibility when it comes to multipart ZynAddSubFX presets which are connected to different MIDI channels. --- plugins/zynaddsubfx/LocalZynAddSubFx.cpp | 12 ++++++------ plugins/zynaddsubfx/ZynAddSubFx.cpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/zynaddsubfx/LocalZynAddSubFx.cpp b/plugins/zynaddsubfx/LocalZynAddSubFx.cpp index bd270816f..24e4ff182 100644 --- a/plugins/zynaddsubfx/LocalZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/LocalZynAddSubFx.cpp @@ -1,7 +1,7 @@ /* * LocalZynAddSubFx.cpp - local implementation of ZynAddSubFx plugin * - * Copyright (c) 2009-2010 Tobias Doerffel + * Copyright (c) 2009-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -206,10 +206,10 @@ void LocalZynAddSubFx::processMidiEvent( const midiEvent & _e ) } if( m_runningNotes[_e.key()] > 0 ) { - m_master->NoteOff( 0, _e.key() ); + m_master->NoteOff( _e.channel(), _e.key() ); } ++m_runningNotes[_e.key()]; - m_master->NoteOn( 0, _e.key(), _e.velocity() ); + m_master->NoteOn( _e.channel(), _e.key(), _e.velocity() ); break; } case MidiNoteOff: @@ -219,16 +219,16 @@ void LocalZynAddSubFx::processMidiEvent( const midiEvent & _e ) } if( --m_runningNotes[_e.key()] <= 0 ) { - m_master->NoteOff( 0, _e.key() ); + m_master->NoteOff( _e.channel(), _e.key() ); } break; case MidiPitchBend: - m_master->SetController( 0, C_pitchwheel, + m_master->SetController( _e.channel(), C_pitchwheel, _e.m_data.m_param[0] + _e.m_data.m_param[1]*128-8192 ); break; case MidiControlChange: - m_master->SetController( 0, + m_master->SetController( _e.channel(), midiIn.getcontroller( _e.m_data.m_param[0] ), _e.m_data.m_param[1] ); break; diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index f1483c19b..5ad842a00 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -1,7 +1,7 @@ /* * ZynAddSubFx.cpp - ZynAddSubxFX-embedding plugin * - * Copyright (c) 2008-2010 Tobias Doerffel + * Copyright (c) 2008-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -450,7 +450,7 @@ void ZynAddSubFxInstrument::initPlugin() void ZynAddSubFxInstrument::sendControlChange( MidiControllers midiCtl, float value ) { - handleMidiEvent( midiEvent( MidiControlChange, 0, midiCtl, (int) value, this ), + handleMidiEvent( midiEvent( MidiControlChange, instrumentTrack()->midiPort()->realOutputChannel(), midiCtl, (int) value, this ), midiTime() ); } From b784d3daf05a3b5943b960a28795c8f8d77dc730 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Tue, 8 Jan 2013 12:59:46 +0100 Subject: [PATCH 085/133] VST Effects: fix for uncontrollable plugin window Supposed fix for 3595560, see also http://sourceforge.net/apps/phpbb/lmms/viewtopic.php?f=7&t=778 http://sourceforge.net/tracker/?func=detail&aid=3595560&group_id=105168&atid=640434 VST effects pluginWidget is not deleted, when we close General Settings of instrument track, on which are directly attached VST effects, but only when we remove this VST effects or tracks itself. Signed-off-by: Tobias Doerffel --- plugins/vst_effect/VstEffect.cpp | 4 ++++ plugins/vst_effect/VstEffectControlDialog.cpp | 2 +- src/gui/widgets/pixmap_button.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/vst_effect/VstEffect.cpp b/plugins/vst_effect/VstEffect.cpp index 2ada8ca14..a6286d38e 100644 --- a/plugins/vst_effect/VstEffect.cpp +++ b/plugins/vst_effect/VstEffect.cpp @@ -162,6 +162,10 @@ void VstEffect::openPlugin( const QString & _plugin ) void VstEffect::closePlugin() { m_pluginMutex.lock(); + if( m_plugin->pluginWidget() != NULL ) + { + delete m_plugin->pluginWidget(); + } delete m_plugin; m_plugin = NULL; m_pluginMutex.unlock(); diff --git a/plugins/vst_effect/VstEffectControlDialog.cpp b/plugins/vst_effect/VstEffectControlDialog.cpp index bb012b93b..ce598fade 100644 --- a/plugins/vst_effect/VstEffectControlDialog.cpp +++ b/plugins/vst_effect/VstEffectControlDialog.cpp @@ -226,7 +226,7 @@ void VstEffectControlDialog::paintEvent( QPaintEvent * ) VstEffectControlDialog::~VstEffectControlDialog() { - delete m_pluginWidget; + //delete m_pluginWidget; } diff --git a/src/gui/widgets/pixmap_button.cpp b/src/gui/widgets/pixmap_button.cpp index 0320b4932..795915da5 100644 --- a/src/gui/widgets/pixmap_button.cpp +++ b/src/gui/widgets/pixmap_button.cpp @@ -57,7 +57,7 @@ void pixmapButton::paintEvent( QPaintEvent * ) { QPainter p( this ); - if( model()->value() || m_pressed ) + if( model() != NULL && model()->value() || m_pressed ) { if( !m_activePixmap.isNull() ) { From d943182a6c7af0cd0bb5b8dadf37276d94d4bf58 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sun, 13 Jan 2013 19:07:12 +0100 Subject: [PATCH 086/133] ZynAddSubFX: increased polyphony to 128 It occured to me that ZASF reported polyphony overflows when using it in combination with LMMS' arpeggio and chord functionality. --- plugins/zynaddsubfx/src/globals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/zynaddsubfx/src/globals.h b/plugins/zynaddsubfx/src/globals.h index 7c0717168..9f52c3569 100644 --- a/plugins/zynaddsubfx/src/globals.h +++ b/plugins/zynaddsubfx/src/globals.h @@ -93,7 +93,7 @@ extern int OSCIL_SIZE; /* * The poliphony (notes) */ -#define POLIPHONY 60 +#define POLIPHONY 128 /* * Number of system effects From 071358bd98f7b039044d6f80c403fe4c66d0124b Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 14 Jan 2013 23:06:02 +0100 Subject: [PATCH 087/133] PixmapButton: added parentheses to fix ambiguity Fixes compiler warning. --- src/gui/widgets/pixmap_button.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/widgets/pixmap_button.cpp b/src/gui/widgets/pixmap_button.cpp index 795915da5..a7b5bcf64 100644 --- a/src/gui/widgets/pixmap_button.cpp +++ b/src/gui/widgets/pixmap_button.cpp @@ -2,7 +2,7 @@ * pixmap_button.cpp - implementation of pixmap-button (often used as "themed" * checkboxes/radiobuttons etc) * - * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -57,7 +57,7 @@ void pixmapButton::paintEvent( QPaintEvent * ) { QPainter p( this ); - if( model() != NULL && model()->value() || m_pressed ) + if( ( model() != NULL && model()->value() ) || m_pressed ) { if( !m_activePixmap.isNull() ) { From a184bc039e8ac3064491772fd4d1d512619ac903 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 14 Jan 2013 23:13:21 +0100 Subject: [PATCH 088/133] Sf2Player: update track name after loading file For consistent behaviour with other plugins such as AudioFileProcessor and ZynAddSubFX update the track name after loading a file. --- plugins/sf2_player/sf2_player.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index 93ddb324e..453ac05e6 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -2,7 +2,7 @@ * sf2_player.cpp - a soundfont2 player using fluidSynth * * Copyright (c) 2008 Paul Giblock - * Copyright (c) 2009-2010 Tobias Doerffel + * Copyright (c) 2009-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -389,6 +389,8 @@ void sf2Instrument::openFile( const QString & _sf2File ) } delete[] sf2Ascii; + + instrumentTrack()->setName( QFileInfo( _sf2File ).baseName() ); } From 1c9c76f3994a2054b5f4cb2f6b24c7f9b7fd1a10 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Tue, 15 Jan 2013 00:13:21 +0100 Subject: [PATCH 089/133] VST GUI fixes, improvements Various fixes improvements: + Added support for VST parameters control for windows. + New `Close` button for VST parameter controls. + Faster GUI for all instruments, effects-loading, not only VSTs, and both one-instrument track window mode and normal window mode should be supported. + Better integration for VST GUIs on Linux, e.g. plugin window should not stay always on top of other windows. + VST GUI overlook should remain same with different wine setups ( except for whole virtual desktops emulations ). + VST effect control window merged with VST effect editor window should be more easier to control. + Little corections at effectviews model updates of instrument tracks effect chains. Signed-off-by: Tobias Doerffel --- plugins/vestige/vestige.cpp | 34 ++++-- plugins/vestige/vestige.h | 2 + plugins/vst_base/RemoteVstPlugin.cpp | 8 +- plugins/vst_base/VstPlugin.cpp | 45 ++++++-- plugins/vst_base/VstPlugin.h | 2 +- plugins/vst_effect/VstEffectControlDialog.cpp | 100 +++++++++++------- plugins/vst_effect/VstEffectControlDialog.h | 2 + plugins/vst_effect/VstEffectControls.cpp | 20 +++- plugins/vst_effect/VstEffectControls.h | 2 + src/gui/widgets/EffectRackView.cpp | 23 ++-- src/gui/widgets/EffectView.cpp | 16 ++- src/tracks/InstrumentTrack.cpp | 6 +- 12 files changed, 190 insertions(+), 70 deletions(-) diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index 4a5a3d456..eeb536f58 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -263,7 +263,7 @@ void vestigeInstrument::loadFile( const QString & _file ) return; } - m_plugin->showEditor(); + m_plugin->showEditor( NULL, false ); if( set_ch_name ) { @@ -743,8 +743,7 @@ void VestigeInstrumentView::selPreset( void ) void VestigeInstrumentView::toggleGUI( void ) { - QMutexLocker ml( &m_vi->m_pluginMutex ); - if( m_vi->m_plugin == NULL ) + if( m_vi == NULL || m_vi->m_plugin == NULL ) { return; } @@ -848,14 +847,15 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) p.setPen( QColor( 251, 41, 8 ) ); f.setBold( false ); p.setFont( pointSize<8>( f ) ); - p.drawText( 10, 114, tr( "by" ) + " " + + p.drawText( 10, 114, tr( "by " ) + m_vi->m_plugin->vendorString() ); p.drawText( 10, 225, m_vi->m_plugin->currentProgramName() ); } if( m_vi->m_subWindow != NULL ) { - m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() ); + m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() + + tr( " - VST plugin control" ) ); } // m_pluginMutex.unlock(); } @@ -877,7 +877,8 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume m_vi->m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); m_vi->m_subWindow->setFixedSize( 960, 300); m_vi->m_subWindow->setWidget(m_vi->m_scrollArea); - m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() ); + m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() + + tr( " - VST plugin control" ) ); m_vi->m_subWindow->setWindowIcon( PLUGIN_NAME::getIconPixmap( "logo" ) ); //m_vi->m_subWindow->setAttribute(Qt::WA_DeleteOnClose); @@ -902,6 +903,16 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); + + m_closeButton = new QPushButton( tr( " Close " ), widget ); + connect( m_closeButton, SIGNAL( clicked() ), this, + SLOT( closeWindow() ) ); + m_closeButton->setWhatsThis( + tr( "Close VST plugin knob-controller window." ) ); + + l->addWidget( m_closeButton, 0, 2, 1, 7, Qt::AlignLeft ); + + for( int i = 0; i < 10; i++ ) { l->addItem( new QSpacerItem( 68, 45, QSizePolicy::Fixed, QSizePolicy::Fixed ), 0, i ); @@ -973,6 +984,14 @@ manageVestigeInstrumentView::manageVestigeInstrumentView( Instrument * _instrume +void manageVestigeInstrumentView::closeWindow() +{ + m_vi->m_subWindow->hide(); +} + + + + void manageVestigeInstrumentView::syncPlugin( void ) { char paramStr[35]; @@ -1119,7 +1138,8 @@ void manageVestigeInstrumentView::dropEvent( QDropEvent * _de ) void manageVestigeInstrumentView::paintEvent( QPaintEvent * ) { - m_vi->m_subWindow->setWindowTitle(m_vi->instrumentTrack()->name()); + m_vi->m_subWindow->setWindowTitle( m_vi->instrumentTrack()->name() + + tr( " - VST plugin control" ) ); } diff --git a/plugins/vestige/vestige.h b/plugins/vestige/vestige.h index 299cdda3e..600e87e22 100644 --- a/plugins/vestige/vestige.h +++ b/plugins/vestige/vestige.h @@ -113,6 +113,7 @@ protected slots: void syncPlugin( void ); void displayAutomatedOnly( void ); void setParameter( void ); + void closeWindow(); protected: @@ -130,6 +131,7 @@ private: QGridLayout * l; QPushButton * m_syncButton; QPushButton * m_displayAutomatedOnly; + QPushButton * m_closeButton; } ; diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 40d02cacb..2344d1060 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -558,10 +558,12 @@ void RemoteVstPlugin::initEditor() } #ifdef LMMS_BUILD_LINUX - m_window = CreateWindowEx( 0, "LVSL", m_shortName.c_str(), - ( WS_OVERLAPPEDWINDOW | WS_THICKFRAME ) & ~WS_MAXIMIZEBOX, - 0, 0, 10, 10, NULL, NULL, hInst, NULL ); + //m_window = CreateWindowEx( 0, "LVSL", m_shortName.c_str(), + // ( WS_OVERLAPPEDWINDOW | WS_THICKFRAME ) & ~WS_MAXIMIZEBOX, + // 0, 0, 10, 10, NULL, NULL, hInst, NULL ); + m_window = CreateWindowEx( 0 , "LVSL", m_shortName.c_str(), + WS_POPUP | WS_SYSMENU | WS_BORDER , 0, 0, 10, 10, NULL, NULL, hInst, NULL); #else m_windowID = 1; // arbitrary value on win32 to signal // vstPlugin-class that we have an editor diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index c986de2d8..e548f3a9b 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -49,6 +49,7 @@ #include "MainWindow.h" #include "song.h" #include "templates.h" +#include class vstSubWin : public QMdiSubWindow @@ -200,11 +201,24 @@ void VstPlugin::tryLoad( const QString &remoteVstPluginExecutable ) -void VstPlugin::showEditor( QWidget * _parent ) +void VstPlugin::showEditor( QWidget * _parent, bool isEffect ) { QWidget * w = pluginWidget(); if( w ) { +#ifdef LMMS_BUILD_WIN32 + // hide sw, plugin window wrapper on win32 + // this is obtained from pluginWidget() + if( isEffect ) + { + w->setWindowFlags( Qt::FramelessWindowHint ); + w->setAttribute( Qt::WA_TranslucentBackground ); + } + else + { + w->setWindowFlags( Qt::WindowCloseButtonHint ); + } +#endif w->show(); return; } @@ -222,13 +236,30 @@ void VstPlugin::showEditor( QWidget * _parent ) { vstSubWin * sw = new vstSubWin( engine::mainWindow()->workspace() ); - sw->setWidget( m_pluginWidget ); + if( isEffect ) + { + sw->setAttribute( Qt::WA_TranslucentBackground ); + sw->setWindowFlags( Qt::FramelessWindowHint ); + sw->setWidget( m_pluginWidget ); + + QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); + xe->embedClient( m_pluginWindowID ); + xe->setFixedSize( m_pluginGeometry ); + xe->show(); + } + else + { + sw->setWindowFlags( Qt::WindowCloseButtonHint ); + sw->setWidget( m_pluginWidget ); + + QX11EmbedContainer * xe = new QX11EmbedContainer( sw ); + xe->embedClient( m_pluginWindowID ); + xe->setFixedSize( m_pluginGeometry ); + xe->move( 4, 24 ); + xe->show(); + } } - QX11EmbedContainer * xe = new QX11EmbedContainer( m_pluginWidget ); - xe->embedClient( m_pluginWindowID ); - xe->setFixedSize( m_pluginGeometry ); - xe->show(); #endif if( m_pluginWidget ) @@ -258,7 +289,7 @@ void VstPlugin::loadSettings( const QDomElement & _this ) { if( _this.attribute( "guivisible" ).toInt() ) { - showEditor(); + showEditor( NULL, false ); } else { diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index c932d50aa..992e59ff0 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -53,7 +53,7 @@ public: return m_pluginWindowID != 0; } - void showEditor( QWidget * _parent = NULL ); + void showEditor( QWidget * _parent = NULL, bool isEffect = false ); void hideEditor(); inline const QString & name() const diff --git a/plugins/vst_effect/VstEffectControlDialog.cpp b/plugins/vst_effect/VstEffectControlDialog.cpp index ce598fade..a77fcdf73 100644 --- a/plugins/vst_effect/VstEffectControlDialog.cpp +++ b/plugins/vst_effect/VstEffectControlDialog.cpp @@ -36,34 +36,50 @@ #include #include - +#include "gui_templates.h" + VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : EffectControlDialog( _ctl ), - m_pluginWidget( NULL ) + m_pluginWidget( NULL ), + m_plugin( NULL ) { QGridLayout * l = new QGridLayout( this ); - l->setContentsMargins( 20, 10, 10, 10 ); + l->setContentsMargins( 10, 10, 10, 10 ); l->setVerticalSpacing( 2 ); l->setHorizontalSpacing( 2 ); -#ifdef LMMS_BUILD_LINUX - _ctl->m_effect->m_plugin->showEditor(); - m_pluginWidget = _ctl->m_effect->m_plugin->pluginWidget(); + if( _ctl != NULL && _ctl->m_effect != NULL && + _ctl->m_effect->m_plugin != NULL ) + { + m_plugin = _ctl->m_effect->m_plugin; + m_plugin->showEditor( NULL, true ); + m_pluginWidget = m_plugin->pluginWidget(); + +#ifdef LMMS_BUILD_WIN32 + + if( !m_pluginWidget ) + { + m_pluginWidget = m_plugin->pluginWidget( false ); + } +#endif + } + if( m_pluginWidget ) { setWindowTitle( m_pluginWidget->windowTitle() ); QPushButton * btn = new QPushButton( tr( "Show/hide VST FX GUI" ) ); btn->setCheckable( true ); - l->addWidget( btn, 0, 0, 1, 13, Qt::AlignCenter ); + l->addWidget( btn, 0, 0, 1, 80, Qt::AlignLeft ); connect( btn, SIGNAL( toggled( bool ) ), m_pluginWidget, SLOT( setVisible( bool ) ) ); + emit btn->click(); + btn->setMinimumWidth( 200 ); btn->setMaximumWidth( 200 ); btn->setMinimumHeight( 24 ); btn->setMaximumHeight( 24 ); - m_managePluginButton = new pixmapButton( this, "" ); m_managePluginButton->setCheckable( false ); m_managePluginButton->setCursor( Qt::PointingHandCursor ); @@ -78,13 +94,11 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_managePluginButton->setWhatsThis( tr( "Click here, if you want to control VST-plugin from host." ) ); - m_managePluginButton->setMinimumWidth( 21 ); m_managePluginButton->setMaximumWidth( 21 ); m_managePluginButton->setMinimumHeight( 21 ); m_managePluginButton->setMaximumHeight( 21 ); - m_openPresetButton = new pixmapButton( this, "" ); m_openPresetButton->setCheckable( false ); m_openPresetButton->setCursor( Qt::PointingHandCursor ); @@ -104,7 +118,6 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_openPresetButton->setMinimumHeight( 16 ); m_openPresetButton->setMaximumHeight( 16 ); - m_rolLPresetButton = new pixmapButton( this, "" ); m_rolLPresetButton->setCheckable( false ); m_rolLPresetButton->setCursor( Qt::PointingHandCursor ); @@ -114,6 +127,10 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : "stepper-left" ) ); connect( m_rolLPresetButton, SIGNAL( clicked() ), _ctl, SLOT( rolrPreset() ) ); + + connect( m_rolLPresetButton, SIGNAL( clicked() ), this, + SLOT( update() ) ); + toolTip::add( m_rolLPresetButton, tr( "Previous (-)" ) ); m_rolLPresetButton->setShortcut( Qt::Key_Minus ); @@ -126,7 +143,6 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_rolLPresetButton->setMinimumHeight( 16 ); m_rolLPresetButton->setMaximumHeight( 16 ); - m_rolRPresetButton = new pixmapButton( this, "" ); m_rolRPresetButton->setCheckable( false ); m_rolRPresetButton->setCursor( Qt::PointingHandCursor ); @@ -136,6 +152,10 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : "stepper-right" ) ); connect( m_rolRPresetButton, SIGNAL( clicked() ), _ctl, SLOT( rollPreset() ) ); + + connect( m_rolRPresetButton, SIGNAL( clicked() ), this, + SLOT( update() ) ); + toolTip::add( m_rolRPresetButton, tr( "Next (+)" ) ); m_rolRPresetButton->setShortcut( Qt::Key_Plus ); @@ -148,8 +168,6 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_rolRPresetButton->setMinimumHeight( 16 ); m_rolRPresetButton->setMaximumHeight( 16 ); - - _ctl->m_selPresetButton = new QPushButton( tr( "" ), this ); _ctl->m_selPresetButton->setCheckable( false ); @@ -160,13 +178,11 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : _ctl->m_selPresetButton->setMenu(_ctl->menu); - _ctl->m_selPresetButton->setMinimumWidth( 16 ); _ctl->m_selPresetButton->setMaximumWidth( 16 ); _ctl->m_selPresetButton->setMinimumHeight( 16 ); _ctl->m_selPresetButton->setMaximumHeight( 16 ); - m_savePresetButton = new pixmapButton( this, "" ); m_savePresetButton->setCheckable( false ); m_savePresetButton->setCursor( Qt::PointingHandCursor ); @@ -181,36 +197,28 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_savePresetButton->setWhatsThis( tr( "Click here, if you want to save current VST-plugin preset program." ) ); - m_savePresetButton->setMinimumWidth( 21 ); m_savePresetButton->setMaximumWidth( 21 ); m_savePresetButton->setMinimumHeight( 21 ); m_savePresetButton->setMaximumHeight( 21 ); + for( int i = 0; i < 13; i++ ) + { + l->addItem( new QSpacerItem( 15, 30, QSizePolicy::Fixed, + QSizePolicy::Fixed ), 1, i ); + } + l->addWidget( m_openPresetButton, 1, 6, 1, 1, Qt::AlignLeft ); + l->addWidget( m_rolLPresetButton, 1, 3, 1, 1, Qt::AlignLeft ); + l->addWidget( m_rolRPresetButton, 1, 4, 1, 1, Qt::AlignLeft ); + l->addWidget( _ctl->m_selPresetButton, 1, 5, 1, 1, Qt::AlignLeft ); - l->addWidget( m_openPresetButton, 1, 7, Qt::AlignCenter ); - l->addWidget( m_rolLPresetButton, 1, 4, Qt::AlignCenter ); - l->addWidget( m_rolRPresetButton, 1, 5, Qt::AlignCenter ); - l->addWidget(_ctl->m_selPresetButton, 1, 6, Qt::AlignLeft ); - - l->addWidget( m_managePluginButton, 1, 10, 2, 2, Qt::AlignLeft ); - - l->addWidget( m_savePresetButton, 1, 8, 2, 2, Qt::AlignCenter ); - - l->setRowStretch( 3, 1 ); - l->setColumnStretch( 13, 1 ); + l->addWidget( m_savePresetButton, 1, 7, 2, 2, Qt::AlignLeft ); + l->addWidget( m_managePluginButton, 1, 8, 2, 2, Qt::AlignCenter ); + l->addWidget( m_pluginWidget, 3, 0, 1, 80, Qt::AlignCenter ); + l->setRowStretch( 5, 1 ); + l->setColumnStretch( 80, 1 ); } -#endif -#ifdef LMMS_BUILD_WIN32 - _ctl->m_effect->m_plugin->showEditor( this ); - QWidget * w = _ctl->m_effect->m_plugin->pluginWidget( false ); - if( w ) - { - setWindowTitle( w->windowTitle() ); - l->addWidget( w ); - } -#endif } @@ -218,7 +226,23 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : void VstEffectControlDialog::paintEvent( QPaintEvent * ) { + if( m_plugin != NULL ) + { + QString plugin_name = m_plugin->name(); + QPainter p( this ); + QFont f = p.font(); + f.setBold( true ); + p.setFont( pointSize<10>( f ) ); + p.setPen( QColor( 32, 160, 54 ) ); + p.drawText( 225, 20, plugin_name ); + p.setPen( QColor( 251, 41, 8 ) ); + f.setBold( false ); + p.setFont( pointSize<8>( f ) ); + p.drawText( 225, 34, tr( "Effect by: " ) + m_plugin->vendorString() ); + p.drawText( 225, 47, m_plugin->currentProgramName() ); + + } } diff --git a/plugins/vst_effect/VstEffectControlDialog.h b/plugins/vst_effect/VstEffectControlDialog.h index bd4687511..9ca82408e 100644 --- a/plugins/vst_effect/VstEffectControlDialog.h +++ b/plugins/vst_effect/VstEffectControlDialog.h @@ -26,6 +26,7 @@ #define _VST_EFFECT_CONTROL_DIALOG_H #include "EffectControlDialog.h" +#include "VstPlugin.h" #include #include @@ -57,6 +58,7 @@ private: pixmapButton * m_managePluginButton; pixmapButton * m_savePresetButton; + VstPlugin * m_plugin; } ; #endif diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index 3b1fd16ce..c8aeaf075 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -314,7 +314,7 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * m_vi->m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); m_vi->m_subWindow->setFixedSize( 960, 300); m_vi->m_subWindow->setWidget(m_vi->m_scrollArea); - m_vi->m_subWindow->setWindowTitle(_eff->m_plugin->name()); + m_vi->m_subWindow->setWindowTitle( _eff->m_plugin->name() + tr( " - VST parameter control" ) ); m_vi->m_subWindow->setWindowIcon( PLUGIN_NAME::getIconPixmap( "logo" ) ); //m_vi->m_subWindow->setAttribute(Qt::WA_DeleteOnClose); @@ -339,6 +339,16 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); + + m_closeButton = new QPushButton( tr( " Close " ), widget ); + connect( m_closeButton, SIGNAL( clicked() ), this, + SLOT( closeWindow() ) ); + m_closeButton->setWhatsThis( + tr( "Close VST effect knob-controller window." ) ); + + l->addWidget( m_closeButton, 0, 2, 1, 7, Qt::AlignLeft ); + + for( int i = 0; i < 10; i++ ) { l->addItem( new QSpacerItem( 68, 45, QSizePolicy::Fixed, QSizePolicy::Fixed ), 0, i ); @@ -413,6 +423,14 @@ manageVSTEffectView::manageVSTEffectView( VstEffect * _eff, VstEffectControls * +void manageVSTEffectView::closeWindow() +{ + m_vi2->m_subWindow->hide(); +} + + + + void manageVSTEffectView::syncPlugin( void ) { char paramStr[35]; diff --git a/plugins/vst_effect/VstEffectControls.h b/plugins/vst_effect/VstEffectControls.h index 67622196f..bc33a2088 100644 --- a/plugins/vst_effect/VstEffectControls.h +++ b/plugins/vst_effect/VstEffectControls.h @@ -115,6 +115,7 @@ protected slots: void syncPlugin( void ); void displayAutomatedOnly( void ); void setParameter( void ); + void closeWindow(); private: @@ -131,6 +132,7 @@ private: QPushButton * m_syncButton; QPushButton * m_displayAutomatedOnly; + QPushButton * m_closeButton; } ; diff --git a/src/gui/widgets/EffectRackView.cpp b/src/gui/widgets/EffectRackView.cpp index ad38e17f9..31ab4411e 100644 --- a/src/gui/widgets/EffectRackView.cpp +++ b/src/gui/widgets/EffectRackView.cpp @@ -175,29 +175,36 @@ void EffectRackView::update() Qt::QueuedConnection ); view->show(); m_effectViews.append( view ); - view_map[i] = true; + if( i < view_map.size() ) + { + view_map[i] = true; + } + else + { + view_map.append( true ); + } } } - int i = m_lastY = 0; + int i = m_lastY = 0, nView = 0; for( QVector::Iterator it = m_effectViews.begin(); - it != m_effectViews.end(); ) + it != m_effectViews.end(); i++ ) { - if( i < view_map.size() && i < m_effectViews.size() && - view_map[i] == false ) + if( i < view_map.size() && view_map[i] == false ) { - delete m_effectViews[i]; + delete m_effectViews[nView]; it = m_effectViews.erase( it ); } else { ( *it )->move( 0, m_lastY ); m_lastY += ( *it )->height(); + ++nView; ++it; - ++i; } } + w->setFixedSize( 210, m_lastY ); QWidget::update(); @@ -242,7 +249,7 @@ void EffectRackView::addEffect() void EffectRackView::modelChanged() { - clearViews(); + //clearViews(); m_effectsGroupBox->setModel( &fxChain()->m_enabledModel ); connect( fxChain(), SIGNAL( aboutToClear() ), this, SLOT( clearViews() ) ); diff --git a/src/gui/widgets/EffectView.cpp b/src/gui/widgets/EffectView.cpp index a62ab6f73..0eb94b20f 100644 --- a/src/gui/widgets/EffectView.cpp +++ b/src/gui/widgets/EffectView.cpp @@ -85,6 +85,8 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : "while deciding when to stop processing signals." ) ); + setModel( _model ); + if( effect()->controls()->controlCount() > 0 ) { QPushButton * ctls_btn = new QPushButton( tr( "Controls" ), @@ -94,6 +96,7 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : ctls_btn->setGeometry( 140, 14, 50, 20 ); connect( ctls_btn, SIGNAL( clicked() ), this, SLOT( editControls() ) ); + m_controlView = effect()->controls()->createView(); if( m_controlView ) { @@ -141,7 +144,8 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : "Right clicking will bring up a context menu where you can change the order " "in which the effects are processed or delete an effect altogether." ) ); - setModel( _model ); + //move above vst effect view creation + //setModel( _model ); } @@ -149,7 +153,15 @@ EffectView::EffectView( Effect * _model, QWidget * _parent ) : EffectView::~EffectView() { + +#ifdef LMMS_BUILD_LINUX + delete m_subWindow; +#else + // otherwise on win32 build VST GUI can get lost + m_subWindow->hide(); +#endif + } @@ -159,7 +171,7 @@ void EffectView::editControls() { if( m_subWindow ) { - if( !effect()->controls()->isViewVisible() ) + if( !m_subWindow->isVisible() ) { m_subWindow->show(); m_subWindow->raise(); diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 016c54d3d..4ffe3c22d 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1047,9 +1047,9 @@ void InstrumentTrackView::freeInstrumentTrackWindow() model()->setHook( NULL ); m_window->setInstrumentTrackView( NULL ); m_window->parentWidget()->hide(); - m_window->setModel( - engine::dummyTrackContainer()-> - dummyInstrumentTrack() ); + //m_window->setModel( + // engine::dummyTrackContainer()-> + // dummyInstrumentTrack() ); m_window->updateInstrumentView(); s_windowCache << m_window; } From 32e9ddc6e4cd727cadcce258f9e10b786905935e Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Fri, 18 Jan 2013 09:49:28 +0100 Subject: [PATCH 090/133] VST effect control window re-design New outlook for VST effect control window Signed-off-by: Tobias Doerffel --- plugins/vst_effect/VstEffectControlDialog.cpp | 79 +++++++++++-------- plugins/vst_effect/VstEffectControlDialog.h | 3 + 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/plugins/vst_effect/VstEffectControlDialog.cpp b/plugins/vst_effect/VstEffectControlDialog.cpp index a77fcdf73..2261f2466 100644 --- a/plugins/vst_effect/VstEffectControlDialog.cpp +++ b/plugins/vst_effect/VstEffectControlDialog.cpp @@ -37,12 +37,15 @@ #include #include #include "gui_templates.h" +#include +#include VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : EffectControlDialog( _ctl ), m_pluginWidget( NULL ), - m_plugin( NULL ) + m_plugin( NULL ), + tbLabel( NULL ) { QGridLayout * l = new QGridLayout( this ); l->setContentsMargins( 10, 10, 10, 10 ); @@ -68,15 +71,16 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : if( m_pluginWidget ) { setWindowTitle( m_pluginWidget->windowTitle() ); - QPushButton * btn = new QPushButton( tr( "Show/hide VST FX GUI" ) ); + setMinimumWidth( 250 ); + + QPushButton * btn = new QPushButton( tr( "Show/hide" ) ); btn->setCheckable( true ); - l->addWidget( btn, 0, 0, 1, 80, Qt::AlignLeft ); connect( btn, SIGNAL( toggled( bool ) ), m_pluginWidget, SLOT( setVisible( bool ) ) ); emit btn->click(); - btn->setMinimumWidth( 200 ); - btn->setMaximumWidth( 200 ); + btn->setMinimumWidth( 78 ); + btn->setMaximumWidth( 78 ); btn->setMinimumHeight( 24 ); btn->setMaximumHeight( 24 ); @@ -202,22 +206,40 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : m_savePresetButton->setMinimumHeight( 21 ); m_savePresetButton->setMaximumHeight( 21 ); - for( int i = 0; i < 13; i++ ) - { - l->addItem( new QSpacerItem( 15, 30, QSizePolicy::Fixed, - QSizePolicy::Fixed ), 1, i ); - } - l->addWidget( m_openPresetButton, 1, 6, 1, 1, Qt::AlignLeft ); - l->addWidget( m_rolLPresetButton, 1, 3, 1, 1, Qt::AlignLeft ); - l->addWidget( m_rolRPresetButton, 1, 4, 1, 1, Qt::AlignLeft ); - l->addWidget( _ctl->m_selPresetButton, 1, 5, 1, 1, Qt::AlignLeft ); - - l->addWidget( m_savePresetButton, 1, 7, 2, 2, Qt::AlignLeft ); - l->addWidget( m_managePluginButton, 1, 8, 2, 2, Qt::AlignCenter ); - l->addWidget( m_pluginWidget, 3, 0, 1, 80, Qt::AlignCenter ); + int newSize = m_pluginWidget->width() + 20; + newSize = (newSize < 250) ? 250 : newSize; + QWidget* resize = new QWidget(this); + resize->resize( newSize, 10 ); + QWidget* space0 = new QWidget(this); + space0->resize(8, 10); + QWidget* space1 = new QWidget(this); + space1->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + QFont f( "Arial", 10 ); + l->addItem( new QSpacerItem( newSize - 20, 30, QSizePolicy::Fixed, + QSizePolicy::Fixed ), 1, 0 ); + l->addWidget( resize, 2, 0, 1, 1, Qt::AlignCenter ); + l->addWidget( m_pluginWidget, 3, 0, 1, 1, Qt::AlignCenter ); l->setRowStretch( 5, 1 ); - l->setColumnStretch( 80, 1 ); + l->setColumnStretch( 1, 1 ); + + QToolBar * tb = new QToolBar( this ); + tb->resize( newSize , 32 ); + tb->addWidget(space0); + tb->addWidget( m_rolLPresetButton ); + tb->addWidget( m_rolRPresetButton ); + tb->addWidget( _ctl->m_selPresetButton ); + tb->addWidget( m_openPresetButton ); + tb->addWidget( m_savePresetButton ); + tb->addWidget( m_managePluginButton ); + tb->addWidget( btn ); + tb->addWidget(space1); + + tbLabel = new QLabel( tr( "Effect by: " ), this ); + tbLabel->setFont( pointSize<7>( f ) ); + tbLabel->setTextFormat(Qt::RichText); + tbLabel->setAlignment( Qt::AlignTop | Qt::AlignLeft ); + tb->addWidget( tbLabel ); } } @@ -226,22 +248,11 @@ VstEffectControlDialog::VstEffectControlDialog( VstEffectControls * _ctl ) : void VstEffectControlDialog::paintEvent( QPaintEvent * ) { - if( m_plugin != NULL ) + if( m_plugin != NULL && tbLabel != NULL ) { - QString plugin_name = m_plugin->name(); - QPainter p( this ); - QFont f = p.font(); - - f.setBold( true ); - p.setFont( pointSize<10>( f ) ); - p.setPen( QColor( 32, 160, 54 ) ); - p.drawText( 225, 20, plugin_name ); - p.setPen( QColor( 251, 41, 8 ) ); - f.setBold( false ); - p.setFont( pointSize<8>( f ) ); - p.drawText( 225, 34, tr( "Effect by: " ) + m_plugin->vendorString() ); - p.drawText( 225, 47, m_plugin->currentProgramName() ); - + tbLabel->setText( tr( "Effect by: " ) + m_plugin->vendorString() + + tr( "       
" ) + + m_plugin->currentProgramName() ); } } diff --git a/plugins/vst_effect/VstEffectControlDialog.h b/plugins/vst_effect/VstEffectControlDialog.h index 9ca82408e..de8b748dd 100644 --- a/plugins/vst_effect/VstEffectControlDialog.h +++ b/plugins/vst_effect/VstEffectControlDialog.h @@ -30,6 +30,7 @@ #include #include +#include class VstEffectControls; @@ -59,6 +60,8 @@ private: pixmapButton * m_savePresetButton; VstPlugin * m_plugin; + + QLabel * tbLabel; } ; #endif From c1a2c6b1fabca7b3a6af8339038f40c700ff1e9b Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Fri, 18 Jan 2013 09:57:51 +0100 Subject: [PATCH 091/133] VST effects: secure master channel freezing Master channel keeps freezing, when VST effects are placed directly into Master channels FxChain, on slower computers. Provided solution prolongs VST initialisation phase, by waiting on two extra messages, which usually follows initialisation and are possibly not yet implemented well. IdSampleRateInformation, IdBufferSizeInformation This seems to prevent Master channel freezes e.g. on VST effects like Tiny-Q. Signed-off-by: Tobias Doerffel --- plugins/vst_base/RemoteVstPlugin.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 2344d1060..1231cb1c4 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -335,7 +335,12 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : { message m = receiveMessage(); processMessage( m ); - if( m.id == IdVstLoadPlugin || m.id == IdQuit ) + //if( m.id == IdVstLoadPlugin || m.id == IdQuit ) + + // IdBufferSizeInformation is sent right after plugin load + // otherwise causes deadlocks to FxMixer/EffectChain + + if( m.id == IdBufferSizeInformation || m.id == IdQuit ) { break; } From edf93d04cbc452c81aba6549f65586bc8b02978a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 23 Jan 2013 23:08:03 +0100 Subject: [PATCH 092/133] Midi: added new property fromMidiPort This new property is going to signal whether a specific event comes from a MIDI port is was generated internally. Based on patch by nuio , 2013-01-20 --- include/midi.h | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/include/midi.h b/include/midi.h index 71a1d0f57..4452d8762 100644 --- a/include/midi.h +++ b/include/midi.h @@ -1,7 +1,7 @@ /* * midi.h - constants, structs etc. concerning MIDI * - * Copyright (c) 2005-2012 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -132,7 +132,8 @@ struct midiEvent m_metaEvent( MidiMetaInvalid ), m_channel( _channel ), m_sysExData( NULL ), - m_sourcePort( _sourcePort ) + m_sourcePort( _sourcePort ), + m_fromMidiPort( false ) { m_data.m_param[0] = _param1; m_data.m_param[1] = _param2; @@ -144,7 +145,8 @@ struct midiEvent m_metaEvent( MidiMetaInvalid ), m_channel( 0 ), m_sysExData( _sysex_data ), - m_sourcePort( NULL ) + m_sourcePort( NULL ), + m_fromMidiPort( false ) { m_data.m_sysExDataLen = _data_len; } @@ -155,7 +157,8 @@ struct midiEvent m_channel( _copy.m_channel ), m_data( _copy.m_data ), m_sysExData( _copy.m_sysExData ), - m_sourcePort( _copy.m_sourcePort ) + m_sourcePort( _copy.m_sourcePort ), + m_fromMidiPort( _copy.m_fromMidiPort ) { } @@ -222,6 +225,15 @@ struct midiEvent ( (float)( PanningRight - PanningLeft ) ) ); } + void setFromMidiPort( bool enabled ) + { + m_fromMidiPort = enabled; + } + + bool isFromMidiPort() const + { + return m_fromMidiPort; + } MidiEventTypes m_type; // MIDI event type MidiMetaEvents m_metaEvent; // Meta event (mostly unused) @@ -236,6 +248,10 @@ struct midiEvent const char * m_sysExData; const void * m_sourcePort; + +private: + bool m_fromMidiPort; + } ; From 8a6aecfbdcdd3f83069c4f889bfa058e81d954e8 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 23 Jan 2013 23:10:13 +0100 Subject: [PATCH 093/133] MidiPort: set fromMidiPort-flag of MidiEvent instances This completes the functionality introduced with commit edf93d04cbc452c81aba6549f65586bc8b02978a. Based on patch by nuio , 2013-01-20 --- src/core/midi/MidiPort.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index 49c61e674..bdf9d42fd 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -2,7 +2,7 @@ * MidiPort.cpp - abstraction of MIDI-ports which are part of LMMS's MIDI- * sequencing system * - * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -129,20 +129,24 @@ void MidiPort::processInEvent( const midiEvent & _me, const midiTime & _time ) if( inputEnabled() && ( inputChannel()-1 == _me.m_channel || inputChannel() == 0 ) ) { + midiEvent ev = _me; if( _me.m_type == MidiNoteOn || _me.m_type == MidiNoteOff || _me.m_type == MidiKeyPressure ) { - if( _me.key() < 0 || _me.key() >= NumKeys ) + ev.key() = ev.key() + KeysPerOctave; + if( ev.key() < 0 || ev.key() >= NumKeys ) { return; } } - midiEvent ev = _me; + if( fixedInputVelocity() >= 0 && _me.velocity() > 0 ) { ev.velocity() = fixedInputVelocity(); } + + ev.setFromMidiPort( true ); m_midiEventProcessor->processInEvent( ev, _time ); } } From 2327581da7bdd53c22353acb6bcbd0b487d50733 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 23 Jan 2013 23:11:47 +0100 Subject: [PATCH 094/133] MidiPort: coding style fixes --- src/core/midi/MidiPort.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/core/midi/MidiPort.cpp b/src/core/midi/MidiPort.cpp index bdf9d42fd..eb6949356 100644 --- a/src/core/midi/MidiPort.cpp +++ b/src/core/midi/MidiPort.cpp @@ -45,17 +45,17 @@ MidiPort::MidiPort( const QString & _name, MidiClient * _mc, m_outputChannelModel( 1, 1, MidiChannelCount, this, tr( "Output channel" ) ), m_inputControllerModel( 0, 0, MidiControllerCount, this, - tr( "Input controller" ) ), + tr( "Input controller" ) ), m_outputControllerModel( 0, 0, MidiControllerCount, this, - tr( "Output controller" ) ), + tr( "Output controller" ) ), m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, - tr( "Fixed input velocity" ) ), + tr( "Fixed input velocity" ) ), m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), m_fixedOutputNoteModel( -1, -1, MidiMaxNote, this, tr( "Fixed output note" ) ), m_outputProgramModel( 1, 1, MidiProgramCount, this, - tr( "Output MIDI program" ) ), + tr( "Output MIDI program" ) ), m_readableModel( false, this, tr( "Receive MIDI-events" ) ), m_writableModel( false, this, tr( "Send MIDI-events" ) ) { @@ -168,10 +168,11 @@ void MidiPort::processOutEvent( const midiEvent & _me, const midiTime & _time ) --ev.m_channel; } */ if( ( _me.m_type == MidiNoteOn || _me.m_type == MidiNoteOff ) && - fixedOutputNote() >=0 ) { - // Convert MIDI note number (from spinbox) -> LMMS note number - // that will be converted back when outputted. - ev.key() = fixedOutputNote() - KeysPerOctave; + fixedOutputNote() >= 0 ) + { + // Convert MIDI note number (from spinbox) -> LMMS note number + // that will be converted back when outputted. + ev.key() = fixedOutputNote() - KeysPerOctave; } if( fixedOutputVelocity() >= 0 && _me.velocity() > 0 && ( _me.m_type == MidiNoteOn || From a3abcdb2e0ac91887c5ef7b4299ab8113994625c Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 23 Jan 2013 23:12:04 +0100 Subject: [PATCH 095/133] InstrumentTrack: directly forward MIDI events under special circumstances In the special case that a MIDI event comes from a MIDI port, the instrument is MIDI based (VST plugin, Sf2Player etc.) and the user did not set dedicated MIDI output channel, directly forward the MIDI event to the instrument plugin so properties such as MIDI channels are kept. Based on patch by nuio , 2013-01-20 --- src/tracks/InstrumentTrack.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 4ffe3c22d..e60610c6f 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -221,6 +221,18 @@ void InstrumentTrack::processInEvent( const midiEvent & _me, const midiTime & _time ) { engine::getMixer()->lock(); + + // in the special case this event comes from a MIDI port, the instrument + // is MIDI based (VST plugin, Sf2Player etc.) and the user did not set + // a dedicated MIDI output channel, directly pass the MIDI event to the + // instrument plugin + if( _me.isFromMidiPort() && m_instrument->isMidiBased() && + midiPort()->realOutputChannel() < 0 ) + { + m_instrument->handleMidiEvent( _me, _time ); + return; + } + switch( _me.m_type ) { // we don't send MidiNoteOn, MidiNoteOff and MidiKeyPressure From 26625b137f5c6fd9c500d89473fbc58a853ea35d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 23 Jan 2013 23:26:23 +0100 Subject: [PATCH 096/133] InstrumentTrack: do not evaluate realOutputChannel() for the time being When deciding whether to forward a MIDI event directly to the instrument plugin do not evaluate realOutputChannel() for the time being as it is ranged from 0 to 15 and has no don't-care-state like the input channel. This is a workaround - we need a better solution here. --- src/tracks/InstrumentTrack.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index e60610c6f..5dcac3f11 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -226,8 +226,8 @@ void InstrumentTrack::processInEvent( const midiEvent & _me, // is MIDI based (VST plugin, Sf2Player etc.) and the user did not set // a dedicated MIDI output channel, directly pass the MIDI event to the // instrument plugin - if( _me.isFromMidiPort() && m_instrument->isMidiBased() && - midiPort()->realOutputChannel() < 0 ) + if( _me.isFromMidiPort() && m_instrument->isMidiBased()/* && + midiPort()->realOutputChannel() < 0 */ ) { m_instrument->handleMidiEvent( _me, _time ); return; From 0acfff036f36ce813ea1145ed29db7d36d20669a Mon Sep 17 00:00:00 2001 From: Frank Mather Date: Sat, 9 Feb 2013 12:27:17 +0100 Subject: [PATCH 097/133] Added HydrogenImport plugin This is a new plugin that imports hydrogen drum machine songs. Signed-off-by: Tobias Doerffel --- plugins/CMakeLists.txt | 1 + plugins/HydrogenImport/CMakeLists.txt | 4 + plugins/HydrogenImport/HydrogenImport.cpp | 404 ++++++++++++++++++++++ plugins/HydrogenImport/HydrogenImport.h | 27 ++ plugins/HydrogenImport/LocalFileMng.h | 29 ++ plugins/HydrogenImport/local_file_mgr.cpp | 234 +++++++++++++ 6 files changed, 699 insertions(+) create mode 100644 plugins/HydrogenImport/CMakeLists.txt create mode 100644 plugins/HydrogenImport/HydrogenImport.cpp create mode 100644 plugins/HydrogenImport/HydrogenImport.h create mode 100644 plugins/HydrogenImport/LocalFileMng.h create mode 100644 plugins/HydrogenImport/local_file_mgr.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 132524acf..db590e379 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,6 +2,7 @@ ADD_SUBDIRECTORY(audio_file_processor) ADD_SUBDIRECTORY(bass_booster) ADD_SUBDIRECTORY(bit_invader) ADD_SUBDIRECTORY(flp_import) +ADD_SUBDIRECTORY(HydrogenImport) ADD_SUBDIRECTORY(kicker) ADD_SUBDIRECTORY(ladspa_browser) ADD_SUBDIRECTORY(ladspa_effect) diff --git a/plugins/HydrogenImport/CMakeLists.txt b/plugins/HydrogenImport/CMakeLists.txt new file mode 100644 index 000000000..27e8d6a4d --- /dev/null +++ b/plugins/HydrogenImport/CMakeLists.txt @@ -0,0 +1,4 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h local_file_mgr.cpp LocalFileMng.h) + diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp new file mode 100644 index 000000000..6390e4834 --- /dev/null +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "LocalFileMng.h" +#include "HydrogenImport.h" +#include "song.h" +#include "engine.h" +#include "Instrument.h" +#include "InstrumentTrack.h" +#include "note.h" +#include "pattern.h" +#include "track.h" +#include "bb_track.h" +#include "bb_track_container.h" +#include "Instrument.h" + +#define MAX_LAYERS 4 +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "Hydrogen Import", + QT_TRANSLATE_NOOP( "pluginBrowser", + "Filter for importing Hydrogen files into LMMS" ), + "frank mather", + 0x0100, + Plugin::ImportFilter, + NULL, + NULL, + NULL +} ; + +} + +QString filename; +class NoteKey +{ +public: + enum Key { + C = 0, + Cs, + D, + Ef, + E, + F, + Fs, + G, + Af, + A, + Bf, + B, + }; + + static int stringToNoteKey( const QString& str ) + { + int m_key; + + + QString sKey = str.left( str.length() - 1 ); + QString sOct = str.mid( str.length() - 1, str.length() ); + + if ( sKey.endsWith( "-" ) ) + { + sKey.replace( "-", "" ); + sOct.insert( 0, "-" ); + } + int nOctave = sOct.toInt(); + + if ( sKey == "C" ) + { + m_key = NoteKey::C; + } + else if ( sKey == "Cs" ) + { + m_key = NoteKey::Cs; + } + else if ( sKey == "D" ) + { + m_key = NoteKey::D; + } + else if ( sKey == "Ef" ) + { + m_key = NoteKey::Ef; + } + else if ( sKey == "E" ) + { + m_key = NoteKey::E; + } + else if ( sKey == "F" ) + { + m_key = NoteKey::F; + } + else if ( sKey == "Fs" ) + { + m_key = NoteKey::Fs; + } + else if ( sKey == "G" ) + { + m_key = NoteKey::G; + } + else if ( sKey == "Af" ) + { + m_key = NoteKey::Af; + } + else if ( sKey == "A" ) + { + m_key = NoteKey::A; + } + else if ( sKey == "Bf" ) + { + m_key = NoteKey::Bf; + } + else if ( sKey == "B" ) { + m_key = NoteKey::B; + } + return m_key + (nOctave*12)+57; + } + +}; +HydrogenImport::HydrogenImport( const QString & _file ) : + ImportFilter( _file, &hydrogenimport_plugin_descriptor ) +{ + filename = _file; +} + + + + +HydrogenImport::~HydrogenImport() +{ +} +Instrument * ins; +bool HydrogenImport::readSong() +{ + QHash drum_track; + QHash pattern_length; + QHash pattern_id; + + song *s = engine::getSong(); + int song_num_tracks = s->tracks().size(); + if ( QFile( filename ).exists() == false ) + { + printf( "Song file not found \n" ); + return false; + } + QDomDocument doc = LocalFileMng::openXmlDocument( filename ); + QDomNodeList nodeList = doc.elementsByTagName( "song" ); + + if( nodeList.isEmpty() ) + { + printf( "Error reading song: song node not found\n" ); + return NULL; + } + QDomNode songNode = nodeList.at( 0 ); + + QString m_sSongVersion = LocalFileMng::readXmlString( songNode , "version", "Unknown version" ); + + + + + float fBpm = LocalFileMng::readXmlFloat( songNode, "bpm", 120 ); + float fVolume = LocalFileMng::readXmlFloat( songNode, "volume", 0.5 ); + float fMetronomeVolume = LocalFileMng::readXmlFloat( songNode, "metronomeVolume", 0.5 ); + QString sName( LocalFileMng::readXmlString( songNode, "name", "Untitled Song" ) ); + QString sAuthor( LocalFileMng::readXmlString( songNode, "author", "Unknown Author" ) ); + QString sNotes( LocalFileMng::readXmlString( songNode, "notes", "..." ) ); + QString sLicense( LocalFileMng::readXmlString( songNode, "license", "Unknown license" ) ); + bool bLoopEnabled = LocalFileMng::readXmlBool( songNode, "loopEnabled", false ); + QString sMode = LocalFileMng::readXmlString( songNode, "mode", "pattern" ); + + + float fHumanizeTimeValue = LocalFileMng::readXmlFloat( songNode, "humanize_time", 0.0 ); + float fHumanizeVelocityValue = LocalFileMng::readXmlFloat( songNode, "humanize_velocity", 0.0 ); + float fSwingFactor = LocalFileMng::readXmlFloat( songNode, "swing_factor", 0.0 ); + + + QDomNode instrumentListNode = songNode.firstChildElement( "instrumentList" ); + if ( ( ! instrumentListNode.isNull() ) ) + { + + int instrumentList_count = 0; + QDomNode instrumentNode; + instrumentNode = instrumentListNode.firstChildElement( "instrument" ); + while ( ! instrumentNode.isNull() ) + { + instrumentList_count++; + QString sId = LocalFileMng::readXmlString( instrumentNode, "id", "" ); // instrument id + QString sDrumkit = LocalFileMng::readXmlString( instrumentNode, "drumkit", "" ); // drumkit + QString sName = LocalFileMng::readXmlString( instrumentNode, "name", "" ); // name + float fVolume = LocalFileMng::readXmlFloat( instrumentNode, "volume", 1.0 ); // volume + bool bIsMuted = LocalFileMng::readXmlBool( instrumentNode, "isMuted", false ); // is muted + float fPan_L = LocalFileMng::readXmlFloat( instrumentNode, "pan_L", 0.5 ); // pan L + float fPan_R = LocalFileMng::readXmlFloat( instrumentNode, "pan_R", 0.5 ); // pan R + float fFX1Level = LocalFileMng::readXmlFloat( instrumentNode, "FX1Level", 0.0 ); // FX level + float fFX2Level = LocalFileMng::readXmlFloat( instrumentNode, "FX2Level", 0.0 ); // FX level + float fFX3Level = LocalFileMng::readXmlFloat( instrumentNode, "FX3Level", 0.0 ); // FX level + float fFX4Level = LocalFileMng::readXmlFloat( instrumentNode, "FX4Level", 0.0 ); // FX level + float fGain = LocalFileMng::readXmlFloat( instrumentNode, "gain", 1.0, false, false ); // instrument gain + + + int fAttack = LocalFileMng::readXmlInt( instrumentNode, "Attack", 0, false, false ); // Attack + int fDecay = LocalFileMng::readXmlInt( instrumentNode, "Decay", 0, false, false ); // Decay + float fSustain = LocalFileMng::readXmlFloat( instrumentNode, "Sustain", 1.0, false, false ); // Sustain + int fRelease = LocalFileMng::readXmlInt( instrumentNode, "Release", 1000, false, false ); // Release + + float fRandomPitchFactor = LocalFileMng::readXmlFloat( instrumentNode, "randomPitchFactor", 0.0f, false, false ); + + bool bFilterActive = LocalFileMng::readXmlBool( instrumentNode, "filterActive", false ); + float fFilterCutoff = LocalFileMng::readXmlFloat( instrumentNode, "filterCutoff", 1.0f, false ); + float fFilterResonance = LocalFileMng::readXmlFloat( instrumentNode, "filterResonance", 0.0f, false ); + QString sMuteGroup = LocalFileMng::readXmlString( instrumentNode, "muteGroup", "-1", false ); + QString sMidiOutChannel = LocalFileMng::readXmlString( instrumentNode, "midiOutChannel", "-1", false, false ); + QString sMidiOutNote = LocalFileMng::readXmlString( instrumentNode, "midiOutNote", "60", false, false ); + int nMuteGroup = sMuteGroup.toInt(); + bool isStopNote = LocalFileMng::readXmlBool( instrumentNode, "isStopNote", false ); + int nMidiOutChannel = sMidiOutChannel.toInt(); + int nMidiOutNote = sMidiOutNote.toInt(); + + if ( sId.isEmpty() ) { + printf( "Empty ID for instrument. skipping \n" ); + instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" ); + continue; + } + QDomNode filenameNode = instrumentNode.firstChildElement( "filename" ); + + if ( ! filenameNode.isNull() ) + { + return false; + } + else + { + unsigned nLayer = 0; + QDomNode layerNode = instrumentNode.firstChildElement( "layer" ); + while ( ! layerNode.isNull() ) + { + if ( nLayer >= MAX_LAYERS ) + { + printf( "nLayer >= MAX_LAYERS" ); + continue; + } + QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" ); + bool sIsModified = LocalFileMng::readXmlBool( layerNode, "ismodified", false ); + QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" ); + unsigned sStartframe = LocalFileMng::readXmlInt( layerNode, "startframe", 0 ); + unsigned sLoopFrame = LocalFileMng::readXmlInt( layerNode, "loopframe", 0 ); + int sLoops = LocalFileMng::readXmlInt( layerNode, "loops", 0 ); + unsigned sEndframe = LocalFileMng::readXmlInt( layerNode, "endframe", 0 ); + bool sUseRubber = LocalFileMng::readXmlInt( layerNode, "userubber", 0, false ); + float sRubberDivider = LocalFileMng::readXmlFloat( layerNode, "rubberdivider", 0.0 ); + int sRubberCsettings = LocalFileMng::readXmlInt( layerNode, "rubberCsettings", 1 ); + int sRubberPitch = LocalFileMng::readXmlFloat( layerNode, "rubberPitch", 0.0 ); + + float fMin = LocalFileMng::readXmlFloat( layerNode, "min", 0.0 ); + float fMax = LocalFileMng::readXmlFloat( layerNode, "max", 1.0 ); + float fGain = LocalFileMng::readXmlFloat( layerNode, "gain", 1.0 ); + float fPitch = LocalFileMng::readXmlFloat( layerNode, "pitch", 0.0, false, false ); + if ( nLayer == 0 ) + { + drum_track[sId] = ( InstrumentTrack * ) track::create( track::InstrumentTrack,engine::getBBTrackContainer() ); + drum_track[sId]->volumeModel()->setValue( fVolume * 100 ); + drum_track[sId]->panningModel()->setValue( ( fPan_R - fPan_L ) * 100 ); + ins = drum_track[sId]->loadInstrument( "audiofileprocessor" ); + ins->loadFile( sFilename ); + } + nLayer++; + layerNode = ( QDomNode ) layerNode.nextSiblingElement( "layer" ); + } + } + + instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" ); + } + if ( instrumentList_count == 0 ) + { + return false; + } + } + else + { + return false; + } + QDomNode patterns = songNode.firstChildElement( "patternList" ); + int pattern_count = 0; + int nbb = engine::getBBTrackContainer()->numOfBBs(); + QDomNode patternNode = patterns.firstChildElement( "pattern" ); + int pn = 1; + while ( !patternNode.isNull() ) + { + if ( pn > 0 ) + { + pattern_count++; + s->addBBTrack(); + pn = 0; + } + QString sName; // name + sName = LocalFileMng::readXmlString( patternNode, "name", sName ); + + QString sCategory = ""; // category + sCategory = LocalFileMng::readXmlString( patternNode, "category", sCategory ,false ,false ); + int nSize = -1; + nSize = LocalFileMng::readXmlInt( patternNode, "size", nSize, false, false ); + pattern_length[sName] = nSize; + QDomNode pNoteListNode = patternNode.firstChildElement( "noteList" ); + if ( ! pNoteListNode.isNull() ) { + QDomNode noteNode = pNoteListNode.firstChildElement( "note" ); + while ( ! noteNode.isNull() ) { + unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 ); + float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 , false , false ); + float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f ); + float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 ); + float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 ); + int nLength = LocalFileMng::readXmlInt( noteNode, "length", -1, true ); + float nPitch = LocalFileMng::readXmlFloat( noteNode, "pitch", 0.0, false, false ); + QString sKey = LocalFileMng::readXmlString( noteNode, "key", "C0", false, false ); + QString nNoteOff = LocalFileMng::readXmlString( noteNode, "note_off", "false", false, false ); + + QString instrId = LocalFileMng::readXmlString( noteNode, "instrument", 0,false, false ); + int i = pattern_count - 1 + nbb; + pattern_id[sName] = pattern_count - 1; + pattern *p = dynamic_cast( drum_track[instrId]->getTCO( i ) ); + note n; + n.setPos( nPosition ); + if ( (nPosition + 48) <= nSize ) + { + n.setLength( 48 ); + } + else + { + n.setLength( nSize - nPosition ); + } + n.setVolume( fVelocity * 100 ); + n.setPanning( ( fPan_R - fPan_L ) * 100 ); + n.setKey( NoteKey::stringToNoteKey( sKey ) ); + p->addNote( n,false ); + pn = pn + 1; + noteNode = ( QDomNode ) noteNode.nextSiblingElement( "note" ); + } + } + patternNode = ( QDomNode ) patternNode.nextSiblingElement( "pattern" ); + } + // Pattern sequence + QDomNode patternSequenceNode = songNode.firstChildElement( "patternSequence" ); + QDomNode groupNode = patternSequenceNode.firstChildElement( "group" ); + int pos = 0; + while ( !groupNode.isNull() ) + { + int best_length = 0; + QDomNode patternId = groupNode.firstChildElement( "patternID" ); + while ( !patternId.isNull() ) + { + QString patId = patternId.firstChild().nodeValue(); + patternId = ( QDomNode ) patternId.nextSiblingElement( "patternID" ); + + int i = pattern_id[patId]+song_num_tracks; + track *t = ( bbTrack * ) s->tracks().at( i ); + trackContentObject *tco = t->createTCO( pos ); + tco->movePosition( pos ); + + + if ( pattern_length[patId] > best_length ) + { + best_length = pattern_length[patId]; + } + } + pos = pos + best_length; + groupNode = groupNode.nextSiblingElement( "group" ); + } + + if ( pattern_count == 0 ) + { + return false; + } + return true; +} +bool HydrogenImport::tryImport( trackContainer * _tc ) +{ + if( openFile() == false ) + { + return false; + } + return readSong(); +} + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) +{ + return new HydrogenImport( QString::fromUtf8( + static_cast( _data ) ) ); +} + + +} + diff --git a/plugins/HydrogenImport/HydrogenImport.h b/plugins/HydrogenImport/HydrogenImport.h new file mode 100644 index 000000000..10a1cba7c --- /dev/null +++ b/plugins/HydrogenImport/HydrogenImport.h @@ -0,0 +1,27 @@ +#ifndef _HYDROGEN_IMPORT_H +#define _HYDROGEN_IMPORT_H + +#include +#include +#include + +#include "ImportFilter.h" + + +class HydrogenImport : public ImportFilter +{ +public: + HydrogenImport( const QString & _file ); + bool readSong(); + + virtual ~HydrogenImport(); + + virtual PluginView * instantiateView( QWidget * ) + { + return( NULL ); + } +private: + virtual bool tryImport( trackContainer * _tc ); +}; +#endif + diff --git a/plugins/HydrogenImport/LocalFileMng.h b/plugins/HydrogenImport/LocalFileMng.h new file mode 100644 index 000000000..217f95f34 --- /dev/null +++ b/plugins/HydrogenImport/LocalFileMng.h @@ -0,0 +1,29 @@ +#ifndef LFILEMNG_H +#define LFILEMNG_H + +#include +#include +#include +#include +#include + +class LocalFileMng +{ +public: + LocalFileMng(); + ~LocalFileMng(); + std::vector getallPatternList(){ + return m_allPatternList; + } + + static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static bool readXmlBool( QDomNode , const QString& nodeName, bool defaultValue, bool bShouldExists = true , bool tinyXmlCompatMode = false ); + static void convertFromTinyXMLString( QByteArray* str ); + static bool checkTinyXMLCompatMode( const QString& filename ); + static QDomDocument openXmlDocument( const QString& filename ); + std::vector m_allPatternList; +}; +#endif //LFILEMNG_H + diff --git a/plugins/HydrogenImport/local_file_mgr.cpp b/plugins/HydrogenImport/local_file_mgr.cpp new file mode 100644 index 000000000..78560fef9 --- /dev/null +++ b/plugins/HydrogenImport/local_file_mgr.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "LocalFileMng.h" + + +/* New QtXml based methods */ + +QString LocalFileMng::readXmlString( QDomNode node , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return element.text(); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + + } + return defaultValue; + } +} + +float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QLocale c_locale = QLocale::c(); + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return c_locale.toFloat(element.text()); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + +int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QLocale c_locale = QLocale::c(); + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return c_locale.toInt( element.text() ); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + +bool LocalFileMng::readXmlBool( QDomNode node , const QString& nodeName, bool defaultValue, bool bShouldExists, bool tinyXmlCompatMode) +{ + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + if( element.text() == "true"){ + return true; + } else { + return false; + } + } else { + //_WARNINGLOG( "Using default value in " + nodeName ); + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + + +/* Convert (in-place) an XML escape sequence into a literal byte, + * rather than the character it actually refers to. + */ +void LocalFileMng::convertFromTinyXMLString( QByteArray* str ) +{ + /* When TinyXML encountered a non-ASCII character, it would + * simply write the character as "&#xx;" -- where "xx" is + * the hex character code. However, this doesn't respect + * any encodings (e.g. UTF-8, UTF-16). In XML, &#xx; literally + * means "the Unicode character # xx." However, in a UTF-8 + * sequence, this could be an escape character that tells + * whether we have a 2, 3, or 4-byte UTF-8 sequence. + * + * For example, the UTF-8 sequence 0xD184 was being written + * by TinyXML as "Ñ„". However, this is the UTF-8 + * sequence for the cyrillic small letter EF (which looks + * kind of like a thorn or a greek phi). This letter, in + * XML, should be saved as ф, or even literally + * (no escaping). As a consequence, when Ñ is read + * by an XML parser, it will be interpreted as capital N + * with a tilde (~). Then „ will be interpreted as + * an unknown or control character. + * + * So, when we know that TinyXML wrote the file, we can + * simply exchange these hex sequences to literal bytes. + */ + int pos = 0; + + pos = str->indexOf("&#x"); + while( pos != -1 ) { + if( isxdigit(str->at(pos+3)) + && isxdigit(str->at(pos+4)) + && (str->at(pos+5) == ';') ) { + char w1 = str->at(pos+3); + char w2 = str->at(pos+4); + + w1 = tolower(w1) - 0x30; // '0' = 0x30 + if( w1 > 9 ) w1 -= 0x27; // '9' = 0x39, 'a' = 0x61 + w1 = (w1 & 0xF); + + w2 = tolower(w2) - 0x30; // '0' = 0x30 + if( w2 > 9 ) w2 -= 0x27; // '9' = 0x39, 'a' = 0x61 + w2 = (w2 & 0xF); + + char ch = (w1 << 4) | w2; + (*str)[pos] = ch; + ++pos; + str->remove(pos, 5); + } + pos = str->indexOf("&#x"); + } +} + +bool LocalFileMng::checkTinyXMLCompatMode( const QString& filename ) +{ + /* + Check if filename was created with TinyXml or QtXml + TinyXML: return true + QtXml: return false + */ + + QFile file( filename ); + + if ( !file.open(QIODevice::ReadOnly) ) + return false; + + QString line = file.readLine(); + file.close(); + if ( line.startsWith( "name(); + if( enc == QString("System") ) { + enc = "UTF-8"; + } + QByteArray line; + QByteArray buf = QString("\n") + .arg( enc ) + .toLocal8Bit(); + + //_INFOLOG( QString("Using '%1' encoding for TinyXML file").arg(enc) ); + + while( !file.atEnd() ) { + line = file.readLine(); + LocalFileMng::convertFromTinyXMLString( &line ); + buf += line; + } + + if( ! doc.setContent( buf ) ) { + file.close(); + return QDomDocument(); + } + + } else { + if( ! doc.setContent( &file ) ) { + file.close(); + return QDomDocument(); + } + } + file.close(); + + return doc; +} + From 7682bc6ef9fac0fa4698a4d5ce9184b6e4d01f73 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Sat, 9 Feb 2013 12:27:48 +0100 Subject: [PATCH 098/133] Song: added h2song file extension to file import dialog After we added the HydrogenImport plugin we should also change the file import dialog such that the user can chose h2song files. --- src/core/song.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core/song.cpp b/src/core/song.cpp index 58a1e31a7..d233e5946 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1,7 +1,7 @@ /* * song.cpp - root of the model tree * - * Copyright (c) 2004-2012 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -1096,8 +1096,9 @@ void song::importProject() tr("MIDI sequences") + " (*.mid *.midi *.rmi);;" + tr("FL Studio projects") + - " (*.flp" - ");;" + + " (*.flp);;" + + tr("Hydrogen projects") + + " (*.h2song);;" + tr("All file types") + " (*.*)"); From 0ef2997ecef5277dd3c4fff3cbe61100ae8a7e25 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 7 Feb 2013 21:54:06 +0100 Subject: [PATCH 099/133] VST to host sync This patch should bring VST to host synchronization for LMMS. (e.g. for plugins like dBlue Glitch, TAL Filters). Synchronization is done via shared memory, missing song time positions are reccalculated and added to PPQ position sync values (SHM - common input interface for sync of all VST plugins) --- include/RemotePlugin.h | 65 ++++++++++- include/VST_sync_shm.h | 58 ++++++++++ include/aeffectx.h | 20 +++- include/setup_dialog.h | 2 + include/song.h | 8 +- plugins/vst_base/RemoteVstPlugin.cpp | 162 ++++++++++++++++++++++++--- src/core/song.cpp | 159 +++++++++++++++++++++++++- src/gui/setup_dialog.cpp | 26 ++++- 8 files changed, 475 insertions(+), 25 deletions(-) create mode 100644 include/VST_sync_shm.h diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index 5ac33e766..46ffc5e15 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -27,6 +27,7 @@ #include "export.h" #include "midi.h" +#include "VST_sync_shm.h" #include #include @@ -811,7 +812,9 @@ class RemotePluginClient : public RemotePluginBase public: RemotePluginClient( key_t _shm_in, key_t _shm_out ); virtual ~RemotePluginClient(); - +#ifdef USE_QT_SHMEM + sncVST * getQtVSTshm(); +#endif virtual bool processMessage( const message & _m ); virtual void process( const sampleFrame * _in_buf, @@ -879,7 +882,9 @@ private: #ifdef USE_QT_SHMEM QSharedMemory m_shmObj; + QSharedMemory m_shmQtID; #endif + sncVST * m_SncVSTplug; float * m_shm; int m_inputCount; @@ -1007,13 +1012,60 @@ RemotePluginClient::RemotePluginClient( key_t _shm_in, key_t _shm_out ) : RemotePluginBase( new shmFifo( _shm_in ), new shmFifo( _shm_out ) ), #ifdef USE_QT_SHMEM m_shmObj(), + m_shmQtID( "/usr/bin/lmms" ), #endif + m_SncVSTplug( NULL ), m_shm( NULL ), m_inputCount( 0 ), m_outputCount( 0 ), m_sampleRate( 44100 ), m_bufferSize( 0 ) { +#ifdef USE_QT_SHMEM + if( m_shmQtID.attach( QSharedMemory::ReadOnly ) ) + { + m_SncVSTplug = (sncVST *) m_shmQtID.data(); + m_bufferSize = m_SncVSTplug->m_bufferSize; + m_sampleRate = m_SncVSTplug->m_sampleRate; + return; + } +#else + key_t key; + int m_shmID; + + if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) + { + perror( "RemotePluginClient::ftok" ); + } + else + { // connect to shared memory segment + if( ( m_shmID = shmget( key, 0, 0 ) ) == -1 ) + { + perror( "RemotePluginClient::shmget" ); + } + else + { // attach segment + m_SncVSTplug = (sncVST *)shmat(m_shmID, 0, 0); + if( m_SncVSTplug == (sncVST *)( -1 ) ) + { + perror( "RemotePluginClient::shmat" ); + } + else + { + m_bufferSize = m_SncVSTplug->m_bufferSize; + m_sampleRate = m_SncVSTplug->m_sampleRate; + + // detach segment + if( shmdt(m_SncVSTplug) == -1 ) + { + perror("RemotePluginClient::shmdt"); + } + return; + } + } + } +#endif + // if attaching shared memory fails sendMessage( IdSampleRateInformation ); sendMessage( IdBufferSizeInformation ); } @@ -1023,6 +1075,9 @@ RemotePluginClient::RemotePluginClient( key_t _shm_in, key_t _shm_out ) : RemotePluginClient::~RemotePluginClient() { +#ifdef USE_QT_SHMEM + m_shmQtID.detach(); +#endif sendMessage( IdQuit ); #ifndef USE_QT_SHMEM @@ -1032,6 +1087,14 @@ RemotePluginClient::~RemotePluginClient() +#ifdef USE_QT_SHMEM +sncVST * RemotePluginClient::getQtVSTshm() +{ + return m_SncVSTplug; +} +#endif + + bool RemotePluginClient::processMessage( const message & _m ) { diff --git a/include/VST_sync_shm.h b/include/VST_sync_shm.h new file mode 100644 index 000000000..595b21cd9 --- /dev/null +++ b/include/VST_sync_shm.h @@ -0,0 +1,58 @@ +/* + * VST_sync_shm.h - type declarations needed for VST to lmms host sync + * + * Copyright (c) 2004-2013 Tobias Doerffel + * + * 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. + * + */ + +#ifndef _VST_SYNC_SHM_H +#define _VST_SYNC_SHM_H + +// VST sync frequency (in ms), how often will be VST plugin synced +// keep it power of two if possible (not used by now) +//#define VST_SNC_TIMER 1 + +// When defined, latency should be subtracted from song PPQ position +//#define VST_SNC_LATENCY + +// define file for ftok as shared memory shmget key +#define VST_SNC_SHM_KEY_FILE "/dev/null" +//#define VST_SNC_SHM_RND_KEY 3561653564469 + +struct sncVST +{ + bool isPlayin; + double ppqPos; + int timeSigNumer; + int timeSigDenom; + bool isCycle; + bool hasSHM; + double cycleStart; + double cycleEnd; + int m_bufferSize; + int m_sampleRate; + int m_bpm; + +#ifdef VST_SNC_LATENCY + double m_latency; +#endif +} ; + +#endif diff --git a/include/aeffectx.h b/include/aeffectx.h index 1e8dea3f6..393249b03 100644 --- a/include/aeffectx.h +++ b/include/aeffectx.h @@ -114,8 +114,14 @@ const int kEffectMagic = CCONST( 'V', 's', 't', 'P' ); const int kVstLangEnglish = 1; const int kVstMidiType = 1; const int kVstParameterUsesFloatStep = 1 << 2; +const int kVstPpqPosValid = 1 << 9; const int kVstTempoValid = 1 << 10; +const int kVstBarsValid = 1 << 11; +const int kVstCyclePosValid = 1 << 12; +const int kVstTimeSigValid = 1 << 13; const int kVstTransportPlaying = 1 << 1; +const int kVstTransportCycleActive = 1 << 2; +const int kVstTransportChanged = 1; class RemoteVstPlugin; @@ -267,12 +273,18 @@ public: double samplePos; // 08 double sampleRate; - // unconfirmed 10 18 - char empty1[8 + 8]; + // unconfirmed 10 + char empty1[8]; + // 18 + double ppqPos; // 20? double tempo; - // unconfirmed 28 30 38 - char empty2[8 + 8 + 8]; + // 28 + double barStartPos; + // 30? + double cycleStartPos; + // 38? + double cycleEndPos; // 40? int timeSigNumerator; // 44? diff --git a/include/setup_dialog.h b/include/setup_dialog.h index e899cd6e5..d22fd9328 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -108,6 +108,7 @@ private slots: void toggleAutoSave( bool _enabled ); void toggleOneInstrumentTrackWindow( bool _enabled ); void toggleCompactTrackButtons( bool _enabled ); + void toggleSyncVSTPlugins( bool _enabled ); private: @@ -156,6 +157,7 @@ private: bool m_enableAutoSave; bool m_oneInstrumentTrackWindow; bool m_compactTrackButtons; + bool m_syncVSTPlugins; typedef QMap AswMap; typedef QMap MswMap; diff --git a/include/song.h b/include/song.h index 45c0a6f69..eddfe2d88 100644 --- a/include/song.h +++ b/include/song.h @@ -25,13 +25,14 @@ #ifndef _SONG_H #define _SONG_H +#include #include #include "track_container.h" #include "AutomatableModel.h" #include "Controller.h" #include "MeterModel.h" - +#include "VST_sync_shm.h" class AutomationTrack; class pattern; @@ -249,6 +250,8 @@ private slots: void updateFramesPerTick(); + void updateSampleRateSHM(); + private: @@ -323,6 +326,9 @@ private: } ; QVector m_actions; + int m_shmID; + sncVST * m_SncVSTplug; + QSharedMemory m_shmQtID; friend class engine; friend class songEditor; diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 1231cb1c4..254d70071 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -92,7 +92,20 @@ struct ERect #include "midi.h" #include "communication.h" +#include "VST_sync_shm.h" +#ifdef LMMS_BUILD_WIN32 +#define USE_QT_SHMEM +#endif + +#ifndef USE_QT_SHMEM +#include +#include +#include +#include +#include +#include +#endif static VstHostLanguages hlang = LanguageEnglish; @@ -303,6 +316,18 @@ private: double m_currentSamplePos; int m_currentProgram; + // host to plugin synchronisation data structure + struct in + { + double lastppqPos; + double m_Timestamp; + } ; + + in * m_in; + + int m_shmID; + sncVST * m_SncVSTplug; + } ; @@ -324,23 +349,65 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : m_midiEvents(), m_bpm( 0 ), m_currentSamplePos( 0 ), - m_currentProgram( -1 ) + m_currentProgram( -1 ), + m_in( NULL ), + m_shmID( -1 ), + m_SncVSTplug( NULL ) + { pthread_mutex_init( &m_pluginLock, NULL ); __plugin = this; +#ifndef USE_QT_SHMEM + key_t key; + if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) + { + perror( "RemoteVstPlugin.cpp::ftok" ); + } + else + { // connect to shared memory segment + if( ( m_shmID = shmget( key, 0, 0 ) ) == -1 ) + { + perror( "RemoteVstPlugin.cpp::shmget" ); + } + else + { // attach segment + m_SncVSTplug = (sncVST *)shmat(m_shmID, 0, 0); + if( m_SncVSTplug == (sncVST *)( -1 ) ) + { + perror( "RemoteVstPlugin.cpp::shmat" ); + } + } + } +#else + m_SncVSTplug = RemotePluginClient::getQtVSTshm(); +#endif + if( m_SncVSTplug == NULL ) + { + fprintf(stderr, "RemoteVstPlugin.cpp: " + "Failed to initialize shared memory for VST synchronization.\n" + " (VST-host synchronization will be disabled)\n"); + m_SncVSTplug = (sncVST*) malloc( sizeof( sncVST ) ); + m_SncVSTplug->isPlayin = true; + m_SncVSTplug->timeSigNumer = 4; + m_SncVSTplug->timeSigDenom = 4; + m_SncVSTplug->ppqPos = 0; + m_SncVSTplug->isCycle = false; + m_SncVSTplug->hasSHM = false; + m_SncVSTplug->m_sampleRate = sampleRate(); + } + + m_in = ( in* ) new char[ sizeof( in ) ]; + m_in->lastppqPos = 0; + m_in->m_Timestamp = -1; + // process until we have loaded the plugin while( 1 ) { message m = receiveMessage(); processMessage( m ); - //if( m.id == IdVstLoadPlugin || m.id == IdQuit ) - - // IdBufferSizeInformation is sent right after plugin load - // otherwise causes deadlocks to FxMixer/EffectChain - - if( m.id == IdBufferSizeInformation || m.id == IdQuit ) + if( m.id == IdVstLoadPlugin || m.id == IdQuit ) { break; } @@ -352,6 +419,21 @@ RemoteVstPlugin::RemoteVstPlugin( key_t _shm_in, key_t _shm_out ) : RemoteVstPlugin::~RemoteVstPlugin() { +#ifndef USE_QT_SHMEM + // detach shared memory segment + if( shmdt( m_SncVSTplug ) == -1) + { + if( __plugin->m_SncVSTplug->hasSHM ) + { + perror( "~RemoteVstPlugin::shmdt" ); + } + if( m_SncVSTplug != NULL ) + { + delete m_SncVSTplug; + m_SncVSTplug = NULL; + } + } +#endif if( m_window != NULL ) { pluginDispatch( effEditClose ); @@ -491,6 +573,12 @@ void RemoteVstPlugin::init( const std::string & _plugin_file ) updateInOutCount(); + // some plugins have to set samplerate during init + if( m_SncVSTplug->hasSHM ) + { + updateSampleRate(); + } + /* set program to zero */ /* i comment this out because it breaks dfx Geometer * looks like we cant set programs for it @@ -1356,16 +1444,62 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, // fields are required (see valid masks above), as some // items may require extensive conversions - memset( &_timeInfo, 0, sizeof( _timeInfo ) ); + // Shared memory was initialised? - see song.cpp + //assert( __plugin->m_SncVSTplug != NULL ); + memset( &_timeInfo, 0, sizeof( _timeInfo ) ); _timeInfo.samplePos = __plugin->m_currentSamplePos; - _timeInfo.sampleRate = __plugin->sampleRate(); + _timeInfo.sampleRate = __plugin->m_SncVSTplug->hasSHM ? + __plugin->m_SncVSTplug->m_sampleRate : + __plugin->sampleRate(); _timeInfo.flags = 0; - _timeInfo.tempo = __plugin->m_bpm; - _timeInfo.timeSigNumerator = 4; - _timeInfo.timeSigDenominator = 4; - _timeInfo.flags |= (/* kVstBarsValid|*/kVstTempoValid ); - _timeInfo.flags |= kVstTransportPlaying; + _timeInfo.tempo = __plugin->m_SncVSTplug->hasSHM ? + __plugin->m_SncVSTplug->m_bpm : + __plugin->m_bpm; + _timeInfo.timeSigNumerator = __plugin->m_SncVSTplug->timeSigNumer; + _timeInfo.timeSigDenominator = __plugin->m_SncVSTplug->timeSigDenom; + _timeInfo.flags |= kVstTempoValid; + _timeInfo.flags |= kVstTimeSigValid; + + if( __plugin->m_SncVSTplug->isCycle ) + { + _timeInfo.cycleStartPos = __plugin->m_SncVSTplug->cycleStart; + _timeInfo.cycleEndPos = __plugin->m_SncVSTplug->cycleEnd; + _timeInfo.flags |= kVstCyclePosValid; + _timeInfo.flags |= kVstTransportCycleActive; + } + + if( __plugin->m_SncVSTplug->ppqPos != + __plugin->m_in->m_Timestamp ) + { + _timeInfo.ppqPos = __plugin->m_SncVSTplug->ppqPos; + _timeInfo.flags |= kVstTransportChanged; + __plugin->m_in->lastppqPos = __plugin->m_SncVSTplug->ppqPos; + __plugin->m_in->m_Timestamp = __plugin->m_SncVSTplug->ppqPos; + } + else if( __plugin->m_SncVSTplug->isPlayin ) + { + __plugin->m_in->lastppqPos += ( + __plugin->m_SncVSTplug->hasSHM ? + __plugin->m_SncVSTplug->m_bpm : + __plugin->m_bpm ) / (double)10340; + _timeInfo.ppqPos = __plugin->m_in->lastppqPos; + } +// _timeInfo.ppqPos = __plugin->m_SncVSTplug->ppqPos; + _timeInfo.flags |= kVstPpqPosValid; + + if( __plugin->m_SncVSTplug->isPlayin ) + { + _timeInfo.flags |= kVstTransportPlaying; + } + _timeInfo.barStartPos = ( (int) ( _timeInfo.ppqPos / + ( 4 *__plugin->m_SncVSTplug->timeSigNumer + / (float) __plugin->m_SncVSTplug->timeSigDenom ) ) ) * + ( 4 * __plugin->m_SncVSTplug->timeSigNumer + / (float) __plugin->m_SncVSTplug->timeSigDenom ); + + _timeInfo.flags |= kVstBarsValid; + #ifdef LMMS_BUILD_WIN64 return (long long) &_timeInfo; #else diff --git a/src/core/song.cpp b/src/core/song.cpp index d233e5946..e1ce8ae86 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -61,6 +61,21 @@ #include "text_float.h" #include "timeline.h" +#ifdef LMMS_BUILD_WIN32 +#ifndef USE_QT_SHMEM +#define USE_QT_SHMEM +#endif +#endif + +#ifndef USE_QT_SHMEM +#include +#include +#include +#include +#include +#include +#endif + tick_t midiTime::s_ticksPerTact = DefaultTicksPerTact; @@ -88,7 +103,10 @@ song::song() : m_length( 0 ), m_trackToPlay( NULL ), m_patternToPlay( NULL ), - m_loopPattern( false ) + m_loopPattern( false ), + m_shmID( -1 ), + m_SncVSTplug( NULL ), + m_shmQtID( "/usr/bin/lmms" ) { connect( &m_tempoModel, SIGNAL( dataChanged() ), this, SLOT( setTempo() ) ); @@ -101,6 +119,59 @@ song::song() : connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateFramesPerTick() ) ); + // handle VST plugins sync + if( configManager::inst()->value( "ui", "syncvstplugins" ).toInt() ) + { + connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), this, + SLOT( updateSampleRateSHM() ) ); +#ifdef USE_QT_SHMEM + if ( !m_shmQtID.create( sizeof( sncVST ) ) ) + { + fprintf(stderr, "song.cpp::m_shmQtID create SHM error: %s\n", + m_shmQtID.errorString().toStdString().c_str() ); + } + m_SncVSTplug = (sncVST *) m_shmQtID.data(); +#else + key_t key; // make the key: + if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) + { + perror( "song.cpp::ftok" ); + } + else + { // connect to shared memory segment + if( ( m_shmID = shmget( key, sizeof( sncVST ), + 0644 | IPC_CREAT ) ) == -1 ) + { + perror( "song.cpp::shmget" ); + } + else + { // attach segment + m_SncVSTplug = (sncVST *)shmat(m_shmID, 0, 0); + if( m_SncVSTplug == (sncVST *)( -1 ) ) + { + perror( "song.cpp::shmat" ); + } + } + } +#endif + // if we are connected into shared memory + if( m_SncVSTplug != NULL ) + { + m_SncVSTplug->isPlayin = m_playing | m_exporting; + m_SncVSTplug->hasSHM = true; + m_SncVSTplug->m_sampleRate = + engine::getMixer()->processingSampleRate(); + m_SncVSTplug->m_bufferSize = + engine::getMixer()->framesPerPeriod(); + m_SncVSTplug->timeSigNumer = 4; + m_SncVSTplug->timeSigDenom = 4; + } + } // end of VST plugin sync section + + if( m_SncVSTplug == NULL ) + { + m_SncVSTplug = (sncVST*) malloc( sizeof( sncVST ) ); + } connect( &m_masterVolumeModel, SIGNAL( dataChanged() ), this, SLOT( masterVolumeChanged() ) ); @@ -116,6 +187,24 @@ song::song() : song::~song() { + // detach shared memory, delete it: +#ifdef USE_QT_SHMEM + m_shmQtID.detach(); +#else + if( shmdt( m_SncVSTplug ) == -1) + { + if( m_SncVSTplug->hasSHM ) + { + perror("~song::shmdt"); + } + if( m_SncVSTplug != NULL ) + { + delete m_SncVSTplug; + m_SncVSTplug = NULL; + } + } + shmctl(m_shmID, IPC_RMID, NULL); +#endif m_playing = false; delete m_globalAutomationTrack; } @@ -150,6 +239,13 @@ void song::setTempo() engine::updateFramesPerTick(); + m_SncVSTplug->m_bpm = tempo; + +#ifdef VST_SNC_LATENCY + m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * tempo / + ( (double) m_SncVSTplug->m_sampleRate * 60 ); +#endif + emit tempoChanged( tempo ); } @@ -162,6 +258,8 @@ void song::setTimeSignature() emit timeSignatureChanged( m_oldTicksPerTact, ticksPerTact() ); emit dataChanged(); m_oldTicksPerTact = ticksPerTact(); + m_SncVSTplug->timeSigNumer = getTimeSigModel().getNumerator(); + m_SncVSTplug->timeSigDenom = getTimeSigModel().getDenominator(); } @@ -178,6 +276,7 @@ void song::doActions() timeLine * tl = m_playPos[m_playMode].m_timeLine; m_playing = false; + m_SncVSTplug->isPlayin = m_exporting; m_recording = true; if( tl != NULL ) { @@ -219,31 +318,37 @@ void song::doActions() case ActionPlaySong: m_playMode = Mode_PlaySong; m_playing = true; + m_SncVSTplug->isPlayin = true; Controller::resetFrameCounter(); break; case ActionPlayTrack: m_playMode = Mode_PlayTrack; m_playing = true; + m_SncVSTplug->isPlayin = true; break; case ActionPlayBB: m_playMode = Mode_PlayBB; m_playing = true; + m_SncVSTplug->isPlayin = true; break; case ActionPlayPattern: m_playMode = Mode_PlayPattern; m_playing = true; + m_SncVSTplug->isPlayin = true; break; case ActionPause: m_playing = false;// just set the play-flag + m_SncVSTplug->isPlayin = m_exporting; m_paused = true; break; case ActionResumeFromPause: m_playing = true;// just set the play-flag + m_SncVSTplug->isPlayin = true; m_paused = false; break; } @@ -360,8 +465,14 @@ void song::processNextBuffer() while( total_frames_played < engine::getMixer()->framesPerPeriod() ) { - f_cnt_t played_frames = engine::getMixer() - ->framesPerPeriod() - total_frames_played; + f_cnt_t played_frames = ( m_SncVSTplug->m_bufferSize = engine::getMixer() + ->framesPerPeriod() ) - total_frames_played; + +#ifdef VST_SNC_LATENCY + m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * + m_SncVSTplug->m_bpm / + ( (double) m_SncVSTplug->m_sampleRate * 60 ); +#endif float current_frame = m_playPos[m_playMode].currentFrame(); // did we play a tick? @@ -369,6 +480,14 @@ void song::processNextBuffer() { int ticks = m_playPos[m_playMode].getTicks() + (int)( current_frame / frames_per_tick ); + +#ifdef VST_SNC_LATENCY + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (double)48 ) - + m_SncVSTplug->m_latency; +#else + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (double)48 ); +#endif + // did we play a whole tact? if( ticks >= midiTime::ticksPerTact() ) { @@ -402,18 +521,37 @@ void song::processNextBuffer() // offset ticks = ticks % ( max_tact * midiTime::ticksPerTact() ); +#ifdef VST_SNC_LATENCY + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) + / (double)48 ) + - m_SncVSTplug->m_latency; +#else + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) + / (double)48 ); +#endif } } m_playPos[m_playMode].setTicks( ticks ); if( check_loop ) { + m_SncVSTplug->isCycle = true; + m_SncVSTplug->cycleStart = + ( tl->loopBegin().getTicks() ) + / (double)48; + m_SncVSTplug->cycleEnd = + ( tl->loopEnd().getTicks() ) + / (double)48; if( m_playPos[m_playMode] >= tl->loopEnd() ) { m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); } } + else + { + m_SncVSTplug->isCycle = false; + } current_frame = fmodf( current_frame, frames_per_tick ); m_playPos[m_playMode].setCurrentFrame( current_frame ); @@ -637,6 +775,7 @@ void song::startExport() doActions(); m_exporting = true; + m_SncVSTplug->isPlayin = true; } @@ -647,6 +786,7 @@ void song::stopExport() stop(); m_exporting = false; m_exportLoop = false; + m_SncVSTplug->isPlayin = m_playing; } @@ -1216,6 +1356,19 @@ void song::updateFramesPerTick() +void song::updateSampleRateSHM() +{ + m_SncVSTplug->m_sampleRate = engine::getMixer()->processingSampleRate(); + +#ifdef VST_SNC_LATENCY + m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * m_SncVSTplug->m_bpm / + ( (double) m_SncVSTplug->m_sampleRate * 60 ); +#endif +} + + + + void song::setModified() { if( !m_loadingProject ) diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 26566d19b..b8f38f9bf 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -117,7 +117,9 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_oneInstrumentTrackWindow( configManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() ), m_compactTrackButtons( configManager::inst()->value( "ui", - "compacttrackbuttons" ).toInt() ) + "compacttrackbuttons" ).toInt() ), + m_syncVSTPlugins( configManager::inst()->value( "ui", + "syncvstplugins" ).toInt() ) { setWindowIcon( embed::getIconPixmap( "setup_general" ) ); setWindowTitle( tr( "Setup LMMS" ) ); @@ -186,7 +188,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : tabWidget * misc_tw = new tabWidget( tr( "MISC" ), general ); - misc_tw->setFixedHeight( 156 ); + misc_tw->setFixedHeight( 174 ); ledCheckBox * enable_tooltips = new ledCheckBox( tr( "Enable tooltips" ), @@ -247,6 +249,15 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : this, SLOT( toggleCompactTrackButtons( bool ) ) ); + ledCheckBox * syncVST = new ledCheckBox( + tr( "Sync VST plugins to host playback" ), + misc_tw ); + syncVST->move( 10, 144 ); + syncVST->setChecked( m_syncVSTPlugins ); + connect( syncVST, SIGNAL( toggled( bool ) ), + this, SLOT( toggleSyncVSTPlugins( bool ) ) ); + + gen_layout->addWidget( bufsize_tw ); gen_layout->addSpacing( 10 ); @@ -774,6 +785,8 @@ void setupDialog::accept() QString::number( m_oneInstrumentTrackWindow ) ); configManager::inst()->setValue( "ui", "compacttrackbuttons", QString::number( m_compactTrackButtons ) ); + configManager::inst()->setValue( "ui", "syncvstplugins", + QString::number( m_syncVSTPlugins ) ); configManager::inst()->setWorkingDir( m_workingDir ); configManager::inst()->setVSTDir( m_vstDir ); @@ -956,6 +969,15 @@ void setupDialog::toggleCompactTrackButtons( bool _enabled ) +void setupDialog::toggleSyncVSTPlugins( bool _enabled ) +{ + m_syncVSTPlugins = _enabled; +} + + + + + void setupDialog::toggleOneInstrumentTrackWindow( bool _enabled ) { m_oneInstrumentTrackWindow = _enabled; From 7084ec0be6d4c47de27498c4c4ad7909c5748add Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 7 Feb 2013 21:58:55 +0100 Subject: [PATCH 100/133] VST Effects: preserve effect name after LMMS project reload This patch will set plugin name (information which is not stored with lmms project file) according plugin file, without *.dll, when is LMMS project loaded from the file. Future verion could maybe use PluginBrowser or EffectSellectDialog for the same. Signed-off-by: Tobias Doerffel --- plugins/vst_effect/VstEffect.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/vst_effect/VstEffect.cpp b/plugins/vst_effect/VstEffect.cpp index a6286d38e..a4215cc18 100644 --- a/plugins/vst_effect/VstEffect.cpp +++ b/plugins/vst_effect/VstEffect.cpp @@ -65,7 +65,8 @@ VstEffect::VstEffect( Model * _parent, { openPlugin( m_key.attributes["file"] ); } - setDisplayName( m_key.name ); + setDisplayName( m_key.attributes["file"].section( ".dll", 0, 0 ).isEmpty() + ? m_key.name : m_key.attributes["file"].section( ".dll", 0, 0 ) ); } From 0b394269537c2d444b2ee03a4e0ea73182ea4b09 Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Thu, 7 Feb 2013 22:03:05 +0100 Subject: [PATCH 101/133] VST Effects: Open each effect only once, when loaded from project file Folowing change should ensure VST effect is opened just once, once loaded from project file. Double opening seems to trace back to commit 184ddc4d1c2947e459c96158cc77f0ff4699846f from 2006, when this VST effect save / load feature was introduced as new. Anyway VST effect parameters seems to load corectly in VstEffectControls::loadSettings, from project file even without double VST effect opening. Signed-off-by: Tobias Doerffel --- plugins/vst_effect/VstEffectControls.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/vst_effect/VstEffectControls.cpp b/plugins/vst_effect/VstEffectControls.cpp index c8aeaf075..0dd31ed10 100644 --- a/plugins/vst_effect/VstEffectControls.cpp +++ b/plugins/vst_effect/VstEffectControls.cpp @@ -61,8 +61,8 @@ VstEffectControls::~VstEffectControls() void VstEffectControls::loadSettings( const QDomElement & _this ) { - m_effect->closePlugin(); - m_effect->openPlugin( _this.attribute( "plugin" ) ); + //m_effect->closePlugin(); + //m_effect->openPlugin( _this.attribute( "plugin" ) ); m_effect->m_pluginMutex.lock(); if( m_effect->m_plugin != NULL ) { From 50c2242caad44c5ccf684e6bc6c287e31189ba0b Mon Sep 17 00:00:00 2001 From: Mike Choi Date: Mon, 11 Feb 2013 00:19:08 +0100 Subject: [PATCH 102/133] VST sync patch: compatibility fix for 64bit builds It seems 64bit builds for some reason have problems with VST Sync feature on, workaround seems to be converting VST sync patch from double to floats, which does work both with 32 and 64bit builds. Double precision seems to produce odd numbers with 64bit build. (tested on VirtualBox Linux Mint 14.1 64 bit OS) (cherry picked from commit 011f87e6e60cccd16f3783e9c4885e03d95c1e56) Signed-off-by: Tobias Doerffel --- include/VST_sync_shm.h | 8 ++++---- plugins/vst_base/RemoteVstPlugin.cpp | 6 +++--- src/core/song.cpp | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/VST_sync_shm.h b/include/VST_sync_shm.h index 595b21cd9..7b6a813d5 100644 --- a/include/VST_sync_shm.h +++ b/include/VST_sync_shm.h @@ -39,19 +39,19 @@ struct sncVST { bool isPlayin; - double ppqPos; + float ppqPos; int timeSigNumer; int timeSigDenom; bool isCycle; bool hasSHM; - double cycleStart; - double cycleEnd; + float cycleStart; + float cycleEnd; int m_bufferSize; int m_sampleRate; int m_bpm; #ifdef VST_SNC_LATENCY - double m_latency; + float m_latency; #endif } ; diff --git a/plugins/vst_base/RemoteVstPlugin.cpp b/plugins/vst_base/RemoteVstPlugin.cpp index 254d70071..122dda3bc 100644 --- a/plugins/vst_base/RemoteVstPlugin.cpp +++ b/plugins/vst_base/RemoteVstPlugin.cpp @@ -319,8 +319,8 @@ private: // host to plugin synchronisation data structure struct in { - double lastppqPos; - double m_Timestamp; + float lastppqPos; + float m_Timestamp; } ; in * m_in; @@ -1482,7 +1482,7 @@ intptr_t RemoteVstPlugin::hostCallback( AEffect * _effect, int32_t _opcode, __plugin->m_in->lastppqPos += ( __plugin->m_SncVSTplug->hasSHM ? __plugin->m_SncVSTplug->m_bpm : - __plugin->m_bpm ) / (double)10340; + __plugin->m_bpm ) / (float)10340; _timeInfo.ppqPos = __plugin->m_in->lastppqPos; } // _timeInfo.ppqPos = __plugin->m_SncVSTplug->ppqPos; diff --git a/src/core/song.cpp b/src/core/song.cpp index e1ce8ae86..6fdba2b21 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -243,7 +243,7 @@ void song::setTempo() #ifdef VST_SNC_LATENCY m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * tempo / - ( (double) m_SncVSTplug->m_sampleRate * 60 ); + ( (float) m_SncVSTplug->m_sampleRate * 60 ); #endif emit tempoChanged( tempo ); @@ -471,7 +471,7 @@ void song::processNextBuffer() #ifdef VST_SNC_LATENCY m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * m_SncVSTplug->m_bpm / - ( (double) m_SncVSTplug->m_sampleRate * 60 ); + ( (float) m_SncVSTplug->m_sampleRate * 60 ); #endif float current_frame = m_playPos[m_playMode].currentFrame(); @@ -482,10 +482,10 @@ void song::processNextBuffer() + (int)( current_frame / frames_per_tick ); #ifdef VST_SNC_LATENCY - m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (double)48 ) - + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (float)48 ) - m_SncVSTplug->m_latency; #else - m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (double)48 ); + m_SncVSTplug->ppqPos = ( ( ticks + 0 ) / (float)48 ); #endif // did we play a whole tact? @@ -523,11 +523,11 @@ void song::processNextBuffer() midiTime::ticksPerTact() ); #ifdef VST_SNC_LATENCY m_SncVSTplug->ppqPos = ( ( ticks + 0 ) - / (double)48 ) + / (float)48 ) - m_SncVSTplug->m_latency; #else m_SncVSTplug->ppqPos = ( ( ticks + 0 ) - / (double)48 ); + / (float)48 ); #endif } } @@ -538,10 +538,10 @@ void song::processNextBuffer() m_SncVSTplug->isCycle = true; m_SncVSTplug->cycleStart = ( tl->loopBegin().getTicks() ) - / (double)48; + / (float)48; m_SncVSTplug->cycleEnd = ( tl->loopEnd().getTicks() ) - / (double)48; + / (float)48; if( m_playPos[m_playMode] >= tl->loopEnd() ) { m_playPos[m_playMode].setTicks( @@ -1362,7 +1362,7 @@ void song::updateSampleRateSHM() #ifdef VST_SNC_LATENCY m_SncVSTplug->m_latency = m_SncVSTplug->m_bufferSize * m_SncVSTplug->m_bpm / - ( (double) m_SncVSTplug->m_sampleRate * 60 ); + ( (float) m_SncVSTplug->m_sampleRate * 60 ); #endif } From 2375b7f0a0c29b047594433c4c7689d718df209e Mon Sep 17 00:00:00 2001 From: NoiseByNorthwest Date: Mon, 18 Feb 2013 22:21:27 +0100 Subject: [PATCH 103/133] Fixes #3604316: LMMS crashes using AudioFileProcessor Signed-off-by: Tobias Doerffel --- plugins/audio_file_processor/audio_file_processor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 208a03d9d..729e9e429 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -927,6 +927,10 @@ void AudioFileProcessorWaveView::slideSamplePointByFrames( knobType _point, f_cn void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) { + if( m_sampleBuffer.frames() <= 1 ) + { + return; + } const double v = double( _frames ) / m_sampleBuffer.frames(); m_startKnob->slideBy( v, false ); m_endKnob->slideBy( v, false ); From 9268398626e35844cea7fcc48a08622c6559182d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 27 Feb 2013 21:48:49 +0100 Subject: [PATCH 104/133] AboutDialog: updated copyright year --- src/gui/dialogs/about_dialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/dialogs/about_dialog.ui b/src/gui/dialogs/about_dialog.ui index 40a6d4c99..b795fd0f8 100644 --- a/src/gui/dialogs/about_dialog.ui +++ b/src/gui/dialogs/about_dialog.ui @@ -125,7 +125,7 @@ - Copyright (c) 2004-2012, LMMS developers + Copyright (c) 2004-2013, LMMS developers true From fe35743ef088671eecda2802908a7bb0fd331e52 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 27 Feb 2013 22:13:52 +0100 Subject: [PATCH 105/133] Default theme: new graphics for note icons Thanks to John Serafino for the creative work. --- data/themes/default/note.png | Bin 1006 -> 974 bytes data/themes/default/note_double_whole.png | Bin 1159 -> 1219 bytes data/themes/default/note_eighth.png | Bin 985 -> 974 bytes data/themes/default/note_half.png | Bin 860 -> 556 bytes data/themes/default/note_quarter.png | Bin 852 -> 547 bytes data/themes/default/note_sixteenth.png | Bin 894 -> 1110 bytes data/themes/default/note_thirtysecond.png | Bin 1090 -> 1191 bytes data/themes/default/note_tripleteighth.png | Bin 1240 -> 1088 bytes data/themes/default/note_triplethalf.png | Bin 1037 -> 1069 bytes data/themes/default/note_tripletquarter.png | Bin 1012 -> 726 bytes data/themes/default/note_tripletsixteenth.png | Bin 1122 -> 1291 bytes .../default/note_tripletthirtysecond.png | Bin 1399 -> 1453 bytes data/themes/default/note_whole.png | Bin 3718 -> 709 bytes 13 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/themes/default/note.png b/data/themes/default/note.png index 3f475b0ea497624eca88f9553b2ad24ad16105a5..f81425daed4eb550016c7efff9a545f72db8ad77 100644 GIT binary patch delta 952 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 diff --git a/data/themes/default/note_half.png b/data/themes/default/note_half.png index d73ccb4f576f6eb618fa15edcb0adddaff005d43..aff73e593196300dd5ed6a865c956e3a5991e5eb 100644 GIT binary patch delta 525 zcmV+o0`mRb2CM{-Du4d~{{a7>y{D4^000SaNLh0L00l7s00l7tx9uoW00007bV*G` z2i*Y=6cZ^w1y1Dv00Fs4L_t(Y$L*ERYZE~jhM#X|XLo}pJ*W-XC2Amu6-0#k4-^mH z1+Ri&d#?vUPf|qCqy7idt9q#-6!9YTs+VH%ASOr*B(2rPO@A^oUUsF}nwStg<%5BN zVcuun`SA`6R|15yu18p?g?-N(COH|@!sq+;-OMl z`dFE+%x#_9%5T;;y2isF#X+{BKwVX5r7XA2j(J~Qt=?Z>S$_@a`8)I1qD$eu*3@o1 z2;lW`;Hxw0qKN43YWG%crS<~Y27at9uf1vPG&+sm_J1zW16B3_$1Dk-q%Adf*i}SJ(Y4&GWGPuB zp#ij0$UYaUp92z%*)T8FX{AKObCEkPU8&o*_GV|f^B6-bbzuIsMZW;11A?-5%^aTq P00004}!IWj&Yt20YQ`;biN*Qa+8UV(D z^1{3TN}n=c(tldJ##hZw0#TsKOTSlwvNKqCSP0JIShF?|1F9EJ15iRMJs0TVN zz@$q+Uti{*UZ8k8^}$~XjL8rZMXeXB{uu!j^DnGNVzq1l)!@!0Z9ah|!0FSMLmpkA zRDbDyvA!U}EwN6Z&efq}uLe+<$vZz4sb8wLh8hXkhcM*|a*j`Zk*ak^+lD7^2AhEX zpLM7>F@KL9JvsM(?hWp3x!gIi{mTBr)@!?`^-EF3orsE_jVr^bIs& zO3G+m;}Ify@L5maqHDm&dTsMhbySirQ)L<^8)3@2# zad7O;*h8Gwx&*m^gI^{vCrdJf2wIvL>D!LfdDW8dPk8_U002ov JPDHLkV1f*}iBJFl diff --git a/data/themes/default/note_quarter.png b/data/themes/default/note_quarter.png index 01746885530cfe34f0f14c8accd3e9809437caf3..6c3acf2cd6e01763d5ba450fe43ffa39ae7c8c97 100644 GIT binary patch delta 515 zcmV+e0{s2d2BQR!Du4d~{{a7>y{D4^000SaNLh0L00l7s00l7tx9uoW00007bV*G` z2i*Y=6ciJ-NCA)l00FQ`L_t(Y$L*E9i_}0E#($F!HutgTez@ocRBnZ9+{V`Cu&`IK zvlIUX{|~|X8cQ4N1I1nh?c@qUL^vbP?Z=r-LNfE(BzPQpB!9tjUzlc?d4BV}!#v|s zuM`jfm!G_P@cz>6VEpCv_m4mU$VD5S1^^e>a6M-NHUv6#_SXee5FbE{xFt~iPr#oN zR2}g)0M+y??HsHDYrq=NsJt4lwu%L(%}e$!5O>Nzt42_TU6a`G{vxc(`2g5DBisRc z03xE=I;bLfn}2Pt4DSygjrKAo_>ms1;ML(C+8iwz?Qr3J{?@`Py0Iq#-lMe!t2DV7;7!FZ&_d1Y-))Eam4+lVk~T5|d^rztYSArWBUC4t%PG2E%f`oQFSx4U9EJIwCJ} zP8KsF9dZ0FIsg*DHf@msR_3#Lw&*OLXL;u9`3b(~Q<$97_*7P3lNb4${A~#^O}@aY zMFG(B@A~`B#CfbX)hz@oi5!`4=4EkM90D3xooUlQ>o;;bpf#qlRk;8F002ovPDHLk FV1jTx@6P}L delta 823 zcmV-71IYZN1k?tQDt`a~0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i5@<76>CC0sl7u00QAjL_t(Y$E}pjOH^SL#((d7@65fr9W|C1zy0JyL zNe3cf2r6h%{RsuFTm&sz1T6#C^#?>z(5`4o1szJlG$Qk}Mt>)@8LuWeGqUY6xZ6qQb>`fmV`O~11SMXRg_Rd3%VeWwLWfd2l0a)&HXYBKjC zTayv~a6>y#W$&S6tPYS_PZzg1slU9s%Nr@rU%(@SEEP5NO{(VJmd=rhg(hHf>mEv8 z%*&T=Y=1w`K3`vWpnbJ}=C-xu*h(X}{t@rRwfG^AhN`^&lQUj=8rT(bmxjArwGYS44iS`fw@H zobf&TJOZ)^1r3z>s}wWG*YrXKP-C+xefDhEhJRxg#bQtC2y`sV2?4WJ@TD4ND`CzL zGk$7DyqK%*gk7XoYSB9hO?Wb8^M%ApO1Z@8yd_{2m|Lf1(pRXCdB@^|Bd>umUe{N@8)*^jSV@==sq5HEV4Ohy$a*9FQ%QWH%w2K%HS2;cPbB z1TeVx)ys_imP%ERLvaZ^sn33zoiJtpg;L1@egetL{Rd<^EOP(=002ovPDHLkV1faC BbO!(c diff --git a/data/themes/default/note_sixteenth.png b/data/themes/default/note_sixteenth.png index 33cb9ad7a692b24f615b836502ad2656697f0824..661bcb7be0043519a80ee0e6b452bd71aac3ad78 100644 GIT binary patch delta 1083 zcmV-B1jPIP2G$6WDu4d~{{a7>y{D4^000SaNLh0L00l7s00l7tx9uoW00007bV*G` z2i*Y=6cq(qhlPLu00ZMmL_t(Y$DLJgXj@ej|K0oEYtopeNwY3Z29-3e18sEvD>!8g znX*k~W3Ug}4XNl9-SEQ@{ZJ9gL_`WxCMZ~tf#_sT>j&9}V}H8MVaM88?B-f+X{$|_ zw8=}FyuA1N-n-Wiy%}*$WzB`dy>Rb6zkAN_cg~e?dxHRQ0a*LuiJgOi4)+s_syUTU z7#09tX3GX$1%L#=BPmi;lF0h|UWpyQ>)G%l02%t)`=o z^~SaV2maJ z*Q^WO>&;y_bZyW4Z~0Dr6k2guf>*A8{h`-dcLVSNsDG&jU@2^w1X`bt{BY09k>@YI zfBn1Jq3kx!xJd{hP4_qSggY8rgtBN=001PVp8DGlG=22UH}Qi*pZs|xf5Z3?00c}B27|N8~`9p17K4nx;1p@Oyb+_f!?trR>7JAfLeb|hmbU_j%0(SjEs|5oT8YU zRLRl0o_{y@oPG4&Lel78ijPRu%9I0uW7yND4h(iqe4p-K_?66Qmt-s~=JtAGbQ1t* zH;kVF*Z>$^rE@MwUIdUD?i>H=+}k5N)FJB^eMEu_cWhubGp>Cxd0YVSs$@ziVE{A$ znS8>SIQP!g{aVU6zXVR*(XHVJZYM=V7%iKZHh%~>00)2xAa50|x$DQKJ}B+`n!OK~ z*7E;2r=sK~v38X;yOLa)Fk^01OK`>=LWz{0DO?Z_;PMKumPMh|idhkrSufaF)|O-2 zL(PN|$=2<^M!%dsDWYSQ0Av7Oc(FFPEBIVvTjP_yI$zMXY&|oUxy(5S=bYY>LfRA$ zY=2}n%Z&B^voD`sOjiOZ9rnmkITqX<>}zR@#v_dp1blutj3FIO$Nxyqa>5{&al_X% zdt^_@y(KY_=^Q_u91#MrD{-*&Yi*X^Xb1)30bc{cjbYpwX@*wRz!-zNppq=~=5qPA z?~C0%b2-~J+&4Z6zyNUbE3Fhk4e^qVjDK@Djt#>!Ft4Ven>sXIgJBxbOw$o0G62p` z-M^m~_gRVDQq9N}3WLWyUd30E zsRN+sOgM*guG|sRGHcS=3=~;`?O4cbd0zZm|J?e~ItxG-j(LSzy?;9$JSN7)QJW}UQ70Z;)H%hIvp_!ni_tN8;xg%tn*002ovPDHLkV1ml* B{tW;C delta 865 zcmV-n1D^cW2>u3;Dt`a~0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i5@<76v&(;48xb00Rq2L_t(Y$E}rHNK{c6g};4fbVkRSQPUc9tdZQPn3*A%u*WSt2MQyxmKRDX6K7FK371IvNLnP{1nP3i4>URDSmyFa}qTh7Z_A6)&fb>RT9gF zIakYlmBJ3Uj#6GSot;a41%g?-1!M#9W4xQB58!sZa1T5ldL%gyi-b&9J!_Ib*^0At zP1E)OY2&pjT3I^K7xISntyPDms3@O{?MGIbbeqfWw0|mH*F8Y;biJ^!!!_V|I`0RX z_MFn$;r((@a65fKHINHfrm6ryRH8Xdya*YVKJNEit65U}+H}t0Qc9Tti7qN8AdtGg z-72hv!sHD=CoSJDhoAjmReqrzh&6`q52GhDjbT3YHK=bXR_gt4ff%48=k45*ckkaM z0E06z)qnAnyS}TcGy#qSIe-iB81WH1Zi}u|*O;RY!1lqrvMa>$Wd(VF4Hy8r06#D^ zE`dK3&?XoXjbg_^_E|frOz6R`6_aS9q+-iKiP$k zNkUtaa5`Rq8^#-9F$f%!xkW0;G5d1ht4^aYVC+jA<;W09sTdI%se0SC!r&+Z`QIF< zjk3CgCJS^pp~GSPozP;Xw0|=qGHk>MqYjnPUQCo7Y9Oe~@V%}lKWFunGo;{45Y^Pp zv0C8+p*xpuKLA>QCgTQ$|MxVGhZrEGtgLLaNtxHRwzk?{zN+sPk$&Ja&|nbsj!_ad rL4+h3m}i)v10kd89w7Ly>H&TM=JE@h$Jz=a00000NkvXXu0mjfo@t3v diff --git a/data/themes/default/note_thirtysecond.png b/data/themes/default/note_thirtysecond.png index 80f970a7ceaf2965ebb74098a7b41c294b2b8b9c..a3daee57927420c2b03de184fe65194e8346a90d 100644 GIT binary patch delta 1165 zcmV;81akYr2&V~y{D4^000SaNLh0L00l7s00l7tx9uoW00007bV*G` z2i*Y~6D>N5LkfTZ00cBiL_t(Y$DNg1Y!p=($Ny($XK!t{Y_}I$2(?{WOlc{QY6=Np zzzXrf5Dd|_yhs2g(ZrC@m^7G>=tEz?2;oJHu^O#fsJ?hXM1Q0)q$RcV2DX|)OE2zr zXJ>D-b3fzDOtTb9=$_;}opZk5_y2B(#nUYUzy+ZA$mRV5{^#9Is-R3I=VZ+~H?951 zVpaff0PwOLTfwqqL;YL9?&{{!Z2!M0h71&$odN(U+QRG4w4l>myD=5t&B>OL=MkIxRJd*9~{2kbmDQ4 ztN;!VE!!$UCa~S-t*bB?*b^u%n7@K_&WjQH?-6o7xE*MgP=(OOG-13jN~6t(SdWtlJ!-1{LOq_)!1`0Fzx|5BM5dYK|vT zaf9I)qkp%3=%h4PmP7Knj4i(nccyKnR%pYFA6s_ZvGE zS-pPy+|>TmxG`zQo#nyZrAM84PN#i8s}Pw0Ynp(s(%pFQ;?8S_np@S$pZ6 zyBh0S%NwmSMRrLB(Tx~^7G|*Enc7hHhNHJ(TYr6llF+^c09~Qd({!>O62`6w8z2et zVE;!G&C}hnGl^S>+*-I%N?)Jv(TN(AW7^HT-46!sBs-p9oUvru+hvUaFdUgGt##}eIDh|`np8F08D<&79Dp2a6c+1=9HgPaym9(Y+n$b1U0jZ%x^h>^mdJ2?61LS|qig{D0`raa4=EI%Az-sR9;&mwkafSy1Ay^ZN== zT2zYivUL#CA}FO05(0@qT9+~NDF78f%AN0EQz5Bi?5q2?0ccB;Y%6e)T(X^-CJaM| ztjLH6JS0VeD2b328KR;Xmgh7BA`O2#_hGm%EL*@w0Ct@lFp!8PVr#-~4-9G`#D9mN z=^CPO0U delta 1063 zcmV+?1laqh3Bm}FDt`a~0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G` z2i5@<76=0yZ{Pm_00YlSL_t(Y$E{XvXj@ene$LI;O?s22N!NC@>5|wkTT_|KI-~}n zox=KouAmm(n9xy_;RoUe{*e9<6cqd;BEtSmK?L#76#SfsZGWkDU{+>Vw5|^u+}tEL#~;05m#kSDeBp55-k0}zpZ9&`3 z?dRWm{gu{P5`P#@2D4j8i9rB40L4l`)ln71oZwQOvU{7GpB^06=kJqG2BMpX=&JY! z3Pqt3o2qNc8yUC0ThsI5o1chnNB3)b$GbcIOR>KEjnpgvUSUH~qp9H8ce?NLqjcY> zD*=(+)^FU zds-q5%jt9~2tWWJ*Hj`zvSR)L-7{;(SpDcDLu%(^2W&SdL$bV-S>)210MBoQ0VDww zY6AdB(O~G$xJ1VfFZ`TLNJ*yWWp_jLmv}yTEjmpp4FcFK1=!0}%1{%(UaQ$M*(|Ei zdyCVT&wowcV4h^1MON)5glM%|w&oseTY-sKx*EqHI&;jdbDG;%r(=H>V%g08MteIS ziciUMVI4qrr<(S{fMSUcWke})eZ?;|b(`_{mqYz}HK)x6V&5z0lokM~odE!;MSU#Y zoW9@x-2C)$l5S@(F@JHvSc<$G3%Zz_%C$(uXg23IJ`H|7 zH|F~SRy7BJWf-Q6ff@j^V!F8FNBK0KFt);2cnSu_feosnF-i{(AL{ab5W9pG;U@^v zB|{x2WQ4T5s|3%UeNBJIo{VrK9Tz;QiSXO>)x;ar5^SaT%|e^rbyAkqzXQPQIOsZj zz<*(P05DbRD*-?#rT;k$uxAHWm=Sp`sbI~6vMwe)H|0IQ;&t&0~w zlJ0(^DK<0nEsk4`-(nUCu+jj8tiXyHh<})XunCR6I$3m= diff --git a/data/themes/default/note_tripleteighth.png b/data/themes/default/note_tripleteighth.png index b33cd4fb628b19078367412a9298b44c9963c45d..4e4d214dea14a86c7bed29c03761f743d7fc0a4f 100644 GIT binary patch delta 1045 zcmV+w1nT?P3BU-DB#|*0e+4lB00l7tx9uoW00007bV*G`2i*c17A-TNL8b8k00YfQ zL_t(Y$L*EPYg|H&LbB(KGjLxS6YQiDG?DJd19t!_Z%)hke^@tY3bWu)4ilEJe@|Ag(w~A zz>VyOpE|tIb$i*}0MUq*YkA_j=T0qt1vCH`kgadD@cw~Uf3vBP`R*+2;pApmzcUh?GL&=MK;Y`r80eGM#h*jGb`f`UVhg0~n=Lq!ePfw*tVA zL*npNLPY>d6UD|s912JP;ntA<29R9>rPOeT!+b|=#~RK5towlbfd3Alw&j01=(cJ~ zMASVz@9;1&e`M<}umB@+7f^3IX)Ot5IM{8_)*qbM>9J*+0&E})z2r94YEou$^Sak+kjD2NP@wT@fQJa}~f$+`V=rSe3X zAP87lU6HW0+0u!J67M-hXQtp3s7zOwEKZWKGpg2Kw*V_OpIUIr&awQcL%B3Xr94Y6 zJ4!F~e_5wL4%qgb{f?7&N(HCDzH)_$LWy)Lt?KS(%7~KUW_OcXvql(&c%DbKUgi2q z^&;R&N{Zcnhi1FU;>uqbW2iUl)LQju>FllVfzZd#Z*(@OuC3zv9s_T{t$LN^$vrzF6zgXt@cVsE)32LKBvPPFaT2K zNpmPsNgT&!9~y#l!Kv`~@N#_P0p!dp=A}d~@ra~lTH5ln|DAstkH+8g0j0K0%}ufZ P3jhEBNkvXXu0mjfZoCD9 delta 1196 zcmV;d1XKIK2-pdbBnkm@Qb$4nuFf3kks%y^3ljhU3ljkVnw%H_000McNliru*a#Z} zAsY9nmr4Kt1XxK#K~zY`rPWJJ9CaMP@$bye?#%AaE|5}L1Xe({+h^L6iq^)qcqvF? zv6$9YV`8FBjP+m>>j@thjaCmPdTA4z25mg*Nux~~4OM8N(7H@a#YC#KgThYp90)Z)0bp4OS{5s?DHdj98WSNMFz*3&7M>6)bH@nIwgXup zw`7li=qNn;<;(Bh3{URL3?+i(^Rb10J>sJ(0AL_>@vRdrbp zl3FI|x9qRnRZ?=evB{p>V(c57h^{w7+BM7n1<>tse{fu`de2+EIP17e+_V~C z>QbVVsdO)E#A#z4uNtBJtT~iCuir@gdN-sMEG95rvP<3VY*{g|#r{mmzeTqb!&5hm zI(xI_DXGf1p#6z%c6&wd&4qJ+UrpIu;6}wz@o7iQ;glpyz-e?EeO9kz;&=Lh{KU|$_QeF|rP!zz%}%Wp z@b)PU;c)0Cki;Sk3MMfMq-wye>w0!E0g%-%8G)4o!9bu_*K<8UNVu+leW#BYR_j(< z1(L89ur0kR3?yM-r2r5ECV**kC_ZG(*``GoA|{1^eQAO`!PJ?R0`uMH00YP*uBAGJ z4Wb8358x01T3b)ejpnR1uF9!h9;*|lWIgtA!Q=AsuHKnyc(Lx8V`Tw#& zG!ix!koy5Z;W@R8{r2O39Cm(%JHO4mW17B3e;rUR0i?A={vP0ne#BiO~C_<=}BV=O$X+KQDe*+ce!qrMi8qswS#G&4?u zG?eK~mPdKMVRyYmv)Wwq!n(7M0=kWhjAqA)qs;+jQ8w<~w7<_Ea{Myf4PqV)3z*8> zER-DKM5sVwpWNna@u({Xvbq{z%@vKzP-56M zn(WLrSz9#K3W5NC$u$1yBE#O{2wb_`)hmhp!m0q}Z+oDpyQ|yfatG>EZ;xxU>(J!X zl%l58ROfGoP_v=BLH5aStOfug^QYJesPpf68i)fI>Hh%3wGDLutcP&`0000< KMNUMnLSTXgyg$_d diff --git a/data/themes/default/note_triplethalf.png b/data/themes/default/note_triplethalf.png index 58343a8fcd5f0141a886324f09a9dec8abc53fe4..5c8a7e90c88eef94dc05180fde462abe3c09f7d0 100644 GIT binary patch delta 1025 zcmV+c1pfPt2(1W^B#|*0e+4lB00l7tx9uoW00007bV*G`2i*c17BU-Ht^<_-00X*7 zL_t(Y$L*DEh!s^B$A8Z`bLY(5S?`_esztsnX0EpA24O~9Mx-DFQNdC~5Yh)RmZIzh z!7sh&ml8~Z2>g(tUo^}TOe6%uNc4jt(F^LW2z9Tzt-HE=-)7F~e}i-5w%j6!zWEG) zW;oAt{{M5HGxHzeuc3e(kk8;}W1T|y=SYE?H)VnXf_sJ*ubI^sZ>mn#$HX}WA}GM! zAQd0+3RZLx$(0;2x~sKnqdDTn84)Xj z!?q}#MAM?_^=3^^6>xyN&F}fik2QPt?b~+wv&KPS0!XqJ`oQ$Jk8U{d`H}tKop|Hi ztH4#Dk#YDyyyfug+rIhvXph%n*H&p|0hD0^7zW0&Ljwo_e~X9{EeUWrLp{qIAkm&q z;LmPN>63^9$OvjcHQRw?sAYw=EnjOb5U7dZ{#Kwv+iV8O?yf`LwF$WP_x^v4n*uil z{(E?BwkvJuWX&zN?Nrk~rrYx^0i8V?z$DFNs*UT6TmwPoH|<@}nG@@b!*`i6wn#%D z?|1uAW2BLt&VOM3 zYkhP2`e*7HmGjk+YBZVR914h7%&d4_ysp2m|Cyesr`nuoUb>QwdRP>Yr-W}_F;Dm3 zwQSv@(xSkt=Ps6$3qFA7Z}JD#61ClEbJDALL$?%ef4zMozP|QM`a^0HG$FCsXbwa{ zjuH<)pz zdNw}){iWS@Wxw#V<=XWLuIId=^1!#IDlsg&b2^Jf461C2>UK~zY`t=3yiTXh`3@$Wf3?P<&DDbynE2Dr85A_KP&!ye2g3&sRA zvIH@@nE0kIKIt<)_}~j(-Za^Z8sANfiOFK3St1VVxVR~o29YtOz)E4IIlY{l4{M=+ zc*|P+y_|>t@BjJze*ff`9H=&)>~;Rji2#VD@Ba*pM28p&y^kQC0KBC<033esq z0$uVOENZvGHUSf`5AI3z=ekECAEk2HpuMWy1~kCnpwfd5#u3C2;Hm5Y5J*^c-Tm!v zi~WK3fYwsZ;UqiRc&M&PZ)pxkfDXWaUpBD;uE`5*F15lbr?Kp3c_BB5rWJf$G%KkuyXXpYlz z0FVKhPSt&E0N_&pux!Wo)^h3MJ;$irjoWbo@U**Q2L);d0I{$4{LiUz6DLgKQzzzZ4$^tgO z?cb>u0Hh;LB=qZrU|vvT;a;GBK?E2ZyXq??R1E-bE+{7%_PoU_bswNw=j<)pZX4|A z1DYj(d@gI15j&w@FI+LMsIhQwW-0k?2?!7{U&1S%CS^J7Ezxcp?C6_bN;Y%Rb1L>~ z`*_3^kqu|joXAXi3rz-hLu-oL?RiWB82{;=ddINuP19qxuw#k;1^k$QneyhFbnb@M z6j#vQQ+(0$x}wP5*{hRZYP)-Dx+8Bh;CXrbY&twJ`1D1I%gW{0Q2XZ(3lz)16X|f* zr86(g2b12pujXf2+258j(z?|uq}g}&V2a^QZPP39jQoY7$V%0KA%y*+Du}zASZxgv z(z0$i#VB4ehvIxHxG3O%&A2xt0J4B@W4+A{11p2DBUXsBzD=_|)W#s;bN9)l)NSk8gB4 zm2&v=YG&e_iP^<$b{kj_8lMl1W&7Ok=3EXAfnZUc_Iw}zZgN^mRn_I*^Sw=f1+ou- zMPLCiWLa)06bdH+7jPf=16UIxk!WYEEBdUvU4E?=0N4TY6+Zz-;!j>2Xa_$PF}DB! N002ovPDHLkV1k;F*eL)2 diff --git a/data/themes/default/note_tripletquarter.png b/data/themes/default/note_tripletquarter.png index 032549b9c768affcf476a7f7d1826ce44290ec4e..39c037b2a238f70ac269b19cdee434c1c0c53ac7 100644 GIT binary patch delta 702 zcmV;v0zv)s2i66UB!2{RLP=Bz2nYy#2xN!=000SaNLh0L00l7s00l7tx9uoW0000P zbVXQnQ*UN;cVTj60C#tHE@^ISb7Ns}WiD@WXPfRk8UO$SQ%OWYR7i>Kkv~imaTLeD zzw1GJcU)TzTj-HOfm(>7#*h#P6J4BK9oS58Fgl=7ObmmDM1LGi#F*${G!7VJ;$WOz zATc2>EIOePspaUOD_2^2*K&8g=K_ORHy4wB*Y79qllLAYA{@-@(zVfaF&%t);r;R- zC4h%3(}&ix+GswX{`~gRveAsWvv_q(D{cr}v$6rY2hh7_9RS$2?=%rxbbY?@zcC&# z=K(L83jSqUf`68vC1?p+g8xFm0|1+4{E#>_*(rA(;~0HC5le*+g7LZdcqAN|^tZKT zx+NvlEB}tLDdtc1C9fz!Wytfqwc|YlVF2KzX=!XEHuCy#e{#4ts=^}=nfwO9v?>6= zF0(dORTokNsfmQz4@rj}9JLNPqSv<7!le>$=F~GX&Gha^Ub- z?vRI-kb*>S0^MER5CbC1o7;W>5M}XVhtx3J#{i(+*FG;x za$E^17*G?4DiH{NffdZM$dgAsJLV1wMga}40o%5b({s>@`A-1g!Rs+=RT$d_zGv3J z^E`Azhkw3ZY-m~S0{}R#6RlJ$$QARj9UHs$F0y(KKlRNeo)LSL-b_ESY9>TMg5x?U zl}g|0jrz^K^S(05rR%P{+LhbN0br<@m0G!4p0VaF4FHJI(;wRU{i4@$YUn z>?YX+f?$&pP(nfqjU&T2cxs1w5$FLlRzDbpeNUiR=l@8@}*clMcGIP7|9*m}T@0Eo5c z{|rR>CWv@GM-Yz#_F5bOmVLmzl<`DhP=1%cv|C{6fDV{D=cKlwaK!)B{X)LOT+wa; z8c<~?({=^Z2!Emnur*cy2qX+gc(ngRakTwYpr;nI+es!r>v0^bbo;!1AOy754e_V| zU~+Hse&X)(pH{DYs-v$n1hmy}M?5M}Ta~rg`ZXymk(H_wz_az65RVGf_H`pwyjoPt z7+SdscmW3u0Zjw-E(NJ_GqaY_EWL6&AOkWdT*{FFfPXWild?JcFj>f!H!W|-VHC@8 zz}Bw>b`mrV0OE!@QX0oyRNIO3MS$iHJ!XU~5cZ za9g`{Sbt#8yg(`chjv{U5Itae0DEHslfQHSJPEjX@jGWNW*-1Zdx}U@el2wr1y{g3 z47f#rrvcea+N>l01ptLpY8R8X4><4m0+%sluA63XJTwCMB!FTeZ|ngsR4=#!-r>hf zshxIu8@|63%l3eO0Yl!B$Xa>haBhjUlkAKs)=L2u`PoE!u-KE)cT*_S`n<}!J zPO6GxdrktFzVT6;Jmr4>U?4R8=Gbd4&7R|O{s%It0;}ny+R~xCP(EjSCm8YA=k%*2 zOKapQLRXayUzS(KPM?vOQl|nF{gr&=Xmm9GRN9`(?GT*f6ht>RJ|qszVf`Ki569nm7nqm~VcpsY3nJTqUQ zbT_~3Om5AT&srk|)e3?DTSY<-+`8?$JwAV9Zb1^4gr)%HcUwFbor^l1uK0-B6LW^0 z=QcJs6;)HUnV)8EZ)saAKuqX7d+g)<%U8Bf_5))4=5MiIoK9DKcx1?P0I&co0##X- zdrGB}53mA{fK^~s=+Ks~T%>V!Z M07*qoM6N<$f{rP@(f|Me diff --git a/data/themes/default/note_tripletsixteenth.png b/data/themes/default/note_tripletsixteenth.png index 6314c398ae29efb500c4187134db20f91bc01694..a307b542a9edfe5aa61e91226995603f2a5793ea 100644 GIT binary patch delta 1249 zcmV<71Rne12#X4kB#|*0e+4lB00l7tx9uoW00007bV*G`2i*c17X%WitUz-B00fvx zL_t(Y$K91nXk29!$A8~<@11*RCiBQ7nY7bj6Rl~81q-!Z_*nFTl%g)gR*@EUC5SF{ zBf3&hsJIhEMJo!Gx{>Na5CsKA2@P88Thla6o6KY;Gnw~&9T&a9f7DK?;LZbw%jKN! z{LlG+=bTURwL zY0ihga?YPpK||wL(L>qc5w9I|)EWQ{*uc>KBRi)%K`X~i#2gUJfe8maA7>R{DRiimlE4evC?$xjO$4u~&9Gv{+5}xA9R{tGPc?oD zWrNZn0VrSDDpaA_E5r5i+s-X}#mSGK`DzQ$jevt~Q!k(R;QUcw z1u!DJ4{Uwm=%K@v?$V&<*5U&aX%kS*YAXq#6aqL=Z~+?8e>Yr9$=W-J1BEA#?ES7S z_2EXPc}{69D6VlND4i3X7&bG>b#|c>TbZ9sY^!gN?GaI-{NjJ3&a_S_ucWo(_Kmn@ zrFv1MJh}-#^i3ygPxh+Eorhq>#pt&;oX(!)Z2C8hxPS$*q=l&!LA{IiZ@gpy!jzR= zMB!YTzVfNIe{@G_6`&9V6?%a=v_3zP?o02U{o&Hr?G^WeVQW_>Bs5}zXmrpG?eQ(+ zkF-0jc&`eAw3RLUc*8%xefB+|99^_TT&MQGy6MrBlis~@sr~KrhZnw#xc4K1ua)TB{I18!A|1U#&m`{%Q>K7A-0_(VO}3PoIA?2meX_KLD!7WYc32s$2Nn5W|c> zR^NgcV2NA@sMh#cfE9+KdjK1eUKhiR4%yKjN&p!NLSs#ZW@KCS$#@;l1_uezYu*tZ z%|H^!e~5?{L770BK86Yh3y1pi{S#ifTUu&WGe%5SGfo0__Mz;3g>8k$XiDvhEM*3y zkMLR|HeiuuVC3M))BOYeW1VuRT&!2JTBA&BkF|4b=h$1D$HzxDflWK_czt}+_+TMl!1sOT7Uv{b zt}knI!h9t&kQvEka*S>mWq4qigp*LkcFh7Td!PMywvgSGO=Tzyt!K2bk#sUeEAXgF zdkBa-o1GUk{h7gBCdXJ|lzeW8xE)uePTe*|ORnCmQ>+vT!T{HGStu=Vd2Zn};EE#y zf6aD-O0~itbAMu*CZ$S=;&Lgx_~X^@0pG*xUumqcu(XKlxpdtwS4#`b&R(82r6ixs zTC*>;+D#JiB%be4S*%=XOgG*DmV;BlvGQQ$fbaY1h4KQRsMTw3^+NTX;B;^XkZ?Kt zY{tC!T%*+_?!@stm!)EPra9GoQvoB^S6=HuJ*e+dw%RC7`MrC*dxR!SKnJkR9p+ue zn6Vd!+tG+W<-Z%849-T|79edtZ0<8s#@!N^4N{dU@3?mim!-b|(SizxToL0q00000 LNkvXXu0mjfx-ndm delta 1077 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 O0000mzLe*g(^ zNLh0L00l7s00l7tx9uoW00007bV*G`2i*Y~6E7~Jue^T%00L4;L_t(Y$L&>3XcJKo z{$_vk)9oga)L5Dn5sG*a#Giu^EJ(2z6?^H)Uc73B9=z$*gSQIOo(mqNMbIL65DXq~x>xpTH<+fgm*uM}2ZFN`kC z18@LTCD1P*)C52W(1ugUnnUKDY*)4~XJxfa`~YmjhR*_(Z(g^9wP45Id&?diM<*C4VTBtGXQFV``8iS zC->R~t(%h6&&*y3PJKEqjaJ^-jU(i5g4lI14Md**#g(Etmsa)~E+kzc}xKsSyme_3UaB6N^_cEuBM z-h1fHNXd%eMTY?>0Ae_aoO#t8CyVksmCr7h{Wt!wJLWzQlWDU)&;UdcLBhCc3?hSm ziqc^K0(F%AFa9$;#-#h!Ey>K?!1!pX(Y8gP%WgIUq7ahvO}ToFng7YK+ipB1@a;Ev l=&+-|Fw%MN)%(Bg6$dix-3&BC`4|8I002ovPDHLkV1hvkK^_1A literal 3718 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 F{{W0Z{v!YY From 8bc7f8ce54fe2a0d6a1e6a9aceeb63795cfdc350 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 27 Feb 2013 22:29:06 +0100 Subject: [PATCH 106/133] Updated chord and scale icon based on new note icons --- data/themes/default/chord.png | Bin 1006 -> 1044 bytes data/themes/default/scale.png | Bin 1006 -> 1056 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/themes/default/chord.png b/data/themes/default/chord.png index 3f475b0ea497624eca88f9553b2ad24ad16105a5..8aa83cdbeb43597f5ccf458d1b2219f836692b3e 100644 GIT binary patch delta 1022 zcmVnJebcI4y6c9onfs>t6ohO%?0KnR8Hba)@V4Ptb`pBj^ z3Q@oheMCuwgfrwkXG;O3lz&Dn0u3n$A>bKDO|L-+fqytn;D=Rf zd%2pwJn}~yX_jJ4O=y}1Aq05F5wYktQOK*ZA}I?~nCx$1`ZMm?m z1~m}k>(sx z5}~Hoz&Jx83ItJrAPy0y37BC1QUC(-bCM?K$A1$KkP?iu2R%r50zaJKC^-5t`!G9L z3Xt0Ar$%JFI1c@%SeC-122}Na4{{Ek@!z50mC8ddN)fq0blRZXM3PMarvVfIt^>Hn zOa8IkmAp#7um|!JIcvYu{-xLIJ<+tAkC@b;AmntGWlslj@E3sZ{?`P~+s?&f?y=p~ z?tiMiUL9ih(g%POgS~rqdKO9 zG_q+1#uCw>mL-xjfv)KgLV$4w zKbpXsc>DY+zqJ&A|IPP}HRG8u4mVkvz@R2nMMYWO>%p{`eZQiwyjFP&|K;0aZksOv s*aYyHgv|ZvX%Q07*qoM6N<$f(QZHUjP6A delta 984 zcmV;}11J2H2<``vB!3BTNLh0L01FcU01FcV0GgZ_00007bV*G`2i5`>4I>z$9CFnF z00WIlL_t(Y$E}r3ZyQw}2n&eo+AfU~+kYu`a60a|8P5z0rVxUX z3?E0jtGPPQdCz&z9dHtH+IW)>06*XbBpimz(XD6cjh~Hwy6j(mO8-Xx2@4Hq02cp4 z2+lElW$B7FyF6?4zSo=J0H=TepzNi_U8%29>#UIjHh%yEKz2B0Ni|i3EMSSBQx{S* zsbI>BL<=68Im*7kL?FzI#7OcbP_$K;&Hy@ErBOj@l3*Vet9(z9jQv$1OLKX;{3Idv z1LoGwI0IB!yRY9zaVrqQ$G50J2KW^afQ*TkG#;RCpML--acAYh3Y|(P9HPVCX3HL^ zVM8i*<$uCL@JbBo95#OYuvuhW%k` zR*f1pR7<61)u>V>XXXet1z9u~TeQ!$n?N3Tu+tp=+M){_9gU5?=9b+Q^&-M0z=E<> zW`Fk4ED1A#)}(_Kj&}tWI>;;hI1M*~FpidO_BEk|_rp3HE&-KmgG?d2giDf(8 zG%HU2Xj=io+vf` zfF&RaWNihG0K$I6`hf@#vIp1zYIdasV8uRV;Nfn`0e%C+&sPW89Z{A50000JNR7i=Xl*@}8RTRd* zQ}3#&s(w~?wQ=HNz<(g1q9CYi9}F=IaT6jz42DRYxRDGEf`6D%5JAO_M3I2_0Jn-{ zkwp*}ZU!aHCI-iZba%b$R&~9u3&$X0#-TjxbH3j>_ni9?=N$hhCYzJTI-SmQmSH_0 zi?Y=4=GT;^Uv3}T{)9ivYibLiDQRPjKX3=`@o|5IuHA*Es3^)3K^)+=H~aqj?E1RY zwsSq!->}E_@qfPE!)P#qJ9J?g7NRtQD2T|H`4j!Le@1B4{V6gfQF>XHB*>BsMOL7z z8cbsak|=>_FOj3&1e&4^tGZH_#S$!M2;&d}37Go}qzx@PBTP19`xl|_+% zJ7?afH_qMoolcQy>6R8}an9f8$=_@L6JVdS9~litU$}$uWY8VJG)w?M!V)Zk#eC+? z*RO3{yMJ=G`hAydoHbUvcIUgE-P4_(gNZZ2us1;6G?;la)J;81v*a_H(i-37)xT-% z{|^Wok|gVjBtuaY=$Z!G>_As_h@uE00zikI(s{Vm1OQgcvP53wNLY$R;9*%TQC1cF z$VZe!NV61ao@T881lkUh@EJ{2K_nnsW>94XRexO}VJW=O!`z?$%q;fPT{ZSwvUS>W zYNh@Wz7H=<&I`6_Kcp!tIByD;WtYR)oBs0Z&Q+R{rbg;v z;(t`-RldvLPBLJ|>QfwbeD;J354(-GgbYD5|PnpeePob^#nGi3AW4i2gK_vMNzj zC90}IRadB+`u=Xvwc7wB09Lll_RcN52X#8mxoh0?0V=t7!0f_D*)WtvCSu0fsodP?GsK5&!@I07*qoL4I>z$9CFnF z00WIlL_t(Y$E}r3ZyQw}2n&eo+AfU~+kYu`a60a|8P5z0rVxUX z3?E0jtGPPQdCz&z9dHtH+IW)>06*XbBpimz(XD6cjh~Hwy6j(mO8-Xx2@4Hq02cp4 z2+lElW$B7FyF6?4zSo=J0H=TepzNi_U8%29>#UIjHh%yEKz2B0Ni|i3EMSSBQx{S* zsbI>BL<=68Im*7kL?FzI#7OcbP_$K;&Hy@ErBOj@l3*Vet9(z9jQv$1OLKX;{3Idv z1LoGwI0IB!yRY9zaVrqQ$G50J2KW^afQ*TkG#;RCpML--acAYh3Y|(P9HPVCX3HL^ zVM8i*<$uCL@JbBo95#OYuvuhW%k` zR*f1pR7<61)u>V>XXXet1z9u~TeQ!$n?N3Tu+tp=+M){_9gU5?=9b+Q^&-M0z=E<> zW`Fk4ED1A#)}(_Kj&}tWI>;;hI1M*~FpidO_BEk|_rp3HE&-KmgG?d2giDf(8 zG%HU2Xj=io+vf` zfF&RaWNihG0K$I6`hf@#vIp1zYIdasV8uRV;Nfn`0e%C+&sPW89Z{A50000 Date: Wed, 27 Feb 2013 21:57:05 +0100 Subject: [PATCH 107/133] Made 0.4.14 release Bumped version number to 0.4.14 in CMakeLists.txt, README and lmms.rc.in. --- CMakeLists.txt | 2 +- README | 4 ++-- lmms.rc.in | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f37ef069..ba56de4ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ INCLUDE(FindPkgConfig) SET(VERSION_MAJOR "0") SET(VERSION_MINOR "4") SET(VERSION_PATCH "14") -SET(VERSION_SUFFIX "rc1") +#SET(VERSION_SUFFIX "") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") IF(VERSION_SUFFIX) SET(VERSION "${VERSION}-${VERSION_SUFFIX}") diff --git a/README b/README index b6a49776c..76087fd25 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ -Linux MultiMedia Studio 0.4.13 +Linux MultiMedia Studio 0.4.14 =============================== -Copyright (c) 2004-2012 by LMMS-developers +Copyright (c) 2004-2013 by LMMS-developers This program is free software; you can redistribute it and/or modify diff --git a/lmms.rc.in b/lmms.rc.in index 11d548768..311e9a5ba 100644 --- a/lmms.rc.in +++ b/lmms.rc.in @@ -2,7 +2,7 @@ lmmsicon ICON data/lmms.ico #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,13,0 + FILEVERSION 0,4,14,0 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP @@ -17,7 +17,7 @@ BEGIN VALUE "CompanyName", "LMMS Developers\0" VALUE "FileDescription", "Linux MultiMedia Studio\0" VALUE "FileVersion", "@VERSION@\0" - VALUE "LegalCopyright", "Copyright (c) 2004-2012 LMMS Developers\0" + VALUE "LegalCopyright", "Copyright (c) 2004-2013 LMMS Developers\0" VALUE "OriginalFilename", "lmms.exe\0" VALUE "ProductName", "LMMS\0" VALUE "ProductVersion", "@VERSION@\0" From e29de773b3982aa6ec26eadaafe0747846e9b7df Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 1 May 2013 21:51:37 +0200 Subject: [PATCH 108/133] InstrumentTrack: fix freeze when forwarding MIDI events to instrument In commit a3abcdb2e0ac91887c5ef7b4299ab8113994625c we introduced multitimbral MIDI support. However the new code path causes the mixer not being unlocked at exit and thus causes a freeze of LMMS. Thanks to nuio for pointing out this issue and providing a patch. Closes #532. --- src/tracks/InstrumentTrack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 5dcac3f11..ce7a444a4 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -2,7 +2,7 @@ * InstrumentTrack.cpp - implementation of instrument-track-class * (window + data-structures) * - * Copyright (c) 2004-2012 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -230,6 +230,7 @@ void InstrumentTrack::processInEvent( const midiEvent & _me, midiPort()->realOutputChannel() < 0 */ ) { m_instrument->handleMidiEvent( _me, _time ); + engine::getMixer()->unlock(); return; } From 0f3851d597d5a913f13f4455a475dfd196991022 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Tue, 11 Jun 2013 13:11:59 +0200 Subject: [PATCH 109/133] AudioFileProcessor: fix crash with reversed samples AudioFileProcessorWaveView::slideSampleByFrames() gets called before the knobs are created and/or after they are destroyed if the sample is reversed. Added checks for nonexistant knobs. Closes Patch #41 and Bug #525. --- plugins/audio_file_processor/audio_file_processor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 729e9e429..5520c7302 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -932,8 +932,12 @@ void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) return; } const double v = double( _frames ) / m_sampleBuffer.frames(); - m_startKnob->slideBy( v, false ); - m_endKnob->slideBy( v, false ); + if(m_startKnob) { + m_startKnob->slideBy( v, false ); + } + if(m_endKnob) { + m_endKnob->slideBy( v, false ); + } } From 8e2ec9e48fb45697dd141683686928381d04efc7 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Tue, 11 Jun 2013 13:19:00 +0200 Subject: [PATCH 110/133] AudioFileProcessor: Make AFP cursor configurable Add possibility to turn off playback cursor in AFP. Might be good for weaker systems. Closes Patch #40. --- include/setup_dialog.h | 2 ++ .../audio_file_processor.cpp | 5 ++-- .../audio_file_processor.h | 3 +-- src/gui/setup_dialog.cpp | 25 ++++++++++++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/setup_dialog.h b/include/setup_dialog.h index d22fd9328..4bac5e0fe 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -109,6 +109,7 @@ private slots: void toggleOneInstrumentTrackWindow( bool _enabled ); void toggleCompactTrackButtons( bool _enabled ); void toggleSyncVSTPlugins( bool _enabled ); + void toggleAnimateAFP( bool _enabled ); private: @@ -158,6 +159,7 @@ private: bool m_oneInstrumentTrackWindow; bool m_compactTrackButtons; bool m_syncVSTPlugins; + bool m_animateAFP; typedef QMap AswMap; typedef QMap MswMap; diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 5520c7302..04fca5c37 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -522,7 +522,8 @@ AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _ m_endKnob( 0 ), m_isDragging( false ), m_reversed( false ), - m_framesPlayed( 0 ) + m_framesPlayed( 0 ), + m_animation(configManager::inst()->value("ui", "animateafp").toInt()) { setFixedSize( _w, _h ); setMouseTracking( true ); @@ -701,7 +702,7 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) QColor( 255, 255, 0, 70 ) ); - if( m_framesPlayed ) + if( m_framesPlayed && m_animation) { const int played_width_px = m_framesPlayed / double( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index 1617e745b..cdcbbd46a 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -91,7 +91,6 @@ private: BoolModel m_reverseModel; BoolModel m_loopModel; - friend class AudioFileProcessorView; } ; @@ -235,7 +234,7 @@ private: draggingType m_draggingType; bool m_reversed; f_cnt_t m_framesPlayed; - + bool m_animation; public: AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, sampleBuffer & _buf ); diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index b8f38f9bf..29cdce5c4 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -1,7 +1,7 @@ /* * setup_dialog.cpp - dialog for setting up LMMS * - * Copyright (c) 2005-2010 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -119,7 +119,9 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_compactTrackButtons( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ), m_syncVSTPlugins( configManager::inst()->value( "ui", - "syncvstplugins" ).toInt() ) + "syncvstplugins" ).toInt() ), + m_animateAFP(configManager::inst()->value( "ui", + "animateafp").toInt() ) { setWindowIcon( embed::getIconPixmap( "setup_general" ) ); setWindowTitle( tr( "Setup LMMS" ) ); @@ -476,7 +478,7 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : tabWidget * ui_fx_tw = new tabWidget( tr( "UI effects vs. " "performance" ).toUpper(), performance ); - ui_fx_tw->setFixedHeight( 100 ); + ui_fx_tw->setFixedHeight( 120 ); ledCheckBox * disable_ch_act_ind = new ledCheckBox( tr( "Disable channel activity indicators" ), @@ -511,6 +513,15 @@ setupDialog::setupDialog( ConfigTabs _tab_to_open ) : this, SLOT( toggleAutoSave( bool ) ) ); + ledCheckBox * animAFP = new ledCheckBox( + tr( "Show playback cursor in AudioFileProcessor" ), + ui_fx_tw ); + animAFP->move( 10, 100 ); + animAFP->setChecked( m_animateAFP ); + connect( animAFP, SIGNAL( toggled( bool ) ), + this, SLOT( toggleAnimateAFP( bool ) ) ); + + perf_layout->addWidget( ui_fx_tw ); perf_layout->addStretch(); @@ -787,6 +798,9 @@ void setupDialog::accept() QString::number( m_compactTrackButtons ) ); configManager::inst()->setValue( "ui", "syncvstplugins", QString::number( m_syncVSTPlugins ) ); + configManager::inst()->setValue( "ui", "animateafp", + QString::number( m_animateAFP ) ); + configManager::inst()->setWorkingDir( m_workingDir ); configManager::inst()->setVSTDir( m_vstDir ); @@ -974,6 +988,11 @@ void setupDialog::toggleSyncVSTPlugins( bool _enabled ) m_syncVSTPlugins = _enabled; } +void setupDialog::toggleAnimateAFP( bool _enabled ) +{ + m_animateAFP = _enabled; +} + From 44c37775a4af86a44327b2078c4694d92ff1341a Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 13:25:18 +0200 Subject: [PATCH 111/133] Sf2Player: preserve custom track name when loading settings Commit a184bc039e8ac3064491772fd4d1d512619ac903 introduced a regression which causes a custom track name not being preserved when loading setings. Closes #534. --- plugins/sf2_player/sf2_player.cpp | 9 ++++++--- plugins/sf2_player/sf2_player.h | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index 453ac05e6..f03153783 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -228,7 +228,7 @@ void sf2Instrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) void sf2Instrument::loadSettings( const QDomElement & _this ) { - openFile( _this.attribute( "src" ) ); + openFile( _this.attribute( "src" ), false ); m_patchNum.loadSettings( _this, "patch" ); m_bankNum.loadSettings( _this, "bank" ); @@ -329,7 +329,7 @@ void sf2Instrument::freeFont() -void sf2Instrument::openFile( const QString & _sf2File ) +void sf2Instrument::openFile( const QString & _sf2File, bool updateTrackName ) { emit fileLoading(); @@ -390,7 +390,10 @@ void sf2Instrument::openFile( const QString & _sf2File ) delete[] sf2Ascii; - instrumentTrack()->setName( QFileInfo( _sf2File ).baseName() ); + if( updateTrackName ) + { + instrumentTrack()->setName( QFileInfo( _sf2File ).baseName() ); + } } diff --git a/plugins/sf2_player/sf2_player.h b/plugins/sf2_player/sf2_player.h index 1c1ef2764..9c7412bc1 100644 --- a/plugins/sf2_player/sf2_player.h +++ b/plugins/sf2_player/sf2_player.h @@ -2,7 +2,7 @@ * sf2_player.h - a soundfont2 player using fluidSynth * * Copyright (c) 2008 Paul Giblock - * Copyright (c) 2009 Tobias Doerffel + * Copyright (c) 2009-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -91,7 +91,7 @@ public: public slots: - void openFile( const QString & _sf2File ); + void openFile( const QString & _sf2File, bool updateTrackName = true ); void updatePatch(); void updateSampleRate(); From 4bdc94c6089f9540f4a6d41a3c9fd18144c76e37 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 13:37:07 +0200 Subject: [PATCH 112/133] AudioFileWave: test for properly opened output file Do not pretend things gone right if output file could not be opened for some reason. Closes #539. --- src/core/audio/AudioFileWave.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index 01a9a5a92..da558d7e5 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -2,7 +2,7 @@ * AudioFileWave.cpp - audio-device which encodes wave-stream and writes it * into a WAVE-file. This is used for song-export. * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -38,9 +38,10 @@ AudioFileWave::AudioFileWave( const sample_rate_t _sample_rate, mixer * _mixer ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) + _depth, _mixer ), + m_sf( NULL ) { - _success_ful = startEncoding(); + _success_ful = outputFileOpened() && startEncoding(); } @@ -110,6 +111,9 @@ void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, void AudioFileWave::finishEncoding() { - sf_close( m_sf ); + if( m_sf ) + { + sf_close( m_sf ); + } } From d4478461a32c990acc0b05d950a5a2898a84f128 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 13:38:07 +0200 Subject: [PATCH 113/133] AudioFileOgg: test for properly opened output file Do not pretend things gone right if output file could not be opened for some reason. Closes #539. --- src/core/audio/AudioFileOgg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index 3ef2856e7..e0e4e9b03 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -5,7 +5,7 @@ * This file is based on encode.c from vorbis-tools-source, for more information * see below. * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -49,7 +49,7 @@ AudioFileOgg::AudioFileOgg( const sample_rate_t _sample_rate, _nom_bitrate, _min_bitrate, _max_bitrate, _depth, _mixer ) { - m_ok = _success_ful = startEncoding(); + m_ok = _success_ful = outputFileOpened() && startEncoding(); } From 005f2dde50af9338cd11dacada933a10b3348fc1 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 13:48:58 +0200 Subject: [PATCH 114/133] InstrumentFunctions/ChordCreator: fixed major pentatonic The last key of the major pentatonic chord was wrong and has been fixed. Thanks to Mike804 for reporting the issue. Closes #538. --- src/core/InstrumentFunctions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 5868d19b4..71751e188 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -113,7 +113,7 @@ ChordCreator::ChordTable::Init ChordCreator::ChordTable::s_initTable[] = { QT_TRANSLATE_NOOP( "ChordCreator", "Melodic minor" ), { 0, 2, 3, 5, 7, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Whole tone" ), { 0, 2, 4, 6, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Diminished" ), { 0, 2, 3, 5, 6, 8, 9, 11, -1 } }, - { QT_TRANSLATE_NOOP( "ChordCreator", "Major pentatonic" ), { 0, 2, 4, 7, 10, -1 } }, + { QT_TRANSLATE_NOOP( "ChordCreator", "Major pentatonic" ), { 0, 2, 4, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Minor pentatonic" ), { 0, 3, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Jap in sen" ), { 0, 1, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "ChordCreator", "Major bebop" ), { 0, 2, 4, 5, 7, 8, 9, 11, -1 } }, From f83b9b04a31a40af89bb4af299c7497cc0a163f4 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 14:50:27 +0200 Subject: [PATCH 115/133] AutomationPattern: removed unused member variable The variable m_hasAutomation is not required anymore and thus can be dropped. --- include/AutomationPattern.h | 2 +- src/core/AutomationPattern.cpp | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/include/AutomationPattern.h b/include/AutomationPattern.h index 62bf92d06..1d144d9bd 100644 --- a/include/AutomationPattern.h +++ b/include/AutomationPattern.h @@ -71,7 +71,7 @@ public: inline bool hasAutomation() const { - return m_hasAutomation; + return m_timeMap.isEmpty() == false; } float valueAt( const midiTime & _time ) const; diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index 6ec1ce319..fda913256 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -41,8 +41,7 @@ AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) : trackContentObject( _auto_track ), m_autoTrack( _auto_track ), - m_objects(), - m_hasAutomation( false ) + m_objects() { changeLength( midiTime( 1, 0 ) ); } @@ -53,8 +52,7 @@ AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) : AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) : trackContentObject( _pat_to_copy.m_autoTrack ), m_autoTrack( _pat_to_copy.m_autoTrack ), - m_objects( _pat_to_copy.m_objects ), - m_hasAutomation( _pat_to_copy.m_hasAutomation ) + m_objects( _pat_to_copy.m_objects ) { for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin(); it != _pat_to_copy.m_timeMap.end(); ++it ) @@ -154,8 +152,6 @@ midiTime AutomationPattern::putValue( const midiTime & _time, m_timeMap[newTime] = _value; - m_hasAutomation = true; - // we need to maximize our length in case we're part of a hidden // automation track as the user can't resize this pattern if( getTrack() && getTrack()->type() == track::HiddenAutomationTrack ) @@ -183,11 +179,6 @@ void AutomationPattern::removeValue( const midiTime & _time ) changeLength( length() ); } - if( m_timeMap.isEmpty() ) - { - m_hasAutomation = false; - } - emit dataChanged(); } @@ -277,8 +268,6 @@ void AutomationPattern::loadSettings( const QDomElement & _this ) } } - m_hasAutomation = m_timeMap.size() > 0; - int len = _this.attribute( "len" ).toInt(); if( len <= 0 ) { @@ -353,7 +342,7 @@ bool AutomationPattern::isAutomated( const AutomatableModel * _m ) { const AutomationPattern * a = dynamic_cast( *j ); - if( a && a->m_hasAutomation ) + if( a && a->hasAutomation() ) { for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k ) From 0aaed00cc14f2d6c354c1d98b398b1be7ef5628d Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 14:50:51 +0200 Subject: [PATCH 116/133] AutomationPattern: initialize with current value of first object When attaching a control object to an AutomationPattern initialize the value at position 0 with the current value of the control object. This is similiar to the behaviour found in LMMS < 0.4.14 and has been reintroduced as requested by some users. Closes #535. --- src/core/AutomationPattern.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/AutomationPattern.cpp b/src/core/AutomationPattern.cpp index fda913256..e4cb69f5b 100644 --- a/src/core/AutomationPattern.cpp +++ b/src/core/AutomationPattern.cpp @@ -97,7 +97,15 @@ void AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup ) if( addIt ) { + // been empty before? + if( m_objects.isEmpty() && hasAutomation() == false ) + { + // then initialize default value + putValue( 0, _obj->value(), false ); + } + m_objects += _obj; + connect( _obj, SIGNAL( destroyed( jo_id_t ) ), this, SLOT( objectDestroyed( jo_id_t ) ), Qt::DirectConnection ); From 7eafca807bd28af392ee91917c2ce0de687de607 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 16:29:58 +0200 Subject: [PATCH 117/133] AudioFileWave: do not use UTF-8 filenames on Windows As libsndfile does not seem to have an UTF8-compatible implementation on Windows we have to pass the filename as local 8 bit encoding in order to make the file written properly. Closes #530. --- src/core/audio/AudioFileWave.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index da558d7e5..cedd4de84 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -69,7 +69,13 @@ bool AudioFileWave::startEncoding() case 16: default: m_si.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; break; } - m_sf = sf_open( outputFile().toUtf8().constData(), SFM_WRITE, &m_si ); + m_sf = sf_open( +#ifdef LMMS_BUILD_WIN32 + outputFile().toLocal8Bit().constData(), +#else + outputFile().toUtf8().constData(), +#endif + SFM_WRITE, &m_si ); sf_set_string ( m_sf, SF_STR_SOFTWARE, "LMMS" ); return true; } From 05f1b45ab7adcf7284e32530d91ab39574fc7dcc Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 11 Jun 2013 17:46:42 +0200 Subject: [PATCH 118/133] Made 0.4.15 release Bumped version number to 0.4.15 in CMakeLists.txt, README and lmms.rc.in. --- CMakeLists.txt | 2 +- README | 2 +- lmms.rc.in | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba56de4ca..dba9a52b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ INCLUDE(FindPkgConfig) SET(VERSION_MAJOR "0") SET(VERSION_MINOR "4") -SET(VERSION_PATCH "14") +SET(VERSION_PATCH "15") #SET(VERSION_SUFFIX "") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") IF(VERSION_SUFFIX) diff --git a/README b/README index 76087fd25..56a29cf88 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Linux MultiMedia Studio 0.4.14 +Linux MultiMedia Studio 0.4.15 =============================== Copyright (c) 2004-2013 by LMMS-developers diff --git a/lmms.rc.in b/lmms.rc.in index 311e9a5ba..707794c2a 100644 --- a/lmms.rc.in +++ b/lmms.rc.in @@ -2,7 +2,7 @@ lmmsicon ICON data/lmms.ico #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,4,14,0 + FILEVERSION 0,4,15,0 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP From 3fce2dd666adda095a4c11618676114a31a1bb34 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Thu, 25 Jul 2013 22:22:43 +0200 Subject: [PATCH 119/133] ZynAddSubFx: finalize controller connections in toggleUI() As controller connections are not fully restored via AutomatableModel::loadSettings(), a call to ControllerConnection::finalizeConnections() is neccessary to do so. Normally this function is called at the end when loading a project but when the user just toggles ZASF's UI settings are saved/loaded without finalizing connections which results in connected knobs which are not reacting to the controller they are connected to. Closes #544. --- plugins/zynaddsubfx/ZynAddSubFx.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index 5ad842a00..3db7a59e6 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -41,6 +41,7 @@ #include "string_pair_drag.h" #include "RemoteZynAddSubFx.h" #include "LocalZynAddSubFx.h" +#include "ControllerConnection.h" #include "embed.cpp" #include "moc_ZynAddSubFx.cxx" @@ -624,6 +625,8 @@ void ZynAddSubFxView::toggleUI() connect( model->m_remotePlugin, SIGNAL( clickedCloseButton() ), m_toggleUIButton, SLOT( toggle() ) ); } + + ControllerConnection::finalizeConnections(); } } From 7633affa20acc7ff063605ad245719e0b16640a2 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Fri, 13 Sep 2013 21:29:53 +0200 Subject: [PATCH 120/133] Use new QFileDialog::DontUseCustomDirectoryIcons flag for faster file dialogs In Qt 4.8.6 there's a new option QFileDialog::DontUseCustomDirectoryIcons promising much better performance when there are many folders. Closes #511. --- plugins/patman/patman.cpp | 5 ++++- plugins/sf2_player/sf2_player.cpp | 3 +++ plugins/vestige/vestige.cpp | 5 ++++- plugins/vst_base/VstPlugin.cpp | 8 +++++++- src/core/sample_buffer.cpp | 5 ++++- src/core/song.cpp | 6 ++++++ src/gui/MainWindow.cpp | 6 ++++++ src/gui/setup_dialog.cpp | 6 +++++- 8 files changed, 39 insertions(+), 5 deletions(-) diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index 7459c92b7..85457871f 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -2,7 +2,7 @@ * patman.cpp - a GUS-compatible patch instrument plugin * * Copyright (c) 2007-2008 Javier Serrano Polo - * Copyright (c) 2009-2011 Tobias Doerffel + * Copyright (c) 2009-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -511,6 +511,9 @@ PatmanView::~PatmanView() void PatmanView::openFile( void ) { QFileDialog ofd( NULL, tr( "Open patch file" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif ofd.setFileMode( QFileDialog::ExistingFiles ); QStringList types; diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index f03153783..ff590b622 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -1046,6 +1046,9 @@ void sf2InstrumentView::showFileDialog() sf2Instrument * k = castModel(); QFileDialog ofd( NULL, tr( "Open SoundFont file" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif ofd.setFileMode( QFileDialog::ExistingFiles ); QStringList types; diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index eeb536f58..df636c412 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -1,7 +1,7 @@ /* * vestige.cpp - instrument-plugin for hosting VST-instruments * - * Copyright (c) 2005-2012 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -607,6 +607,9 @@ void VestigeInstrumentView::modelChanged() void VestigeInstrumentView::openPlugin() { QFileDialog ofd( NULL, tr( "Open VST-plugin" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif QString dir; if( m_vi->m_pluginDLL != "" ) diff --git a/plugins/vst_base/VstPlugin.cpp b/plugins/vst_base/VstPlugin.cpp index e548f3a9b..d040c61a8 100644 --- a/plugins/vst_base/VstPlugin.cpp +++ b/plugins/vst_base/VstPlugin.cpp @@ -1,7 +1,7 @@ /* * VstPlugin.cpp - implementation of VstPlugin class * - * Copyright (c) 2005-2012 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -512,6 +512,9 @@ void VstPlugin::openPreset( ) QFileDialog ofd( NULL, tr( "Open Preset" ), "", tr( "Vst Plugin Preset (*.fxp *.fxb)" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif ofd.setFileMode( QFileDialog::ExistingFiles ); if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() ) @@ -571,6 +574,9 @@ void VstPlugin::savePreset( ) QFileDialog sfd( NULL, tr( "Save Preset" ), presName.section(": ", 1, 1) + tr(".fxp"), tr( "Vst Plugin Preset (*.fxp *.fxb)" ) ); +#if QT_VERSION >= 0x040806 + sfd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif if( p_name != "" ) // remember last directory { sfd.setDirectory( QFileInfo( p_name ).absolutePath() ); diff --git a/src/core/sample_buffer.cpp b/src/core/sample_buffer.cpp index 40b0bc036..dfcd9f967 100644 --- a/src/core/sample_buffer.cpp +++ b/src/core/sample_buffer.cpp @@ -1,7 +1,7 @@ /* * sample_buffer.cpp - container-class sampleBuffer * - * Copyright (c) 2005-2010 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -742,6 +742,9 @@ void sampleBuffer::visualize( QPainter & _p, const QRect & _dr, QString sampleBuffer::openAudioFile() const { QFileDialog ofd( NULL, tr( "Open audio file" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif QString dir; if( !m_audioFile.isEmpty() ) diff --git a/src/core/song.cpp b/src/core/song.cpp index 6fdba2b21..b8d281a0e 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1241,6 +1241,9 @@ void song::importProject() " (*.h2song);;" + tr("All file types") + " (*.*)"); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif ofd.setFileMode( QFileDialog::ExistingFiles ); if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() ) @@ -1296,6 +1299,9 @@ void song::exportProject(bool multiExport) } QFileDialog efd( engine::mainWindow() ); +#if QT_VERSION >= 0x040806 + efd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif if (multiExport) { efd.setFileMode( QFileDialog::Directory); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 98bf7e408..0cb8f09cf 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -688,6 +688,9 @@ void MainWindow::openProject( void ) { QFileDialog ofd( this, tr( "Open project" ), "", tr( "MultiMedia Project (*.mmp *.mmpz *.xml)" ) ); +#if QT_VERSION >= 0x040806 + ofd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif ofd.setDirectory( configManager::inst()->userProjectsDir() ); ofd.setFileMode( QFileDialog::ExistingFiles ); if( ofd.exec () == QDialog::Accepted && @@ -751,6 +754,9 @@ bool MainWindow::saveProjectAs( void ) QFileDialog sfd( this, tr( "Save project" ), "", tr( "MultiMedia Project (*.mmp *.mmpz);;" "MultiMedia Project Template (*.mpt)" ) ); +#if QT_VERSION >= 0x040806 + sfd.setOption( QFileDialog::DontUseCustomDirectoryIcons ); +#endif sfd.setAcceptMode( QFileDialog::AcceptSave ); sfd.setFileMode( QFileDialog::AnyFile ); QString f = engine::getSong()->projectFileName(); diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 29cdce5c4..7780f2bae 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -1010,7 +1010,11 @@ void setupDialog::openWorkingDir() { QString new_dir = QFileDialog::getExistingDirectory( this, tr( "Choose LMMS working directory" ), - m_workingDir ); + m_workingDir +#if QT_VERSION >= 0x040806 + , QFileDialog::DontUseCustomDirectoryIcons +#endif + ); if( new_dir != QString::null ) { m_wdLineEdit->setText( new_dir ); From d43eb46d81da287ade8838029e2bb9805a7dc575 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Fri, 13 Sep 2013 22:43:20 +0200 Subject: [PATCH 121/133] PianoRoll: fix crash when trying to mark octave chord --- src/gui/piano_roll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/piano_roll.cpp b/src/gui/piano_roll.cpp index e94c02208..8cccc6687 100644 --- a/src/gui/piano_roll.cpp +++ b/src/gui/piano_roll.cpp @@ -667,7 +667,7 @@ void pianoRoll::markSemiTone( int i ) const int first = chord->isScale() ? 0 : key; const int last = chord->isScale() ? NumKeys : key + chord->last(); - const int cap = chord->isScale() ? KeysPerOctave : chord->last(); + const int cap = ( chord->isScale() || chord->last() == 0 ) ? KeysPerOctave : chord->last(); for( int i = first; i <= last; i++ ) { From 03931e8b17c1eb84c6b74b86f1ad45748498adc1 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Fri, 13 Sep 2013 23:07:36 +0200 Subject: [PATCH 122/133] Added Progressive House preset and demo project Preset and demo project provided as public domain by Salehen Shovon Rahman . --- .../Plucked/progressive-house-pluck.xiz | 465 ++++++++++++++++++ .../Shovon-ProgressiveHousePluckDemo.mmpz | Bin 0 -> 16596 bytes 2 files changed, 465 insertions(+) create mode 100644 data/presets/ZynAddSubFX/Plucked/progressive-house-pluck.xiz create mode 100644 data/projects/Demos/Shovon-ProgressiveHousePluckDemo.mmpz diff --git a/data/presets/ZynAddSubFX/Plucked/progressive-house-pluck.xiz b/data/presets/ZynAddSubFX/Plucked/progressive-house-pluck.xiz new file mode 100644 index 000000000..c9c255de4 --- /dev/null +++ b/data/presets/ZynAddSubFX/Plucked/progressive-house-pluck.xiz @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + Analog Piano 3 + + + + + + + + + + Analog Piano 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/projects/Demos/Shovon-ProgressiveHousePluckDemo.mmpz b/data/projects/Demos/Shovon-ProgressiveHousePluckDemo.mmpz new file mode 100644 index 0000000000000000000000000000000000000000..1781abdcc15f99f6fcf1030f5980e872456c46e0 GIT binary patch literal 16596 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 Date: Fri, 13 Sep 2013 23:46:22 +0200 Subject: [PATCH 123/133] Added Galician localization files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Xosé for the contribution! --- data/locale/gl.qm | Bin 0 -> 203614 bytes data/locale/gl.ts | 6469 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 6469 insertions(+) create mode 100644 data/locale/gl.qm create mode 100644 data/locale/gl.ts diff --git a/data/locale/gl.qm b/data/locale/gl.qm new file mode 100644 index 0000000000000000000000000000000000000000..97cee737aecbcddaed6aa75080b5432666dc2615 GIT binary patch literal 203614 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%0ApjRpHTz1 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 literal 0 HcmV?d00001 diff --git a/data/locale/gl.ts b/data/locale/gl.ts new file mode 100644 index 000000000..546ed3698 --- /dev/null +++ b/data/locale/gl.ts @@ -0,0 +1,6469 @@ + + + + + AboutDialog + + About LMMS + Sobre o LMMS + + + LMMS (Linux MultiMedia Studio) + LMMS (Estudio Multimedia de Linux) + + + Version %1 (%2/%3, Qt %4, %5) + Versión %1 (%2/%3, Qt %4, %5) + + + About + Sobre + + + LMMS - easy music production for everyone + LMMS - produción musical fácil para calquera + + + Authors + Autores + + + Translation + Tradución + + + Current language not translated (or native English). + +If you're interested in translating LMMS in another language or want to improve existing translations, you're welcome to help us! Simply contact the maintainer! + Este idioma non está traducido (ou é inglés nativo). + +Se lle interesa traducir o LMMS a outro idioma ou desexa mellorar as traducións existentes, síntase á vontade axudándonos! Simplemente contacte co mantedor! + + + License + Licenza + + + Copyright (c) 2004-2013, LMMS developers + Copyright (c) 2004-2009, os desenvolvedores do LMMS {2004-2013,?} + + + http://lmms.sourceforge.net + + + + + Arpeggiator + + Arpeggio + Arpexo + + + Arpeggio type + Tipo de arpexo + + + Arpeggio range + Intervalo do arpexo + + + Arpeggio time + Tempo do arpexo + + + Arpeggio gate + Porta do arpexo + + + Arpeggio direction + Dirección do arpexo + + + Arpeggio mode + Modo do arpexo + + + Up + Arriba + + + Down + Abaixo + + + Up and down + Arriba e abaixo + + + Random + Aleatorio + + + Free + Libre + + + Sort + Ordenar + + + Sync + Sincronizar + + + + ArpeggiatorView + + ARPEGGIO + ARPEXO + + + An arpeggio is a method playing (especially plucked) instruments, which makes the music much livelier. The strings of such instruments (e.g. harps) are plucked like chords. The only difference is that this is done in a sequential order, so the notes are not played at the same time. Typical arpeggios are major or minor triads, but there are a lot of other possible chords, you can select. + Un arpexo é unha técnica de tocar instrumentos (especialmente de corda), que fai que a música sexa máis viva. As cordas deses instrumentos (harpas, por exemplo), púlsanse como acordes. A única diferenza é que se fai de maneira secuencial, polo que as notas non se tocan todas ao mesmo tempo. Os arpexos típicos son tríadas maiores ou menores, aínda que existen moitos outros acordes posíbeis que se poden escoller. + + + RANGE + Intervalo + + + Arpeggio range: + Tesitura do arpexo: + + + octave(s) + octava(s) + + + Use this knob for setting the arpeggio range in octaves. The selected arpeggio will be played within specified number of octaves. + Empregue este botón para indicar a tesitura do arpexo en oitavas. O arpexo escollido reproducirase dentro do número de oitavas indicado. + + + TIME + Tempo + + + Arpeggio time: + Tempo do arpexo: + + + ms + ms + + + Use this knob for setting the arpeggio time in milliseconds. The arpeggio time specifies how long each arpeggio-tone should be played. + + + + GATE + PORTA + + + Arpeggio gate: + Porta do arpexo: + + + % + % + + + Use this knob for setting the arpeggio gate. The arpeggio gate specifies the percent of a whole arpeggio-tone that should be played. With this you can make cool staccato arpeggios. + + + + Direction: + Dirección: + + + Mode: + Modo: + + + + AudioAlsa::setupWidget + + DEVICE + DISPOSITIVO + + + CHANNELS + CANLES + + + + AudioFileProcessorView + + Open other sample + Abrir outra mostra + + + Click here, if you want to open another audio-file. A dialog will appear where you can select your file. Settings like looping-mode, start and end-points, amplify-value, and so on are not reset. So, it may not sound like the original sample. + Prema aquí se desexa abrir outro ficheiro de son. Aparecerá un diálogo no que se pode escoller un ficheiro. As opcións tipo modo de bucle, puntos iniciais e final, valor da amplificación, etc. non se reinician. Polo tanto, pode non soar igual que a mostra orixinal. + + + Reverse sample + Inverter a mostra + + + If you enable this button, the whole sample is reversed. This is useful for cool effects, e.g. a reversed crash. + Ao activar este botón invértese a mostra completa. Isto é útil para efectos gaioleiros, como un crash invertido. + + + Loop sample at start- and end-point + Repetir a mostra en bucle nos puntos inicial e final + + + Here you can set, whether looping-mode is enabled. If enabled, AudioFileProcessor loops between start and end-points of a sample until the whole note is played. This is useful for things like string and choir samples. + Aquí pódese configurar se se desexa activar o modo de bucle. De activalo, o procesador de ficheiros de son repite entre os puntos inicial e final dunha mostra até que se toca a nota enteira. Isto é últil para cousas como mostras de corda e coro. + + + Amplify: + Amplificar: + + + With this knob you can set the amplify ratio. When you set a value of 100% your sample isn't changed. Otherwise it will be amplified up or down (your actual sample-file isn't touched!) + Con este botón pódese indicar a relación de amplificación. Cando se indica un valor de 100%, a mostra fica como estaba. Se non, amplifícase para arriba ou para abaixo (o ficheiro mesmo coa mostra non se toca!) + + + Startpoint: + Punto inicial: + + + With this knob you can set the point where AudioFileProcessor should begin playing your sample. If you enable looping-mode, this is the point to which AudioFileProcessor returns if a note is longer than the sample between the start and end-points. + Con este botón pódese indicar o punto se desexa que o AudioFileProcessor comece a reproducir a mostra. Se o modo en bucle estiver activado, este é o punto ao que volta o AudioFileProcessor se unha nota é máis longa do que a mostra entre os puntos inicial e final. + + + Endpoint: + Punto final: + + + With this knob you can set the point where AudioFileProcessor should stop playing your sample. If you enable looping-mode, this is the point where AudioFileProcessor returns if a note is longer than the sample between the start and end-points. + Con este botón pódese indicar o punto no que desexe o AudioFileProcessor pare a reprodución da mostra. Se o modo en bucle estiver activado, este é o punto ao que o AudioFileProcessor volve se a nota for máis longa que a mostra entre os puntos inicial e final. + + + + AudioFileProcessorWaveView + + Sample length: + + + + + AudioJack + + JACK client restarted + Reiniciouse o cliente de JACK + + + LMMS was kicked by JACK for some reason. Therefore the JACK backend of LMMS has been restarted. You will have to make manual connections again. + LMMS foi expulsado por JACK por algunha razón. En consecuencia, reiniciouse a infraestrutura de JACK do LMMS. Terá que realizar as conexións manualmente de novo. + + + JACK server down + O servidor de JACK non está a funcionar + + + The JACK server seems to have been shutdown and starting a new instance failed. Therefore LMMS is unable to proceed. You should save your project and restart JACK and LMMS. + Semella que o servidor de JACK foi apagado e non foi posíbel iniciar unha instancia nova. En consecuencia, o LMMS non pode preseguir. Hai que gravar este proxecto e reiniciar o JACK e o LMMS. + + + + AudioJack::setupWidget + + CLIENT-NAME + CLINTE-NOME + + + CHANNELS + CANLES + + + + AudioOss::setupWidget + + DEVICE + DISPOSITIVO + + + CHANNELS + CANLES + + + + AudioPortAudio::setupWidget + + BACKEND + INFRAESTRUTURA + + + DEVICE + DISPOSITIVO + + + + AudioPulseAudio::setupWidget + + DEVICE + DISPOSITIVO + + + CHANNELS + CANLES + + + + AudioSdl::setupWidget + + DEVICE + DISPOSITIVO + + + + AutomatableModel + + &Reset (%1%2) + &Reiniciar (%1%2) + + + &Copy value (%1%2) + &Copiar o valor (%1%2) + + + &Paste value (%1%2) + A&pegar o valor (%1%2) + + + Edit song-global automation + Editar a automatización global da canción + + + Connected to %1 + Ligado a %1 + + + Connected to controller + Ligado ao controlador + + + Edit connection... + Editar a conexión... + + + Remove connection + Eliminar a conexión + + + Connect to controller... + Ligar a un controlador... + + + Remove song-global automation + + + + + AutomationEditor + + Play/pause current pattern (Space) + Reproducir/Deter o padrón actual (Espazo) + + + Stop playing of current pattern (Space) + Parar a execución do padrón actual (Espazo) + + + Draw mode (Shift+D) + Modo de debuxo (Maiúsculas+D) + + + Erase mode (Shift+E) + Modo de borrado (Maiúsculas+E) + + + Select mode (Shift+S) + Modo de selección (Maiúscula+S) + + + Move selection mode (Shift+M) + Modo de movemento de selección (Maiúsculas+M) + + + If you click here, move-mode will be activated. In this mode you can move the values you selected in select-mode. You can also press 'Shift+M' on your keyboard to activate this mode. + Se preme aquí actívase o modo de movemento. Neste modo pódense mover os valores escollidos no modo de selección. Tamén se pode premer «Maiúsculas+M» no teclado para activar este modo. + + + Cut selected values (Ctrl+X) + Recortar os valores escollidos (Ctrl+X) + + + Copy selected values (Ctrl+C) + Copiar os valores escollidos (Ctrl+C) + + + Paste values from clipboard (Ctrl+V) + Apegar os valores do porta-retallos (Ctrl+V) + + + Automation Editor - no pattern + Editor de automatización - non hai ningún padrón + + + Automation Editor - %1 + Editor de automatización - %1 + + + Please open an automation pattern with the context menu of a control! + Abra un padrón de automatización co menú de contexto dun control! + + + Values copied + Valores copiados + + + All selected values were copied to the clipboard. + Copiáronse todos os valores escollidos no porta-retallos. + + + Click here if you want to play the current pattern. This is useful while editing it. The pattern is automatically looped when the end is reached. + Prema aquí se desexa reproducir este padrón. Isto é útil mentres se edita. O padrón repítese en bucle automaticamente ao chegar ao final. + + + Click here if you want to stop playing of the current pattern. + Prema aquí se desexa parar a reprodución deste padrón. + + + Click here and draw-mode will be activated. In this mode you can add and move single values. This is the default mode which is used most of the time. You can also press 'Shift+D' on your keyboard to activate this mode. + Prema aquí e o activarase o modo de debuxo. Neste modo pode engadir e mover valores individuais. Este é o modo por omisión que se emprega a maior parte do tempo. Tamén pode premer «Maiúsculas+D» no teclado para activar este modo. + + + Click here and erase-mode will be activated. In this mode you can erase single values. You can also press 'Shift+E' on your keyboard to activate this mode. + Prema aquí e activarase o modo de borrado. Neste modo pódense borrar valores individuais. Tamén pode premer «Maiúsculas+E» no teclado para activar este modo. + + + Click here and select-mode will be activated. In this mode you can select values. This is necessary if you want to cut, copy, paste, delete, or move values. You can also press 'Shift+S' on your keyboard to activate this mode. + Prema aquí e activarase o modo de selección. Neste modo pódense escoller valores. Isto é necesario se se desexa recortar, copiar, apegar, eliminar ou mover valores. Tamén se pode premer «Maiúsculas+S» no teclado para activar este modo. + + + Click here and selected values will be cut into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Prema aquí e os valores escollidos recórtanse e van para o porta-retallos. Pódeos apegar en calquera lugar de calquera padrón premendo o botón de apegar. + + + Click here and selected values will be copied into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Prema aquí e os valores escollidos cópianse no porta-retallos. Pódeos apegar en calquera lugar de calquera padrón premendo o botón de apegar. + + + Click here and the values from the clipboard will be pasted at the first visible measure. + Prema aquí e os valores do porta-retallos apegaranse no primeiro compás visíbel. + + + + AutomationPattern + + Drag a control while pressing <Ctrl> + Arrastre un control mentres ten <Ctrl> premido + + + + AutomationPatternView + + double-click to open this pattern in automation editor + faga duplo clic para abrir este padrón no editor de automatización + + + Open in Automation editor + Abrir o editor de automatización + + + Clear + Limpar + + + Reset name + Restaurar o nome + + + Change name + Mudar o nome + + + %1 Connections + %1 conexións + + + Disconnect "%1" + Desconectar «%1» + + + + AutomationTrack + + Automation track + Pista de automatización + + + + ChordCreator + + octave + oitava + + + Major + Maior + + + Majb5 + Majb5 + + + minor + menor + + + minb5 + minb5 + + + sus2 + sus2 + + + sus4 + sus4 + + + aug + aum + + + augsus4 + augsus4 + + + tri + tri + + + 6 + 6 + + + 6sus4 + 6sus4 + + + 6add9 + 6add9 + + + m6 + m6 + + + m6add9 + m6add9 + + + 7 + 7 + + + 7sus4 + 7sus4 + + + 7#5 + 7#5 + + + 7b5 + 7b5 + + + 7#9 + 7#9 + + + 7b9 + 7b9 + + + 7#5#9 + 7#5#9 + + + 7#5b9 + 7#5b9 + + + 7b5b9 + 7b5b9 + + + 7add11 + 7add11 + + + 7add13 + 7add13 + + + 7#11 + 7#11 + + + Maj7 + Maj7 + + + Maj7b5 + Maj7b5 + + + Maj7#5 + Maj7#5 + + + Maj7#11 + Maj7#11 + + + Maj7add13 + Maj7add13 + + + m7 + m7 + + + m7b5 + m7b5 + + + m7b9 + m7b9 + + + m7add11 + m7add11 + + + m7add13 + m7add13 + + + m-Maj7 + m-Maj7 + + + m-Maj7add11 + m-Maj7add11 + + + m-Maj7add13 + m-Maj7add13 + + + 9 + 9 + + + 9sus4 + 9sus4 + + + add9 + add9 + + + 9#5 + + + + 9b5 + 9b5 + + + 9#11 + + + + 9b13 + 9b13 + + + Maj9 + + + + Maj9sus4 + + + + Maj9#5 + + + + Maj9#11 + + + + m9 + m9 + + + madd9 + + + + m9b5 + + + + m9-Maj7 + + + + 11 + + + + 11b9 + 11b9 + + + Maj11 + + + + m11 + m11 + + + m-Maj11 + + + + 13 + + + + 13#9 + + + + 13b9 + 13b9 + + + 13b5b9 + 13b5b9 + + + Maj13 + + + + m13 + m13 + + + m-Maj13 + + + + Harmonic minor + Harmónico menor + + + Melodic minor + Melódico menor + + + Whole tone + Un ton + + + Diminished + Diminuído + + + Major pentatonic + Pentatónico maior + + + Minor pentatonic + Pentatónico menor + + + Jap in sen + + + + Major bebop + Maior de bebop + + + Dominant bebop + Dominante de bebop + + + Blues + Blues + + + Arabic + Árabe + + + Enigmatic + Enigmático + + + Neopolitan + Napolitano + + + Neopolitan minor + Napolitano menor + + + Hungarian minor + Húngaro menor + + + Dorian + Dorio + + + Phrygolydian + Frixio-lidio + + + Lydian + Lidio + + + Mixolydian + Mixolidio + + + Aeolian + Eolio + + + Locrian + Locrio + + + Chords + Acordes + + + Chord type + Tipo de acorde + + + Chord range + Intervalo do acorde + + + Minor + + + + + ChordCreatorView + + CHORDS + ACORDES + + + RANGE + INTERVALO + + + Chord range: + Intervalo do acorde: + + + octave(s) + oitava(s) + + + Use this knob for setting the chord range in octaves. The selected chord will be played within specified number of octaves. + Empregue este botón para indicar a tesitura do acorde en oitavas. O acorde escollido tocarase dentro do número indicado de oitavas. + + + + Controller + + Controller %1 + Controlador %1 + + + + ControllerConnectionDialog + + Connection Settings + Configuración da conexión + + + MIDI CONTROLLER + CONTROLADOR DE MIDI + + + Input channel + Canle de entrada + + + CHANNEL + CANLE + + + Input controller + Controlador de entrada + + + CONTROLLER + CONTROLADOR + + + Auto Detect + Detectar automaticamente + + + MIDI-devices to receive MIDI-events from + Dispositivos MIDI dos que recibir acontecementos + + + USER CONTROLLER + CONTROLADOR DO USUARIO + + + MAPPING FUNCTION + FUNCIÓN DE ASIGNACIÓN + + + OK + Aceptar + + + Cancel + Cancelar + + + LMMS + LMMS + + + Cycle Detected. + Detectouse un ciclo. + + + + ControllerRackView + + Controller Rack + Bastidor de controladores + + + Add + Engadir + + + + ControllerView + + Controls + Controles + + + Controllers are able to automate the value of a knob, slider, and other controls. + Os controladores poden automatizar o valor dun botón xiratorio ou linear e outros controles. + + + Rename controller + Renomear o controlador + + + Enter the new name for this controller + Introduza o novo nome deste controlador + + + &Remove this plugin + Elimina&r este engadido + + + &Help + &Axuda + + + + Effect + + Effect enabled + Efecto activado + + + Wet/Dry mix + Mestura húmida/seca + + + Gate + Porta + + + Decay + Decaemento + + + + EffectChain + + Effects enabled + Efectos activados + + + + EffectRackView + + EFFECTS CHAIN + CADEA DE EFECTOS + + + Add effect + Engadir un efecto + + + + EffectSelectDialog + + Add effect + Engadir un efecto + + + Plugin description + Descrición do engadido + + + + EffectView + + Toggles the effect on or off. + Conmuta a activación do efecto. + + + On/Off + Activar/Desactivar + + + W/D + H/S + + + Wet Level: + Nivel de humidade: + + + The Wet/Dry knob sets the ratio between the input signal and the effect signal that forms the output. + O botón Húmido/Seco indica a relación entre o sinal de entrada e o sinal de efecto que forma a saída. + + + DECAY + DECAE + + + Time: + Tempo: + + + The Decay knob controls how many buffers of silence must pass before the plugin stops processing. Smaller values will reduce the CPU overhead but run the risk of clipping the tail on delay and reverb effects. + O botón Decaemento controla cantos búferes de silencio han de pasar antes de que o engadido pare de procesar. Valores máis pequenos reducen o esforzo da CPU a risco de recortar a cola nos efectos de demora e reverberación. + + + GATE + PORTA + + + Gate: + Porta: + + + The Gate knob controls the signal level that is considered to be 'silence' while deciding when to stop processing signals. + O botón Porta controla o nivel do sinal que se considere como «silencio» mentres se decide cando parar de procesar os sinais. + + + Controls + Controles + + + Effect plugins function as a chained series of effects where the signal will be processed from top to bottom. + +The On/Off switch allows you to bypass a given plugin at any point in time. + +The Wet/Dry knob controls the balance between the input signal and the effected signal that is the resulting output from the effect. The input for the stage is the output from the previous stage. So, the 'dry' signal for effects lower in the chain contains all of the previous effects. + +The Decay knob controls how long the signal will continue to be processed after the notes have been released. The effect will stop processing signals when the volume has dropped below a given threshold for a given length of time. This knob sets the 'given length of time'. Longer times will require more CPU, so this number should be set low for most effects. It needs to be bumped up for effects that produce lengthy periods of silence, e.g. delays. + +The Gate knob controls the 'given threshold' for the effect's auto shutdown. The clock for the 'given length of time' will begin as soon as the processed signal level drops below the level specified with this knob. + +The Controls button opens a dialog for editing the effect's parameters. + +Right clicking will bring up a context menu where you can change the order in which the effects are processed or delete an effect altogether. + Os engadidos de efectos funcionan como unha serie encadeada de efectos na que o sinal se procesa desde arriba para abaixo. + +O interruptor Activar/Desactivar permite omitir un engadido dado en calquera momento. + +O botón Húmido/Seco controla o balance entre o sinal de entrada eo sinal producido que é a saída resultante do efecto. A entrada da etapa é a saída da etapa anterior. Polo tanto, o sinal «seco» dos efectos de abaixo na cadea contén todos os efectos previos. + +O botón Decaemento controla como se continúa a procesar o sinal despois de relaxar as notas. O efecto para o procesamento dos sinais cando o volume baixa por debaixo dun limiar dado durante un tempo dado. Este botón indica o «tempo dado». Tempos maiores requiren máis CPU, polo que este número debería ser baixo para a maioría dos efectos. Haino que subir para os efectos que producen períodos de silencio longos, como por exemplo as demoras. + +O botón Porta controla o «limiar dado» do apagado automático do efecto. O reloxo do «tempo dado» comeza así que o nivel do sinal procesado cae por debaixo do nivel indicado con este botón. + +O botón Controles abre un diálogo para editar os parámetros do efecto. + +Ao premer co botón dereito aparece un menú de contexto no que se pode cambiar a orden na que se procesan os efectos ou eliminar un efecto de vez. + + + Move &up + S&ubir + + + Move &down + &Baixar + + + &Remove this plugin + Elimina&r este engadido + + + &Help + &Axuda + + + + EnvelopeAndLfoParameters + + Predelay + Tempo de reverberación + + + Attack + Ataque + + + Hold + Retención + + + Decay + Decaemento + + + Sustain + Sustentación + + + Release + Relaxamento + + + Modulation + Modulación + + + LFO Predelay + Tempo de reverberación do LFO + + + LFO Attack + Ataque do LFO + + + LFO speed + Velocidade do LFO + + + LFO Modulation + Modulación do LFO + + + LFO Wave Shape + Forma da onda do LFO + + + Freq x 100 + Freq x 100 + + + Modulate Env-Amount + Modular a cantidade de env + + + + EnvelopeAndLfoView + + DEL + TMP REV + + + Predelay: + Tempo de reverberación: + + + Use this knob for setting predelay of the current envelope. The bigger this value the longer the time before start of actual envelope. + Empregue este botón para indicar o tempo de reverberación desta envolvente. Canto maior for este valor maior será o intervalo até que comece a envolvente en si. + + + ATT + ATAQ + + + Attack: + Ataque: + + + Use this knob for setting attack-time of the current envelope. The bigger this value the longer the envelope needs to increase to attack-level. Choose a small value for instruments like pianos and a big value for strings. + Empregue este botón para indicar o tempo de ataque da envolvente escollida. Canto maior for o valor máis tempo lle levará á envolvente aumentar até o nivel de ataque. Escolla un valor pequeno para instrumentos como os pianos e un valor grande para as cordas. + + + HOLD + RETEN + + + Hold: + Retención: + + + Use this knob for setting hold-time of the current envelope. The bigger this value the longer the envelope holds attack-level before it begins to decrease to sustain-level. + Empregue este botón para indicar o tempo de retención da envolvente escollida. Canto maior for este valor máis tempo mantén a envolvente o nivel de ataque antes de comezar a diminuír até o nivel de sustentación. + + + DEC + DEC + + + Decay: + Decaemento: + + + Use this knob for setting decay-time of the current envelope. The bigger this value the longer the envelope needs to decrease from attack-level to sustain-level. Choose a small value for instruments like pianos. + Empregue este botón para indicar o tempo de decaemento da envolvente escollida. Canto maior for este valor máis tempo lle levará á envolvente para diminuír desde o nivel de ataque até o nivel de sustentación. Escolla un valor pequenos para instrumentos como o piano. + + + SUST + SUST + + + Sustain: + Sustentación: + + + Use this knob for setting sustain-level of the current envelope. The bigger this value the higher the level on which the envelope stays before going down to zero. + Empregue este botón para indicar o nivel de sustentación da envolvente escollida. Canto maior for este valor máis alto é o nivel no que fica a envolvente antes de baixar até cero. + + + REL + + + + Release: + Relaxamento: + + + Use this knob for setting release-time of the current envelope. The bigger this value the longer the envelope needs to decrease from sustain-level to zero. Choose a big value for soft instruments like strings. + Empregue este botón para indicar o tempo de relaxamento da envolvente escollida. Canto maior for este valor máis tempo lle leva diminuír desde o nivel de sustentación até cero. Escolla un valor grande para instrumentos como as cordas. + + + AMT + CANTIDADE + + + Modulation amount: + Cantidade de modulación: + + + Use this knob for setting modulation amount of the current envelope. The bigger this value the more the according size (e.g. volume or cutoff-frequency) will be influenced by this envelope. + Empregue este botón para indicar a cantidade de modulación da envolvente escollida. Canto maior for este valor maior máis se verá influenciado o tamaño correspondente (p.ex. o volume ou a frecuencia de corte) por esta envolvente. + + + LFO predelay: + Tempo de reverberación do LFO: + + + Use this knob for setting predelay-time of the current LFO. The bigger this value the the time until the LFO starts to oscillate. + Empregue este botón para indicar o tempo de reverberación deste LFO. Canto maior for este valor, maior será o intervalo até que o LFO comece a oscilar. + + + LFO- attack: + Ataque do LFO: + + + Use this knob for setting attack-time of the current LFO. The bigger this value the longer the LFO needs to increase its amplitude to maximum. + Empregue este botón para indicar o tempo de ataque do LFO escollido. Canto maior for este valor máis tempo lle levará ao LFO para aumentar a súa amplitude até o máximo. + + + SPD + SPD + + + LFO speed: + Velocidade do LFO: + + + Use this knob for setting speed of the current LFO. The bigger this value the faster the LFO oscillates and the faster will be your effect. + Empregue este botón para indicar a velocidade do LFO escollido. Canto maior for este valor máis rápido oscila o LFO e máis rápido é o efecto. + + + Use this knob for setting modulation amount of the current LFO. The bigger this value the more the selected size (e.g. volume or cutoff-frequency) will be influenced by this LFO. + Empregue este botón para indicar a cantidade de modulación do LFO escollido. Canto maior for este valor máis se verá influenciado o tamaño escollido (p.ex. o volume ou a frecuencia de corte) por este LFO. + + + Click here for a sine-wave. + Prema aquí para unha onda senoidal. + + + Click here for a triangle-wave. + Prema aquí para unha onda triangular. + + + Click here for a saw-wave for current. + Prema aquí para unha onda de dente de serra. + + + Click here for a square-wave. + Prema aquí para unha onda cadrada. + + + Click here for a user-defined wave. Afterwards, drag an according sample-file onto the LFO graph. + Prema aquí para unha onda definida polo usuario. Posteriormente, arrastre un ficheiro de mostra correspondente sobre o gráfico de LFO. + + + FREQ x 100 + FREQ x 100 + + + Click here if the frequency of this LFO should be multiplied by 100. + Prema aquí se desexa multiplicar por 100 a frecuencia deste LFO. + + + multiply LFO-frequency by 100 + multiplicar a frecuencia de LFO por 100 + + + MODULATE ENV-AMOUNT + MODULAR CANT. ENVO + + + Click here to make the envelope-amount controlled by this LFO. + Prema aquí para facer que a cantidade de envolvente sexa controlada por este LFO. + + + control envelope-amount by this LFO + controlar a cantidade de envolvente con este LFO + + + ms/LFO: + + + + Hint + Suxestión + + + Drag a sample from somewhere and drop it in this window. + Arrastre un exemplo doutro sitio e sólteo sobre esta xanela. + + + + ExportProjectDialog + + Export project + Exportar o proxecto + + + Output + Saída + + + File format: + Formato de ficheiro: + + + Samplerate: + Taxa de mostraxe: + + + 44100 Hz + 44100 Hz + + + 48000 Hz + 48000 Hz + + + 88200 Hz + 88200 Hz + + + 96000 Hz + 96000 Hz + + + 192000 Hz + 192000 Hz + + + Bitrate: + Taxa de bits: + + + 64 KBit/s + 64 KBit/s + + + 128 KBit/s + 128 KBit/s + + + 160 KBit/s + 160 KBit/s + + + 192 KBit/s + 192 KBit/s + + + 256 KBit/s + 256 KBit/s + + + 320 KBit/s + 320 KBit/s + + + Depth: + Profundidade: + + + 16 Bit Integer + Enteiro de 16 bits + + + 32 Bit Float + Vírgula flutuante de 32 bits + + + Please note that not all of the parameters above apply for all file formats. + Teña en conta que non todos os parámetros de enriba se poden aplicar a todos os formatos de ficheiro. + + + Quality settings + Configuración da calidade + + + Interpolation: + Interpolación: + + + Zero Order Hold + Retención de orde cero + + + Sinc Fastest + Whittaker–Shannon máis rápida + + + Sinc Medium (recommended) + Whittaker–Shannon media (recomendada) + + + Sinc Best (very slow!) + Whittaker–Shannon mellor (moi lenta!) + + + Oversampling (use with care!): + Sobresampleado (usar con coidado!): + + + 1x (None) + 1x (Ningún) + + + 2x + 2x + + + 4x + 4x + + + 8x + 8x + + + Sample-exact controllers + Controladores de mostras exactas + + + Alias-free oscillators + Osciladores sen alias + + + Start + Comezar + + + Cancel + Cancelar + + + Export as loop (remove end silence) + + + + + FxMixer + + Master + Global + + + FX %1 + Efecto %1 + + + + FxMixerView + + Rename FX channel + Mudar o nome desta canle de efectos especiais + + + Enter the new name for this FX channel + Introduza o novo nome para esta canle de efectos especiais + + + FX-Mixer + Mesturador de efectos especiais + + + FX Fader %1 + Fader de efectos %1 + + + Mute + Silenciar + + + Mute this FX channel + Silenciar esta canle de efectos especiais + + + + InstrumentMidiIOView + + ENABLE MIDI INPUT + ACTIVAR A ENTRADA DE MIDI + + + CHANNEL + CANLE + + + VELOCITY + VELOCIDADE + + + ENABLE MIDI OUTPUT + ACTIVAR A SAÍDA DE MIDI + + + PROGRAM + PROGRAMA + + + MIDI devices to receive MIDI events from + Dispositivos MIDI dos que recibir acontecementos MIDI + + + MIDI devices to send MIDI events to + Dispositivos MIDI aos que enviar acontecementos MIDI + + + NOTE + + + + + InstrumentSoundShaping + + VOLUME + VOLUME + + + Volume + Volume + + + CUTOFF + FREC. CORTE + + + Cutoff frequency + Frecuencia de corte + + + RESO + RESO + + + Resonance + Resonancia + + + Envelopes/LFOs + Envolventes/LFO + + + Filter type + Tipo de filtro + + + Q/Resonance + Q/Resonancia + + + LowPass + Pasa-baixas + + + HiPass + Pasa-altas + + + BandPass csg + Pasa-faixa csg + + + BandPass czpg + Pasa-faixa czpg + + + Notch + Entalle + + + Allpass + Pasa-todo + + + Moog + Moog + + + 2x LowPass + 2x Pasa-baixas + + + RC LowPass 12dB + RC pasa-baixa 12dB + + + RC BandPass 12dB + RC pasa-faixa 12dB + + + RC HighPass 12dB + RC pasa-alta 12dB + + + RC LowPass 24dB + RC pasa-baixa 24dB + + + RC BandPass 24dB + RC pasa-faixa 24dB + + + RC HighPass 24dB + RC pasa-alta 24dB + + + Vocal Formant Filter + Filtro de formante vocal + + + + InstrumentSoundShapingView + + TARGET + DESTINO + + + These tabs contain envelopes. They're very important for modifying a sound, in that they are almost always necessary for substractive synthesis. For example if you have a volume envelope, you can set when the sound should have a specific volume. If you want to create some soft strings then your sound has to fade in and out very softly. This can be done by setting large attack and release times. It's the same for other envelope targets like panning, cutoff frequency for the used filter and so on. Just monkey around with it! You can really make cool sounds out of a saw-wave with just some envelopes...! + Estas lapelas conteñen envolventes. Son moi importantes para modificar un son dado que son case sempre necesarias para as sínteses subtractivas. Por exemplo, se se ten unha envolvente de volume pódese indicar cando se desexa que o son teña un volume determinado. Se se desexan crear cordas brandas o son ten que entrar e saír moi suavemente. Isto pódese facer indicando tempos de ataque e relaxamento longos. É o mesmo para outros destinos de envolvente, como panning, frecuencia de corte do filtro empregado e así por diante. Fedelle! Seguro que pode crear sons interesantes a partir dunha onde de dente de serra con unhas imples envolventes...! + + + FILTER + FILTRO + + + Here you can select the built-in filter you want to use for this instrument-track. Filters are very important for changing the characteristics of a sound. + Aquí pódese escoller o filtro incorporado que se desexe empregar para esta pista de instrumento. Os filtros son moi importantes para cambiar as características dun son. + + + CUTOFF + FREC. CORTE + + + cutoff-frequency: + frecuencia de corte: + + + Hz + Hz + + + Use this knob for setting the cutoff frequency for the selected filter. The cutoff frequency specifies the frequency for cutting the signal by a filter. For example a lowpass-filter cuts all frequencies above the cutoff frequency. A highpass-filter cuts all frequencies below cutoff frequency, and so on... + Empregue este botón para indicar a frecuencia de corte do filtro escollido. A frecuencia de corte indica a frecuencia á que un filtro corta o sinal. Por exemplo, un filtro pasa-baixas corta todas as frecuencias que ultrapasen a frecuencia de corte. Un filtro pasa-altas corta todas as frecuencias por debaixo da frecuencia de corte, e así por diante... + + + RESO + RESO + + + Resonance: + Resonancia: + + + Use this knob for setting Q/Resonance for the selected filter. Q/Resonance tells the filter how much it should amplify frequencies near Cutoff-frequency. + Empregue este botón para indicar a Q/Resonancia do filtro escollido. A Q/Resonancia indícalle ao filtro canto se desexa amplificar as frecuencias próximas á de corte. + + + + InstrumentTrack + + unnamed_track + pista_sen_nome + + + Volume + Volume + + + Panning + Panorámica + + + Pitch + Altura + + + FX channel + Canle de efectos especiais + + + Default preset + Predefinido + + + With this knob you can set the volume of the opened channel. + Con este botón pódese indicar o volume da canle aberta. + + + Base note + Nota base + + + + InstrumentTrackView + + Volume + Volume + + + Volume: + Volume: + + + VOL + VOL + + + Panning + Panorámica + + + Panning: + Panorámica: + + + PAN + PAN + + + MIDI + MIDI + + + Input + Entrada + + + Output + Saída + + + + InstrumentTrackWindow + + GENERAL SETTINGS + CONFIGURACIÓN XERAL + + + Click here, if you want to save current channel settings in a preset-file. Later you can load this preset by double-clicking it in the preset-browser. + Prema aquí se desexa gravar a configuración desta canle nun ficheiro de predefinicións. Noutras ocasións poderá cargar estas predefinicións facendo duplo clic no navegador de predefinicións. + + + Instrument volume + Volume do instrumento + + + Volume: + Volume: + + + VOL + VOL + + + Panning + Panorámica + + + Panning: + Panorámica: + + + PAN + PAN + + + Pitch + Altura + + + Pitch: + Altura: + + + cents + cents + + + PITCH + ALTURA + + + FX channel + Canlde de FX + + + FX CHNL + CANLE DE FX + + + ENV/LFO + ENV/LFO + + + FUNC + FUNC + + + FX + FX + + + MIDI + MIDI + + + Save preset + Gardar as predefinicións + + + XML preset file (*.xpf) + Ficheiro de predefinicións en XML (*.xpf) + + + PLUGIN + ENGADIDO + + + Save current channel settings in a preset-file + + + + + LadspaControl + + Link channels + Ligar canles + + + + LadspaControlDialog + + Link Channels + Ligar canles + + + Channel + Canle + + + + LadspaControlView + + Link channels + Ligar canles + + + Value: + Valor: + + + Sorry, no help available. + Desculpe, non hai axuda dispoñíbel. + + + + LadspaEffect + + Effect + Efecto + + + Unknown LADSPA plugin %1 requested. + Solicitouse un engadido de LADSPA, %1, que é descoñecido. + + + + LfoController + + LFO Controller + Controlador de LFO + + + Base value + Valor base + + + Oscillator speed + Velocidade do oscilador + + + Oscillator amount + Cantidade de oscilador + + + Oscillator phase + Fase do oscilador + + + Oscillator waveform + Forma de onda do oscilador + + + Frequency Multiplier + Multiplicador de frecuencia + + + + LfoControllerDialog + + LFO + LFO + + + LFO Controller + Controlador de LFO + + + BASE + BASE + + + Base amount: + Cantidade base: + + + todo + por facer + + + SPD + SPD + + + LFO-speed: + Velocidade de SPD: + + + Use this knob for setting speed of the LFO. The bigger this value the faster the LFO oscillates and the faster the effect. + Empregue este botón para indicar a velocidade do oscilador de frecuencia baixa (LFO). Canto maior sexa este valor, máis rápido oscila o LFO e máis rápido resulta o efecto. + + + AMT + + + + Modulation amount: + Cantidade de modulación: + + + Use this knob for setting modulation amount of the LFO. The bigger this value, the more the connected control (e.g. volume or cutoff-frequency) will be influenced by the LFO. + Empregue este botón para indicar a cantidade de modulación do LFO. Canto maior for este valor, máis se verá influenciado o control conectado (p.ex. volume ou frecuencia de corte) polo LFO. + + + PHS + FASE + + + Phase offset: + Desprazamento da fase: + + + degrees + graos + + + With this knob you can set the phase offset of the LFO. That means you can move the point within an oscillation where the oscillator begins to oscillate. For example if you have a sine-wave and have a phase-offset of 180 degrees the wave will first go down. It's the same with a square-wave. + Con este botón pódese indicar o desprazamento da fase do oscilador de frecuencia baixa (LFO). Iso significa que se pode mover o punto dunha oscilación no que o oscilador comeza a oscilar. Se, por exemplo, se ten unha onda senoidal e un desprazamento de fase de 180 graos, a onda baixo primeiro. Cunha onda cadrada é o mesmo. + + + Click here for a sine-wave. + Prema aquí para unha onda senoidal. + + + Click here for a triangle-wave. + Prema aquí para unha onda triangular. + + + Click here for a saw-wave. + Prema aquí para unha onda de dente de serra. + + + Click here for a square-wave. + Prema aquí para unha onda cadrada. + + + Click here for a a moog saw-wave. + Prema aquí para unha onda de dente de serra tipo Moog. + + + Click here for an exponential wave. + Prema aquí para unha onda exponencial. + + + Click here for white-noise. + Prema aquí para ruído branco. + + + Click here for a user-defined shape. + Prema aquí para unha forma definida polo usuario. + + + + MainWindow + + Working directory + Directorio de traballo + + + The LMMS working directory %1 does not exist. Create it now? You can change the directory later via Edit -> Settings. + O directorio de traballo do LMMS %1 non existe. Desexa crealo agora? Pode cambiar o directorio máis tarde indo a Editar -> Configuración. + + + Could not save config-file + Non foi posíbel gravar o ficheiro de configuración + + + Could not save configuration file %1. You're probably not permitted to write to this file. +Please make sure you have write-access to the file and try again. + Non foi posíbel gravar o ficheiro de configuración %1. Probabelmente non se lle permita escribir neste ficheiro. Asegúrese de dispor de acceso para escribir neste ficheiro e ténteo de novo. + + + &Project + &Proxecto + + + &New + &Novo + + + &Open... + &Abrir... + + + Recently opened projects + Proxectos abertos recentemente + + + &Save + &Gardar + + + Save &As... + Gr&avar como... + + + Import... + Importar... + + + E&xport... + E&xportar... + + + &Quit + &Saír + + + &Edit + &Editar + + + Undo + Desfacer + + + Redo + Refacer + + + Settings + Configuración + + + &Tools + Ferramen&tas + + + &Help + &Axuda + + + Online help + Axuda na Internet + + + Help + Axuda + + + What's this? + Que é isto? + + + About + Sobre + + + Create new project + Crear un proxecto novo + + + Create new project from template + Crear un proxecto novo a partir dun modelo + + + Open existing project + Abrir un projecto existente + + + Recently opened project + Proxecto aberto recentemente + + + Save current project + Gravar este proxecto + + + Export current project + Exportar este proxecto + + + Show/hide Song-Editor + Mostrar/Agochar o editor de cancións + + + By pressing this button, you can show or hide the Song-Editor. With the help of the Song-Editor you can edit song-playlist and specify when which track should be played. You can also insert and move samples (e.g. rap samples) directly into the playlist. + Premendo este botón pódese mostrar ou agochar o Editor de Cancións. Coa axuda do Editor de Cancións pódese editar listas de reprodución e indicar cando tocar unha pista. Tamén se poden inserir e mover mostras (p.ex. mostras de rap) directamente na lista de reprodución. + + + Show/hide Beat+Bassline Editor + Mostrar/Agochar o Editor de ritmos e liña do baixo + + + By pressing this button, you can show or hide the Beat+Bassline Editor. The Beat+Bassline Editor is needed for creating beats, and for opening, adding, and removing channels, and for cutting, copying and pasting beat and bassline-patterns, and for other things like that. + Premendo este botón pódese mostrar ou agochar o Editor de ritmos e liña do baixo. Este Editor de ritmos e liña do baixo é necesario para crear ritmos e para abrir, engadir e eliminar canles, así como para recortar, copiar e pegar ritmos e padróns de liñas do baixo e para outras cousas semellantes. + + + Show/hide Piano-Roll + Mostrar/Agochar a pianola + + + Click here to show or hide the Piano-Roll. With the help of the Piano-Roll you can edit melodies in an easy way. + Prema aquí para mostrar ou agochar a pianola. Coa axuda da pianola pódense editar melodías facilmente. + + + Show/hide Automation Editor + Mostrar/Agochar o Editor de automatización + + + Click here to show or hide the Automation Editor. With the help of the Automation Editor you can edit dynamic values in an easy way. + Prema aquí para mostrar ou agochar o Editor de automatización. Coa axuda do Editor de automatización pódense editar os valores dinámicos facilmente. + + + Show/hide FX Mixer + Mostrar/Agochar os Mesturador de efectos especiais + + + Click here to show or hide the FX Mixer. The FX Mixer is a very powerful tool for managing effects for your song. You can insert effects into different effect-channels. + Prema aquí para mostrar ou agochar o Mesturador de efectos especiais. O Mesturador de efectos especiais é unha ferramenta potente para xestionar os efectos das cancións. Pódense inserir efectos nas diferentes canles de efectos. + + + Show/hide project notes + Mostrar/Agochar as notas do proxecto + + + Click here to show or hide the project notes window. In this window you can put down your project notes. + Prema aquí para mostrar ou agochar a xanela coas notas do proxecto. Nela pódense apuntar as notas do proxecto. + + + Show/hide controller rack + Mostrar/Agochar o bastidor de controladores + + + Untitled + Sen título + + + LMMS %1 + LMMS %1 + + + Project not saved + Proxecto non gardado + + + The current project was modified since last saving. Do you want to save it now? + Este proxecto foi modificado desde que se gardou a última vez. Desexa gardalo agora? + + + Open project + Abrir un proxecto + + + MultiMedia Project (*.mmp *.mmpz *.xml) + Proxecto multimedia (*.mmp *.mmpz *.xml) + + + Save project + Gardar o proxecto + + + MultiMedia Project (*.mmp *.mmpz);;MultiMedia Project Template (*.mpt) + Proxecto multimedia (*.mmp *.mmpz);;Modelo de proxecto multimedia (*.mpt) + + + Help not available + Non hai axuda dispoñíbel + + + Currently there's no help available in LMMS. +Please visit http://lmms.sf.net/wiki for documentation on LMMS. + De momento non hai axuda dispoñíbel no LMMS. +Visitehttp://lmms.sf.net/wiki para documentación sobre o LMMS. + + + My projects + + + + My samples + + + + My presets + + + + My home + + + + My computer + + + + Root directory + + + + E&xport tracks... + + + + Project recovery + + + + It looks like the last session did not end properly. Do you want to recover the project of this session? + + + + Configuration file + + + + Error while parsing configuration file at line %1:%2: %3 + + + + + MeterDialog + + Meter Numerator + Numerador do compás + + + Meter Denominator + Denominador do compás + + + TIME SIG + COMPÁS + + + + MeterModel + + Numerator + Numerador + + + Denominator + Denominador + + + + MidiAlsaRaw::setupWidget + + DEVICE + DISPOSITIVO + + + + MidiAlsaSeq::setupWidget + + DEVICE + DISPOSITIVO + + + + MidiController + + MIDI Controller + Controlador de MIDI + + + unnamed_midi_controller + controlador_de_midi_sen_nome + + + + MidiImport + + Setup incomplete + A configuración está incompleta + + + You do not have set up a default soundfont in the settings dialog (Edit->Settings). Therefore no sound will be played back after importing this MIDI file. You should download a General MIDI soundfont, specify it in settings dialog and try again. + Non se indicou unha fonte de son por omisión no diálogo de configuración (Editar->Configuración). En consecuencia, non se ha de reproducir ningún son unha vez importado este ficheiro de MIDI. Debería descargar unha fonte de son de General MIDI, indicala no diálogo de configuración e tentar de novo. + + + You did not compile LMMS with support for SoundFont2 player, which is used to add default sound to imported MIDI files. Therefore no sound will be played back after importing this MIDI file. + O LMMS non foi compilado para que admitise un reprodutor de SoundFont2, que se utiliza para engadir un son por omisión aos ficheiros de MIDI. En consecuencia, non se ha de reproducir ningún son unha vez importado este ficheiro de MIDI. + + + + MidiOss::setupWidget + + DEVICE + DISPOSITIVO + + + + MidiPort + + Input channel + Canle de entrada + + + Output channel + Canle de saída + + + Input controller + Controlador de entrada + + + Output controller + Controlador de saída + + + Fixed input velocity + Velocidade de entrada fixa + + + Fixed output velocity + Velocidade de saída fixa + + + Output MIDI program + Programa MIDI de saída + + + Receive MIDI-events + Recibir acontecementos de MIDI + + + Send MIDI-events + Enviar acontecementos de MIDI + + + Fixed output note + + + + + OscillatorObject + + Osc %1 volume + Volume do oscilador %1 + + + Osc %1 panning + Panorámica do oscilador %1 + + + Osc %1 coarse detuning + Desafinación bruta do oscilador %1 + + + Osc %1 fine detuning left + Desafinación fina esquerda do oscilador %1 + + + Osc %1 fine detuning right + Desafinación fina dereita do oscilador %1 + + + Osc %1 phase-offset + Desprazamento da fase do oscilador %1 + + + Osc %1 stereo phase-detuning + Desafinación de fase en estéreo do oscilador %1 + + + Osc %1 wave shape + Forma da onda do oscilador %1 + + + Modulation type %1 + Tipo de modulación %1 + + + Osc %1 waveform + Forma de onda do oscilador %1 + + + + PatmanView + + Open other patch + Abrir outro parche + + + Click here to open another patch-file. Loop and Tune settings are not reset. + Prema aquí para abrir outro ficheiro de parche. A configuración dos bucles e a afinación non se restauran. + + + Loop + Bucle + + + Loop mode + Modo de bucle + + + Here you can toggle the Loop mode. If enabled, PatMan will use the loop information available in the file. + Aquí pódese alternar entre modos de bucle. Cando está activado, o PatMan emprega a información sobre o bucle dispoñíbel no ficheiro. + + + Tune + Afinación + + + Tune mode + Modo de afinación + + + Here you can toggle the Tune mode. If enabled, PatMan will tune the sample to match the note's frequency. + Aquí pódese alternar entre modos de afinación. Cando está activado o Patman afina a mostra para que coincida coa frecuencia da nota. + + + No file selected + Non escolleu ningún ficheiro + + + Open patch file + Abrir un ficheiro de parches + + + Patch-Files (*.pat) + Ficheiros de parches (*.pat) + + + + PeakController + + Peak Controller + Controlador de picos + + + + PeakControllerDialog + + PEAK + PICO + + + LFO Controller + Controlador do LFO + + + + PeakControllerEffectControlDialog + + BASE + BASE + + + Base amount: + Cantidade base: + + + AMT + AMT + + + Modulation amount: + Cantidade de modulación: + + + ATTACK + ATAQUE + + + Attack: + Ataque: + + + DECAY + DECAEMENTO + + + Release: + Relaxamento: + + + + PeakControllerEffectControls + + Base value + Valor base + + + Modulation amount + Cantidade de modulación + + + Attack + Ataque + + + Release + Relaxamento + + + Mute output + Silenciar a saída + + + + PianoView + + Base note + Nota base + + + + Plugin + + Plugin not found + Non se atopou o engadido + + + The plugin "%1" wasn't found or could not be loaded! +Reason: "%2" + Non se atopou o engadido «%1» ou non foi posíbel cargalo! +Razón: «%2» + + + Error while loading plugin + Produciuse un erro ao cargar o engadido + + + Failed to load plugin "%1"! + Fallou a carga do engadido «%1»! + + + + ProjectRenderer + + WAV-File (*.wav) + Ficheiro wav (*.wav) + + + Compressed OGG-File (*.ogg) + Ficheiro OGG comprimido (*.ogg) + + + + QWidget + + Name: + Nome: + + + Maker: + Creador: + + + Copyright: + Copyright: + + + Requires Real Time: + Require tempo real: + + + Yes + Si + + + No + Non + + + Real Time Capable: + Capacidade de tempo real: + + + In Place Broken: + En sitio rachado: + + + Channels In: + Canles de entrada: + + + Channels Out: + Canles de saída: + + + File: + Ficheiro: + + + + TempoSyncKnob + + Tempo Sync + Sincronización do tempo + + + No Sync + Non sincronizar + + + Eight beats + Oito tempos + + + Whole note + Redonda + + + Half note + Branca + + + Quarter note + Negra + + + 8th note + Corchea + + + 16th note + Semicorchea + + + 32nd note + Fusa + + + Custom... + Personalizada... + + + &Help + &Axuda + + + Custom + Personalizada + + + Synced to Eight Beats + Sincronizado a oito tempos + + + Synced to Whole Note + Sincronizado á redonda + + + Synced to Half Note + Sincronizado á branca + + + Synced to Quarter Note + Sincronizado á negra + + + Synced to 8th Note + Sincronizado á corchea + + + Synced to 16th Note + Sincronizado á semicorchea + + + Synced to 32nd Note + Sincronizado á fusa + + + + TripleOscillatorView + + Use phase modulation for modulating oscillator 2 with oscillator 1 + Empregar a modulación de fase para modular o oscilador 2 co oscilador 1 + + + Use amplitude modulation for modulating oscillator 2 with oscillator 1 + Empregar a modulación de amplitude para modular o oscilador 2 co oscilador 1 + + + Mix output of oscillator 1 & 2 + Misturar a saída dos osciladores 1 e 2 + + + Synchronize oscillator 1 with oscillator 2 + Sincronizar o oscilador 1 co oscilador 2 + + + Use frequency modulation for modulating oscillator 2 with oscillator 1 + Empregar a modulación de frecuencia para modular o oscilador 2 co oscilador 1 + + + Use phase modulation for modulating oscillator 3 with oscillator 2 + Empregar a modulación de fase para modular o oscilador 3 co oscilador 2 + + + Use amplitude modulation for modulating oscillator 3 with oscillator 2 + Empregar a modulación de amplitude para modular o oscilador 3 co oscilador 2 + + + Mix output of oscillator 2 & 3 + Misturar a saída dos osciladores 2 e 3 + + + Synchronize oscillator 2 with oscillator 3 + Sincronizar o oscilador 2 co oscilador 3 + + + Use frequency modulation for modulating oscillator 3 with oscillator 2 + Empregar a modulación de frecuencia para modular o oscilador 3 co oscilador 2 + + + Osc %1 volume: + Volume do oscilador %1: + + + With this knob you can set the volume of oscillator %1. When setting a value of 0 the oscillator is turned off. Otherwise you can hear the oscillator as loud as you set it here. + Con este botón pódese indicar o volume do oscilador %1. Ao indicar un valor de 0 o oscilador apágase. Caso contrario pódese ouvir o oscilador tan alto como se indique aquí. + + + Osc %1 panning: + Panorámica do oscilador %1 + + + With this knob you can set the panning of the oscillator %1. A value of -100 means 100% left and a value of 100 moves oscillator-output right. + Con este botón pódese indicar o panorama («panning») do oscilador %1. Un valor de -100 significa 100% esquerda e un valor de 100 move a saída do oscilador para a dereita. + + + Osc %1 coarse detuning: + Desafinación bruta do oscilador %1: + + + semitones + semitóns + + + With this knob you can set the coarse detuning of oscillator %1. You can detune the oscillator 12 semitones (1 octave) up and down. This is useful for creating sounds with a chord. + Con este botón pódese definir a desafinación bruta do oscilador %1. Pódese desafinar o oscilador 12 semitóns (unha oitava) para arriba e para abaixo. Isto é útil para crear sons cun acorde. + + + Osc %1 fine detuning left: + Desafinación fina esquerda do oscilador %1: + + + cents + cents + + + With this knob you can set the fine detuning of oscillator %1 for the left channel. The fine-detuning is ranged between -100 cents and +100 cents. This is useful for creating "fat" sounds. + Con este botón pódese indicar a desafinación fina do oscilador %1 pola canle esquerda. A desafinación fina ten como intervalo -100 cents e +100 cents. Isto é útil para crear sons «gordos». + + + Osc %1 fine detuning right: + Desafinación fina dereita do oscilador %1: + + + With this knob you can set the fine detuning of oscillator %1 for the right channel. The fine-detuning is ranged between -100 cents and +100 cents. This is useful for creating "fat" sounds. + Con este botón pódese indicar a desafinación fina do oscilador %1 pola canle dereita. A desafinación fina ten como intervalo -100 cents e +100 cents. Isto é útil para crear sons «gordos». + + + Osc %1 phase-offset: + Desprazamento da fase do oscilador %1: + + + degrees + graos + + + With this knob you can set the phase-offset of oscillator %1. That means you can move the point within an oscillation where the oscillator begins to oscillate. For example if you have a sine-wave and have a phase-offset of 180 degrees the wave will first go down. It's the same with a square-wave. + Con este botón pódese indicar o desprazamento de fase do oscilador %1. Iso significa que se pode mover o punto dunha oscilación no que o oscilador comeza a oscilar. Por exemplo, se se ten unha onda senoidal e un desprazamento de fase de 180 graos, a onda vai primeiro para abaixo. O mesmo acontece cunha onda cadrada. + + + Osc %1 stereo phase-detuning: + Desafinación de fase en estéreo do oscilador %1: + + + With this knob you can set the stereo phase-detuning of oscillator %1. The stereo phase-detuning specifies the size of the difference between the phase-offset of left and right channel. This is very good for creating wide stereo sounds. + Conte este botón pódese indicar a desafinación de fase en estéreo do oscilador %1. A desafinación de fase en estéreo indica o tamaño da diferenza entre o desprazamento de fase das canles esquerda e dereita. Isto é moi bon para crear sons con estéreo amplo. + + + Use a sine-wave for current oscillator. + Empregar unha onda senoidal para este oscilador. + + + Use a triangle-wave for current oscillator. + Empregar unha onda triangular para este oscilador. + + + Use a saw-wave for current oscillator. + Empregar unha onda de dente de serra para este oscilador. + + + Use a square-wave for current oscillator. + Empregar unha onda cadrada para este oscilador. + + + Use a moog-like saw-wave for current oscillator. + Empregar unha onda de dente de serra tipo Moog para este oscilador. + + + Use an exponential wave for current oscillator. + Empregar unha onda exponencial para este oscilador. + + + Use white-noise for current oscillator. + Empregar ruído branco para este oscilador. + + + Use a user-defined waveform for current oscillator. + Empregar unha forma de onda predefinida para este oscilador. + + + + VestigeInstrumentView + + Open other VST-plugin + Abrir outro engadido de VST + + + Click here, if you want to open another VST-plugin. After clicking on this button, a file-open-dialog appears and you can select your file. + Prema aquí se desexa abrir outro engadido de VST. Ao premer este botón aparece un diálogo para abrir ficheiros no que se pode escoller o ficheiro. + + + Show/hide GUI + Mostrar/Agochar a interface gráfica + + + Click here to show or hide the graphical user interface (GUI) of your VST-plugin. + Prema aquí para mostrar ou agochar a interface gráfica de usuario do engadido de VST. + + + Turn off all notes + Apagar todas as notas + + + Open VST-plugin + Abrir o engadido de VST + + + DLL-files (*.dll) + Ficheiros DLL (.*dll) + + + EXE-files (*.exe) + Ficheiros EXE (*.exe) + + + No VST-plugin loaded + Non se cargou ningún engadido de VST + + + Control VST-plugin from LMMS host + + + + Click here, if you want to control VST-plugin from host. + + + + Open VST-plugin preset + + + + Click here, if you want to open another *.fxp, *.fxb VST-plugin preset. + + + + Previous (-) + + + + Click here, if you want to switch to another VST-plugin preset program. + + + + Save preset + Gardar as predefinicións + + + Click here, if you want to save current VST-plugin preset program. + + + + Next (+) + + + + Click here to select presets that are currently loaded in VST. + + + + Preset + Predefinición + + + by + + + + - VST plugin control + + + + + VstEffectControlDialog + + Show/hide + + + + Control VST-plugin from LMMS host + + + + Click here, if you want to control VST-plugin from host. + + + + Open VST-plugin preset + + + + Click here, if you want to open another *.fxp, *.fxb VST-plugin preset. + + + + Previous (-) + + + + Click here, if you want to switch to another VST-plugin preset program. + + + + Next (+) + + + + Click here to select presets that are currently loaded in VST. + + + + Save preset + Gardar as predefinicións + + + Click here, if you want to save current VST-plugin preset program. + + + + Effect by: + + + + &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> + + + + + VstPlugin + + Loading plugin + A cargar un engadido + + + Please wait while loading VST-plugin... + Agarde mentres se carga o engadido de VST... + + + Failed loading VST-plugin + Fallou a carga do engadido de VST + + + The VST-plugin %1 could not be loaded for some reason. +If it runs with other VST-software under Linux, please contact an LMMS-developer! + Non foi posíbel cargar o engadido de VST %1 por algunha razón. +Se funciona con outro software de VST en Linux, contacte cun desenvolvedor do LMMS! + + + Open Preset + + + + Vst Plugin Preset (*.fxp *.fxb) + + + + : default + + + + " + + + + ' + + + + Save Preset + + + + .fxp + + + + .FXP + + + + .FXB + + + + .fxb + + + + + ZynAddSubFxInstrument + + Portamento + + + + Filter Frequency + + + + Filter Resonance + + + + Bandwidth + + + + FM Gain + + + + Resonance Center Frequency + + + + Resonance Bandwidth + + + + Forward MIDI Control Change Events + + + + + ZynAddSubFxView + + Show GUI + Mostrar a interface gráfica + + + Click here to show or hide the graphical user interface (GUI) of ZynAddSubFX. + Prema aquí para mostrar ou agochar a interface gráfica deusuario de ZynAddSubFX. + + + Portamento: + + + + PORT + + + + Filter Frequency: + + + + FREQ + FREQ + + + Filter Resonance: + + + + RES + RES + + + Bandwidth: + + + + BW + + + + FM Gain: + + + + FM GAIN + + + + Resonance center frequency: + + + + RES CF + + + + Resonance bandwidth: + + + + RES BW + + + + Forward MIDI Control Changes + + + + + audioFileProcessor + + Amplify + Amplificar + + + Start of sample + Inicio da mostra + + + End of sample + Final da mostra + + + Reverse sample + Inverter a mostra + + + Loop + Bucle + + + + bassBoosterControlDialog + + FREQ + FREQ + + + Frequency: + Frecuencia: + + + GAIN + GAIN + + + Gain: + Ganancia + + + RATIO + TAXA + + + Ratio: + Taxa: + + + + bassBoosterControls + + Frequency + Frecuencia + + + Gain + Ganancia + + + Ratio + Taxa + + + + bbEditor + + Beat+Bassline Editor + Editor de ritmos e liña do baixo + + + Play/pause current beat/bassline (Space) + Reproducir/Deter o ritmo/a liña do baixo actual (Espazo) + + + Add beat/bassline + Engadir un ritmo/liña do baixo + + + Add automation-track + Engadir unha pista de automatización + + + Stop playback of current beat/bassline (Space) + Parar a reprodución do ritmo/da liña do baixo actual (Espazo) + + + Click here to play the current beat/bassline. The beat/bassline is automatically looped when its end is reached. + Prema aquí para reproducir o ritmo/liña do baixo actual. O ritmo/liña do baixo repítese en bucle automaticamente ao chegar ao final. + + + Click here to stop playing of current beat/bassline. + Prema aquí para parar a reprodución do ritmo/liña do baixo actual. + + + + bbTCOView + + Open in Beat+Bassline-Editor + Abrir no editor de ritmos e liña do baixo + + + Reset name + Restaurar o nome + + + Change name + Mudar o nome + + + Change color + Mudar a cor + + + + bbTrack + + Beat/Bassline %1 + Ritmo/Liña do baixo %1 + + + Clone of %1 + + + + + bitInvader + + Samplelength + + + + + bitInvaderView + + Sample Length + Lonxitude da mostra + + + Sine wave + Onda senoidal + + + Triangle wave + Onda triangular + + + Saw wave + Onda de dente de serra + + + Square wave + Onda cadrada + + + White noise wave + Onda de ruído branco + + + User defined wave + Onda definida polo usuario + + + Smooth + Suave + + + Click here to smooth waveform. + Prema aquí para unha forma de onda suave. + + + Interpolation + Interpolación + + + Normalize + Normalizar + + + Draw your own waveform here by dragging your mouse on this graph. + Debuxe aquí a súa propia forma de onda arrastrando o rato polo gráfico. + + + Click for a sine-wave. + Prema para unha onda senoidal. + + + Click here for a triangle-wave. + Prema aquí para unha onda triangular. + + + Click here for a saw-wave. + Prema aquí para unha onda de dente de serra. + + + Click here for a square-wave. + Prema aquí para unha onda cadrada. + + + Click here for white-noise. + Prema aquí para ruído branco. + + + Click here for a user-defined shape. + Prema aquí para unha forma definida polo usuario. + + + + exportProjectDialog + + Export project to %1 + Exportar o proxecto a %1 + + + Error + Erro + + + Error while determining file-encoder device. Please try to choose a different output format. + Produciuse un erro ao determinar o dispositivo codificador do ficheiro. Tente escollendo un formato de saída diferente. + + + Rendering: %1% + A renderizar: %1% + + + Could not open file + Non foi posíbel abrir o ficheiro + + + Could not open file %1 for writing. +Please make sure you have write-permission to the file and the directory containing the file and try again! + Non foi posíbel abrir o ficheiro %1 para escribir nel. +Asegúrese de ter permisos sobre o ficheiro e o directorio que o contén e tente de novo! + + + + fader + + Please enter a new value between %1 and %2: + Introduza un valor novo entre %1 e %2: + + + + fileBrowser + + Browser + + + + + fileBrowserTreeWidget + + Send to active instrument-track + + + + Open in new instrument-track/Song-Editor + + + + Open in new instrument-track/B+B Editor + + + + Loading sample + + + + Please wait, loading sample for preview... + + + + --- Factory files --- + + + + + graphModel + + Graph + Gráfico + + + + kickerInstrument + + Start frequency + Frecuencia inicial + + + End frequency + Frecuencia final + + + Decay + Decaemento + + + Distortion + Distorsión + + + Gain + Ganancia + + + + kickerInstrumentView + + Start frequency: + Frecuencia inicial: + + + End frequency: + Frecuencia final: + + + Decay: + Decaemento: + + + Distortion: + Distorsión: + + + Gain: + Ganancia: + + + + knob + + &Help + &Axuda + + + Please enter a new value between -96.0 dBV and 6.0 dBV: + Introduza un valor novo entre -96,0 dBV e 6,0 dBV: + + + Please enter a new value between %1 and %2: + Introduza un valor novo entre %1 e %2: + + + + ladspaBrowserView + + Available Effects + Efectos dispoñíbeis + + + Unavailable Effects + Efectos non dispoñíbeis + + + Instruments + Instrumentos + + + Analysis Tools + Ferramentas de análise + + + Don't know + Non sei + + + This dialog displays information on all of the LADSPA plugins LMMS was able to locate. The plugins are divided into five categories based upon an interpretation of the port types and names. + +Available Effects are those that can be used by LMMS. In order for LMMS to be able to use an effect, it must, first and foremost, be an effect, which is to say, it has to have both input channels and output channels. LMMS identifies an input channel as an audio rate port containing 'in' in the name. Output channels are identified by the letters 'out'. Furthermore, the effect must have the same number of inputs and outputs and be real time capable. + +Unavailable Effects are those that were identified as effects, but either didn't have the same number of input and output channels or weren't real time capable. + +Instruments are plugins for which only output channels were identified. + +Analysis Tools are plugins for which only input channels were identified. + +Don't Knows are plugins for which no input or output channels were identified. + +Double clicking any of the plugins will bring up information on the ports. + Este diálogo mostra información sobre todos os engadidos de LADSPA que o LMMS deu localizado. Os engadidos divídense en cinco categorías baseándose nunha interpretación dos tipos e nomes dos portos. + +Os efectos dispoñíbeis son os que se poden empregar co LMMS. Para que o LMMS poida utilizar un efecto ten que ser, en primeiro lugar e o máis importante, un efecto, o que quere dicir que ten que ter tanto canles de entrada como canles de saída. O LMMS identifica unha canle de entrada como un porto de taxa de son que conteña «in» no nome. As canles de saída identifícanse coas letras «out». Alén disto, o efecto ten que ter o mesmo número de entradas que de saídas e ter capacidade de tempo real. + +Os efectos non dispoñíbeis son os que foron identificados como efectos mais que non tiñan o mesmo número de canles de entrada que de saída ou non tiñan capacidade de tempo real. + +Os instrumentos son engadidos nos que só se identificaron canles de saída +. +As ferramentas de análise son engadidos nos que só se identificaron canles de entrada. + +Os «non sei» son engadidos nos que non se identificaron canles de entrada nin de saída. + +Facendo duplo clic sobre calquera dos engadidos mostra información sobre os portos. + + + Type: + Tipo: + + + + ladspaDescription + + Plugins + Engadidos + + + Description + Descrición + + + + ladspaPortDialog + + Ports + Portos + + + Name + Nome + + + Rate + Taxa + + + Direction + Dirección + + + Type + Tipo + + + Min < Default < Max + Mín < Predefinido < Máx + + + Logarithmic + Logarítmica + + + SR Dependent + Dependente de SR + + + Audio + Son + + + Control + Control + + + Input + Entrada + + + Output + Saída + + + Toggled + Conmutado + + + Integer + Enteiro + + + Float + Vírgula flutuante + + + Yes + Si + + + + lb302Synth + + VCF Cutoff Frequency + Frecuencia de corte do VCF + + + VCF Resonance + Resonancia do VCF + + + VCF Envelope Mod + Modo de envolvente do VCF + + + VCF Envelope Decay + Decaemento da envolvente do VCF + + + Distortion + Distorsión + + + Waveform + Forma da onda + + + Slide Decay + Decamento ao escorregar + + + Slide + Escorregar + + + Accent + Acento + + + Dead + Morte + + + 24dB/oct Filter + Filtro de 24dB/oitava + + + + lb302SynthView + + Cutoff Freq: + Frec. de corte: + + + CUT + CORTE + + + Resonance: + Resonancia: + + + RES + RES + + + Env Mod: + Mod env: + + + ENV MOD + MOD ENV + + + Decay: + Decaemento: + + + DEC + DEC + + + 303-es-que, 24dB/octave, 3 pole filter + Filtro tipo 303, 24dB/oitava, 3 polos + + + Slide Decay: + Decamento ao escorregar: + + + SLIDE + Escorregar + + + DIST: + DIST: + + + DIST + DIST + + + Saw wave + Onda de dente de serra + + + Click here for a saw-wave. + Prema aquí para unha onda de dente de serra. + + + Triangle wave + Onda triangular + + + Click here for a triangle-wave. + Prema aquí para unha onda triangular. + + + Square wave + Onda cadrada + + + Click here for a square-wave. + Prema aquí para unha onda cadrada. + + + Rounded square wave + Onda cadrada arredondada + + + Click here for a square-wave with a rounded end. + Prema aquí para unha onda cadrada con final arredondado. + + + Moog wave + Onda tipo Moog + + + Click here for a moog-like wave. + Prema aquí para unha onda tipo Moog. + + + Sine wave + Onda senoidal + + + Click for a sine-wave. + Prema para unha onda senoidal. + + + White noise wave + Onda de ruído branco + + + Click here for an exponential wave. + Prema aquí para unha onda exponencial. + + + Click here for white-noise. + Prema aquí para ruído branco. + + + + lb303Synth + + VCF Cutoff Frequency + Frecuencia de corte do VCF + + + VCF Resonance + Resonancia do VCF + + + VCF Envelope Mod + Modo de envolvente do VCF + + + VCF Envelope Decay + Decaemento da envolvente do VCF + + + Distortion + Distorsión + + + Waveform + Forma da onda + + + Slide Decay + Decamento ao escorregar + + + Slide + Escorregar + + + Accent + Acento + + + Dead + Morte + + + 24dB/oct Filter + Filtro de 24dB/oitava + + + + lb303SynthView + + Cutoff Freq: + Frec. de corte: + + + CUT + CORTE + + + Resonance: + Resonancia: + + + RES + RES + + + Env Mod: + Mod env: + + + ENV MOD + MOD ENV + + + Decay: + Decaemento: + + + DEC + DEC + + + 303-es-que, 24dB/octave, 3 pole filter + Filtro tipo 303, 24dB/oitava, 3 polos + + + Slide Decay: + Decamento ao escorregar: + + + SLIDE + Escorregar + + + DIST: + DIST: + + + DIST + DIST + + + WAVE: + ONDA: + + + WAVE + ONDA + + + + malletsInstrument + + Hardness + Dureza + + + Position + Posición + + + Vibrato Gain + Ganancia do vibrato + + + Vibrato Freq + Frecuencia do vibrato + + + Stick Mix + Mestura de paus + + + Modulator + Modulador + + + Crossfade + Transición por esvaecemento + + + LFO Speed + Velocidade do LFO + + + LFO Depth + Profundidade do LFO + + + ADSR + ADSR + + + Pressure + Presión + + + Motion + Movemento + + + Speed + Velocidade + + + Bowed + Con arco + + + Spread + Propagar + + + Marimba + Marimba + + + Vibraphone + Vibráfono + + + Agogo + Agogó + + + Wood1 + Madeira1 + + + Reso + + + + Wood2 + Madeira2 + + + Beats + Golpes + + + Two Fixed + Dous fixos + + + Clump + Esmagar + + + Tubular Bells + Campás tubulares + + + Uniform Bar + Lámina uniforme + + + Tuned Bar + Lámina afinada + + + Glass + Vidro + + + Tibetan Bowl + Cunca tibetana + + + Missing files + Faltan ficheiros + + + Your Stk-installation seems to be incomplete. Please make sure the full Stk-package is installed! + A instalación de Stk semella estar incompleta. Asegúrese de que o paquete Stk completo está instalado! + + + + malletsInstrumentView + + Instrument + Instrumento + + + Spread + Propagar + + + Spread: + Propagar: + + + Hardness + Dureza + + + Hardness: + Dureza: + + + Position + Posición + + + Position: + Posición: + + + Vib Gain + Gan. vib. + + + Vib Gain: + Gan. vib.: + + + Vib Freq + Frec. vib.: + + + Vib Freq: + Frec. vib.: + + + Stick Mix + Mestura de paus + + + Stick Mix: + Mestura de paus: + + + Modulator + Modulador + + + Modulator: + Modulador: + + + Crossfade + Transición por esvaecemento + + + Crossfade: + Transición por esvaecemento: + + + LFO Speed + Velocidade do LFO + + + LFO Speed: + Velocidade do LFO: + + + LFO Depth + Profundidade do LFO + + + LFO Depth: + Profundidade do LFO: + + + ADSR + ADSR + + + ADSR: + ADSR: + + + Bowed + Con arco + + + Pressure + Presión + + + Pressure: + Presión: + + + Motion + Movemento + + + Motion: + Movemento: + + + Speed + Velocidade + + + Speed: + Velocidade: + + + Vibrato + Vibrato + + + Vibrato: + Vibrato: + + + + manageVSTEffectView + + - VST parameter control + + + + VST Sync + + + + Click here if you want to synchronize all parameters with VST plugin. + + + + Automated + + + + Click here if you want to display automated parameters only. + + + + Close + + + + Close VST effect knob-controller window. + + + + + manageVestigeInstrumentView + + - VST plugin control + + + + VST Sync + + + + Click here if you want to synchronize all parameters with VST plugin. + + + + Automated + + + + Click here if you want to display automated parameters only. + + + + Close + + + + Close VST plugin knob-controller window. + + + + + nineButtonSelector + + &Help + &Axuda + + + + organicInstrument + + Distortion + Distorsión + + + Volume + Volume + + + + organicInstrumentView + + Distortion: + Distorsión: + + + Volume: + Volume: + + + Randomise + Aleatorio + + + Osc %1 waveform: + Forma de onda do osciloscopio %1: + + + Osc %1 volume: + Volume do oscilador %1: + + + Osc %1 panning: + Panorámica do oscilador %1: + + + Osc %1 fine detuning left: + Desafinación fina esquerda do oscilador %1: + + + cents + cents + + + + papuInstrument + + Sweep time + Tempo da varredura + + + Sweep direction + Dirección da varredura + + + Sweep RtShift amount + Cantidade de Cambio de varredura + + + Wave Pattern Duty + Padrón de onda + + + Channel 1 volume + Volume da canle 1 + + + Volume sweep direction + Dirección da varredura do volume + + + Length of each step in sweep + Lonxitude de cada paso en varredura + + + Channel 2 volume + Volume da canle 2 + + + Channel 3 volume + Volume da canle 3 + + + Channel 4 volume + Volume da canle 4 + + + Right Output level + Nivel da saída dereita + + + Left Output level + Nivel da saída esquerda + + + Channel 1 to SO2 (Left) + Canle 1 a SO1 (Esquerda) + + + Channel 2 to SO2 (Left) + Canle 2 a SO2 (Esquerda) + + + Channel 3 to SO2 (Left) + Canle 3 a SO2 (Esquerda) + + + Channel 4 to SO2 (Left) + Canle 4 a SO2 (Esquerda) + + + Channel 1 to SO1 (Right) + Canle 1 a SO1 (Dereita) + + + Channel 2 to SO1 (Right) + Canle 2 a SO1 (Dereita) + + + Channel 3 to SO1 (Right) + Canle 3 a SO1 (Dereita) + + + Channel 4 to SO1 (Right) + Canle 4 a SO1 (Dereita) + + + Treble + Agudos + + + Bass + Graves + + + Shift Register width + Cambiar a largura do rexistro + + + + papuInstrumentView + + Sweep Time: + Tempo da varredura: + + + Sweep Time + Tempo da varredura + + + Sweep RtShift amount: + Cantidade de cambio de varredura: + + + Sweep RtShift amount + Cantidade de cambio de varredura + + + Wave pattern duty: + Padrón de onda de deber: + + + Wave Pattern Duty + Padrón de onda de deber + + + Square Channel 1 Volume: + Volume da canle cadrada 1: + + + Length of each step in sweep: + Lonxitude de cada paso en varredura: + + + Length of each step in sweep + Lonxitude de cada paso en varredura + + + Wave pattern duty + Padrón de onda de deber + + + Square Channel 2 Volume: + Volume da canle cadrada 2: + + + Square Channel 2 Volume + Volume da canle cadrada 2 + + + Wave Channel Volume: + Volume da canle de ondas: + + + Wave Channel Volume + Volume da canle de ondas + + + Noise Channel Volume: + Volume da canle de ruído: + + + Noise Channel Volume + Volume da canle de ruído + + + SO1 Volume (Right): + Volume de SO1 (Dereita): + + + SO1 Volume (Right) + Volume de SO1 (Dereita) + + + SO2 Volume (Left): + Volume de SO2 (Esquerda): + + + SO2 Volume (Left) + Volume de SO2 (Esquerda) + + + Treble: + Agudos: + + + Treble + Agudos + + + Bass: + Graves: + + + Bass + Graves + + + Sweep Direction + Dirección da varredura + + + Volume Sweep Direction + Dirección da varredura do volume + + + Shift Register Width + Cambiar a largura do rexistro + + + Channel1 to SO1 (Right) + Canle 1 a SO1 (Dereita) + + + Channel2 to SO1 (Right) + Canle 1 a SO1 (Dereita) + + + Channel3 to SO1 (Right) + Canle 3 a SO1 (Dereita) + + + Channel4 to SO1 (Right) + Canle 4 a SO1 (Dereita) + + + Channel1 to SO2 (Left) + Canle 1 a SO2 (Esquerda) + + + Channel2 to SO2 (Left) + Canle 2 a SO2 (Esquerda) + + + Channel3 to SO2 (Left) + Canle 3 a SO2 (Esquerda) + + + Channel4 to SO2 (Left) + Canle 4 a SO2 (Esquerda) + + + Wave Pattern + Padrón de onda + + + The amount of increase or decrease in frequency + A cantidade de aumento ou redución da frecuencia + + + The rate at which increase or decrease in frequency occurs + A taxa á que se produce o aumento ou a redución da frecuencia + + + The duty cycle is the ratio of the duration (time) that a signal is ON versus the total period of the signal. + O ciclo de deber é a relación entre a duración (tempo) durante a que un sinal está ACTIVO fronte ao período total do sinal. + + + Square Channel 1 Volume + Volume da canle cadrada 1 + + + The delay between step change + A demora entre cambios de paso + + + Draw the wave here + Debuxe a onda aquí + + + + pattern + + Cannot freeze pattern + Non é posíbel conxelar o padrón + + + The pattern currently cannot be freezed because you're in play-mode. Please stop and try again! + Non é posíbel conxelar agora o padrón porque estamos no modo de reprodución. Pare e ténteo de novo! + + + + patternFreezeStatusDialog + + Freezing pattern... + A conxelar o padrón... + + + Cancel + Cancelar + + + + patternView + + double-click to open this pattern in piano-roll +use mouse wheel to set volume of a step + faga duplo clic para abrir este padrón na pianola +empregue a roda do rato para modificar o volume un paso + + + Open in piano-roll + Abrir na pianola + + + Clear all notes + Limpar todas as notas + + + Reset name + Restaurar o nome + + + Change name + Mudar o nome + + + Refreeze + Reconxelar + + + Freeze + Conxelar + + + Unfreeze + Desconxelar + + + Add steps + Engadir pasos + + + Remove steps + Eliminar pasos + + + 1 step + 1 paso + + + %1 steps + %1 pasos + + + + pianoRoll + + Play/pause current pattern (Space) + Reproducir/Deter o padrón actual (Espazo) + + + Stop playing of current pattern (Space) + Parar a execución do padrón actual (Espazo) + + + Cut selected notes (Ctrl+X) + Recortar as notas escollidas (Ctrl+X) + + + Copy selected notes (Ctrl+C) + Copiar as notas escollidas (Ctrl+C) + + + Paste notes from clipboard (Ctrl+V) + Apegar as notas do porta-retallos (Ctrl+V) + + + Piano-Roll - no pattern + Pianola - non hai ningún padrón + + + Piano-Roll - %1 + Pianola - %1 + + + Please open a pattern by double-clicking on it! + Abra un padrón facendo duplo clic nel! + + + Record notes from MIDI-device/channel-piano + Gravar notas dun dispositivo MIDI/piano de canle + + + Record notes from MIDI-device/channel-piano while playing song or BB track + Gravar notas dun dispositivo MIDI/piano de canle mentres se reproduce a canción ou pista de ritmos/liña do baixo + + + Draw mode (Shift+D) + Modo de debuxo (Maiúsculas+D) + + + Erase mode (Shift+E) + Modo de borrado (Maiúsculas+E) + + + Select mode (Shift+S) + Modo de selección (Maiúscula+S) + + + Last note + Última nota + + + Click here to play the current pattern. This is useful while editing it. The pattern is automatically looped when its end is reached. + Prema aquí para reproducir este padrón. Isto é útil mentres se edita. O padrón repítese en bucle automaticamente ao chegar ao final. + + + Click here to record notes from a MIDI-device or the virtual test-piano of the according channel-window to the current pattern. When recording all notes you play will be written to this pattern and you can play and edit them afterwards. + Prema aquí para gravar notas desde un dispositivo MIDI ou desde o piano de proba virtual da xanela da canle correspondente no padrón actual. As notas tocadas ao gravar escríbense neste padrón e despois pódense editar. + + + Click here to record notes from a MIDI-device or the virtual test-piano of the according channel-window to the current pattern. When recording all notes you play will be written to this pattern and you will hear the song or BB track in the background. + Prema aquí para gravar notas desde un dispositivo MIDI ou desde o piano de proba virtual da xanela da canle correspondente no padrón actual. As notas tocadas ao gravar escríbense neste padrón e escóitase a canción ou pista de ritmos/liña do baixo no fondo. + + + Click here to stop playback of current pattern. + Prema aquí para parar a reprodución deste padrón. + + + Click here and the selected notes will be cut into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Prema aquí e os valores escollidos recórtanse e van para o porta-retallos. Pódeos apegar en calquera lugar de calquera padrón premendo o botón de apegar. + + + Click here and the selected notes will be copied into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Prema aquí e os valores escollidos cópianse no porta-retallos. Pódeos apegar en calquera lugar de calquera padrón premendo o botón de apegar. + + + Click here and the notes from the clipboard will be pasted at the first visible measure. + Prema aquí e os valores do porta-retallos apegaranse no primeiro compás visíbel. + + + Note lock + Bloqueo de notas + + + Note Volume + Volume das notas + + + Note Panning + Panormámica das notas + + + Detune mode (Shift+T) + Modo de desafinación (Maiúsculas+T) + + + Click here and draw mode will be activated. In this mode you can add, resize and move notes. This is the default mode which is used most of the time. You can also press 'Shift+D' on your keyboard to activate this mode. In this mode, hold Ctrl to temporarily go into select mode. + Prema aquí e o activarase o modo de debuxo. Neste modo pode engadir e mover valores individuais. Este é o modo por omisión que se emprega a maior parte do tempo. Tamén pode premer «Maiúsculas+D» no teclado para activar este modo. Neste modo, manteña Ctrl para ir temporalmente ao modo de selección. + + + Click here and erase mode will be activated. In this mode you can erase notes. You can also press 'Shift+E' on your keyboard to activate this mode. + Prema aquí e activarase o modo de borrado. Neste modo pódense borrar valores individuais. Tamén pode premer «Maiúsculas+E» no teclado para activar este modo. + + + Click here and select mode will be activated. In this mode you can select notes. Alternatively, you can hold Ctrl in draw mode to temporarily use select mode. + Prema aquí e activarase o modo de borrado. Neste modo pódense borrar valores individuais. Como alternativa pode premer Ctrl no modo de debuxo para empregar temporalmente o modo de selección. + + + Click here and detune mode will be activated. In this mode you can click a note to open its automation detuning. You can utilize this to slide notes from one to another. You can also press 'Shift+T' on your keyboard to activate this mode. + Prema aquí e actívase o modo de desafinación.Neste modo pódese premer unhanota para abrir a súa desafinación de automatización. Pódese empregar isto para escorregar entre as notas. Tamén se pode premer «Maiúsculas+T» no teclado para activar este mdo. + + + Mark/unmark current semitone + + + + Mark current scale + + + + Mark current chord + + + + Unmark all + + + + No scale + + + + No chord + + + + + pluginBrowser + + no description + sen descrición + + + Instrument plugins + Engadidos de instrumento + + + Incomplete monophonic imitation tb303 + Imitación monofónica incompleta tb303 + + + Plugin for freely manipulating stereo output + Engadido para manipular libremente a saída en estéreo + + + Plugin for controlling knobs with sound peaks + Engadido para controlar botóns con picos de son + + + Plugin for enhancing stereo separation of a stereo input file + Engadido para mellorar a separación en estéreo dun ficheiro de entrada en estéreo + + + List installed LADSPA plugins + Enumerar os engadidos de LADSPA instalados + + + three powerful oscillators you can modulate in several ways + tres potentes osciladores que se poden modular de varias maneiras + + + Filter for importing FL Studio projects into LMMS + Filtro para importar proxectos do FL Studio ao LMMS + + + versatile kick- & bassdrum-synthesizer + sintetizador de kick e tambor baixo versátil + + + GUS-compatible patch instrument + Instrumento de parcheo compatíbel con GUS + + + plugin for using arbitrary VST-effects inside LMMS. + engadido para empregar efectos de VST arbitrarios no LMMS. + + + Additive Synthesizer for organ-like sounds + Sintetizador aditivo para sons tipo órgano + + + plugin for boosting bass + engadido para potenciar os graves + + + Tuneful things to bang on + Cousas melodiosas nas que bater + + + simple sampler with various settings for using samples (e.g. drums) in an instrument-track + sampleador simple con varias opcións para empregar mostras (p.ex. batería) nunha pista de instrumento + + + VST-host for using VST(i)-plugins within LMMS + Hóspede de VST para empregar engadidos de VST(i) co LMMS + + + Vibrating string modeler + Modelador de cordas vibrantes + + + plugin for using arbitrary LADSPA-effects inside LMMS. + engadido para empregar efectos de LADSPA arbitrarios no LMMS. + + + Filter for importing MIDI-files into LMMS + Filtro para importar ficheiros MIDI ao LMMS + + + Instrument browser + Navegador de instrumentos + + + Drag an instrument into either the Song-Editor, the Beat+Bassline Editor or into an existing instrument track. + Arrastre un instrumento para o Editor de cancións, o Editor de ritmos+liña do baixo ou para unha pista de instrumento existente. + + + Emulation of the MOS6581 and MOS8580 SID. +This chip was used in the Commodore 64 computer. + Emulación dos SID MOS6581 e o MOS8580. +Este chip empregábase no computador Commodore 64. + + + Player for SoundFont files + Reprodutor de ficheiros SoundFont + + + Emulation of GameBoy (TM) APU + Emulación da APU da GameBoy (TM) + + + Customizable wavetable synthesizer + Sintetizador de táboa de ondas personalizábel + + + Embedded ZynAddSubFX + ZynAddSubFX incorporado + + + Filter for importing Hydrogen files into LMMS + + + + + projectNotes + + Project notes + Notas do proxecto + + + Put down your project notes here. + Anote aquí as súas notas sobre o proxecto. + + + Edit Actions + Editar as accións + + + &Undo + Desfa&cer + + + Ctrl+Z + Ctrl+Z + + + &Redo + &Refacer + + + Ctrl+Y + Ctrl+Y + + + &Copy + &Copiar + + + Ctrl+C + Ctrl+C + + + Cu&t + Cor&tar + + + Ctrl+X + Ctrl+X + + + &Paste + A&pegar + + + Ctrl+V + Ctrl+V + + + Format Actions + Accións de formato + + + &Bold + &Negrita + + + Ctrl+B + Ctrl+B + + + &Italic + Cursi&va + + + Ctrl+I + Ctrl+I + + + &Underline + S&ubliñado + + + Ctrl+U + Ctrl+U + + + &Left + &Esquerda + + + Ctrl+L + Ctrl+L + + + C&enter + C&entro + + + Ctrl+E + Ctrl+E + + + &Right + De&reita + + + Ctrl+R + Ctrl+R + + + &Justify + &Xustificar + + + Ctrl+J + Ctrl+J + + + &Color... + &Cor... + + + + renameDialog + + Rename... + Mudar o nome... + + + + sampleBuffer + + Open audio file + Abrir un ficheiro de son + + + Wave-Files (*.wav) + Ficheiros wave (*.wav) + + + OGG-Files (*.ogg) + Ficheiros OGG (*.ogg) + + + VOC-Files (*.voc) + Ficheiros VOC (*.voc) + + + AIFF-Files (*.aif *.aiff) + Ficheiros AIFF (*.aif *.aiff) + + + AU-Files (*.au) + Ficheiros AU (*.au) + + + RAW-Files (*.raw) + Ficheiros RAW (*.raw) + + + All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc *.aif *.aiff *.au *.raw *.mp3) + Todos os ficheiros de son (*.wav *.ogg *.ds *.flac *.spx *.voc *.aif *.aiff *.au *.raw *.mp3) + + + DrumSynth-Files (*.ds) + Ficheiros do DrumSynth (*.ds) + + + FLAC-Files (*.flac) + Ficheiros FLAC (*.flac) + + + SPEEX-Files (*.spx) + Ficheiros SPEEX (*.spx) + + + MP3-Files (*.mp3) + Ficheiros MP3 (*.mp3) + + + + sampleTCOView + + double-click to select sample + faga duplo clic para escoller unha mostra + + + Delete (middle mousebutton) + Eliminar (botón do medio do rato) + + + Cut + Recortar + + + Copy + Copiar + + + Paste + Apegar + + + Mute/unmute (<Ctrl> + middle click) + Silenciar/Darlle volume (<Ctrl> + botón central do rato) + + + Set/clear record + Indicar/Limpar a gravación + + + + sampleTrack + + Sample track + Pista de mostras + + + Volume + Volume + + + + sampleTrackView + + Track volume + Volume da pista + + + Channel volume: + Volume da canle: + + + VOL + VOL + + + + setupDialog + + Setup LMMS + Configuración do LMMS + + + General settings + Configuración xeral + + + BUFFER SIZE + TAMAÑO DO BÚFER + + + Reset to default-value + Restaurar o valor por omisión + + + MISC + DIVERSOS + + + Enable tooltips + Activar as axudiñas + + + Show restart warning after changing settings + Mostrar o aviso sobre o reinicio despois de cambiar a configuración + + + Display volume as dBV + Mostrar o volume como dBV + + + Compress project files per default + Comprimir os ficheiros dos proxectos por omisión + + + HQ-mode for output audio-device + Modo de calidade alta para o dispositivo de son de saída + + + LMMS working directory + Directorio de traballo do LMMS + + + VST-plugin directory + Directorio dos engadidos de VST + + + Artwork directory + Directorio do material gráfico + + + FL Studio installation directory + Directorio de instalación do FL Studio + + + STK rawwave directory + Directorio de ondas cruas de STK + + + Performance settings + Configuración do desempeño + + + UI effects vs. performance + Efectos da interface fronte a desempeño + + + Disable channel activity indicators + Desactivar os indicadores de actividade das canles + + + Only press keys on channel-piano manually + Só tocar as teclas no piano da canle manualmente + + + Audio settings + Configuración do son + + + AUDIO INTERFACE + INTERFACE DO SON + + + MIDI settings + Configuración do MIDI + + + MIDI INTERFACE + INTERFACE MIDI + + + OK + Aceptar + + + Cancel + Cancelar + + + Restart LMMS + Reiniciar o LMMS + + + Please note that most changes won't take effect until you restart LMMS! + Teña en conta que a maioría dos cambios non serán efectivos até que se reinicie o LMMS! + + + Frames: %1 +Latency: %2 ms + Cadencia: %1 +Latencia: %2 ms + + + Here you can setup the internal buffer-size used by LMMS. Smaller values result in a lower latency but also may cause unusable sound or bad performance, especially on older computers or systems with a non-realtime kernel. + Aquí pódese configurar o tamaño do búfer interno empregado polo LMMS. Valores máis pequenos resultan nunha latencia menor mais poden tamén causar son non usábel ou desempeño inadecuado, especialmente en computadores vellos ou en sistemas cun kernel que non sexa de tempo real. + + + Choose LMMS working directory + Escoller o directorio de traballo do LMMS + + + Choose your VST-plugin directory + Escoller o directorio dos engadidos de VST + + + Choose artwork-theme directory + Escoller o directorio do material gráfico + + + Choose FL Studio installation directory + Escoller o directorio de instalación do FL Studio + + + Choose LADSPA plugin directory + Escoller o directorio dos engadidos de LADSPA + + + Choose STK rawwave directory + Escoller o directorio de ondas cruas de STK + + + Here you can select your preferred audio-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, JACK, OSS and more. Below you see a box which offers controls to setup the selected audio-interface. + Aquí pódese escoller a interface de son preferida. Dependendo da configuración do sistema durante a compilación pódese escoller entre ALSA, JACK, OSS e máis. Embaixo vese unha caixa que oferece controles para configurar a interface de son escollida. + + + Here you can select your preferred MIDI-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, OSS and more. Below you see a box which offers controls to setup the selected MIDI-interface. + Aquí pódese escoller a interface de MIDI preferida. Dependendo da configuración do sistema durante a compilación pódese escoller entre ALSA, OSS e máis. Embaixo vese unha caixa que oferece controles para configurar a interface de MIDI escollida. + + + Paths + Rutas + + + LADSPA plugin paths + Rutas aos engadidos de LADSPA + + + Default Soundfont File + Ficheiro Soundfont por omisión + + + Background artwork + Gráficos do fondo + + + Choose default SoundFont + Escoller a SoundFont por omisión + + + Choose background artwork + Escoller os gráficos do fondo + + + One instrument track window mode + + + + Compact track buttons + + + + Sync VST plugins to host playback + + + + Smooth scroll in Song Editor + + + + Enable auto save feature + + + + Show playback cursor in AudioFileProcessor + + + + + sf2Instrument + + Bank + Banco + + + Patch + Parche + + + Gain + Ganancia + + + Reverb + Reverberación + + + Reverb Roomsize + Tamaño da sala de reverberación + + + Reverb Damping + Tampón de reverberación + + + Reverb Width + Largura da reverberación + + + Reverb Level + Nivel de reverberación + + + Chorus + Coro + + + Chorus Lines + Liñas do coro + + + Chorus Level + Nivel do coro + + + Chorus Speed + Velocidade do coro + + + Chorus Depth + Profundidade do coro + + + + sf2InstrumentView + + Open other SoundFont file + Abrir outro ficheiro de SoundFont + + + Click here to open another SF2 file + Prema aquí para abrir outro ficheiro de SF2 + + + Choose the patch + Escoller o parche + + + Gain + Ganancia + + + Apply reverb (if supported) + Aplicar reverberación (se o admitir) + + + This button enables the reverb effect. This is useful for cool effects, but only works on files that support it. + Este botón activa o efecto de reverberación. Isto é útil para efectos gaioleiros, mais só funciona cos ficheiros que o admiten. + + + Reverb Roomsize: + Tamaño da sala de reverberación: + + + Reverb Damping: + Tampón de reverberación: + + + Reverb Width: + Largura da reverberación: + + + Reverb Level: + Nivel de reverberación: + + + Apply chorus (if supported) + Aplicar un coro (se o admitir) + + + This button enables the chorus effect. This is useful for cool echo effects, but only works on files that support it. + Este botón activa o efecto de coro. Isto é útil para efectos de echo gaioleiros, mais só funciona cos ficheiros que o admiten. + + + Chorus Lines: + Liñas de coro: + + + Chorus Level: + Nivel do coro: + + + Chorus Speed: + Velocidade do coro: + + + Chorus Depth: + Profundidade do coro: + + + Open SoundFont file + Abrir un ficheiro de SoundFont + + + SoundFont2 Files (*.sf2) + Ficheiros de SoundFont2 (*.sf2) + + + + sidInstrument + + Cutoff + Corte + + + Resonance + Resonancia + + + Filter type + Tipo de filtro + + + Voice 3 off + Voz 3 apagada + + + Volume + Volume + + + Chip model + Modelo de chip + + + + sidInstrumentView + + Volume: + Volume: + + + Resonance: + Resonancia: + + + Cutoff frequency: + Frecuencia de corte: + + + High-Pass filter + Filtro pasa-alta + + + Band-Pass filter + Filtro pasa-faixa + + + Low-Pass filter + Filtro pasa-baixa + + + Voice3 Off + Voz3 apagada + + + MOS6581 SID + SID MOS6581 + + + MOS8580 SID + SID MOS6580 + + + Attack: + Ataque: + + + Attack rate determines how rapidly the output of Voice %1 rises from zero to peak amplitude. + A taxa de ataque determina como de rápido sobe a Voz %1 desde cero á amplitude de pico. + + + Decay: + Decaemento: + + + Decay rate determines how rapidly the output falls from the peak amplitude to the selected Sustain level. + A taxa de decaemento determina como de rápido cae a saída desde a amplitude de pico ao nivel de Sustentación escollido. + + + Sustain: + Sustentación: + + + Output of Voice %1 will remain at the selected Sustain amplitude as long as the note is held. + A saída da voz %1 fica na amplitude de sustentación indicada mentres se manteña a nota. + + + Release: + Relaxamento: + + + The output of of Voice %1 will fall from Sustain amplitude to zero amplitude at the selected Release rate. + A saída da voz %1 cae desde a amplitude de sustentación até cero na relación de relaxamento escollida. + + + Pulse Width: + Largura do pulso: + + + The Pulse Width resolution allows the width to be smoothly swept with no discernable stepping. The Pulse waveform on Oscillator %1 must be selected to have any audible effect. + A resolución da largura do pulso permite varrer a largura suavemente sen que sexa posíbel discernir os pasos. Hai que escoller a forma de onda pulso no oscilador %1 para que se ouza un efecto audíbel. + + + Coarse: + Crú: + + + The Coarse detuning allows to detune Voice %1 one octave up or down. + A desafinación crúa permite desafinar a voz %1 unha oitava para arriba ou para abaixo. + + + Pulse Wave + Onda de pulso + + + Triangle Wave + Onda triangular + + + SawTooth + Dente de serra + + + Noise + Ruído + + + Sync + Sincronizar + + + Sync synchronizes the fundamental frequency of Oscillator %1 with the fundamental frequency of Oscillator %2 producing "Hard Sync" effects. + Sincroniza a frecuencia fundamental do oscilador %1 coa frecuencia fundamental do oscilador %2, producindo efectos de «sincronización dura». + + + Ring-Mod + Modo anel + + + Ring-mod replaces the Triangle Waveform output of Oscillator %1 with a "Ring Modulated" combination of Oscillators %1 and %2. + O modo anel substitúe a saída da onda de forma triangular do oscilador %1 cunha combinación «modulada en anel» dos osciladores %1 e %2. + + + Filtered + Filtrado + + + When Filtered is on, Voice %1 will be processed through the Filter. When Filtered is off, Voice %1 appears directly at the output, and the Filter has no effect on it. + Cando Filtrado está activado, a voz %1 procésase a través do filtro. Cando Filtrado está desactivado, a voz %1 aparece directamente na saída e o filtro non ten ningún efecto sobre ela. + + + Test + Proba + + + Test, when set, resets and locks Oscillator %1 at zero until Test is turned off. + Proba, cando escollido, restaura e bloquea o oscilador %1 a cero até que remate a proba. + + + + song + + Tempo + Tempo + + + Master volume + Volume global + + + Master pitch + Altura global + + + Project saved + Proxecto gravado + + + The project %1 is now saved. + O proxecto %1 xa está gravado. + + + Project NOT saved. + O proxecto NON está gravado. + + + The project %1 was not saved! + O proxecto %1 no nestá gravado! + + + Import file + Importar un ficheiro + + + untitled + sen título + + + Select file for project-export... + Escolla o ficheiro para a exportación do proxecto... + + + Empty project + Proxecto baleiro + + + This project is empty so exporting makes no sense. Please put some items into Song Editor first! + Este proxecto está baleiro, polo que exportalo non ten xeito. Poña algo primeiro no Editor de cancións! + + + MIDI sequences + Secuencias de MIDI + + + FL Studio projects + Proxectos de FL Studio + + + All file types + Todos os tipos de ficheiro + + + Hydrogen projects + + + + Select directory for writing exported tracks... + + + + + songEditor + + Song-Editor + Editor de cancións + + + Play song (Space) + Reproducir unha canción (Espazo) + + + 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. + Prema aquí se desexa reproducir a canción enteira. A reprodución comeza no marcador de posición da canción (verde). Tamén se pode mover durante a reprodución. + + + Stop song (Space) + Parar a canción (Espazo) + + + Click here, if you want to stop playing of your song. The song-position-marker will be set to the start of your song. + Prema aquí se desexa parar a reprodución da canción. O marcador de posición da canción irá para o principio da canción. + + + Add beat/bassline + Engadir un ritmo/liña do baixo + + + Add sample-track + Engadir unha pista de mostra + + + Could not open file + Non foi posíbel abrir o ficheiro + + + Could not write file + Non foi posíbel escribir no ficheiro + + + Could not write file %1. You probably are not permitted to write to this file. +Please make sure you have write-access to the file and try again. + Non foi posíbel escribir no ficheiro %1. Probabelmente vostede non teña permiso para escribir neste ficheiro. Asegúrese de ter acceso de escrita ao ficheiro e ténteo de novo. + + + Add automation-track + Engaidr unha pista de automatización + + + Draw mode + Modo de debuxo + + + Edit mode (select and move) + Modo de edición (escoller e mover) + + + Record samples from Audio-device + Gravar mostras dun dispositivo de son + + + Record samples from Audio-device while playing song or BB track + Gravar mostras dun dispositivo de son mentres se reproduce a canción ou pista de ritmos/liña do baixo + + + Could not open file %1. You probably have no permissions to read this file. + Please make sure to have at least read permissions to the file and try again. + Non foi posíbel abrir o ficheiro %1. Probabelmente vostede non teña permiso para ler este ficheiro. Asegúrese de ter cando menos permiso para ler o ficheiro e ténteo de novo. + + + Error in file + Hai un erro no ficheiro + + + The file %1 seems to contain errors and therefore can't be loaded. + Parece que o ficheiro %1 contén erros e por iso non se pode cargar. + + + Tempo + Tempo + + + TEMPO/BPM + TEMPO/BPM + + + tempo of song + tempo da canción + + + 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). + O tempo dunha canción indícase en pulsos por minuto (BPM). Para cambiar o tempo da canción hai que cambiar este valor. Cada compás ten catro pulsos, polo que o tempo en BPM indica cantos compases / 4 hai que tocar nun minuto (ou cantos compases habería que tocar en catro minutos). + + + High quality mode + Modo de alta calidade + + + Master volume + Volume global + + + master volume + volume global + + + Master pitch + Altura global + + + master pitch + altura global + + + Value: %1% + Valor: %1% + + + Value: %1 semitones + Valor: %1 semitóns + + + + spectrumAnalyzerControlDialog + + Linear spectrum + Espectro linear + + + Linear Y axis + Eixo linear Y + + + + spectrumAnalyzerControls + + Linear spectrum + Espectro linear + + + Linear Y-axis + Eixo linear Y + + + Channel mode + Modo da canle + + + + stereoEnhancerControlDialog + + WIDE + LARGO + + + Width: + Largura: + + + + stereoEnhancerControls + + Width + Largura + + + + stereoMatrixControlDialog + + Left to Left Vol: + Volume esquerda para esquerda: + + + Left to Right Vol: + Volume esquerda para dereita: + + + Right to Left Vol: + Volume dereita para esquerda: + + + Right to Right Vol: + Volume dereita para dereita: + + + + stereoMatrixControls + + Left to Left + Esquerda para esquerda + + + Left to Right + Esquerda para dereita + + + Right to Left + Dereita para esquerda + + + Right to Right + Dereita para dereita + + + + timeLine + + Enable/disable auto-scrolling + Activar/Desactivar o desprazamento automático + + + Enable/disable loop-points + Activar/Desactivar os puntos de bucle + + + After stopping go back to begin + Despois de parar voltar ao principio + + + After stopping go back to position at which playing was started + Despois de parar voltar á posición na que se iniciou a reprodución + + + After stopping keep position + Despois de parar manter a posición + + + Hint + Suxestión + + + Press <Ctrl> to disable magnetic loop-points. + Prema <Ctrl> para desactivar os puntos de bucle magnéticos. + + + + track + + Muted + Silenciado + + + Solo + Solo + + + + trackContainer + + Couldn't import file + Non foi posíbel importar o ficheiro + + + Couldn't find a filter for importing file %1. +You should convert this file into a format supported by LMMS using another software. + Non foi posíbel atopar un filtro para importar o ficheiro %1. +Debería converter este ficheiro a un formato que o LMMS recoñeza usando outro software. + + + Couldn't open file + Non foi posíbel abrir o ficheiro + + + Couldn't open file %1 for reading. +Please make sure you have read-permission to the file and the directory containing the file and try again! + Non foi posíbel abrir o ficheiro %1 para lelo. +Asegúrese de ter permiso de lectura sobre o ficheiro e o directorio que o contén e tente de novo! + + + Loading project... + A cargar o proxecto... + + + Cancel + Cancelar + + + Please wait... + Agarde un anaco... + + + Importing FLP-file... + A importar un ficheiro FLP... + + + Importing MIDI-file... + A importar un ficheiro MIDI... + + + + trackContentObject + + Muted + Silenciado + + + + trackContentObjectView + + Current position + Posición actual + + + Hint + Suxestión + + + Press <Ctrl> and drag to make a copy. + Prema <Ctrl> e arrastre para facer unha copia. + + + Current length + Duración actual + + + Press <Ctrl> for free resizing. + Prema <Ctrl> para modificar o tamaño libremente + + + %1:%2 (%3:%4 to %5:%6) + %1:%2 (%3:%4 a %5:%6) + + + Delete (middle mousebutton) + Eliminar (botón do medio do rato) + + + Cut + Recortar + + + Copy + Copiar + + + Paste + Apegar + + + Mute/unmute (<Ctrl> + middle click) + Silenciar/Darlle volume (<Ctrl> + botón central do rato) + + + + trackOperationsWidget + + Press <Ctrl> while clicking on move-grip to begin a new drag'n'drop-action. + Prema <Ctrl> mentres ten a asa de mover premida para iniciar unha acción de arrastrar e soltar. + + + Actions for this track + Accións para esta pista + + + Mute + Silenciar + + + Mute this track + Silenciar esta pista + + + Solo + Solo + + + Clone this track + Clonar esta pista + + + Remove this track + Eliminar esta pista + + + + vestigeInstrument + + Loading plugin + A cargar un engadido + + + Please wait while loading VST-plugin... + Agarde mentres se carga o engadido de VST... + + + Failed loading VST-plugin + Fallou a carga do engadido de VST + + + The VST-plugin %1 could not be loaded for some reason. +If it runs with other VST-software under Linux, please contact an LMMS-developer! + Non foi posíbel cargar o engadido de VST %1 por algunha razón. +Se funciona con outro software de VST en Linux, contacte cun desenvolvedor do LMMS! + + + + vibed + + String %1 volume + Volume da corda %1 + + + String %1 stiffness + Tensión da corda %1 + + + Pick %1 position + Posición da púa %1 + + + Pickup %1 position + Posición do captador %1 + + + Pan %1 + Pan %1 + + + Detune %1 + Desafinar %1 + + + Fuzziness %1 + Difuso %1 + + + Length %1 + Lonxitude %1 + + + Impulse %1 + Impulso %1 + + + Octave %1 + Oitava %1 + + + + vibedView + + Volume: + Volume: + + + The 'V' knob sets the volume of the selected string. + O botón «V» indica o volume da corda escollida. + + + String stiffness: + Tensión da corda: + + + The 'S' knob sets the stiffness of the selected string. The stiffness of the string affects how long the string will ring out. The lower the setting, the longer the string will ring. + O botón «S» indica a tensión da corda escollida. A tensión da corda afecta ao tempo durante o que esta soa. Canto menor sexa, máis tempo ha de soar. + + + Pick position: + Posición da púa: + + + The 'P' knob sets the position where the selected string will be 'picked'. The lower the setting the closer the pick is to the bridge. + O botón «P» indica a posición na que se pulsa a corda escollida. Canto menor sexa, máis próxima estará da ponte. + + + Pickup position: + Posición do captador: + + + The 'PU' knob sets the position where the vibrations will be monitored for the selected string. The lower the setting, the closer the pickup is to the bridge. + O botón «PU» indica a posición desde a que se recollen as vibracións da corda escollida. Canto menor sexa, máis próximo será da ponte. + + + Pan: + Pan: + + + The Pan knob determines the location of the selected string in the stereo field. + O botón Pan determina o lugar que ocupa a cadea escollida no campo estéreo. + + + Detune: + Desafinar: + + + The Detune knob modifies the pitch of the selected string. Settings less than zero will cause the string to sound flat. Settings greater than zero will cause the string to sound sharp. + O botón Desafinar modifica a altura da corda escollida. Indicar menos de cero fai que a corda soe plana. Con máis de cero a corda soará viva. + + + Fuzziness: + Difuso: + + + The Slap knob adds a bit of fuzz to the selected string which is most apparent during the attack, though it can also be used to make the string sound more 'metallic'. + O botón Slap engádelle algo de distorsión tipo «fuzz» á corda escollida que se nota máis durante o ataque, aínda que tamén se pode usar para facer que a corda soe máis «metálica». + + + Length: + Lonxitude: + + + The Length knob sets the length of the selected string. Longer strings will both ring longer and sound brighter, however, they will also eat up more CPU cycles. + O botón Lonxitude indica a lonxitude da corda escollida. As cordas máis longas soan durante máis tempo e con máis brillo; porén, tamén consumen máis ciclos da CPU. + + + Impulse or initial state + Impulso ou estado inicial + + + The 'Imp' selector determines whether the waveform in the graph is to be treated as an impulse imparted to the string by the pick or the initial state of the string. + O selector «Imp» determina se hai que tratar a forma de onda do gráfico como un impulso impartido na corda pola púba ou o estado inicial da corda. + + + Octave + Oitava + + + The Octave selector is used to choose which harmonic of the note the string will ring at. For example, '-2' means the string will ring two octaves below the fundamental, 'F' means the string will ring at the fundamental, and '6' means the string will ring six octaves above the fundamental. + O selector Oitava emprégase para escoller con que harmónico da nota soa a corda. Por exemplo, «-2» significa que a corda soa dúas oitavas por debaixo da fundamental, «F» significa que a corda soa na fundamental e «6» significa que a cadea soa seix oitavas por riba da fundamental. + + + Impulse Editor + Editor de impulsos + + + The waveform editor provides control over the initial state or impulse that is used to start the string vibrating. The buttons to the right of the graph will initialize the waveform to the selected type. The '?' button will load a waveform from a file--only the first 128 samples will be loaded. + +The waveform can also be drawn in the graph. + +The 'S' button will smooth the waveform. + +The 'N' button will normalize the waveform. + O editor de formas de onda permite controlar o estado inicial ou impulso usado para facer que a corda comece a vibrar. Os botóns que hai á dereita da gráfica inicializan a forma de onda no tipo escollido. O botón «?» carga unha forma de onda desde un ficheiro - só se cargan as primeiras 128 mostras. + +A forma de onda tamén se pode debuxar na gráfica. + +O botón «S» suaviza a forma de onda. + +O botón «N» normaliza a forma de onda. + + + Vibed models up to nine independently vibrating strings. The 'String' selector allows you to choose which string is being edited. The 'Imp' selector chooses whether the graph represents an impulse or the initial state of the string. The 'Octave' selector chooses which harmonic the string should vibrate at. + +The graph allows you to control the initial state or impulse used to set the string in motion. + +The 'V' knob controls the volume. The 'S' knob controls the string's stiffness. The 'P' knob controls the pick position. The 'PU' knob controls the pickup position. + +'Pan' and 'Detune' hopefully don't need explanation. The 'Slap' knob adds a bit of fuzz to the sound of the string. + +The 'Length' knob controls the length of the string. + +The LED in the lower right corner of the waveform editor determines whether the string is active in the current instrument. + Vibed modela até nove cordas vibrando independentemente. O selector «Corda» permite escoller a corda que se desexe editar. O selector «Imp» escolle se o gráfico representa un impulso ou o estado inicial da corda. O selector «Oitava» escolle o harmónico co que debería vibrar a corda. + +A gráfica permite controlar o estado inicial ou o impulso usados para pór a corda en movemento. + +O botón «V» controla o volume. O botón «S» controla a tensión.da corda. O botón «P» controla a posición da púa. O botón «PU» controla a posición de observación. + +«Pan» e «Detune» non deberían precisar explicación. O botón «Slap» engádelle un pouco de distorsión tipo «fuzz» ao son da corda. + +O botón «Lonxitude» controla a lonxitude da corda. + +O LED do recanto inferior dereito do editor da forma da onda determina se a corda está activa no instrumento. + + + Enable waveform + Activar a forma de onda + + + Click here to enable/disable waveform. + Prema aquí para activar/desactivar a forma da onda. + + + String + Corda + + + The String selector is used to choose which string the controls are editing. A Vibed instrument can contain up to nine independently vibrating strings. The LED in the lower right corner of the waveform editor indicates whether the selected string is active. + O selector Corda emprégase para escoller a corda que editan os controles. Un instrumento de Vibed pode conter até nove cordas vibrando independentemente. O LED do recanto inferior dereito do editor da forma da onda indica se a corda escollida esstá activada. + + + Sine wave + Onda senoidal + + + Triangle wave + Onda triangular + + + Saw wave + Onda de dente de serra + + + Square wave + Onda cadrada + + + White noise wave + Onda de ruído branco + + + User defined wave + Onda definida polo usuario + + + Smooth + Suave + + + Click here to smooth waveform. + Prema aquí para unha forma de onda suave. + + + Normalize + Normalizar + + + Click here to normalize waveform. + Prema aquí para normalizar a forma da onda. + + + &Help + &Axuda + + + Use a sine-wave for current oscillator. + Empregar unha onda senoidal para este oscilador. + + + Use a triangle-wave for current oscillator. + Empregar unha onda triangular para este oscilador. + + + Use a saw-wave for current oscillator. + Empregar unha onda de dente de serra para este oscilador. + + + Use a square-wave for current oscillator. + Empregar unha onda cadrada para este oscilador. + + + Use white-noise for current oscillator. + Empregar ruído branco para este oscilador. + + + Use a user-defined waveform for current oscillator. + Empregar unha forma de onda predefinida para este oscilador. + + + + visualizationWidget + + click to enable/disable visualization of master-output + Prema para des/activar a visualización da saída global + + + Click to enable + Prema para activar + + + + voiceObject + + Voice %1 pulse width + Largo do pulso da voz %1 + + + Voice %1 attack + Ataque da voz %1 + + + Voice %1 decay + Decamento da voz %1 + + + Voice %1 sustain + Sustentación da voz %1 + + + Voice %1 release + Relaxamento da voz %1 + + + Voice %1 coarse detuning + Desafinación bruta da voz %1 + + + Voice %1 wave shape + Fonda da onda da voz %1 + + + Voice %1 sync + Sincronización da voz %1 + + + Voice %1 ring modulate + Modulación de anel da voz %1 + + + Voice %1 filtered + Filtrado da voz %1 + + + Voice %1 test + Proba da voz %1 + + + From 87cf991c325ba1b203d12e3c34b3d180f4c76c43 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Wed, 20 Nov 2013 23:49:13 +0100 Subject: [PATCH 124/133] MidiAlsaSeq: protect concurrent sequencer accesses with mutex The ALSA sequencer interface is not reentrant and thus we can't queue MIDI events for output from different threads (which happens with multicore rendering as processOutEvent() is being called by individual note play handles). We therefore have to care that we don't call the ALSA sequencer functions concurrently. Thanks to Benjamin Kusch for providing a testcase. Closes #531. --- include/MidiAlsaSeq.h | 4 ++- src/core/midi/MidiAlsaSeq.cpp | 47 ++++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/include/MidiAlsaSeq.h b/include/MidiAlsaSeq.h index 3860952e8..13d250c3f 100644 --- a/include/MidiAlsaSeq.h +++ b/include/MidiAlsaSeq.h @@ -1,7 +1,7 @@ /* * MidiAlsaSeq.h - ALSA-sequencer-client * - * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -31,6 +31,7 @@ #include #endif +#include #include #include @@ -130,6 +131,7 @@ private: virtual void run(); #ifdef LMMS_HAVE_ALSA + QMutex m_seqMutex; snd_seq_t * m_seqHandle; struct Ports { diff --git a/src/core/midi/MidiAlsaSeq.cpp b/src/core/midi/MidiAlsaSeq.cpp index 4521f9c22..bd79888a7 100644 --- a/src/core/midi/MidiAlsaSeq.cpp +++ b/src/core/midi/MidiAlsaSeq.cpp @@ -1,7 +1,7 @@ /* * MidiAlsaSeq.cpp - ALSA sequencer client * - * Copyright (c) 2005-2009 Tobias Doerffel + * Copyright (c) 2005-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -73,6 +73,7 @@ static QString __portName( snd_seq_t * _seq, const snd_seq_addr_t * _addr ) MidiAlsaSeq::MidiAlsaSeq() : MidiClient(), + m_seqMutex(), m_seqHandle( NULL ), m_queueID( -1 ), m_quit( false ), @@ -131,9 +132,11 @@ MidiAlsaSeq::~MidiAlsaSeq() m_quit = true; wait( EventPollTimeOut*2 ); + m_seqMutex.lock(); snd_seq_stop_queue( m_seqHandle, m_queueID, NULL ); snd_seq_free_queue( m_seqHandle, m_queueID ); snd_seq_close( m_seqHandle ); + m_seqMutex.unlock(); } } @@ -228,8 +231,10 @@ void MidiAlsaSeq::processOutEvent( const midiEvent & _me, return; } + m_seqMutex.lock(); snd_seq_event_output( m_seqHandle, &ev ); snd_seq_drain_output( m_seqHandle ); + m_seqMutex.unlock(); } @@ -238,6 +243,8 @@ void MidiAlsaSeq::processOutEvent( const midiEvent & _me, void MidiAlsaSeq::applyPortMode( MidiPort * _port ) { + m_seqMutex.lock(); + // determine port-capabilities unsigned int caps[2] = { 0, 0 }; @@ -297,6 +304,7 @@ void MidiAlsaSeq::applyPortMode( MidiPort * _port ) } } + m_seqMutex.unlock(); } @@ -304,6 +312,8 @@ void MidiAlsaSeq::applyPortMode( MidiPort * _port ) void MidiAlsaSeq::applyPortName( MidiPort * _port ) { + m_seqMutex.lock(); + for( int i = 0; i < 2; ++i ) { if( m_portIDs[_port][i] == -1 ) @@ -320,6 +330,8 @@ void MidiAlsaSeq::applyPortName( MidiPort * _port ) port_info ); snd_seq_port_info_free( port_info ); } + + m_seqMutex.unlock(); } @@ -329,8 +341,11 @@ void MidiAlsaSeq::removePort( MidiPort * _port ) { if( m_portIDs.contains( _port ) ) { + m_seqMutex.lock(); snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] ); snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] ); + m_seqMutex.unlock(); + m_portIDs.remove( _port ); } MidiClient::removePort( _port ); @@ -361,11 +376,16 @@ void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port, { return; } + + m_seqMutex.lock(); + snd_seq_addr_t sender; if( snd_seq_parse_address( m_seqHandle, &sender, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "error parsing sender-address!\n" ); + + m_seqMutex.unlock(); return; } snd_seq_port_info_t * port_info; @@ -386,6 +406,8 @@ void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port, } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); + + m_seqMutex.unlock(); } @@ -406,11 +428,14 @@ void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port, return; } + m_seqMutex.lock(); + snd_seq_addr_t dest; if( snd_seq_parse_address( m_seqHandle, &dest, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "error parsing dest-address!\n" ); + m_seqMutex.unlock(); return; } snd_seq_port_info_t * port_info; @@ -431,6 +456,7 @@ void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port, } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); + m_seqMutex.unlock(); } @@ -471,15 +497,20 @@ void MidiAlsaSeq::run() break; } + m_seqMutex.lock(); + // while event queue is not empty while( snd_seq_event_input_pending( m_seqHandle, true ) > 0 ) { snd_seq_event_t * ev; if( snd_seq_event_input( m_seqHandle, &ev ) < 0 ) { + m_seqMutex.unlock(); + qCritical( "error while fetching MIDI event from sequencer" ); break; } + m_seqMutex.unlock(); snd_seq_addr_t * source = NULL; MidiPort * dest = NULL; @@ -582,8 +613,12 @@ void MidiAlsaSeq::run() break; } // end switch + m_seqMutex.lock(); + } // end while + m_seqMutex.unlock(); + } delete[] pollfd_set; @@ -594,9 +629,13 @@ void MidiAlsaSeq::run() void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm ) { + m_seqMutex.lock(); + snd_seq_change_queue_tempo( m_seqHandle, m_queueID, 60000000 / (int) _bpm, NULL ); snd_seq_drain_output( m_seqHandle ); + + m_seqMutex.unlock(); } @@ -615,6 +654,9 @@ void MidiAlsaSeq::updatePortList() snd_seq_port_info_malloc( &pinfo ); snd_seq_client_info_set_client( cinfo, -1 ); + + m_seqMutex.lock(); + while( snd_seq_query_next_client( m_seqHandle, cinfo ) >= 0 ) { int client = snd_seq_client_info_get_client( cinfo ); @@ -643,6 +685,9 @@ void MidiAlsaSeq::updatePortList() } } + m_seqMutex.unlock(); + + snd_seq_client_info_free( cinfo ); snd_seq_port_info_free( pinfo ); From f17285581dd6c4161cad7e382171e5a2ffc60720 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:36:41 +0100 Subject: [PATCH 125/133] EnvelopeAndLfoView: directly alter models instead of views Do not make use of deprecated AutomatableModelView::setValue() method anymore and alter the models directly instead. --- src/gui/widgets/EnvelopeAndLfoView.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index 42002c34e..a0ac1fcb6 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -2,7 +2,7 @@ * EnvelopeAndLfoView.cpp - widget which is m_used by envelope/lfo/filter- * tab of instrument track window * - * Copyright (c) 2004-2009 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -341,25 +341,25 @@ void EnvelopeAndLfoView::mousePressEvent( QMouseEvent * _me ) if( QRect( ENV_GRAPH_X, ENV_GRAPH_Y, s_envGraph->width(), s_envGraph->height() ).contains( _me->pos() ) == true ) { - if( m_amountKnob->value() < 1.0f ) + if( m_params->m_amountModel.value() < 1.0f ) { - m_amountKnob->setValue( 1.0f ); + m_params->m_amountModel.setValue( 1.0f ); } else { - m_amountKnob->setValue( 0.0f ); + m_params->m_amountModel.setValue( 0.0f ); } } else if( QRect( LFO_GRAPH_X, LFO_GRAPH_Y, s_lfoGraph->width(), s_lfoGraph->height() ).contains( _me->pos() ) == true ) { - if( m_lfoAmountKnob->value() < 1.0f ) + if( m_params->m_lfoAmountModel.value() < 1.0f ) { - m_lfoAmountKnob->setValue( 1.0f ); + m_params->m_lfoAmountModel.setValue( 1.0f ); } else { - m_lfoAmountKnob->setValue( 0.0f ); + m_params->m_lfoAmountModel.setValue( 0.0f ); } } } From 5c7b32cf51f01d818a69c7cba29c9088cd74d851 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:38:24 +0100 Subject: [PATCH 126/133] AutomatableModelView: removed deprecated setValue() helper The setValue() helper method dates back to when we introduced model/view separation. It's not required anymore. --- include/AutomatableModelView.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/include/AutomatableModelView.h b/include/AutomatableModelView.h index 6c59d8f61..1c35d02aa 100644 --- a/include/AutomatableModelView.h +++ b/include/AutomatableModelView.h @@ -1,7 +1,7 @@ /* * AutomatableModelView.h - class AutomatableModelView * - * Copyright (c) 2008-2013 Tobias Doerffel + * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -56,15 +56,6 @@ public: return( modelUntyped() ? modelUntyped()->value() : 0 ); } - inline void setValue( const float _value ) - { - if( modelUntyped() ) - { - modelUntyped()->setValue( _value ); - } - } - - inline void setDescription( const QString & _desc ) { m_description = _desc; From ec6c30549a25bfef449f855527ceae8a75b6f7b7 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:39:50 +0100 Subject: [PATCH 127/133] LcdWidget: new class for displaying numbers in LCD style Code based on LcdSpinBox implementation. --- include/LcdWidget.h | 87 ++++++++++++ src/gui/widgets/LcdWidget.cpp | 246 ++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 include/LcdWidget.h create mode 100644 src/gui/widgets/LcdWidget.cpp diff --git a/include/LcdWidget.h b/include/LcdWidget.h new file mode 100644 index 000000000..de90f1f42 --- /dev/null +++ b/include/LcdWidget.h @@ -0,0 +1,87 @@ +/* + * LcdWidget.h - a widget for displaying numbers in LCD style + * + * Copyright (c) 2014 Tobias Doerffel + * + * 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. + * + */ + + +#ifndef _LCD_WIDGET_H +#define _LCD_WIDGET_H + +#include +#include + +#include "export.h" + +class EXPORT LcdWidget : public QWidget +{ + Q_OBJECT +public: + LcdWidget( int numDigits, QWidget* parent, const QString& name = QString::null ); + + LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name = QString::null ); + + virtual ~LcdWidget(); + + void setValue( int value ); + void setLabel( const QString& label ); + + void addTextForValue( int value, const QString& text ) + { + m_textForValue[value] = text; + update(); + } + + +public slots: + virtual void setMarginWidth( int _width ); + + +protected: + virtual void paintEvent( QPaintEvent * _me ); + + virtual void updateSize(); + + int cellHeight() const + { + return m_cellHeight; + } + + +private: + + static const int charsPerPixmap = 12; + + QMap m_textForValue; + + QString m_display; + + QString m_label; + QPixmap* m_lcdPixmap; + + int m_cellWidth; + int m_cellHeight; + int m_numDigits; + int m_marginWidth; + +} ; + +#endif diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp new file mode 100644 index 000000000..21add2532 --- /dev/null +++ b/src/gui/widgets/LcdWidget.cpp @@ -0,0 +1,246 @@ +/* + * LcdWidget.cpp - a widget for displaying numbers in LCD style + * + * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2008 Paul Giblock + * + * 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 +#include +#include +#include +#include +#include + +#include "LcdWidget.h" +#include "engine.h" +#include "embed.h" +#include "gui_templates.h" +#include "MainWindow.h" + + + +LcdWidget::LcdWidget( int numDigits, QWidget* parent, const QString& name ) : + QWidget( parent ), + m_label(), + m_numDigits( numDigits ) +{ + setEnabled( true ); + + setWindowTitle( name ); + + m_lcdPixmap = new QPixmap( embed::getIconPixmap( "lcd_19green" ) ); + + m_cellWidth = m_lcdPixmap->size().width() / LcdWidget::charsPerPixmap; + m_cellHeight = m_lcdPixmap->size().height() / 2; + + m_marginWidth = m_cellWidth / 2; + + updateSize(); +} + + + + +LcdWidget::LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name ) : + QWidget( parent ), + m_label(), + m_numDigits( numDigits ) +{ + setEnabled( true ); + + setWindowTitle( name ); + + // We should make a factory for these or something. + m_lcdPixmap = new QPixmap( embed::getIconPixmap( QString( "lcd_" + style ).toUtf8().constData() ) ); + + m_cellWidth = m_lcdPixmap->size().width() / LcdWidget::charsPerPixmap; + m_cellHeight = m_lcdPixmap->size().height() / 2; + + m_marginWidth = m_cellWidth / 2; + + updateSize(); +} + + + + +LcdWidget::~LcdWidget() +{ + delete m_lcdPixmap; +} + + + + +void LcdWidget::setValue( int value ) +{ + QString s = m_textForValue[value]; + if( s.isEmpty() ) + { + s = QString::number( value ); + // TODO: if pad == true + /* + while( (int) s.length() < m_numDigits ) + { + s = "0" + s; + } + */ + } + + m_display = s; + + update(); +} + + + + +void LcdWidget::paintEvent( QPaintEvent* ) +{ + QPainter p( this ); + + QSize cellSize( m_cellWidth, m_cellHeight ); + + QRect cellRect( 0, 0, m_cellWidth, m_cellHeight ); + + int margin = 1; // QStyle::PM_DefaultFrameWidth; + //int lcdWidth = m_cellWidth * m_numDigits + (margin*m_marginWidth)*2; + +// p.translate( width() / 2 - lcdWidth / 2, 0 ); + p.save(); + + p.translate( margin, margin ); + + // Left Margin + p.drawPixmap( cellRect, *m_lcdPixmap, + QRect( QPoint( charsPerPixmap*m_cellWidth, + isEnabled()?0:m_cellHeight ), + cellSize ) ); + + p.translate( m_marginWidth, 0 ); + + // Padding + for( int i=0; i < m_numDigits - m_display.length(); i++ ) + { + p.drawPixmap( cellRect, *m_lcdPixmap, + QRect( QPoint( 10 * m_cellWidth, isEnabled()?0:m_cellHeight) , cellSize ) ); + p.translate( m_cellWidth, 0 ); + } + + // Digits + for( int i=0; i < m_display.length(); i++ ) + { + int val = m_display[i].digitValue(); + if( val < 0 ) + { + if( m_display[i] == '-' ) + val = 11; + else + val = 10; + } + p.drawPixmap( cellRect, *m_lcdPixmap, + QRect( QPoint( val*m_cellWidth, + isEnabled()?0:m_cellHeight ), + cellSize ) ); + p.translate( m_cellWidth, 0 ); + } + + // Right Margin + p.drawPixmap( QRect( 0, 0, m_marginWidth-1, m_cellHeight ), *m_lcdPixmap, + QRect( charsPerPixmap*m_cellWidth, isEnabled()?0:m_cellHeight, + m_cellWidth / 2, m_cellHeight ) ); + + + p.restore(); + + // Border + QStyleOptionFrame opt; + opt.initFrom( this ); + opt.state = QStyle::State_Sunken; + opt.rect = QRect( 0, 0, m_cellWidth * m_numDigits + (margin+m_marginWidth)*2 - 1, + m_cellHeight + (margin*2) ); + + style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); + + p.resetTransform(); + + // Label + if( !m_label.isEmpty() ) + { + p.setFont( pointSize<6>( p.font() ) ); + p.setPen( QColor( 64, 64, 64 ) ); + p.drawText( width() / 2 - + p.fontMetrics().width( m_label ) / 2 + 1, + height(), m_label ); + p.setPen( QColor( 255, 255, 255 ) ); + p.drawText( width() / 2 - + p.fontMetrics().width( m_label ) / 2, + height() - 1, m_label ); + } + +} + + + + +void LcdWidget::setLabel( const QString & _txt ) +{ + m_label = _txt; + updateSize(); +} + + + + +void LcdWidget::setMarginWidth( int _width ) +{ + m_marginWidth = _width; + + updateSize(); +} + + + + +void LcdWidget::updateSize() +{ + int margin = 1; + if (m_label.isEmpty()) { + setFixedSize( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), + m_cellHeight + (2*margin) ); + } + else { + setFixedSize( qMax( + m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), + QFontMetrics( pointSize<6>( font() ) ).width( m_label ) ), + m_cellHeight + (2*margin) + 10 ); + } + + update(); +} + + + +#include "moc_LcdWidget.cxx" + From f568a81c7a672e7882900f1ec489dc738d84d412 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:40:28 +0100 Subject: [PATCH 128/133] LcdSpinBox: use LcdWidget as base class We can now use the new LcdWidget class as base class for actually displaying our model's values. --- include/lcd_spinbox.h | 44 +----- src/gui/widgets/lcd_spinbox.cpp | 229 +++----------------------------- 2 files changed, 27 insertions(+), 246 deletions(-) diff --git a/include/lcd_spinbox.h b/include/lcd_spinbox.h index db599006e..43ce4a32d 100644 --- a/include/lcd_spinbox.h +++ b/include/lcd_spinbox.h @@ -1,8 +1,8 @@ /* * lcd_spinbox.h - class lcdSpinBox, an improved QLCDNumber * - * Copyright (c) 2005-2008 Tobias Doerffel - * + * Copyright (c) 2005-2014 Tobias Doerffel + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -26,32 +26,20 @@ #ifndef _LCD_SPINBOX_H #define _LCD_SPINBOX_H -#include -#include - +#include "LcdWidget.h" #include "AutomatableModelView.h" -class EXPORT lcdSpinBox : public QWidget, public IntModelView +class EXPORT lcdSpinBox : public LcdWidget, public IntModelView { Q_OBJECT public: - lcdSpinBox( int _num_digits, QWidget * _parent, const QString & _name = - QString::null ); + lcdSpinBox( int numDigits, QWidget* parent, const QString& name = QString::null ); - lcdSpinBox( int _num_digits, const QString & _lcd_style, - QWidget * _parent, const QString & _name = QString::null ); + lcdSpinBox( int numDigits, const QString& style, QWidget* parent, const QString& name = QString::null ); virtual ~lcdSpinBox(); - void setLabel( const QString & _txt ); - - inline void addTextForValue( int _val, const QString & _text ) - { - m_textForValue[_val] = _text; - update(); - } - virtual void modelChanged() { ModelView::modelChanged(); @@ -60,8 +48,6 @@ public: public slots: - virtual void setEnabled( bool _on ); - virtual void setMarginWidth( int _width ); virtual void update(); @@ -71,26 +57,8 @@ protected: virtual void mouseMoveEvent( QMouseEvent * _me ); virtual void mouseReleaseEvent( QMouseEvent * _me ); virtual void wheelEvent( QWheelEvent * _we ); - virtual void paintEvent( QPaintEvent * _me ); - - virtual void updateSize(); private: - - static const int charsPerPixmap = 12; - - QMap m_textForValue; - - QString m_display; - - QString m_label; - QPixmap * m_lcdPixmap; - - int m_cellWidth; - int m_cellHeight; - int m_numDigits; - int m_marginWidth; - QPoint m_origMousePos; diff --git a/src/gui/widgets/lcd_spinbox.cpp b/src/gui/widgets/lcd_spinbox.cpp index 16708a2c3..dcc1ebd09 100644 --- a/src/gui/widgets/lcd_spinbox.cpp +++ b/src/gui/widgets/lcd_spinbox.cpp @@ -1,9 +1,9 @@ /* * lcd_spinbox.cpp - class lcdSpinBox, an improved QLCDNumber * - * Copyright (c) 2005-2011 Tobias Doerffel + * Copyright (c) 2005-2014 Tobias Doerffel * Copyright (c) 2008 Paul Giblock - * + * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or @@ -23,8 +23,6 @@ * */ - - #include #include #include @@ -42,228 +40,43 @@ -lcdSpinBox::lcdSpinBox( int _num_digits, QWidget * _parent, - const QString & _name ) : - QWidget( _parent ), - IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this ), - m_label(), - m_numDigits( _num_digits ), +lcdSpinBox::lcdSpinBox( int numDigits, QWidget* parent, const QString& name ) : + LcdWidget( numDigits, parent, name ), + IntModelView( new IntModel( 0, 0, 0, NULL, name, true ), this ), m_origMousePos() { - setEnabled( true ); - - setWindowTitle( _name ); - - m_lcdPixmap = new QPixmap( embed::getIconPixmap( "lcd_19green" ) ); - - m_cellWidth = m_lcdPixmap->size().width() / lcdSpinBox::charsPerPixmap; - m_cellHeight = m_lcdPixmap->size().height() / 2; - - m_marginWidth = m_cellWidth / 2; - - updateSize(); } -lcdSpinBox::lcdSpinBox( int _num_digits, const QString & _lcd_style, - QWidget * _parent, const QString & _name ) : - QWidget( _parent ), - IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this ), - m_label(), - m_numDigits( _num_digits ), +lcdSpinBox::lcdSpinBox( int numDigits, const QString& style, QWidget* parent, const QString& name ) : + LcdWidget( numDigits, parent, name ), + IntModelView( new IntModel( 0, 0, 0, NULL, name, true ), this ), m_origMousePos() { - setEnabled( true ); - - setWindowTitle( _name ); - - // We should make a factory for these or something. - m_lcdPixmap = new QPixmap( embed::getIconPixmap( QString( "lcd_" + - _lcd_style ).toUtf8().constData() ) ); - - m_cellWidth = m_lcdPixmap->size().width() / lcdSpinBox::charsPerPixmap; - m_cellHeight = m_lcdPixmap->size().height() / 2; - - m_marginWidth = m_cellWidth / 2; - - updateSize(); } - lcdSpinBox::~lcdSpinBox() { - delete m_lcdPixmap; } - -void lcdSpinBox::paintEvent( QPaintEvent * _me ) -{ - QRect ur = _me->rect(); - - QPainter p( this ); - - QSize cellSize( m_cellWidth, m_cellHeight ); - - QRect cellRect( 0, 0, m_cellWidth, m_cellHeight ); - - int margin = 1; // QStyle::PM_DefaultFrameWidth; - //int lcdWidth = m_cellWidth * m_numDigits + (margin*m_marginWidth)*2; - -// p.translate( width() / 2 - lcdWidth / 2, 0 ); - p.save(); - - p.translate( margin, margin ); - - // Left Margin - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( charsPerPixmap*m_cellWidth, - isEnabled()?0:m_cellHeight ), - cellSize ) ); - - p.translate( m_marginWidth, 0 ); - - // Padding - for( int i=0; i < m_numDigits - m_display.length(); i++ ) - { - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( 10 * m_cellWidth, isEnabled()?0:m_cellHeight) , cellSize ) ); - p.translate( m_cellWidth, 0 ); - } - - // Digits - for( int i=0; i < m_display.length(); i++ ) - { - int val = m_display[i].digitValue(); - if( val < 0 ) - { - if( m_display[i] == '-' ) - val = 11; - else - val = 10; - } - p.drawPixmap( cellRect, *m_lcdPixmap, - QRect( QPoint( val*m_cellWidth, - isEnabled()?0:m_cellHeight ), - cellSize ) ); - p.translate( m_cellWidth, 0 ); - } - - // Right Margin - p.drawPixmap( QRect( 0, 0, m_marginWidth-1, m_cellHeight ), *m_lcdPixmap, - QRect( charsPerPixmap*m_cellWidth, isEnabled()?0:m_cellHeight, - m_cellWidth / 2, m_cellHeight ) ); - - - p.restore(); - - // Border - QStyleOptionFrame opt; - opt.initFrom( this ); - opt.state = QStyle::State_Sunken; - opt.rect = QRect( 0, 0, m_cellWidth * m_numDigits + (margin+m_marginWidth)*2 - 1, - m_cellHeight + (margin*2) ); - - style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); - - p.resetTransform(); - - // Label - if( !m_label.isEmpty() ) - { - p.setFont( pointSize<6>( p.font() ) ); - p.setPen( QColor( 64, 64, 64 ) ); - p.drawText( width() / 2 - - p.fontMetrics().width( m_label ) / 2 + 1, - height(), m_label ); - p.setPen( QColor( 255, 255, 255 ) ); - p.drawText( width() / 2 - - p.fontMetrics().width( m_label ) / 2, - height() - 1, m_label ); - } - -} - - - - void lcdSpinBox::update() { - QString s = m_textForValue[model()->value()]; - if( s == "" ) - { - s = QString::number( model()->value() ); - // TODO: if pad == true - /* - while( (int) s.length() < m_numDigits ) - { - s = "0" + s; - } - */ - } - m_display = s; + setValue( model()->value() ); QWidget::update(); } - -void lcdSpinBox::setLabel( const QString & _txt ) +void lcdSpinBox::contextMenuEvent( QContextMenuEvent* event ) { - m_label = _txt; - updateSize(); -} - - - - -void lcdSpinBox::setEnabled( bool _on ) -{ - QWidget::setEnabled( _on ); -} - - - - -void lcdSpinBox::setMarginWidth( int _width ) -{ - m_marginWidth = _width; - - updateSize(); -} - - - - -void lcdSpinBox::updateSize() -{ - int margin = 1; - if (m_label.isEmpty()) { - setFixedSize( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), - m_cellHeight + (2*margin) ); - } - else { - setFixedSize( qMax( - m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), - QFontMetrics( pointSize<6>( font() ) ).width( m_label ) ), - m_cellHeight + (2*margin) + 10 ); - } - - update(); -} - - - - -void lcdSpinBox::contextMenuEvent( QContextMenuEvent * _me ) -{ - m_origMousePos = _me->globalPos(); + m_origMousePos = event->globalPos(); // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden @@ -279,30 +92,30 @@ void lcdSpinBox::contextMenuEvent( QContextMenuEvent * _me ) -void lcdSpinBox::mousePressEvent( QMouseEvent * _me ) +void lcdSpinBox::mousePressEvent( QMouseEvent* event ) { - if( _me->button() == Qt::LeftButton && - ! ( _me->modifiers() & Qt::ControlModifier ) && - _me->y() < m_cellHeight + 2 ) + if( event->button() == Qt::LeftButton && + ! ( event->modifiers() & Qt::ControlModifier ) && + event->y() < cellHeight() + 2 ) { - m_origMousePos = _me->globalPos(); + m_origMousePos = event->globalPos(); QApplication::setOverrideCursor( Qt::BlankCursor ); model()->prepareJournalEntryFromOldVal(); } else { - IntModelView::mousePressEvent( _me ); + IntModelView::mousePressEvent( event ); } } -void lcdSpinBox::mouseMoveEvent( QMouseEvent * _me ) +void lcdSpinBox::mouseMoveEvent( QMouseEvent* event ) { - if( _me->buttons() & Qt::LeftButton ) + if( event->buttons() & Qt::LeftButton ) { - int dy = _me->globalY() - m_origMousePos.y(); + int dy = event->globalY() - m_origMousePos.y(); if( dy > 1 || dy < -1 ) { model()->setInitValue( model()->value() - @@ -316,7 +129,7 @@ void lcdSpinBox::mouseMoveEvent( QMouseEvent * _me ) -void lcdSpinBox::mouseReleaseEvent( QMouseEvent * _me ) +void lcdSpinBox::mouseReleaseEvent( QMouseEvent* event ) { model()->addJournalEntryFromOldToCurVal(); From 5cc24839586cfc16e83ed3b27a9937e9c92e4861 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:45:27 +0100 Subject: [PATCH 129/133] Song: added functions to maintain playback time information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now also maintain information about playback time in order to display it later. Most of the code provided by Rubén Ibarra Pastor . Further improvements by Raine M. Ekman . --- include/song.h | 51 ++++++++++++++++++++++++++++++++++++------- src/core/song.cpp | 16 ++++++++++++-- src/core/timeline.cpp | 3 ++- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/include/song.h b/include/song.h index eddfe2d88..6a6a73d7c 100644 --- a/include/song.h +++ b/include/song.h @@ -1,7 +1,7 @@ /* * song.h - class song - the root of the model-tree * - * Copyright (c) 2004-2012 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -94,7 +94,46 @@ public: void processNextBuffer(); + inline int getMilliseconds() const + { + return m_elapsedMilliSeconds; + } + inline void setMilliSeconds( float _ellapsedMilliSeconds ) + { + m_elapsedMilliSeconds = (_ellapsedMilliSeconds); + } + inline int getTacts() const + { + return currentTact(); + } + inline int ticksPerTact() const + { + return DefaultTicksPerTact * + m_timeSigModel.getNumerator() / + m_timeSigModel.getDenominator(); + } + + // Returns the beat position inside the bar, 0-based + inline int getBeat() const + { + return (currentTick() - currentTact()*ticksPerTact()) / + (ticksPerTact() / m_timeSigModel.getNumerator() ); + } + // the remainder after bar and beat are removed + inline int getBeatTicks() const + { + return (currentTick() - currentTact()*ticksPerTact()) % + (ticksPerTact() / m_timeSigModel.getNumerator() ); + } + inline int getTicks() const + { + return currentTick(); + } + inline bool isTempoAutomated() + { + return m_tempoModel.isAutomated(); + } inline bool isPaused() const { return m_paused; @@ -260,13 +299,6 @@ private: virtual ~song(); - inline int ticksPerTact() const - { - return DefaultTicksPerTact * - m_timeSigModel.getNumerator() / - m_timeSigModel.getDenominator(); - } - inline tact_t currentTact() const { return m_playPos[m_playMode].getTact(); @@ -313,6 +345,9 @@ private: pattern * m_patternToPlay; bool m_loopPattern; + double m_elapsedMilliSeconds; + tick_t m_elapsedTicks; + tact_t m_elapsedTacts; enum Actions { diff --git a/src/core/song.cpp b/src/core/song.cpp index b8d281a0e..ee1f30fbb 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -1,7 +1,7 @@ /* * song.cpp - root of the model tree * - * Copyright (c) 2004-2013 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -104,6 +104,9 @@ song::song() : m_trackToPlay( NULL ), m_patternToPlay( NULL ), m_loopPattern( false ), + m_elapsedMilliSeconds( 0 ), + m_elapsedTicks( 0 ), + m_elapsedTacts( 0 ), m_shmID( -1 ), m_SncVSTplug( NULL ), m_shmQtID( "/usr/bin/lmms" ) @@ -179,7 +182,6 @@ song::song() : this, SLOT( masterPitchChanged() ) );*/ qRegisterMetaType( "note" ); - } @@ -285,6 +287,7 @@ void song::doActions() { case timeLine::BackToZero: m_playPos[m_playMode].setTicks( 0 ); + m_elapsedMilliSeconds = 0; break; case timeLine::BackToStart: @@ -292,6 +295,7 @@ void song::doActions() { m_playPos[m_playMode].setTicks( tl->savedPos().getTicks() ); + m_elapsedMilliSeconds = (((tl->savedPos().getTicks())*60*1000/48)/getTempo()); tl->savePos( -1 ); } break; @@ -305,6 +309,7 @@ void song::doActions() else { m_playPos[m_playMode].setTicks( 0 ); + m_elapsedMilliSeconds = 0; } m_playPos[m_playMode].setCurrentFrame( 0 ); @@ -454,6 +459,7 @@ void song::processNextBuffer() if( m_playPos[m_playMode] < tl->loopBegin() || m_playPos[m_playMode] >= tl->loopEnd() ) { + m_elapsedMilliSeconds = (tl->loopBegin().getTicks()*60*1000/48)/getTempo(); m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); } @@ -546,6 +552,7 @@ void song::processNextBuffer() { m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); + m_elapsedMilliSeconds = ((tl->loopBegin().getTicks())*60*1000/48)/getTempo(); } } else @@ -599,6 +606,9 @@ void song::processNextBuffer() total_frames_played += played_frames; m_playPos[m_playMode].setCurrentFrame( played_frames + current_frame ); + m_elapsedMilliSeconds += (((played_frames/frames_per_tick)*60*1000/48)/getTempo()); + m_elapsedTacts = m_playPos[Mode_PlaySong].getTact(); + m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks()%ticksPerTact())/48; } } @@ -733,6 +743,8 @@ void song::updateLength() void song::setPlayPos( tick_t _ticks, PlayModes _play_mode ) { + m_elapsedTicks += m_playPos[_play_mode].getTicks() - _ticks; + m_elapsedMilliSeconds += (((( _ticks - m_playPos[_play_mode].getTicks()))*60*1000/48)/getTempo()); m_playPos[_play_mode].setTicks( _ticks ); m_playPos[_play_mode].setCurrentFrame( 0.0f ); } diff --git a/src/core/timeline.cpp b/src/core/timeline.cpp index f3dc59d6f..11aed487f 100644 --- a/src/core/timeline.cpp +++ b/src/core/timeline.cpp @@ -1,7 +1,7 @@ /* * timeline.cpp - class timeLine, representing a time-line with position marker * - * Copyright (c) 2004-2008 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -324,6 +324,7 @@ void timeLine::mouseMoveEvent( QMouseEvent * _me ) { case MovePositionMarker: m_pos.setTicks( t.getTicks() ); + engine::getSong()->setMilliSeconds(((((t.getTicks()))*60*1000/48)/engine::getSong()->getTempo())); m_pos.setCurrentFrame( 0 ); updatePosition(); break; From 8a4440936c4ce373c132c898357b9ec5815ee341 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 20:48:40 +0100 Subject: [PATCH 130/133] TimeDisplayWidget: new widget for displaying current playback time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new widget displays current playback times in LCD style. An initial basic implementation has been provided by by Rubén Ibarra Pastor. --- include/TimeDisplayWidget.h | 70 +++++++++++++ src/gui/widgets/TimeDisplayWidget.cpp | 139 ++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 include/TimeDisplayWidget.h create mode 100644 src/gui/widgets/TimeDisplayWidget.cpp diff --git a/include/TimeDisplayWidget.h b/include/TimeDisplayWidget.h new file mode 100644 index 000000000..cde431d96 --- /dev/null +++ b/include/TimeDisplayWidget.h @@ -0,0 +1,70 @@ +/* + * TimeDisplayWidget.h - widget for displaying current playback time + * + * Copyright (c) 2014 Ruben Ibarra + * Copyright (c) 2014 Tobias Doerffel + * + * 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. + * + */ + +#ifndef _TIME_DISPLAY_WIDGET +#define _TIME_DISPLAY_WIDGET + +#include +#include + +#include "LcdWidget.h" + + +class TimeDisplayWidget : public QWidget +{ + Q_OBJECT +public: + TimeDisplayWidget(); + virtual ~TimeDisplayWidget(); + + +protected: + virtual void mousePressEvent( QMouseEvent* mouseEvent ); + + +private slots: + void updateTime(); + + +private: + enum DisplayModes + { + MinutesSeconds, + BarsTicks, + DisplayModeCount + }; + typedef DisplayModes DisplayMode; + + void setDisplayMode( DisplayMode displayMode ); + + DisplayMode m_displayMode; + QHBoxLayout m_spinBoxesLayout; + LcdWidget m_majorLCD; + LcdWidget m_minorLCD; + LcdWidget m_milliSecondsLCD; + +} ; + +#endif diff --git a/src/gui/widgets/TimeDisplayWidget.cpp b/src/gui/widgets/TimeDisplayWidget.cpp new file mode 100644 index 000000000..a984b6dae --- /dev/null +++ b/src/gui/widgets/TimeDisplayWidget.cpp @@ -0,0 +1,139 @@ +/* + * TimeDisplayWidget.cpp - widget for displaying current playback time + * + * Copyright (c) 2014 Tobias Doerffel + * + * 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 + +#include "TimeDisplayWidget.h" +#include "MainWindow.h" +#include "engine.h" +#include "tooltip.h" +#include "song.h" + + + +TimeDisplayWidget::TimeDisplayWidget() : + QWidget(), + m_displayMode( MinutesSeconds ), + m_spinBoxesLayout( this ), + m_majorLCD( 2, this ), + m_minorLCD( 2, this ), + m_milliSecondsLCD( 3, this ) +{ + m_spinBoxesLayout.setSpacing( 0 ); + m_spinBoxesLayout.setMargin( 0 ); + m_spinBoxesLayout.addWidget( &m_majorLCD ); + m_spinBoxesLayout.addWidget( &m_minorLCD ); + m_spinBoxesLayout.addWidget( &m_milliSecondsLCD ); + + setMaximumHeight( 32 ); + + toolTip::add( this, tr( "click to change time units" ) ); + + // update labels of LCD spinboxes + setDisplayMode( m_displayMode ); + + connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), + this, SLOT( updateTime() ) ); +} + + + + +TimeDisplayWidget::~TimeDisplayWidget() +{ +} + + + + + +void TimeDisplayWidget::setDisplayMode( DisplayMode displayMode ) +{ + m_displayMode = displayMode; + + m_milliSecondsLCD.setLabel( "MSEC" ); + + switch( m_displayMode ) + { + case MinutesSeconds: + m_majorLCD.setLabel( "MIN" ); + m_minorLCD.setLabel( "SEC" ); + break; + + case BarsTicks: + m_majorLCD.setLabel( "BAR" ); + m_minorLCD.setLabel( "TICK" ); + break; + + default: break; + } +} + + + + +void TimeDisplayWidget::updateTime() +{ + song* s = engine::getSong(); + + switch( m_displayMode ) + { + case MinutesSeconds: + m_majorLCD.setValue( s->getMilliseconds() / 60000 ); + m_minorLCD.setValue( ( s->getMilliseconds() / 1000 ) % 60 ); + break; + + case BarsTicks: + m_majorLCD.setValue( s->getTacts() ); + m_minorLCD.setValue( ( s->getTicks() % s->ticksPerTact() ) / 3 ); + break; + + default: break; + } + m_milliSecondsLCD.setValue( s->getMilliseconds() % 1000 ); +} + + + + +void TimeDisplayWidget::mousePressEvent( QMouseEvent* mouseEvent ) +{ + if( mouseEvent->button() == Qt::LeftButton ) + { + if( m_displayMode == MinutesSeconds ) + { + setDisplayMode( BarsTicks ); + } + else + { + setDisplayMode( MinutesSeconds ); + } + } +} + + + +#include "moc_TimeDisplayWidget.cxx" + + From a73e8636eba74b24a2c1c489e620dc3dbc2a9d51 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 21:17:48 +0100 Subject: [PATCH 131/133] SongEditor: integrated new TimeDisplayWidget --- src/gui/song_editor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/song_editor.cpp b/src/gui/song_editor.cpp index cd1bba8cc..b1b9de039 100644 --- a/src/gui/song_editor.cpp +++ b/src/gui/song_editor.cpp @@ -1,7 +1,7 @@ /* * song_editor.cpp - basic window for song-editing * - * Copyright (c) 2004-2011 Tobias Doerffel + * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -50,6 +50,7 @@ #include "tool_button.h" #include "tooltip.h" #include "visualization_widget.h" +#include "TimeDisplayWidget.h" #include "AudioDevice.h" #include "piano_roll.h" #include "config_mgr.h" @@ -130,7 +131,7 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) : "should be played within a minute (or how many measures " "should be played within four minutes)." ) ); - engine::mainWindow()->addWidgetToToolBar( m_tempoSpinBox, 0 ); + int tempoSpinBoxCol = engine::mainWindow()->addWidgetToToolBar( m_tempoSpinBox, 0 ); #if 0 toolButton * hq_btn = new toolButton( embed::getIconPixmap( "hq_mode" ), @@ -145,6 +146,9 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) : engine::mainWindow()->addSpacingToToolBar( 10 ); + engine::mainWindow()->addWidgetToToolBar( new TimeDisplayWidget, 1, tempoSpinBoxCol ); + + engine::mainWindow()->addSpacingToToolBar( 10 ); m_timeSigDisplay = new MeterDialog( this, TRUE ); m_timeSigDisplay->setModel( &m_s->m_timeSigModel ); From 90024c369947b8f5848526288e636158fffc3997 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 6 Jan 2014 21:18:20 +0100 Subject: [PATCH 132/133] LcdWidget: decreased spacing between text label and LCD label --- src/gui/widgets/LcdWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/widgets/LcdWidget.cpp b/src/gui/widgets/LcdWidget.cpp index 21add2532..16a5df793 100644 --- a/src/gui/widgets/LcdWidget.cpp +++ b/src/gui/widgets/LcdWidget.cpp @@ -234,7 +234,7 @@ void LcdWidget::updateSize() setFixedSize( qMax( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), QFontMetrics( pointSize<6>( font() ) ).width( m_label ) ), - m_cellHeight + (2*margin) + 10 ); + m_cellHeight + (2*margin) + 8 ); } update(); From 8fa4866ff8e5ec9402ed055e0eb30f697f86d552 Mon Sep 17 00:00:00 2001 From: "Raine M. Ekman" Date: Mon, 6 Jan 2014 21:26:26 +0100 Subject: [PATCH 133/133] Added OpulenZ, an FM synth for LMMS Signed-off-by: Tobias Doerffel --- data/themes/default/style.css | 7 + plugins/CMakeLists.txt | 1 + plugins/opl2/CMakeLists.txt | 3 + plugins/opl2/README | 62 ++ plugins/opl2/adlibemu.c | 600 +++++++++++++ plugins/opl2/adlibemu.h | 26 + plugins/opl2/artwork.png | Bin 0 -> 116949 bytes plugins/opl2/fmopl.c | 1390 +++++++++++++++++++++++++++++++ plugins/opl2/fmopl.h | 174 ++++ plugins/opl2/kemuopl.h | 61 ++ plugins/opl2/logo.png | Bin 0 -> 285 bytes plugins/opl2/mididata.h | 174 ++++ plugins/opl2/opl.h | 69 ++ plugins/opl2/opl2_led_off.png | Bin 0 -> 586 bytes plugins/opl2/opl2_led_on.png | Bin 0 -> 665 bytes plugins/opl2/opl2instrument.cpp | 613 ++++++++++++++ plugins/opl2/opl2instrument.h | 173 ++++ plugins/opl2/temuopl.cpp | 75 ++ plugins/opl2/temuopl.h | 47 ++ plugins/opl2/wave1_off.png | Bin 0 -> 749 bytes plugins/opl2/wave1_on.png | Bin 0 -> 786 bytes plugins/opl2/wave2_off.png | Bin 0 -> 435 bytes plugins/opl2/wave2_on.png | Bin 0 -> 520 bytes plugins/opl2/wave3_off.png | Bin 0 -> 497 bytes plugins/opl2/wave3_on.png | Bin 0 -> 580 bytes plugins/opl2/wave4_off.png | Bin 0 -> 693 bytes plugins/opl2/wave4_on.png | Bin 0 -> 742 bytes 27 files changed, 3475 insertions(+) create mode 100644 plugins/opl2/CMakeLists.txt create mode 100644 plugins/opl2/README create mode 100644 plugins/opl2/adlibemu.c create mode 100644 plugins/opl2/adlibemu.h create mode 100644 plugins/opl2/artwork.png create mode 100644 plugins/opl2/fmopl.c create mode 100644 plugins/opl2/fmopl.h create mode 100644 plugins/opl2/kemuopl.h create mode 100644 plugins/opl2/logo.png create mode 100644 plugins/opl2/mididata.h create mode 100644 plugins/opl2/opl.h create mode 100644 plugins/opl2/opl2_led_off.png create mode 100644 plugins/opl2/opl2_led_on.png create mode 100644 plugins/opl2/opl2instrument.cpp create mode 100644 plugins/opl2/opl2instrument.h create mode 100644 plugins/opl2/temuopl.cpp create mode 100644 plugins/opl2/temuopl.h create mode 100644 plugins/opl2/wave1_off.png create mode 100644 plugins/opl2/wave1_on.png create mode 100644 plugins/opl2/wave2_off.png create mode 100644 plugins/opl2/wave2_on.png create mode 100644 plugins/opl2/wave3_off.png create mode 100644 plugins/opl2/wave3_on.png create mode 100644 plugins/opl2/wave4_off.png create mode 100644 plugins/opl2/wave4_on.png diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 49de862e8..908e609b4 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -224,6 +224,13 @@ sf2InstrumentView knob { qproperty-lineWidth: 2; } +opl2instrumentView knob { + color: rgb(128,128,128); + qproperty-outerColor: rgb(255,255,255); + qproperty-innerRadius: 2; + qproperty-outerRadius: 9; + qproperty-lineWidth: 2; +} /* Notes: lcd-spinbox colors: (12, 250, 150), (37, 57, 42) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index db590e379..0cbdc4359 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_SUBDIRECTORY(lb302) #ADD_SUBDIRECTORY(lb303) ADD_SUBDIRECTORY(midi_import) ADD_SUBDIRECTORY(organic) +ADD_SUBDIRECTORY(opl2) ADD_SUBDIRECTORY(papu) ADD_SUBDIRECTORY(patman) ADD_SUBDIRECTORY(peak_controller_effect) diff --git a/plugins/opl2/CMakeLists.txt b/plugins/opl2/CMakeLists.txt new file mode 100644 index 000000000..0b9bd32cd --- /dev/null +++ b/plugins/opl2/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(OPL2 opl2instrument.cpp opl2instrument.h opl.h kemuopl.h adlibemu.c adlibemu.h fmopl.c fmopl.h temuopl.cpp temuopl.h MOCFILES opl2instrument.h EMBEDDED_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.png) \ No newline at end of file diff --git a/plugins/opl2/README b/plugins/opl2/README new file mode 100644 index 000000000..a9796c1cf --- /dev/null +++ b/plugins/opl2/README @@ -0,0 +1,62 @@ +Snatched from AdPlug 2.2.1: +adlibemu.c +adlibemu.h +fmopl.c +fmopl.h +kemuopl.h +opl.h +temuopl.cpp +temuopl.h +mididata.h + +1.2.2 Sound generation section +------------------------------ + +The Sound generation section is responsible for generating the OPL's +sound, according to the input of the Playback section. This sound is +either routed back to the application for it to do something with it or +routed directly to a specific audio hardware. + + The following headers provide the interface to the sound generation +section: `emuopl.h', `temuopl.h', `kemuopl.h', `realopl.h', +`silentopl.h', `analopl.h' and `diskopl.h'. All classes inside these +headers are derived from the abstract base class `Copl', declared +inside the file `opl.h', which provides the common interface for the +Backend layer of the Playback section. This interface is not meant for +the Frontend layer (i.e. your application). Your application, however, +has to call special methods of these classes in order to route the data +back, if there is any. + + `emuopl.h' provides the class `CEmuopl', which implements a virtual +OPL emulator, which automatically selects the best available OPL chip +emulation and type for each replayer. + + `temuopl.h' provides the class `CTEmuopl', which is a wrapper class +around Tatsuyuki Satoh's fmopl OPL2 emulator, which generates wave +audio data to be routed back to the application. + + `kemuopl.h' provides the class `CKemuopl', which is a wrapper class +around Ken Silverman's adlibemu OPL2 emulator, which generates wave +audio data to be routed back to the application. + + `realopl.h' provides the class `CRealopl', which outputs to a real +hardware OPL2 or OPL3 chip. No data is routed back to the application. +This class is currently only working on x86 hardware. + + `silentopl.h' provides the class `CSilentopl', which is a dummy +OPL2/3, which generates nothing. All data sent to it is forgotten +immediately. No data is routed back to the application. + + `analopl.h' provides the class `CAnalopl', which is the same as `CRealopl', +but also provides a 9-channel loudness pseudo-analyzer interface for +the application. The loudness data is the only data routed back to the +application. + + `diskopl.h' provides the class `CDiskopl', which is an OPL3 emulator +that does not output any sound to the soundcard, but instead writes all +received OPL commands to a file in the RdosPlay RAW format. + + +Patches can be found at: +http://cd.textfiles.com/soundsensations/SYNTH/ +(list at http://cd.textfiles.com/soundsensations/NEWMENU/SYNTH.BBS ) \ No newline at end of file diff --git a/plugins/opl2/adlibemu.c b/plugins/opl2/adlibemu.c new file mode 100644 index 000000000..c35d6da7d --- /dev/null +++ b/plugins/opl2/adlibemu.c @@ -0,0 +1,600 @@ +/* + * ADLIBEMU.C + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* +This file is a digital Adlib emulator for OPL2 and possibly OPL3 + +Features that could be added in a future version: +- Amplitude and Frequency Vibrato Bits (not hard, but a big speed hit) +- Global Keyboard Split Number Bit (need to research this one some more) +- 2nd Adlib chip for OPL3 (simply need to make my cell array bigger) +- Advanced connection modes of OPL3 (Just need to add more "docell" cases) +- L/R Stereo bits of OPL3 (Need adlibgetsample to return stereo) + +Features that aren't worth supporting: +- Anything related to adlib timers&interrupts (Sorry - I always used IRQ0) +- Composite sine wave mode (CSM) (Supported only on ancient cards) + +I'm not sure about a few things in my code: +- Attack curve. What function is this anyway? I chose to use an order-3 + polynomial to approximate but this doesn't seem right. +- Attack/Decay/Release constants - my constants may not be exact +- What should ADJUSTSPEED be? +- Haven't verified that Global Keyboard Split Number Bit works yet +- Some of the drums don't always sound right. It's pretty hard to guess + the exact waveform of drums when you look at random data which is + slightly randomized due to digital ADC recording. +- Adlib seems to have a lot more treble than my emulator does. I'm not + sure if this is simply unfixable due to the sound blaster's different + filtering on FM and digital playback or if it's a serious bug in my + code. +*/ + +#include +#include + +#if !defined(max) && !defined(__cplusplus) +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#if !defined(min) && !defined(__cplusplus) +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define PI 3.141592653589793 +#define MAXCELLS 18 +#define WAVPREC 2048 + +static float AMPSCALE=(8192.0); +#define FRQSCALE (49716/512.0) + +//Constants for Ken's Awe32, on a PII-266 (Ken says: Use these for KSM's!) +#define MODFACTOR 4.0 //How much of modulator cell goes into carrier +#define MFBFACTOR 1.0 //How much feedback goes back into modulator +#define ADJUSTSPEED 0.75 //0<=x<=1 Simulate finite rate of change of state + +//Constants for Ken's Awe64G, on a P-133 +//#define MODFACTOR 4.25 //How much of modulator cell goes into carrier +//#define MFBFACTOR 0.5 //How much feedback goes back into modulator +//#define ADJUSTSPEED 0.85 //0<=x<=1 Simulate finite rate of change of state + +typedef struct +{ + float val, t, tinc, vol, sustain, amp, mfb; + float a0, a1, a2, a3, decaymul, releasemul; + short *waveform; + long wavemask; + void (*cellfunc)(void *, float); + unsigned char flags, dum0, dum1, dum2; +} celltype; + +static long numspeakers, bytespersample; +static float recipsamp; +static celltype cell[MAXCELLS]; +static signed short wavtable[WAVPREC*3]; +static float kslmul[4] = {0.0,0.5,0.25,1.0}; +static float frqmul[16] = {.5,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15}, nfrqmul[16]; +static unsigned char adlibreg[256], ksl[8][16]; +static unsigned char modulatorbase[9] = {0,1,2,8,9,10,16,17,18}; +static unsigned char odrumstat = 0; +static unsigned char base2cell[22] = {0,1,2,0,1,2,0,0,3,4,5,3,4,5,0,0,6,7,8,6,7,8}; + +float lvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on left speaker +float rvol[9] = {1,1,1,1,1,1,1,1,1}; //Volume multiplier on right speaker +long lplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on left speaker +long rplc[9] = {0,0,0,0,0,0,0,0,0}; //Samples to delay on right speaker + +long nlvol[9], nrvol[9]; +long nlplc[9], nrplc[9]; +long rend = 0; +#define FIFOSIZ 256 +static float *rptr[9], *nrptr[9]; +static float rbuf[9][FIFOSIZ*2]; +static float snd[FIFOSIZ*2]; + +#ifndef USING_ASM +#define _inline +#endif + +#ifdef USING_ASM +static _inline void ftol (float f, long *a) +{ + _asm + { + mov eax, a + fld f + fistp dword ptr [eax] + } +} +#else +static void ftol(float f, long *a) { + *a=f; +} +#endif + +#define ctc ((celltype *)c) //A rare attempt to make code easier to read! +void docell4 (void *c, float modulator) { } +void docell3 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell2 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + if (*(long *)&ctc->amp <= 0x37800000) + { + ctc->amp = 0; + ctc->cellfunc = docell4; + } + ctc->amp *= ctc->releasemul; + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell1 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + if ((*(long *)&ctc->amp) <= (*(long *)&ctc->sustain)) + { + if (ctc->flags&32) + { + ctc->amp = ctc->sustain; + ctc->cellfunc = docell3; + } + else + ctc->cellfunc = docell2; + } + else + ctc->amp *= ctc->decaymul; + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} +void docell0 (void *c, float modulator) +{ + long i; + + ftol(ctc->t+modulator,&i); + + ctc->amp = ((ctc->a3*ctc->amp + ctc->a2)*ctc->amp + ctc->a1)*ctc->amp + ctc->a0; + if ((*(long *)&ctc->amp) > 0x3f800000) + { + ctc->amp = 1; + ctc->cellfunc = docell1; + } + + ctc->t += ctc->tinc; + ctc->val += (ctc->amp*ctc->vol*((float)ctc->waveform[i&ctc->wavemask])-ctc->val)*ADJUSTSPEED; +} + + +static long waveform[8] = {WAVPREC,WAVPREC>>1,WAVPREC,(WAVPREC*3)>>2,0,0,(WAVPREC*5)>>2,WAVPREC<<1}; +static long wavemask[8] = {WAVPREC-1,WAVPREC-1,(WAVPREC>>1)-1,(WAVPREC>>1)-1,WAVPREC-1,((WAVPREC*3)>>2)-1,WAVPREC>>1,WAVPREC-1}; +static long wavestart[8] = {0,WAVPREC>>1,0,WAVPREC>>2,0,0,0,WAVPREC>>3}; +static float attackconst[4] = {1/2.82624,1/2.25280,1/1.88416,1/1.59744}; +static float decrelconst[4] = {1/39.28064,1/31.41608,1/26.17344,1/22.44608}; +void cellon (long i, long j, celltype *c, unsigned char iscarrier) +{ + long frn, oct, toff; + float f; + + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + toff = (oct<<1) + ((frn>>9)&((frn>>8)|(((adlibreg[8]>>6)&1)^1))); + if (!(adlibreg[j+0x20]&16)) toff >>= 2; + + f = pow(2.0,(adlibreg[j+0x60]>>4)+(toff>>2)-1)*attackconst[toff&3]*recipsamp; + c->a0 = .0377*f; c->a1 = 10.73*f+1; c->a2 = -17.57*f; c->a3 = 7.42*f; + f = -7.4493*decrelconst[toff&3]*recipsamp; + c->decaymul = pow(2.0,f*pow(2.0,(adlibreg[j+0x60]&15)+(toff>>2))); + c->releasemul = pow(2.0,f*pow(2.0,(adlibreg[j+0x80]&15)+(toff>>2))); + c->wavemask = wavemask[adlibreg[j+0xe0]&7]; + c->waveform = &wavtable[waveform[adlibreg[j+0xe0]&7]]; + if (!(adlibreg[1]&0x20)) c->waveform = &wavtable[WAVPREC]; + c->t = wavestart[adlibreg[j+0xe0]&7]; + c->flags = adlibreg[j+0x20]; + c->cellfunc = docell0; + c->tinc = (float)(frn<vol = pow(2.0,((float)(adlibreg[j+0x40]&63) + + (float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14); + c->sustain = pow(2.0,(float)(adlibreg[j+0x80]>>4) * -.5); + if (!iscarrier) c->amp = 0; + c->mfb = pow(2.0,((adlibreg[i+0xc0]>>1)&7)+5)*(WAVPREC/2048.0)*MFBFACTOR; + if (!(adlibreg[i+0xc0]&14)) c->mfb = 0; + c->val = 0; +} + +//This function (and bug fix) written by Chris Moeller +void cellfreq (signed long i, signed long j, celltype *c) +{ + long frn, oct; + + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + + c->tinc = (float)(frn<vol = pow(2.0,((float)(adlibreg[j+0x40]&63) + + (float)kslmul[adlibreg[j+0x40]>>6]*ksl[oct][frn>>6]) * -.125 - 14); +} + +static long initfirstime = 0; +void adlibinit (long dasamplerate, long danumspeakers, long dabytespersample) +{ + long i, j, frn, oct; + + memset((void *)adlibreg,0,sizeof(adlibreg)); + memset((void *)cell,0,sizeof(celltype)*MAXCELLS); + memset((void *)rbuf,0,sizeof(rbuf)); + rend = 0; odrumstat = 0; + + for(i=0;i=0;i--) nfrqmul[i] = frqmul[i]*recipsamp*FRQSCALE*(WAVPREC/2048.0); + + if (!initfirstime) + { + initfirstime = 1; + + for(i=0;i<(WAVPREC>>1);i++) + { + wavtable[i] = + wavtable[(i<<1) +WAVPREC] = (signed short)(16384*sin((float)((i<<1) )*PI*2/WAVPREC)); + wavtable[(i<<1)+1+WAVPREC] = (signed short)(16384*sin((float)((i<<1)+1)*PI*2/WAVPREC)); + } + for(i=0;i<(WAVPREC>>3);i++) + { + wavtable[i+(WAVPREC<<1)] = wavtable[i+(WAVPREC>>3)]-16384; + wavtable[i+((WAVPREC*17)>>3)] = wavtable[i+(WAVPREC>>2)]+16384; + } + + //[table in book]*8/3 + ksl[7][0] = 0; ksl[7][1] = 24; ksl[7][2] = 32; ksl[7][3] = 37; + ksl[7][4] = 40; ksl[7][5] = 43; ksl[7][6] = 45; ksl[7][7] = 47; + ksl[7][8] = 48; for(i=9;i<16;i++) ksl[7][i] = i+41; + for(j=6;j>=0;j--) + for(i=0;i<16;i++) + { + oct = (long)ksl[j+1][i]-8; if (oct < 0) oct = 0; + ksl[j][i] = (unsigned char)oct; + } + } + else + { + for(i=0;i<9;i++) + { + frn = ((((long)adlibreg[i+0xb0])&3)<<8) + (long)adlibreg[i+0xa0]; + oct = ((((long)adlibreg[i+0xb0])>>2)&7); + cell[i].tinc = (float)(frn< (odrumstat&16)) //BassDrum + { + cellon(6,16,&cell[6],0); + cellon(6,19,&cell[15],1); + cell[15].vol *= 2; + } + if ((v&8) > (odrumstat&8)) //Snare + { + cellon(16,20,&cell[16],0); + cell[16].tinc *= 2*(nfrqmul[adlibreg[17+0x20]&15] / nfrqmul[adlibreg[20+0x20]&15]); + if (((adlibreg[20+0xe0]&7) >= 3) && ((adlibreg[20+0xe0]&7) <= 5)) cell[16].vol = 0; + cell[16].vol *= 2; + } + if ((v&4) > (odrumstat&4)) //TomTom + { + cellon(8,18,&cell[8],0); + cell[8].vol *= 2; + } + if ((v&2) > (odrumstat&2)) //Cymbal + { + cellon(17,21,&cell[17],0); + + cell[17].wavemask = wavemask[5]; + cell[17].waveform = &wavtable[waveform[5]]; + cell[17].tinc *= 16; cell[17].vol *= 2; + + //cell[17].waveform = &wavtable[WAVPREC]; cell[17].wavemask = 0; + //if (((adlibreg[21+0xe0]&7) == 0) || ((adlibreg[21+0xe0]&7) == 6)) + // cell[17].waveform = &wavtable[(WAVPREC*7)>>2]; + //if (((adlibreg[21+0xe0]&7) == 2) || ((adlibreg[21+0xe0]&7) == 3)) + // cell[17].waveform = &wavtable[(WAVPREC*5)>>2]; + } + if ((v&1) > (odrumstat&1)) //Hihat + { + cellon(7,17,&cell[7],0); + if (((adlibreg[17+0xe0]&7) == 1) || ((adlibreg[17+0xe0]&7) == 4) || + ((adlibreg[17+0xe0]&7) == 5) || ((adlibreg[17+0xe0]&7) == 7)) cell[7].vol = 0; + if ((adlibreg[17+0xe0]&7) == 6) { cell[7].wavemask = 0; cell[7].waveform = &wavtable[(WAVPREC*7)>>2]; } + } + + odrumstat = v; + } + else if (((unsigned)(i-0x40) < (unsigned)22) && ((i&7) < 6)) + { + if ((i&7) < 3) // Modulator + cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]]); + else // Carrier + cellfreq(base2cell[i-0x40],i-0x40,&cell[base2cell[i-0x40]+9]); + } + else if ((unsigned)(i-0xa0) < (unsigned)9) + { + cellfreq(i-0xa0,modulatorbase[i-0xa0],&cell[i-0xa0]); + cellfreq(i-0xa0,modulatorbase[i-0xa0]+3,&cell[i-0xa0+9]); + } + else if ((unsigned)(i-0xb0) < (unsigned)9) + { + if ((v&32) > (tmp&32)) + { + cellon(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0],0); + cellon(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9],1); + } + else if ((v&32) < (tmp&32)) + cell[i-0xb0].cellfunc = cell[i-0xb0+9].cellfunc = docell2; + cellfreq(i-0xb0,modulatorbase[i-0xb0],&cell[i-0xb0]); + cellfreq(i-0xb0,modulatorbase[i-0xb0]+3,&cell[i-0xb0+9]); + } + + //outdata(i,v); +} + +#ifdef USING_ASM +static long fpuasm; +static float fakeadd = 8388608.0+128.0; +static _inline void clipit8 (float f, long a) +{ + _asm + { + mov edi, a + fld dword ptr f + fadd dword ptr fakeadd + fstp dword ptr fpuasm + mov eax, fpuasm + test eax, 0x007fff00 + jz short skipit + shr eax, 16 + xor eax, -1 + skipit: mov byte ptr [edi], al + } +} + +static _inline void clipit16 (float f, long a) +{ + _asm + { + mov eax, a + fld dword ptr f + fist word ptr [eax] + cmp word ptr [eax], 0x8000 + jne short skipit2 + fst dword ptr [fpuasm] + cmp fpuasm, 0x80000000 + sbb word ptr [eax], 0 + skipit2: fstp st + } +} +#else +static void clipit8(float f,unsigned char *a) { + f/=256.0; + f+=128.0; + if (f>254.5) *a=255; + else if (f<0.5) *a=0; + else *a=f; +} + +static void clipit16(float f,short *a) { + if (f>32766.5) *a=32767; + else if (f<-32767.5) *a=-32768; + else *a=f; +} +#endif + +void adlibsetvolume(int i) { + AMPSCALE=i; +} + +void adlibgetsample (unsigned char *sndptr, long numbytes) +{ + long i, j, k=0, ns, endsamples, rptrs, numsamples; + celltype *cptr; + float f; + short *sndptr2=(short *)sndptr; + + numsamples = (numbytes>>(numspeakers+bytespersample-2)); + + if (bytespersample == 1) f = AMPSCALE/256.0; else f = AMPSCALE; + if (numspeakers == 1) + { + nlvol[0] = lvol[0]*f; + for(i=0;i<9;i++) rptr[i] = &rbuf[0][0]; + rptrs = 1; + } + else + { + rptrs = 0; + for(i=0;i<9;i++) + { + if ((!i) || (lvol[i] != lvol[i-1]) || (rvol[i] != rvol[i-1]) || + (lplc[i] != lplc[i-1]) || (rplc[i] != rplc[i-1])) + { + nlvol[rptrs] = lvol[i]*f; + nrvol[rptrs] = rvol[i]*f; + nlplc[rptrs] = rend-min(max(lplc[i],0),FIFOSIZ); + nrplc[rptrs] = rend-min(max(rplc[i],0),FIFOSIZ); + rptrs++; + } + rptr[i] = &rbuf[rptrs-1][0]; + } + } + + + //CPU time used to be somewhat less when emulator was only mono! + // Because of no delay fifos! + + for(ns=0;ns>1)-1)); //Snare + (cell[7].cellfunc)((void *)&cell[7],k&(WAVPREC-1)); //Hihat + (cell[17].cellfunc)((void *)&cell[17],k&((WAVPREC>>3)-1)); //Cymbal + (cell[8].cellfunc)((void *)&cell[8],0.0); //TomTom + nrptr[7][i] += cell[7].val + cell[16].val; + nrptr[8][i] += cell[8].val + cell[17].val; + } + } + } + for(j=9-1;j>=0;j--) + { + if ((adlibreg[0xbd]&0x20) && (j >= 6) && (j < 9)) continue; + + cptr = &cell[j]; k = j; + if (adlibreg[0xc0+k]&1) + { + if ((cptr[9].cellfunc == docell4) && (cptr->cellfunc == docell4)) continue; + for(i=0;icellfunc)((void *)cptr,cptr->val*cptr->mfb); + (cptr->cellfunc)((void *)&cptr[9],0); + nrptr[j][i] += cptr[9].val + cptr->val; + } + } + else + { + if (cptr[9].cellfunc == docell4) continue; + for(i=0;icellfunc)((void *)cptr,cptr->val*cptr->mfb); + (cptr[9].cellfunc)((void *)&cptr[9],cptr->val*WAVPREC*MODFACTOR); + nrptr[j][i] += cptr[9].val; + } + } + } + + if (numspeakers == 1) + { + if (bytespersample == 1) + { + for(i=endsamples-1;i>=0;i--) + clipit8(nrptr[0][i]*nlvol[0],sndptr+1); + } + else + { + for(i=endsamples-1;i>=0;i--) + clipit16(nrptr[0][i]*nlvol[0],sndptr2+i); + } + } + else + { + memset((void *)snd,0,endsamples*sizeof(float)*2); + for(j=0;j=0;i--) + clipit8(snd[i],sndptr+i); + } + else + { + for(i=(endsamples<<1)-1;i>=0;i--) + clipit16(snd[i],sndptr2+i); + } + } + + sndptr = sndptr+(numspeakers*endsamples); + sndptr2 = sndptr2+(numspeakers*endsamples); + rend = ((rend+endsamples)&(FIFOSIZ*2-1)); + } +} diff --git a/plugins/opl2/adlibemu.h b/plugins/opl2/adlibemu.h new file mode 100644 index 000000000..8600d787d --- /dev/null +++ b/plugins/opl2/adlibemu.h @@ -0,0 +1,26 @@ +/* + * ADLIBEMU.H + * Copyright (C) 1998-2001 Ken Silverman + * Ken Silverman's official web site: "http://www.advsys.net/ken" + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +void adlibinit(long dasamplerate,long danumspeakers,long dabytespersample); +void adlib0(long i,long v); +void adlibgetsample(void *sndptr,long numbytes); +void adlibsetvolume(int i); +void randoinsts(); +extern float lvol[9],rvol[9],lplc[9],rplc[9]; diff --git a/plugins/opl2/artwork.png b/plugins/opl2/artwork.png new file mode 100644 index 0000000000000000000000000000000000000000..2aaf32ebb191708c14ac489f1e6033ae4e898683 GIT binary patch literal 116949 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Ě^ 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 +#include +#include +#include +#include +//#include "driver.h" /* use M.A.M.E. */ +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +/* -------------------- for debug --------------------- */ +/* #define OPL_OUTPUT_LOG */ +#ifdef OPL_OUTPUT_LOG +static FILE *opl_dbg_fp = NULL; +static FM_OPL *opl_dbg_opl[16]; +static int opl_dbg_maxchip,opl_dbg_chip; +#endif + +/* -------------------- preliminary define section --------------------- */ +/* attack/decay rate time rate */ +#define OPL_ARRATE 141280 /* RATE 4 = 2826.24ms @ 3.6MHz */ +#define OPL_DRRATE 1956000 /* RATE 4 = 39280.64ms @ 3.6MHz */ + +#define DELTAT_MIXING_LEVEL (1) /* DELTA-T ADPCM MIXING LEVEL */ + +#define FREQ_BITS 24 /* frequency turn */ + +/* counter bits = 20 , octerve 7 */ +#define FREQ_RATE (1<<(FREQ_BITS-20)) +#define TL_BITS (FREQ_BITS+2) + +/* final output shift , limit minimum and maximum */ +#define OPL_OUTSB (TL_BITS+3-16) /* OPL output final shift 16bit */ +#define OPL_MAXOUT (0x7fff<=LOG_LEVEL ) logerror x +#define LOG(n,x) + +/* --------------------- subroutines --------------------- */ + +INLINE int Limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + +/* status set and IRQ handling */ +INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) +{ + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) + { + if(OPL->status & OPL->statusmask) + { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) +{ + /* reset status flag */ + OPL->status &=~flag; + if((OPL->status & 0x80)) + { + if (!(OPL->status & OPL->statusmask) ) + { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + +/* ----- key on ----- */ +INLINE void OPL_KEYON(OPL_SLOT *SLOT) +{ + /* sin wave restart */ + SLOT->Cnt = 0; + /* set attack */ + SLOT->evm = ENV_MOD_AR; + SLOT->evs = SLOT->evsa; + SLOT->evc = EG_AST; + SLOT->eve = EG_AED; +} +/* ----- key off ----- */ +INLINE void OPL_KEYOFF(OPL_SLOT *SLOT) +{ + if( SLOT->evm > ENV_MOD_RR) + { + /* set envelope counter from envleope output */ + SLOT->evm = ENV_MOD_RR; + if( !(SLOT->evc&EG_DST) ) + //SLOT->evc = (ENV_CURVE[SLOT->evc>>ENV_BITS]<evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +INLINE UINT32 OPL_CALC_SLOT( OPL_SLOT *SLOT ) +{ + /* calcrate envelope generator */ + if( (SLOT->evc+=SLOT->evs) >= SLOT->eve ) + { + switch( SLOT->evm ){ + case ENV_MOD_AR: /* ATTACK -> DECAY1 */ + /* next DR */ + SLOT->evm = ENV_MOD_DR; + SLOT->evc = EG_DST; + SLOT->eve = SLOT->SL; + SLOT->evs = SLOT->evsd; + break; + case ENV_MOD_DR: /* DECAY -> SL or RR */ + SLOT->evc = SLOT->SL; + SLOT->eve = EG_DED; + if(SLOT->eg_typ) + { + SLOT->evs = 0; + } + else + { + SLOT->evm = ENV_MOD_RR; + SLOT->evs = SLOT->evsr; + } + break; + case ENV_MOD_RR: /* RR -> OFF */ + SLOT->evc = EG_OFF; + SLOT->eve = EG_OFF+1; + SLOT->evs = 0; + break; + } + } + /* calcrate envelope */ + return SLOT->TLL+ENV_CURVE[SLOT->evc>>ENV_BITS]+(SLOT->ams ? ams : 0); +} + +/* set algorythm connection */ +static void set_algorythm( OPL_CH *CH) +{ + INT32 *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +INLINE void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) +{ + int ksr; + + /* frequency step counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + /* attack , decay rate recalcration */ + SLOT->evsa = SLOT->AR[ksr]; + SLOT->evsd = SLOT->DR[ksr]; + SLOT->evsr = SLOT->RR[ksr]; + } + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = MUL_TABLE[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_typ = (v&0x20)>>5; + SLOT->vib = (v&0x40); + SLOT->ams = (v&0x80); + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl & tl */ +INLINE void set_ksl_tl(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ksl = v>>6; /* 0 / 1.5 / 3 / 6 db/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)*(0.75/EG_STEP); /* 0.75db step */ + + if( !(OPL->mode&0x80) ) + { /* not CSM latch total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + } +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ar = v>>4; + int dr = v&0x0f; + + SLOT->AR = ar ? &OPL->AR_TABLE[ar<<2] : RATE_0; + SLOT->evsa = SLOT->AR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_AR ) SLOT->evs = SLOT->evsa; + + SLOT->DR = dr ? &OPL->DR_TABLE[dr<<2] : RATE_0; + SLOT->evsd = SLOT->DR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->evs = SLOT->evsd; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int sl = v>>4; + int rr = v & 0x0f; + + SLOT->SL = SL_TABLE[sl]; + if( SLOT->evm == ENV_MOD_DR ) SLOT->eve = SLOT->SL; + SLOT->RR = &OPL->DR_TABLE[rr<<2]; + SLOT->evsr = SLOT->RR[SLOT->ksr]; + if( SLOT->evm == ENV_MOD_RR ) SLOT->evs = SLOT->evsr; +} + +/* operator output calcrator */ +#define OP_OUT(slot,env,con) slot->wavetable[((slot->Cnt+con)/(0x1000000/SIN_ENT))&(SIN_ENT-1)][env] +/* ---------- calcrate one of channel ---------- */ +INLINE void OPL_CALC_CH( OPL_CH *CH ) +{ + UINT32 env_out; + OPL_SLOT *SLOT; + + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH->FB) + { + int feedback1 = (CH->op1_out[0]+CH->op1_out[1])>>CH->FB; + CH->op1_out[1] = CH->op1_out[0]; + *CH->connect1 += CH->op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + *CH->connect1 += OP_OUT(SLOT,env_out,0); + } + }else + { + CH->op1_out[1] = CH->op1_out[0]; + CH->op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH->SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2); + } +} + +/* ---------- calcrate rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +INLINE void OPL_CALC_RH( OPL_CH *CH ) +{ + UINT32 env_tam,env_sd,env_top,env_hh; + int whitenoise = (rand()&1)*(WHITE_NOISE_db/EG_STEP); + INT32 tone8; + + OPL_SLOT *SLOT; + int env_out; + + /* BD : same as FM serial mode and output level is large */ + feedback2 = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + if(CH[6].FB) + { + int feedback1 = (CH[6].op1_out[0]+CH[6].op1_out[1])>>CH[6].FB; + CH[6].op1_out[1] = CH[6].op1_out[0]; + feedback2 = CH[6].op1_out[0] = OP_OUT(SLOT,env_out,feedback1); + } + else + { + feedback2 = OP_OUT(SLOT,env_out,0); + } + }else + { + feedback2 = 0; + CH[6].op1_out[1] = CH[6].op1_out[0]; + CH[6].op1_out[0] = 0; + } + /* SLOT 2 */ + SLOT = &CH[6].SLOT[SLOT2]; + env_out=OPL_CALC_SLOT(SLOT); + if( env_out < EG_ENT-1 ) + { + /* PG */ + if(SLOT->vib) SLOT->Cnt += (SLOT->Incr*vib/VIB_RATE); + else SLOT->Cnt += SLOT->Incr; + /* connectoion */ + outd[0] += OP_OUT(SLOT,env_out, feedback2)*2; + } + + // SD (17) = mul14[fnum7] + white noise + // TAM (15) = mul15[fnum8] + // TOP (18) = fnum6(mul18[fnum8]+whitenoise) + // HH (14) = fnum7(mul18[fnum8]+whitenoise) + white noise + env_sd =OPL_CALC_SLOT(SLOT7_2) + whitenoise; + env_tam=OPL_CALC_SLOT(SLOT8_1); + env_top=OPL_CALC_SLOT(SLOT8_2); + env_hh =OPL_CALC_SLOT(SLOT7_1) + whitenoise; + + /* PG */ + if(SLOT7_1->vib) SLOT7_1->Cnt += (2*SLOT7_1->Incr*vib/VIB_RATE); + else SLOT7_1->Cnt += 2*SLOT7_1->Incr; + if(SLOT7_2->vib) SLOT7_2->Cnt += ((CH[7].fc*8)*vib/VIB_RATE); + else SLOT7_2->Cnt += (CH[7].fc*8); + if(SLOT8_1->vib) SLOT8_1->Cnt += (SLOT8_1->Incr*vib/VIB_RATE); + else SLOT8_1->Cnt += SLOT8_1->Incr; + if(SLOT8_2->vib) SLOT8_2->Cnt += ((CH[8].fc*48)*vib/VIB_RATE); + else SLOT8_2->Cnt += (CH[8].fc*48); + + tone8 = OP_OUT(SLOT8_2,whitenoise,0 ); + + /* SD */ + if( env_sd < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_1,env_sd, 0)*8; + /* TAM */ + if( env_tam < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT8_1,env_tam, 0)*2; + /* TOP-CY */ + if( env_top < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_top,tone8)*2; + /* HH */ + if( env_hh < EG_ENT-1 ) + outd[0] += OP_OUT(SLOT7_2,env_hh,tone8)*2; +} + +/* ----------- initialize time tabls ----------- */ +static void init_timetables( FM_OPL *OPL , int ARRATE , int DRRATE ) +{ + int i; + double rate; + + /* make attack rate & decay rate tables */ + for (i = 0;i < 4;i++) OPL->AR_TABLE[i] = OPL->DR_TABLE[i] = 0; + for (i = 4;i <= 60;i++){ + rate = OPL->freqbase; /* frequency rate */ + if( i < 60 ) rate *= 1.0+(i&3)*0.25; /* b0-1 : x1 , x1.25 , x1.5 , x1.75 */ + rate *= 1<<((i>>2)-1); /* b2-5 : shift bit */ + rate *= (double)(EG_ENT<AR_TABLE[i] = rate / ARRATE; + OPL->DR_TABLE[i] = rate / DRRATE; + } + for (i = 60;i < 76;i++) + { + OPL->AR_TABLE[i] = EG_AED-1; + OPL->DR_TABLE[i] = OPL->DR_TABLE[60]; + } +#if 0 + for (i = 0;i < 64 ;i++){ /* make for overflow area */ + LOG(LOG_WAR,("rate %2d , ar %f ms , dr %f ms \n",i, + ((double)(EG_ENT<AR_TABLE[i]) * (1000.0 / OPL->rate), + ((double)(EG_ENT<DR_TABLE[i]) * (1000.0 / OPL->rate) )); + } +#endif +} + +/* ---------- generic table initialize ---------- */ +static int OPLOpenTable( void ) +{ + int s,t; + double rate; + int i,j; + double pom; + + /* allocate dynamic tables */ + if( (TL_TABLE = malloc(TL_MAX*2*sizeof(INT32))) == NULL) + return 0; + if( (SIN_TABLE = malloc(SIN_ENT*4 *sizeof(INT32 *))) == NULL) + { + free(TL_TABLE); + return 0; + } + if( (AMS_TABLE = malloc(AMS_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + return 0; + } + if( (VIB_TABLE = malloc(VIB_ENT*2 *sizeof(INT32))) == NULL) + { + free(TL_TABLE); + free(SIN_TABLE); + free(AMS_TABLE); + return 0; + } + /* make total level table */ + for (t = 0;t < EG_ENT-1 ;t++){ + rate = ((1< voltage */ + TL_TABLE[ t] = (int)rate; + TL_TABLE[TL_MAX+t] = -TL_TABLE[t]; +/* LOG(LOG_INF,("TotalLevel(%3d) = %x\n",t,TL_TABLE[t]));*/ + } + /* fill volume off area */ + for ( t = EG_ENT-1; t < TL_MAX ;t++){ + TL_TABLE[t] = TL_TABLE[TL_MAX+t] = 0; + } + + /* make sinwave table (total level offet) */ + /* degree 0 = degree 180 = off */ + SIN_TABLE[0] = SIN_TABLE[SIN_ENT/2] = &TL_TABLE[EG_ENT-1]; + for (s = 1;s <= SIN_ENT/4;s++){ + pom = sin(2*PI*s/SIN_ENT); /* sin */ + pom = 20*log10(1/pom); /* decibel */ + j = pom / EG_STEP; /* TL_TABLE steps */ + + /* degree 0 - 90 , degree 180 - 90 : plus section */ + SIN_TABLE[ s] = SIN_TABLE[SIN_ENT/2-s] = &TL_TABLE[j]; + /* degree 180 - 270 , degree 360 - 270 : minus section */ + SIN_TABLE[SIN_ENT/2+s] = SIN_TABLE[SIN_ENT -s] = &TL_TABLE[TL_MAX+j]; +/* LOG(LOG_INF,("sin(%3d) = %f:%f db\n",s,pom,(double)j * EG_STEP));*/ + } + for (s = 0;s < SIN_ENT;s++) + { + SIN_TABLE[SIN_ENT*1+s] = s<(SIN_ENT/2) ? SIN_TABLE[s] : &TL_TABLE[EG_ENT]; + SIN_TABLE[SIN_ENT*2+s] = SIN_TABLE[s % (SIN_ENT/2)]; + SIN_TABLE[SIN_ENT*3+s] = (s/(SIN_ENT/4))&1 ? &TL_TABLE[EG_ENT] : SIN_TABLE[SIN_ENT*2+s]; + } + + /* envelope counter -> envelope output table */ + for (i=0; i= EG_ENT ) pom = EG_ENT-1; */ + ENV_CURVE[i] = (int)pom; + /* DECAY ,RELEASE curve */ + ENV_CURVE[(EG_DST>>ENV_BITS)+i]= i; + } + /* off */ + ENV_CURVE[EG_OFF>>ENV_BITS]= EG_ENT-1; + /* make LFO ams table */ + for (i=0; iSLOT[SLOT1]; + OPL_SLOT *slot2 = &CH->SLOT[SLOT2]; + /* all key off */ + OPL_KEYOFF(slot1); + OPL_KEYOFF(slot2); + /* total level latch */ + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + slot1->TLL = slot1->TL + (CH->ksl_base>>slot1->ksl); + /* key on */ + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(slot1); + OPL_KEYON(slot2); +} + +/* ---------- opl initialize ---------- */ +static void OPL_initalize(FM_OPL *OPL) +{ + int fn; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / OPL->rate) / 72 : 0; + /* Timer base time */ + OPL->TimerBase = 1.0/((double)OPL->clock / 72.0 ); + /* make time tables */ + init_timetables( OPL , OPL_ARRATE , OPL_DRRATE ); + /* make fnumber -> increment counter table */ + for( fn=0 ; fn < 1024 ; fn++ ) + { + OPL->FN_TABLE[fn] = OPL->freqbase * fn * FREQ_RATE * (1<<7) / 2; + } + /* LFO freq.table */ + OPL->amsIncr = OPL->rate ? (double)AMS_ENT*(1<rate * 3.7 * ((double)OPL->clock/3600000) : 0; + OPL->vibIncr = OPL->rate ? (double)VIB_ENT*(1<rate * 6.4 * ((double)OPL->clock/3600000) : 0; +} + +/* ---------- write a OPL registers ---------- */ +static void OPLWriteReg(FM_OPL *OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + int block_fnum; + + switch(r&0xe0) + { + case 0x00: /* 00-1f:controll */ + switch(r&0x1f) + { + case 0x01: + /* wave selector enable */ + if(OPL->type&OPL_TYPE_WAVESEL) + { + OPL->wavesel = v&0x20; + if(!OPL->wavesel) + { + /* preset compatible mode */ + int c; + for(c=0;cmax_ch;c++) + { + OPL->P_CH[c].SLOT[SLOT1].wavetable = &SIN_TABLE[0]; + OPL->P_CH[c].SLOT[SLOT2].wavetable = &SIN_TABLE[0]; + } + } + } + return; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v)*4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v)*16; + return; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v&0x80) + { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL,0x7f); + } + else + { /* set IRQ mask ,timer enable*/ + UINT8 st1 = v&1; + UINT8 st2 = (v>>1)&1; + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL,v&0x78); + OPL_STATUSMASK_SET(OPL,((~v)&0x78)|0x01); + /* timer 2 */ + if(OPL->st[1] != st2) + { + double interval = st2 ? (double)OPL->T[1]*OPL->TimerBase : 0.0; + OPL->st[1] = st2; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+1,interval); + } + /* timer 1 */ + if(OPL->st[0] != st1) + { + double interval = st1 ? (double)OPL->T[0]*OPL->TimerBase : 0.0; + OPL->st[0] = st1; + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+0,interval); + } + } + return; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_w) + OPL->keyboardhandler_w(OPL->keyboard_param,v); + else + LOG(LOG_WAR,("OPL:write unmapped KEYBOARD port\n")); + } + return; + case 0x07: /* DELTA-T controll : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; + case 0x08: /* MODE,DELTA-T : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; + v&=0x1f; /* for DELTA-T unit */ + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* EG-CTRL */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + return; +#if 0 + case 0x15: /* DAC data */ + case 0x16: + case 0x17: /* SHIFT */ + return; + case 0x18: /* I/O CTRL (Direction) */ + if(OPL->type&OPL_TYPE_IO) + OPL->portDirection = v&0x0f; + return; + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + OPL->portLatch = v; + if(OPL->porthandler_w) + OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); + } + return; + case 0x1a: /* PCM data */ + return; +#endif +#endif + } + break; + case 0x20: /* am,vib,ksr,eg type,mul */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_mul(OPL,slot,v); + return; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ksl_tl(OPL,slot,v); + return; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_ar_dr(OPL,slot,v); + return; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot == -1) return; + set_sl_rr(OPL,slot,v); + return; + case 0xa0: + switch(r) + { + case 0xbd: + /* amsep,vibdep,r,bd,sd,tom,tc,hh */ + { + UINT8 rkey = OPL->rythm^v; + OPL->ams_table = &AMS_TABLE[v&0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v&0x40 ? VIB_ENT : 0]; + OPL->rythm = v&0x3f; + if(OPL->rythm&0x20) + { +#if 0 + usrintf_showmessage("OPL Rythm mode select"); +#endif + /* BD key on/off */ + if(rkey&0x10) + { + if(v&0x10) + { + OPL->P_CH[6].op1_out[0] = OPL->P_CH[6].op1_out[1] = 0; + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYON(&OPL->P_CH[6].SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1]); + OPL_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2]); + } + } + /* SD key on/off */ + if(rkey&0x08) + { + if(v&0x08) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2]); + }/* TAM key on/off */ + if(rkey&0x04) + { + if(v&0x04) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1]); + } + /* TOP-CY key on/off */ + if(rkey&0x02) + { + if(v&0x02) OPL_KEYON(&OPL->P_CH[8].SLOT[SLOT2]); + else OPL_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2]); + } + /* HH key on/off */ + if(rkey&0x01) + { + if(v&0x01) OPL_KEYON(&OPL->P_CH[7].SLOT[SLOT1]); + else OPL_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1]); + } + } + } + return; + } + /* keyon,block,fnum */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + if(!(r&0x10)) + { /* a0-a8 */ + block_fnum = (CH->block_fnum&0x1f00) | v; + } + else + { /* b0-b8 */ + int keyon = (v>>5)&1; + block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); + if(CH->keyon != keyon) + { + if( (CH->keyon=keyon) ) + { + CH->op1_out[0] = CH->op1_out[1] = 0; + OPL_KEYON(&CH->SLOT[SLOT1]); + OPL_KEYON(&CH->SLOT[SLOT2]); + } + else + { + OPL_KEYOFF(&CH->SLOT[SLOT1]); + OPL_KEYOFF(&CH->SLOT[SLOT2]); + } + } + } + /* update */ + if(CH->block_fnum != block_fnum) + { + int blockRv = 7-(block_fnum>>10); + int fnum = block_fnum&0x3ff; + CH->block_fnum = block_fnum; + + CH->ksl_base = KSL_TABLE[block_fnum>>6]; + CH->fc = OPL->FN_TABLE[fnum]>>blockRv; + CH->kcode = CH->block_fnum>>9; + if( (OPL->mode&0x40) && CH->block_fnum&0x100) CH->kcode |=1; + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + return; + case 0xc0: + /* FB,C */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + { + int feedback = (v>>1)&7; + CH->FB = feedback ? (8+1) - feedback : 0; + CH->CON = v&1; + set_algorythm(CH); + } + return; + case 0xe0: /* wave type */ + slot = slot_array[r&0x1f]; + if(slot == -1) return; + CH = &OPL->P_CH[slot/2]; + if(OPL->wavesel) + { + /* LOG(LOG_INF,("OPL SLOT %d wave select %d\n",slot,v&3)); */ + CH->SLOT[slot&1].wavetable = &SIN_TABLE[(v&0x03)*SIN_ENT]; + } + return; + } +} + +/* lock/unlock for common table */ +static int OPL_LockTable(void) +{ + num_lock++; + if(num_lock>1) return 0; + /* first time */ + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if( !OPLOpenTable() ) + { + num_lock--; + return -1; + } + return 0; +} + +static void OPL_UnLockTable(void) +{ + if(num_lock) num_lock--; + if(num_lock) return; + /* last time */ + cur_chip = NULL; + OPLCloseTable(); +} + +#if (BUILD_YM3812 || BUILD_YM3526) +/*******************************************************************************/ +/* YM3812 local section */ +/*******************************************************************************/ + +/* ---------- update one of chip ----------- */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length) +{ + int i; + int data; + OPLSAMPLE *buf = buffer; + UINT32 amsCnt = OPL->amsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm&0x20; + OPL_CH *CH,*R_CH; + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chipamsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm&0x20; + OPL_CH *CH,*R_CH; + YM_DELTAT *DELTAT = OPL->deltat; + + /* setup DELTA-T unit */ + YM_DELTAT_DECODE_PRESET(DELTAT); + + if( (void *)OPL != cur_chip ){ + cur_chip = (void *)OPL; + /* channel pointers */ + S_CH = OPL->P_CH; + E_CH = &S_CH[9]; + /* rythm slot */ + SLOT7_1 = &S_CH[7].SLOT[SLOT1]; + SLOT7_2 = &S_CH[7].SLOT[SLOT2]; + SLOT8_1 = &S_CH[8].SLOT[SLOT1]; + SLOT8_2 = &S_CH[8].SLOT[SLOT2]; + /* LFO state */ + amsIncr = OPL->amsIncr; + vibIncr = OPL->vibIncr; + ams_table = OPL->ams_table; + vib_table = OPL->vib_table; + } + R_CH = rythm ? &S_CH[6] : E_CH; + for( i=0; i < length ; i++ ) + { + /* channel A channel B channel C */ + /* LFO */ + ams = ams_table[(amsCnt+=amsIncr)>>AMS_SHIFT]; + vib = vib_table[(vibCnt+=vibIncr)>>VIB_SHIFT]; + outd[0] = 0; + /* deltaT ADPCM */ + if( DELTAT->portstate ) + YM_DELTAT_ADPCM_CALC(DELTAT); + /* FM part */ + for(CH=S_CH ; CH < R_CH ; CH++) + OPL_CALC_CH(CH); + /* Rythn part */ + if(rythm) + OPL_CALC_RH(S_CH); + /* limit check */ + data = Limit( outd[0] , OPL_MAXOUT, OPL_MINOUT ); + /* store to sound buffer */ + buf[i] = data >> OPL_OUTSB; + } + OPL->amsCnt = amsCnt; + OPL->vibCnt = vibCnt; + /* deltaT START flag */ + if( !DELTAT->portstate ) + OPL->status &= 0xfe; +} +#endif + +/* ---------- reset one of chip ---------- */ +void OPLResetChip(FM_OPL *OPL) +{ + int c,s; + int i; + + /* reset chip */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL,0x7f); + /* reset with register write */ + OPLWriteReg(OPL,0x01,0); /* wabesel disable */ + OPLWriteReg(OPL,0x02,0); /* Timer1 */ + OPLWriteReg(OPL,0x03,0); /* Timer2 */ + OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ + for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); + /* reset OPerator paramater */ + for( c = 0 ; c < OPL->max_ch ; c++ ) + { + OPL_CH *CH = &OPL->P_CH[c]; + /* OPL->P_CH[c].PAN = OPN_CENTER; */ + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = &SIN_TABLE[0]; + /* CH->SLOT[s].evm = ENV_MOD_RR; */ + CH->SLOT[s].evc = EG_OFF; + CH->SLOT[s].eve = EG_OFF+1; + CH->SLOT[s].evs = 0; + } + } +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = outd; + DELTAT->portshift = 5; + DELTAT->output_range = DELTAT_MIXING_LEVEL<P_CH = (OPL_CH *)ptr; ptr+=sizeof(OPL_CH)*max_ch; +#if BUILD_Y8950 + if(type&OPL_TYPE_ADPCM) OPL->deltat = (YM_DELTAT *)ptr; ptr+=sizeof(YM_DELTAT); +#endif + /* set channel state pointer */ + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + OPL->max_ch = max_ch; + /* init grobal tables */ + OPL_initalize(OPL); + /* reset chip */ + OPLResetChip(OPL); +#ifdef OPL_OUTPUT_LOG + if(!opl_dbg_fp) + { + opl_dbg_fp = fopen("opllog.opl","wb"); + opl_dbg_maxchip = 0; + } + if(opl_dbg_fp) + { + opl_dbg_opl[opl_dbg_maxchip] = OPL; + fprintf(opl_dbg_fp,"%c%c%c%c%c%c",0x00+opl_dbg_maxchip, + type, + clock&0xff, + (clock/0x100)&0xff, + (clock/0x10000)&0xff, + (clock/0x1000000)&0xff); + opl_dbg_maxchip++; + } +#endif + return OPL; +} + +/* ---------- Destroy one of vietual YM3812 ---------- */ +void OPLDestroy(FM_OPL *OPL) +{ +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + fclose(opl_dbg_fp); + opl_dbg_fp = NULL; + } +#endif + OPL_UnLockTable(); + free(OPL); +} + +/* ---------- Option handlers ---------- */ + +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset) +{ + OPL->TimerHandler = TimerHandler; + OPL->TimerParam = channelOffset; +} +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} +#if BUILD_Y8950 +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param) +{ + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param) +{ + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} +#endif +/* ---------- YM3812 I/O interface ---------- */ +int OPLWrite(FM_OPL *OPL,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + OPL->address = v & 0xff; + } + else + { /* data port */ + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); +#ifdef OPL_OUTPUT_LOG + if(opl_dbg_fp) + { + for(opl_dbg_chip=0;opl_dbg_chipaddress,v); + } +#endif + OPLWriteReg(OPL,OPL->address,v); + } + return OPL->status>>7; +} + +unsigned char OPLRead(FM_OPL *OPL,int a) +{ + if( !(a&1) ) + { /* status port */ + return OPL->status & (OPL->statusmask|0x80); + } + /* data port */ + switch(OPL->address) + { + case 0x05: /* KeyBoard IN */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_r) + return OPL->keyboardhandler_r(OPL->keyboard_param); + else + LOG(LOG_WAR,("OPL:read unmapped KEYBOARD port\n")); + } + return 0; +#if 0 + case 0x0f: /* ADPCM-DATA */ + return 0; +#endif + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + if(OPL->porthandler_r) + return OPL->porthandler_r(OPL->port_param); + else + LOG(LOG_WAR,("OPL:read unmapped I/O port\n")); + } + return 0; + case 0x1a: /* PCM-DATA */ + return 0; + } + return 0; +} + +int OPLTimerOver(FM_OPL *OPL,int c) +{ + if( c ) + { /* Timer B */ + OPL_STATUS_SET(OPL,0x20); + } + else + { /* Timer A */ + OPL_STATUS_SET(OPL,0x40); + /* CSM mode key,TL controll */ + if( OPL->mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch=0;ch<9;ch++) + CSMKeyControll( &OPL->P_CH[ch] ); + } + } + /* reload timer */ + if (OPL->TimerHandler) (OPL->TimerHandler)(OPL->TimerParam+c,(double)OPL->T[c]*OPL->TimerBase); + return OPL->status>>7; +} diff --git a/plugins/opl2/fmopl.h b/plugins/opl2/fmopl.h new file mode 100644 index 000000000..a01ff902c --- /dev/null +++ b/plugins/opl2/fmopl.h @@ -0,0 +1,174 @@ +#ifndef __FMOPL_H_ +#define __FMOPL_H_ + +/* --- select emulation chips --- */ +#define BUILD_YM3812 (HAS_YM3812) +//#define BUILD_YM3526 (HAS_YM3526) +//#define BUILD_Y8950 (HAS_Y8950) + +/* --- system optimize --- */ +/* select bit size of output : 8 or 16 */ +#define OPL_OUTPUT_BIT 16 + +/* compiler dependence */ +#ifndef OSD_CPU_H +#define OSD_CPU_H +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +#endif + +#if (OPL_OUTPUT_BIT==16) +typedef INT16 OPLSAMPLE; +#endif +#if (OPL_OUTPUT_BIT==8) +typedef unsigned char OPLSAMPLE; +#endif + + +#if BUILD_Y8950 +#include "ymdeltat.h" +#endif + +typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); +typedef void (*OPL_IRQHANDLER)(int param,int irq); +typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); +typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); +typedef unsigned char (*OPL_PORTHANDLER_R)(int param); + +/* !!!!! here is private section , do not access there member direct !!!!! */ + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* Saving is necessary for member of the 'R' mark for suspend/resume */ +/* ---------- OPL one of slot ---------- */ +typedef struct fm_opl_slot { + INT32 TL; /* total level :TL << 8 */ + INT32 TLL; /* adjusted now TL */ + UINT8 KSR; /* key scale rate :(shift down bit) */ + INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */ + INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */ + INT32 SL; /* sustin level :SL_TALBE[SL] */ + INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */ + UINT8 ksl; /* keyscale level :(shift down bits) */ + UINT8 ksr; /* key scale rate :kcode>>KSR */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + UINT32 Cnt; /* frequency count : */ + UINT32 Incr; /* frequency step : */ + /* envelope generator state */ + UINT8 eg_typ; /* envelope type flag */ + UINT8 evm; /* envelope phase */ + INT32 evc; /* envelope counter */ + INT32 eve; /* envelope counter end point */ + INT32 evs; /* envelope counter step */ + INT32 evsa; /* envelope step for AR :AR[ksr] */ + INT32 evsd; /* envelope step for DR :DR[ksr] */ + INT32 evsr; /* envelope step for RR :RR[ksr] */ + /* LFO */ + UINT8 ams; /* ams flag */ + UINT8 vib; /* vibrate flag */ + /* wave selector */ + INT32 **wavetable; +}OPL_SLOT; + +/* ---------- OPL one of channel ---------- */ +typedef struct fm_opl_channel { + OPL_SLOT SLOT[2]; + UINT8 CON; /* connection type */ + UINT8 FB; /* feed back :(shift down bit) */ + INT32 *connect1; /* slot1 output pointer */ + INT32 *connect2; /* slot2 output pointer */ + INT32 op1_out[2]; /* slot1 output for selfeedback */ + /* phase generator state */ + UINT32 block_fnum; /* block+fnum : */ + UINT8 kcode; /* key code : KeyScaleCode */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 keyon; /* key on/off flag */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + UINT8 type; /* chip type */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + double TimerBase; /* Timer base time (==sampling time) */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT32 mode; /* Reg.08 : CSM , notesel,etc. */ + /* Timer */ + int T[2]; /* timer counter */ + UINT8 st[2]; /* timer enable */ + /* FM channel slots */ + OPL_CH *P_CH; /* pointer of CH */ + int max_ch; /* maximum channel */ + /* Rythm sention */ + UINT8 rythm; /* Rythm mode , key flag */ +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + YM_DELTAT *deltat; /* DELTA-T ADPCM */ +#endif + /* Keyboard / I/O interface unit (Y8950) */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + int port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + int keyboard_param; + /* time tables */ + INT32 AR_TABLE[75]; /* atttack rate tables */ + INT32 DR_TABLE[75]; /* decay rate tables */ + UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */ + /* LFO */ + INT32 *ams_table; + INT32 *vib_table; + INT32 amsCnt; + INT32 amsIncr; + INT32 vibCnt; + INT32 vibIncr; + /* wave selector enable flag */ + UINT8 wavesel; + /* external event callback handler */ + OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ + int TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + int IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + int UpdateParam; /* stream update parameter */ +} FM_OPL; + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + +FM_OPL *OPLCreate(int type, int clock, int rate); +void OPLDestroy(FM_OPL *OPL); +void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset); +void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param); +void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param); +/* Y8950 port handlers */ +void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param); +void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param); + +void OPLResetChip(FM_OPL *OPL); +int OPLWrite(FM_OPL *OPL,int a,int v); +unsigned char OPLRead(FM_OPL *OPL,int a); +int OPLTimerOver(FM_OPL *OPL,int c); + +/* YM3626/YM3812 local section */ +void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); + +#endif diff --git a/plugins/opl2/kemuopl.h b/plugins/opl2/kemuopl.h new file mode 100644 index 000000000..d2ca6e288 --- /dev/null +++ b/plugins/opl2/kemuopl.h @@ -0,0 +1,61 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2005 Simon Peter, , et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * kemuopl.h - Emulated OPL using Ken Silverman's emulator, by Simon Peter + * + */ + +#ifndef H_ADPLUG_KEMUOPL +#define H_ADPLUG_KEMUOPL + +#include "opl.h" +extern "C" { +#include "adlibemu.h" +} + +class CKemuopl: public Copl +{ +public: + CKemuopl(int rate, bool bit16, bool usestereo) + : use16bit(bit16), stereo(usestereo) + { + adlibinit(rate, usestereo ? 2 : 1, bit16 ? 2 : 1); + currType = TYPE_OPL2; + }; + + void update(short *buf, int samples) + { + if(use16bit) samples *= 2; + if(stereo) samples *= 2; + adlibgetsample(buf, samples); + } + + // template methods + void write(int reg, int val) + { + if(currChip == 0) + adlib0(reg, val); + }; + + void init() {}; + +private: + bool use16bit,stereo; +}; + +#endif diff --git a/plugins/opl2/logo.png b/plugins/opl2/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..913056fe22bd0dd1bcff8862a35b3b0b9ef55e6f GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)I7>k44ofy`glX(f`hz9tCxaJq- zGSoBt|Ig4=&v533p^r= zfz(kDW^8Xc@&zc!UgGKN%6^wafR){>MYm!XP^iSy#WBR<^xi8Mc@HRXxCVyp(B8nL zy@N@+anL&2>ItN$nY4g1L?Z>y~JFn6t VkG|*?KM%B@!PC{xWt~$(696MLW}^TA literal 0 HcmV?d00001 diff --git a/plugins/opl2/mididata.h b/plugins/opl2/mididata.h new file mode 100644 index 000000000..2a83cd997 --- /dev/null +++ b/plugins/opl2/mididata.h @@ -0,0 +1,174 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999, 2000, 2001 Simon Peter, , et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * FM instrument definitions below borrowed from the Allegro library by + * Phil Hassey, - see "adplug/players/mid.cpp" + * for further acknowledgements. + */ + +unsigned char midi_fm_instruments[128][14] = +{ + + /* This set of GM instrument patches was provided by Jorrit Rouwe... + */ + + { 0x21, 0x21, 0x8f, 0x0c, 0xf2, 0xf2, 0x45, 0x76, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Acoustic Grand */ + { 0x31, 0x21, 0x4b, 0x09, 0xf2, 0xf2, 0x54, 0x56, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Bright Acoustic */ + { 0x31, 0x21, 0x49, 0x09, 0xf2, 0xf2, 0x55, 0x76, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Electric Grand */ + { 0xb1, 0x61, 0x0e, 0x09, 0xf2, 0xf3, 0x3b, 0x0b, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Honky-Tonk */ + { 0x01, 0x21, 0x57, 0x09, 0xf1, 0xf1, 0x38, 0x28, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Piano 1 */ + { 0x01, 0x21, 0x93, 0x09, 0xf1, 0xf1, 0x38, 0x28, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Piano 2 */ + { 0x21, 0x36, 0x80, 0x17, 0xa2, 0xf1, 0x01, 0xd5, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Harpsichord */ + { 0x01, 0x01, 0x92, 0x09, 0xc2, 0xc2, 0xa8, 0x58, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Clav */ + { 0x0c, 0x81, 0x5c, 0x09, 0xf6, 0xf3, 0x54, 0xb5, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Celesta */ + { 0x07, 0x11, 0x97, 0x89, 0xf6, 0xf5, 0x32, 0x11, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Glockenspiel */ + { 0x17, 0x01, 0x21, 0x09, 0x56, 0xf6, 0x04, 0x04, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Music Box */ + { 0x18, 0x81, 0x62, 0x09, 0xf3, 0xf2, 0xe6, 0xf6, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Vibraphone */ + { 0x18, 0x21, 0x23, 0x09, 0xf7, 0xe5, 0x55, 0xd8, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Marimba */ + { 0x15, 0x01, 0x91, 0x09, 0xf6, 0xf6, 0xa6, 0xe6, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Xylophone */ + { 0x45, 0x81, 0x59, 0x89, 0xd3, 0xa3, 0x82, 0xe3, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Tubular Bells */ + { 0x03, 0x81, 0x49, 0x89, 0x74, 0xb3, 0x55, 0x05, 0x01, 0x00, 0x04, 0, 0, 0 }, /* Dulcimer */ + { 0x71, 0x31, 0x92, 0x09, 0xf6, 0xf1, 0x14, 0x07, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Drawbar Organ */ + { 0x72, 0x30, 0x14, 0x09, 0xc7, 0xc7, 0x58, 0x08, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Percussive Organ */ + { 0x70, 0xb1, 0x44, 0x09, 0xaa, 0x8a, 0x18, 0x08, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Rock Organ */ + { 0x23, 0xb1, 0x93, 0x09, 0x97, 0x55, 0x23, 0x14, 0x01, 0x00, 0x04, 0, 0, 0 }, /* Church Organ */ + { 0x61, 0xb1, 0x13, 0x89, 0x97, 0x55, 0x04, 0x04, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Reed Organ */ + { 0x24, 0xb1, 0x48, 0x09, 0x98, 0x46, 0x2a, 0x1a, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Accoridan */ + { 0x61, 0x21, 0x13, 0x09, 0x91, 0x61, 0x06, 0x07, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Harmonica */ + { 0x21, 0xa1, 0x13, 0x92, 0x71, 0x61, 0x06, 0x07, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Tango Accordian */ + { 0x02, 0x41, 0x9c, 0x89, 0xf3, 0xf3, 0x94, 0xc8, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Acoustic Guitar(nylon) */ + { 0x03, 0x11, 0x54, 0x09, 0xf3, 0xf1, 0x9a, 0xe7, 0x01, 0x00, 0x0c, 0, 0, 0 }, /* Acoustic Guitar(steel) */ + { 0x23, 0x21, 0x5f, 0x09, 0xf1, 0xf2, 0x3a, 0xf8, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Guitar(jazz) */ + { 0x03, 0x21, 0x87, 0x89, 0xf6, 0xf3, 0x22, 0xf8, 0x01, 0x00, 0x06, 0, 0, 0 }, /* Electric Guitar(clean) */ + { 0x03, 0x21, 0x47, 0x09, 0xf9, 0xf6, 0x54, 0x3a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Electric Guitar(muted) */ + { 0x23, 0x21, 0x4a, 0x0e, 0x91, 0x84, 0x41, 0x19, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Overdriven Guitar */ + { 0x23, 0x21, 0x4a, 0x09, 0x95, 0x94, 0x19, 0x19, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Distortion Guitar */ + { 0x09, 0x84, 0xa1, 0x89, 0x20, 0xd1, 0x4f, 0xf8, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Guitar Harmonics */ + { 0x21, 0xa2, 0x1e, 0x09, 0x94, 0xc3, 0x06, 0xa6, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Acoustic Bass */ + { 0x31, 0x31, 0x12, 0x09, 0xf1, 0xf1, 0x28, 0x18, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Electric Bass(finger) */ + { 0x31, 0x31, 0x8d, 0x09, 0xf1, 0xf1, 0xe8, 0x78, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Electric Bass(pick) */ + { 0x31, 0x32, 0x5b, 0x09, 0x51, 0x71, 0x28, 0x48, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Fretless Bass */ + { 0x01, 0x21, 0x8b, 0x49, 0xa1, 0xf2, 0x9a, 0xdf, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Slap Bass 1 */ + { 0x21, 0x21, 0x8b, 0x11, 0xa2, 0xa1, 0x16, 0xdf, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Slap Bass 2 */ + { 0x31, 0x31, 0x8b, 0x09, 0xf4, 0xf1, 0xe8, 0x78, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Synth Bass 1 */ + { 0x31, 0x31, 0x12, 0x09, 0xf1, 0xf1, 0x28, 0x18, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Synth Bass 2 */ + { 0x31, 0x21, 0x15, 0x09, 0xdd, 0x56, 0x13, 0x26, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Violin */ + { 0x31, 0x21, 0x16, 0x09, 0xdd, 0x66, 0x13, 0x06, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Viola */ + { 0x71, 0x31, 0x49, 0x09, 0xd1, 0x61, 0x1c, 0x0c, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Cello */ + { 0x21, 0x23, 0x4d, 0x89, 0x71, 0x72, 0x12, 0x06, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Contrabass */ + { 0xf1, 0xe1, 0x40, 0x09, 0xf1, 0x6f, 0x21, 0x16, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Tremolo Strings */ + { 0x02, 0x01, 0x1a, 0x89, 0xf5, 0x85, 0x75, 0x35, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Pizzicato Strings */ + { 0x02, 0x01, 0x1d, 0x89, 0xf5, 0xf3, 0x75, 0xf4, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Orchestral Strings */ + { 0x10, 0x11, 0x41, 0x09, 0xf5, 0xf2, 0x05, 0xc3, 0x01, 0x00, 0x02, 0, 0, 0 }, /* Timpani */ + { 0x21, 0xa2, 0x9b, 0x0a, 0xb1, 0x72, 0x25, 0x08, 0x01, 0x00, 0x0e, 0, 0, 0 }, /* String Ensemble 1 */ + { 0xa1, 0x21, 0x98, 0x09, 0x7f, 0x3f, 0x03, 0x07, 0x01, 0x01, 0x00, 0, 0, 0 }, /* String Ensemble 2 */ + { 0xa1, 0x61, 0x93, 0x09, 0xc1, 0x4f, 0x12, 0x05, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* SynthStrings 1 */ + { 0x21, 0x61, 0x18, 0x09, 0xc1, 0x4f, 0x22, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* SynthStrings 2 */ + { 0x31, 0x72, 0x5b, 0x8c, 0xf4, 0x8a, 0x15, 0x05, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Choir Aahs */ + { 0xa1, 0x61, 0x90, 0x09, 0x74, 0x71, 0x39, 0x67, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Voice Oohs */ + { 0x71, 0x72, 0x57, 0x09, 0x54, 0x7a, 0x05, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Synth Voice */ + { 0x90, 0x41, 0x00, 0x09, 0x54, 0xa5, 0x63, 0x45, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Orchestra Hit */ + { 0x21, 0x21, 0x92, 0x0a, 0x85, 0x8f, 0x17, 0x09, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Trumpet */ + { 0x21, 0x21, 0x94, 0x0e, 0x75, 0x8f, 0x17, 0x09, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Trombone */ + { 0x21, 0x61, 0x94, 0x09, 0x76, 0x82, 0x15, 0x37, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Tuba */ + { 0x31, 0x21, 0x43, 0x09, 0x9e, 0x62, 0x17, 0x2c, 0x01, 0x01, 0x02, 0, 0, 0 }, /* Muted Trumpet */ + { 0x21, 0x21, 0x9b, 0x09, 0x61, 0x7f, 0x6a, 0x0a, 0x00, 0x00, 0x02, 0, 0, 0 }, /* French Horn */ + { 0x61, 0x22, 0x8a, 0x0f, 0x75, 0x74, 0x1f, 0x0f, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Brass Section */ + { 0xa1, 0x21, 0x86, 0x8c, 0x72, 0x71, 0x55, 0x18, 0x01, 0x00, 0x00, 0, 0, 0 }, /* SynthBrass 1 */ + { 0x21, 0x21, 0x4d, 0x09, 0x54, 0xa6, 0x3c, 0x1c, 0x00, 0x00, 0x08, 0, 0, 0 }, /* SynthBrass 2 */ + { 0x31, 0x61, 0x8f, 0x09, 0x93, 0x72, 0x02, 0x0b, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Soprano Sax */ + { 0x31, 0x61, 0x8e, 0x09, 0x93, 0x72, 0x03, 0x09, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Alto Sax */ + { 0x31, 0x61, 0x91, 0x09, 0x93, 0x82, 0x03, 0x09, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Tenor Sax */ + { 0x31, 0x61, 0x8e, 0x09, 0x93, 0x72, 0x0f, 0x0f, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Baritone Sax */ + { 0x21, 0x21, 0x4b, 0x09, 0xaa, 0x8f, 0x16, 0x0a, 0x01, 0x00, 0x08, 0, 0, 0 }, /* Oboe */ + { 0x31, 0x21, 0x90, 0x09, 0x7e, 0x8b, 0x17, 0x0c, 0x01, 0x01, 0x06, 0, 0, 0 }, /* English Horn */ + { 0x31, 0x32, 0x81, 0x09, 0x75, 0x61, 0x19, 0x19, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Bassoon */ + { 0x32, 0x21, 0x90, 0x09, 0x9b, 0x72, 0x21, 0x17, 0x00, 0x00, 0x04, 0, 0, 0 }, /* Clarinet */ + { 0xe1, 0xe1, 0x1f, 0x09, 0x85, 0x65, 0x5f, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Piccolo */ + { 0xe1, 0xe1, 0x46, 0x09, 0x88, 0x65, 0x5f, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Flute */ + { 0xa1, 0x21, 0x9c, 0x09, 0x75, 0x75, 0x1f, 0x0a, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Recorder */ + { 0x31, 0x21, 0x8b, 0x09, 0x84, 0x65, 0x58, 0x1a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Pan Flute */ + { 0xe1, 0xa1, 0x4c, 0x09, 0x66, 0x65, 0x56, 0x26, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Blown Bottle */ + { 0x62, 0xa1, 0xcb, 0x09, 0x76, 0x55, 0x46, 0x36, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Skakuhachi */ + { 0x62, 0xa1, 0xa2, 0x09, 0x57, 0x56, 0x07, 0x07, 0x00, 0x00, 0x0b, 0, 0, 0 }, /* Whistle */ + { 0x62, 0xa1, 0x9c, 0x09, 0x77, 0x76, 0x07, 0x07, 0x00, 0x00, 0x0b, 0, 0, 0 }, /* Ocarina */ + { 0x22, 0x21, 0x59, 0x09, 0xff, 0xff, 0x03, 0x0f, 0x02, 0x00, 0x00, 0, 0, 0 }, /* Lead 1 (square) */ + { 0x21, 0x21, 0x0e, 0x09, 0xff, 0xff, 0x0f, 0x0f, 0x01, 0x01, 0x00, 0, 0, 0 }, /* Lead 2 (sawtooth) */ + { 0x22, 0x21, 0x46, 0x89, 0x86, 0x64, 0x55, 0x18, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Lead 3 (calliope) */ + { 0x21, 0xa1, 0x45, 0x09, 0x66, 0x96, 0x12, 0x0a, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Lead 4 (chiff) */ + { 0x21, 0x22, 0x8b, 0x09, 0x92, 0x91, 0x2a, 0x2a, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Lead 5 (charang) */ + { 0xa2, 0x61, 0x9e, 0x49, 0xdf, 0x6f, 0x05, 0x07, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Lead 6 (voice) */ + { 0x20, 0x60, 0x1a, 0x09, 0xef, 0x8f, 0x01, 0x06, 0x00, 0x02, 0x00, 0, 0, 0 }, /* Lead 7 (fifths) */ + { 0x21, 0x21, 0x8f, 0x86, 0xf1, 0xf4, 0x29, 0x09, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Lead 8 (bass+lead) */ + { 0x77, 0xa1, 0xa5, 0x09, 0x53, 0xa0, 0x94, 0x05, 0x00, 0x00, 0x02, 0, 0, 0 }, /* Pad 1 (new age) */ + { 0x61, 0xb1, 0x1f, 0x89, 0xa8, 0x25, 0x11, 0x03, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Pad 2 (warm) */ + { 0x61, 0x61, 0x17, 0x09, 0x91, 0x55, 0x34, 0x16, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Pad 3 (polysynth) */ + { 0x71, 0x72, 0x5d, 0x09, 0x54, 0x6a, 0x01, 0x03, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Pad 4 (choir) */ + { 0x21, 0xa2, 0x97, 0x09, 0x21, 0x42, 0x43, 0x35, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Pad 5 (bowed) */ + { 0xa1, 0x21, 0x1c, 0x09, 0xa1, 0x31, 0x77, 0x47, 0x01, 0x01, 0x00, 0, 0, 0 }, /* Pad 6 (metallic) */ + { 0x21, 0x61, 0x89, 0x0c, 0x11, 0x42, 0x33, 0x25, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Pad 7 (halo) */ + { 0xa1, 0x21, 0x15, 0x09, 0x11, 0xcf, 0x47, 0x07, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Pad 8 (sweep) */ + { 0x3a, 0x51, 0xce, 0x09, 0xf8, 0x86, 0xf6, 0x02, 0x00, 0x00, 0x02, 0, 0, 0 }, /* FX 1 (rain) */ + { 0x21, 0x21, 0x15, 0x09, 0x21, 0x41, 0x23, 0x13, 0x01, 0x00, 0x00, 0, 0, 0 }, /* FX 2 (soundtrack) */ + { 0x06, 0x01, 0x5b, 0x09, 0x74, 0xa5, 0x95, 0x72, 0x00, 0x00, 0x00, 0, 0, 0 }, /* FX 3 (crystal) */ + { 0x22, 0x61, 0x92, 0x8c, 0xb1, 0xf2, 0x81, 0x26, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* FX 4 (atmosphere) */ + { 0x41, 0x42, 0x4d, 0x09, 0xf1, 0xf2, 0x51, 0xf5, 0x01, 0x00, 0x00, 0, 0, 0 }, /* FX 5 (brightness) */ + { 0x61, 0xa3, 0x94, 0x89, 0x11, 0x11, 0x51, 0x13, 0x01, 0x00, 0x06, 0, 0, 0 }, /* FX 6 (goblins) */ + { 0x61, 0xa1, 0x8c, 0x89, 0x11, 0x1d, 0x31, 0x03, 0x00, 0x00, 0x06, 0, 0, 0 }, /* FX 7 (echoes) */ + { 0xa4, 0x61, 0x4c, 0x09, 0xf3, 0x81, 0x73, 0x23, 0x01, 0x00, 0x04, 0, 0, 0 }, /* FX 8 (sci-fi) */ + { 0x02, 0x07, 0x85, 0x0c, 0xd2, 0xf2, 0x53, 0xf6, 0x00, 0x01, 0x00, 0, 0, 0 }, /* Sitar */ + { 0x11, 0x13, 0x0c, 0x89, 0xa3, 0xa2, 0x11, 0xe5, 0x01, 0x00, 0x00, 0, 0, 0 }, /* Banjo */ + { 0x11, 0x11, 0x06, 0x09, 0xf6, 0xf2, 0x41, 0xe6, 0x01, 0x02, 0x04, 0, 0, 0 }, /* Shamisen */ + { 0x93, 0x91, 0x91, 0x09, 0xd4, 0xeb, 0x32, 0x11, 0x00, 0x01, 0x08, 0, 0, 0 }, /* Koto */ + { 0x04, 0x01, 0x4f, 0x09, 0xfa, 0xc2, 0x56, 0x05, 0x00, 0x00, 0x0c, 0, 0, 0 }, /* Kalimba */ + { 0x21, 0x22, 0x49, 0x09, 0x7c, 0x6f, 0x20, 0x0c, 0x00, 0x01, 0x06, 0, 0, 0 }, /* Bagpipe */ + { 0x31, 0x21, 0x85, 0x09, 0xdd, 0x56, 0x33, 0x16, 0x01, 0x00, 0x0a, 0, 0, 0 }, /* Fiddle */ + { 0x20, 0x21, 0x04, 0x8a, 0xda, 0x8f, 0x05, 0x0b, 0x02, 0x00, 0x06, 0, 0, 0 }, /* Shanai */ + { 0x05, 0x03, 0x6a, 0x89, 0xf1, 0xc3, 0xe5, 0xe5, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Tinkle Bell */ + { 0x07, 0x02, 0x15, 0x09, 0xec, 0xf8, 0x26, 0x16, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Agogo */ + { 0x05, 0x01, 0x9d, 0x09, 0x67, 0xdf, 0x35, 0x05, 0x00, 0x00, 0x08, 0, 0, 0 }, /* Steel Drums */ + { 0x18, 0x12, 0x96, 0x09, 0xfa, 0xf8, 0x28, 0xe5, 0x00, 0x00, 0x0a, 0, 0, 0 }, /* Woodblock */ + { 0x10, 0x00, 0x86, 0x0c, 0xa8, 0xfa, 0x07, 0x03, 0x00, 0x00, 0x06, 0, 0, 0 }, /* Taiko Drum */ + { 0x11, 0x10, 0x41, 0x0c, 0xf8, 0xf3, 0x47, 0x03, 0x02, 0x00, 0x04, 0, 0, 0 }, /* Melodic Tom */ + { 0x01, 0x10, 0x8e, 0x09, 0xf1, 0xf3, 0x06, 0x02, 0x02, 0x00, 0x0e, 0, 0, 0 }, /* Synth Drum */ + { 0x0e, 0xc0, 0x00, 0x09, 0x1f, 0x1f, 0x00, 0xff, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Reverse Cymbal */ + { 0x06, 0x03, 0x80, 0x91, 0xf8, 0x56, 0x24, 0x84, 0x00, 0x02, 0x0e, 0, 0, 0 }, /* Guitar Fret Noise */ + { 0x0e, 0xd0, 0x00, 0x0e, 0xf8, 0x34, 0x00, 0x04, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Breath Noise */ + { 0x0e, 0xc0, 0x00, 0x09, 0xf6, 0x1f, 0x00, 0x02, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Seashore */ + { 0xd5, 0xda, 0x95, 0x49, 0x37, 0x56, 0xa3, 0x37, 0x00, 0x00, 0x00, 0, 0, 0 }, /* Bird Tweet */ + { 0x35, 0x14, 0x5c, 0x11, 0xb2, 0xf4, 0x61, 0x15, 0x02, 0x00, 0x0a, 0, 0, 0 }, /* Telephone ring */ + { 0x0e, 0xd0, 0x00, 0x09, 0xf6, 0x4f, 0x00, 0xf5, 0x00, 0x03, 0x0e, 0, 0, 0 }, /* Helicopter */ + { 0x26, 0xe4, 0x00, 0x09, 0xff, 0x12, 0x01, 0x16, 0x00, 0x01, 0x0e, 0, 0, 0 }, /* Applause */ + { 0x00, 0x00, 0x00, 0x09, 0xf3, 0xf6, 0xf0, 0xc9, 0x00, 0x02, 0x0e, 0, 0, 0 } /* Gunshot */ + +}; + +/* logarithmic relationship between midi and FM volumes */ +static int my_midi_fm_vol_table[128] = { + 0, 11, 16, 19, 22, 25, 27, 29, 32, 33, 35, 37, 39, 40, 42, 43, + 45, 46, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, + 64, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 75, 76, 77, + 78, 79, 80, 80, 81, 82, 83, 83, 84, 85, 86, 86, 87, 88, 89, 89, + 90, 91, 91, 92, 93, 93, 94, 95, 96, 96, 97, 97, 98, 99, 99, 100, + 101, 101, 102, 103, 103, 104, 104, 105, 106, 106, 107, 107, 108, + 109, 109, 110, 110, 111, 112, 112, 113, 113, 114, 114, 115, 115, + 116, 117, 117, 118, 118, 119, 119, 120, 120, 121, 121, 122, 122, + 123, 123, 124, 124, 125, 125, 126, 126, 127 +}; + diff --git a/plugins/opl2/opl.h b/plugins/opl2/opl.h new file mode 100644 index 000000000..401bcb982 --- /dev/null +++ b/plugins/opl2/opl.h @@ -0,0 +1,69 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2007 Simon Peter, , et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * opl.h - OPL base class, by Simon Peter + */ + +#ifndef H_ADPLUG_OPL +#define H_ADPLUG_OPL + +class Copl +{ + public: + typedef enum { + TYPE_OPL2, TYPE_OPL3, TYPE_DUAL_OPL2 + } ChipType; + + Copl() + : currChip(0), currType(TYPE_OPL2) + { + } + + virtual ~Copl() + { + } + + virtual void write(int reg, int val) = 0; // combined register select + data write + virtual void setchip(int n) // select OPL chip + { + if(n < 2) + currChip = n; + } + + virtual int getchip() // returns current OPL chip + { + return currChip; + } + + virtual void init(void) = 0; // reinitialize OPL chip(s) + + // return this OPL chip's type + ChipType gettype() + { + return currType; + } + + // Emulation only: fill buffer + virtual void update(short *buf, int samples) {} + + protected: + int currChip; // currently selected OPL chip number + ChipType currType; // this OPL chip's type +}; + +#endif diff --git a/plugins/opl2/opl2_led_off.png b/plugins/opl2/opl2_led_off.png new file mode 100644 index 0000000000000000000000000000000000000000..7c6abb05357b3bcb62bdfc025d6196d4d80c80e9 GIT binary patch literal 586 zcmV-Q0=4~#P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00FW|L_t(I%cYdPY8ycmg}<2{ zt*mB|io`AgLQx}x;?99pk>qjg7s(^I$!nyHilp8m#=!wapcpnN7})F|9K#n_|PQBOb;heh*YgsspTUe}fOsCUnp64Hc-+*0xjce zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00IF?L_t(I%cYb}Yg0iKhMzO{ z=Ht2u$wDh4l$wR1#7Kn-Hw*RWw132((2ak?rCI2z$Q5iC72={6u0%@GMbNf4^Ko3< zn^-VV!GXiT9Ojw#ymMv*U~6kD23kN35N;m}V19af>ZIEI5O@XL1)^Ivd*B>+59G^} zUjn@}P4~OqZYxO=6omjm71clp0oB0C$(MPSWe0!)Z)2bi+)LARzu)h-Mxznq@tC40 z5Gku!&fDAD42Q!Ou$N`oYoHcavZ6=bZtLA>#Np#l$iP5NkVr~L6!6MNdxSYO44w?y zS(ZhBECD3VCSS*6762vMhTIK-j-5LcNO2bUGT2~}7P#5$PaEeBPh~VrW zfLUFxFBRxYQW@$MQ3VC5lGfTaB~*1u0BjYP0WpGlEql+GgqsRh0I!}#qrp$>;2ID` zoq*A@_XJ~}!G=w7eaYxnzooO5MZ%feaQ!eX6c zI-O4QJbwfH0PM}z_yXAY$JaOkJ^ + * + * 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. + * + */ + +// TODO: +// - Pitch bend +// - Velocity (and aftertouch) sensitivity +// - in FM mode: OP2 level, add mode: OP1 and OP2 levels +// - .sbi (or similar) file loading into models +// - Extras: +// - double release: first release is in effect until noteoff (heard if percussive sound), +// second is switched in just before key bit cleared (is this useful???) +// - Unison: 2,3,4, or 9 voices with configurable spread? +// - Portamento (needs mono mode?) +// - Pre-bend/post-bend in poly mode could use portamento speed? +// - SBI file import? + +// - Envelope times in ms for UI: t[0] = 0, t[n] = ( 1< + +#include "opl.h" +#include "temuopl.h" +#include "kemuopl.h" + +#include "embed.cpp" +#include "math.h" + +#include "knob.h" +#include "lcd_spinbox.h" +#include "pixmap_button.h" +#include "tooltip.h" + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT OPL2_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "OpulenZ", + QT_TRANSLATE_NOOP( "pluginBrowser", + "2-operator FM Synth" ), + "Raine M. Ekman ", + 0x0100, + Plugin::Instrument, + new PluginPixmapLoader( "logo" ), + NULL, + NULL +}; + +// necessary for getting instance out of shared lib +Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) +{ + return( new opl2instrument( static_cast( _data ) ) ); +} + +} + +// I'd much rather do without a mutex, but it looks like +// the emulator code isn't really ready for threads +QMutex opl2instrument::emulatorMutex; + +opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : + Instrument( _instrument_track, &OPL2_plugin_descriptor ), + m_patchModel( 0, 0, 127, this, tr( "Patch" ) ), + op1_a_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 Attack" ) ), + op1_d_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 1 Decay" ) ), + op1_s_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 1 Sustain" ) ), + op1_r_mdl(10.0, 0.0, 15.0, 1.0, this, tr( "Op 1 Release" ) ), + op1_lvl_mdl(62.0, 0.0, 63.0, 1.0, this, tr( "Op 1 Level" ) ), + op1_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 1 Level Scaling" ) ), + op1_mul_mdl(0.0, 0.0, 15.0, 1.0, this, tr( "Op 1 Frequency Multiple" ) ), + feedback_mdl(0.0, 0.0, 7.0, 1.0, this, tr( "Op 1 Feedback" ) ), + op1_ksr_mdl(false, this, tr( "Op 1 Key Scaling Rate" ) ), + op1_perc_mdl(false, this, tr( "Op 1 Percussive Envelope" ) ), + op1_trem_mdl(true, this, tr( "Op 1 Tremolo" ) ), + op1_vib_mdl(false, this, tr( "Op 1 Vibrato" ) ), + op1_w0_mdl( ), + op1_w1_mdl( ), + op1_w2_mdl( ), + op1_w3_mdl( ), + op1_waveform_mdl(0,0,3,this, tr( "Op 1 Waveform" ) ), + + + op2_a_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 Attack" ) ), + op2_d_mdl(3.0, 0.0, 15.0, 1.0, this, tr( "Op 2 Decay" ) ), + op2_s_mdl(14.0, 0.0, 15.0, 1.0, this, tr( "Op 2 Sustain" ) ), + op2_r_mdl(12.0, 0.0, 15.0, 1.0, this, tr( "Op 2 Release" ) ), + op2_lvl_mdl(63.0, 0.0, 63.0, 1.0, this, tr( "Op 2 Level" ) ), + op2_scale_mdl(0.0, 0.0, 3.0, 1.0, this, tr( "Op 2 Level Scaling" ) ), + op2_mul_mdl(1.0, 0.0, 15.0, 1.0, this, tr( "Op 2 Frequency Multiple" ) ), + op2_ksr_mdl(false, this, tr( "Op 2 Key Scaling Rate" ) ), + op2_perc_mdl(false, this, tr( "Op 2 Percussive Envelope" ) ), + op2_trem_mdl(false, this, tr( "Op 2 Tremolo" ) ), + op2_vib_mdl(true, this, tr( "Op 2 Vibrato" ) ), + op2_w0_mdl( ), + op2_w1_mdl( ), + op2_w2_mdl( ), + op2_w3_mdl( ), + op2_waveform_mdl(0,0,3,this, tr( "Op 2 Waveform" ) ), + + fm_mdl(true, this, tr( "FM" ) ), + vib_depth_mdl(false, this, tr( "Vibrato Depth" ) ), + trem_depth_mdl(false, this, tr( "Tremolo Depth" ) ) +{ + unsigned char defaultPreset[] = + {0xa0, 0x61, 0x01, 0x00, 0x11, 0xec, 0xc5, + 0x13, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; + + // Connect the plugin to the mixer... + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + engine::getMixer()->addPlayHandle( iph ); + + // Create an emulator - samplerate, 16 bit, mono + // CTemuopl is the better one, CKemuopl kinda sucks (some sounds silent, pitch goes flat after a while) + emulatorMutex.lock(); + // theEmulator = new CKemuopl(engine::getMixer()->processingSampleRate(), true, false); + theEmulator = new CTemuopl(engine::getMixer()->processingSampleRate(), true, false); + theEmulator->init(); + // Enable waveform selection + theEmulator->write(0x01,0x20); + emulatorMutex.unlock(); + + //loadPatch(midi_fm_instruments[0]); + // loadPatch(defaultPreset); + updatePatch(); + + // Can the buffer size change suddenly? I bet that would break lots of stuff + frameCount = engine::getMixer()->framesPerPeriod(); + renderbuffer = new short[frameCount]; + + // Some kind of sane default + tuneEqual(69, 440); + + for(int i=1; i<9; ++i) { + voiceNote[i] = OPL2_VOICE_FREE; + } + connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), + this, SLOT( reloadEmulator() ) ); + // Connect knobs + // This one's for testing... + connect( &m_patchModel, SIGNAL( dataChanged() ), this, SLOT( loadGMPatch() ) ); +#define MOD_CON( model ) connect( &model, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) ); + MOD_CON( op1_a_mdl ); + MOD_CON( op1_d_mdl ); + MOD_CON( op1_s_mdl ); + MOD_CON( op1_r_mdl ); + MOD_CON( op1_lvl_mdl ); + MOD_CON( op1_scale_mdl ); + MOD_CON( op1_mul_mdl ); + MOD_CON( feedback_mdl ); + MOD_CON( op1_ksr_mdl ); + MOD_CON( op1_perc_mdl ); + MOD_CON( op1_trem_mdl ); + MOD_CON( op1_vib_mdl ); + MOD_CON( op1_w0_mdl ); + MOD_CON( op1_w1_mdl ); + MOD_CON( op1_w2_mdl ); + MOD_CON( op1_w3_mdl ); + MOD_CON( op1_waveform_mdl ); + + MOD_CON( op2_a_mdl ); + MOD_CON( op2_d_mdl ); + MOD_CON( op2_s_mdl ); + MOD_CON( op2_r_mdl ); + MOD_CON( op2_lvl_mdl ); + MOD_CON( op2_scale_mdl ); + MOD_CON( op2_mul_mdl ); + MOD_CON( op2_ksr_mdl ); + MOD_CON( op2_perc_mdl ); + MOD_CON( op2_trem_mdl ); + MOD_CON( op2_vib_mdl ); + MOD_CON( op2_w0_mdl ); + MOD_CON( op2_w1_mdl ); + MOD_CON( op2_w2_mdl ); + MOD_CON( op2_w3_mdl ); + MOD_CON( op2_waveform_mdl ); + + MOD_CON( fm_mdl ); + MOD_CON( vib_depth_mdl ); + MOD_CON( trem_depth_mdl ); +} + +// Samplerate changes when choosing oversampling, so this is more or less mandatory +void opl2instrument::reloadEmulator() { + emulatorMutex.lock(); + theEmulator = new CTemuopl(engine::getMixer()->processingSampleRate(), true, false); + theEmulator->init(); + theEmulator->write(0x01,0x20); + emulatorMutex.unlock(); + for(int i=1; i<9; ++i) { + voiceNote[i] = OPL2_VOICE_FREE; + } + // updatePatch(); +} + +bool opl2instrument::handleMidiEvent( const midiEvent & _me, + const midiTime & _time ) +{ + emulatorMutex.lock(); + // Real dummy version... Should at least add: + // - smarter voice allocation: + // - reuse same note, now we have round robin-ish + // - what to do when voices run out and so on... + // - mono mode + // + int key; + static int lastvoice=0; + if( _me.m_type == MidiNoteOn && !isMuted() ) { + // to get us in line with MIDI + key = _me.key() +12; + for(int i=lastvoice+1; i!=lastvoice; ++i,i%=9) { + if( voiceNote[i] == OPL2_VOICE_FREE ) { + theEmulator->write(0xA0+i, fnums[key] & 0xff); + theEmulator->write(0xB0+i, 32 + ((fnums[key] & 0x1f00) >> 8) ); + // printf("%d: %d %d\n", key, (fnums[key] & 0x1c00) >> 10, fnums[key] & 0x3ff); + voiceNote[i] = key; + // printf("Voice %d on\n",i); + lastvoice=i; + break; + } + } + } else if( _me.m_type == MidiNoteOff ) { + key = _me.key() +12; + for(int i=0; i<9; ++i) { + if( voiceNote[i] == key ) { + theEmulator->write(0xA0+i, fnums[key] & 0xff); + theEmulator->write(0xB0+i, (fnums[key] & 0x1f00) >> 8 ); + voiceNote[i] = OPL2_VOICE_FREE; + } + } + } else { + printf("Midi event type %d\n",_me.m_type); + // 224 - pitch wheel + // 160 - aftertouch? + } + emulatorMutex.unlock(); + return true; +} + +QString opl2instrument::nodeName() const +{ + return( OPL2_plugin_descriptor.name ); +} + +PluginView * opl2instrument::instantiateView( QWidget * _parent ) +{ + return( new opl2instrumentView( this, _parent ) ); +} + + +void opl2instrument::play( sampleFrame * _working_buffer ) +{ + emulatorMutex.lock(); + theEmulator->update(renderbuffer, frameCount); + + for( fpp_t frame = 0; frame < frameCount; ++frame ) + { + sample_t s = float(renderbuffer[frame])/32768.0; + for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + { + _working_buffer[frame][ch] = s; + } + } + emulatorMutex.unlock(); + + // Throw the data to the track... + instrumentTrack()->processAudioBuffer( _working_buffer, frameCount, NULL ); + +} + + +void opl2instrument::saveSettings( QDomDocument & _doc, QDomElement & _this ) +{ + op1_a_mdl.saveSettings( _doc, _this, "op1_a" ); + op1_d_mdl.saveSettings( _doc, _this, "op1_d" ); + op1_s_mdl.saveSettings( _doc, _this, "op1_s" ); + op1_r_mdl.saveSettings( _doc, _this, "op1_r" ); + op1_lvl_mdl.saveSettings( _doc, _this, "op1_lvl" ); + op1_scale_mdl.saveSettings( _doc, _this, "op1_scale" ); + op1_mul_mdl.saveSettings( _doc, _this, "op1_mul" ); + feedback_mdl.saveSettings( _doc, _this, "feedback" ); + op1_ksr_mdl.saveSettings( _doc, _this, "op1_ksr" ); + op1_perc_mdl.saveSettings( _doc, _this, "op1_perc" ); + op1_trem_mdl.saveSettings( _doc, _this, "op1_trem" ); + op1_vib_mdl.saveSettings( _doc, _this, "op1_vib" ); + op1_waveform_mdl.saveSettings( _doc, _this, "op1_waveform" ); + + op2_a_mdl.saveSettings( _doc, _this, "op2_a" ); + op2_d_mdl.saveSettings( _doc, _this, "op2_d" ); + op2_s_mdl.saveSettings( _doc, _this, "op2_s" ); + op2_r_mdl.saveSettings( _doc, _this, "op2_r" ); + op2_lvl_mdl.saveSettings( _doc, _this, "op2_lvl" ); + op2_scale_mdl.saveSettings( _doc, _this, "op2_scale" ); + op2_mul_mdl.saveSettings( _doc, _this, "op2_mul" ); + op2_ksr_mdl.saveSettings( _doc, _this, "op2_ksr" ); + op2_perc_mdl.saveSettings( _doc, _this, "op2_perc" ); + op2_trem_mdl.saveSettings( _doc, _this, "op2_trem" ); + op2_vib_mdl.saveSettings( _doc, _this, "op2_vib" ); + op2_waveform_mdl.saveSettings( _doc, _this, "op2_waveform" ); + + fm_mdl.saveSettings( _doc, _this, "fm" ); + vib_depth_mdl.saveSettings( _doc, _this, "vib_depth" ); + trem_depth_mdl.saveSettings( _doc, _this, "trem_depth" ); +} + +void opl2instrument::loadSettings( const QDomElement & _this ) +{ + printf("loadSettings!\n"); + op1_a_mdl.loadSettings( _this, "op1_a" ); + op1_d_mdl.loadSettings( _this, "op1_d" ); + op1_s_mdl.loadSettings( _this, "op1_s" ); + op1_r_mdl.loadSettings( _this, "op1_r" ); + op1_lvl_mdl.loadSettings( _this, "op1_lvl" ); + op1_scale_mdl.loadSettings( _this, "op1_scale" ); + op1_mul_mdl.loadSettings( _this, "op1_mul" ); + feedback_mdl.loadSettings( _this, "feedback" ); + op1_ksr_mdl.loadSettings( _this, "op1_ksr" ); + op1_perc_mdl.loadSettings( _this, "op1_perc" ); + op1_trem_mdl.loadSettings( _this, "op1_trem" ); + op1_vib_mdl.loadSettings( _this, "op1_vib" ); + op1_waveform_mdl.loadSettings( _this, "op1_waveform" ); + + op2_a_mdl.loadSettings( _this, "op2_a" ); + op2_d_mdl.loadSettings( _this, "op2_d" ); + op2_s_mdl.loadSettings( _this, "op2_s" ); + op2_r_mdl.loadSettings( _this, "op2_r" ); + op2_lvl_mdl.loadSettings( _this, "op2_lvl" ); + op2_scale_mdl.loadSettings( _this, "op2_scale" ); + op2_mul_mdl.loadSettings( _this, "op2_mul" ); + op2_ksr_mdl.loadSettings( _this, "op2_ksr" ); + op2_perc_mdl.loadSettings( _this, "op2_perc" ); + op2_trem_mdl.loadSettings( _this, "op2_trem" ); + op2_vib_mdl.loadSettings( _this, "op2_vib" ); + op2_waveform_mdl.loadSettings( _this, "op2_waveform" ); + + fm_mdl.loadSettings( _this, "fm" ); + vib_depth_mdl.loadSettings( _this, "vib_depth" ); + trem_depth_mdl.loadSettings( _this, "trem_depth" ); + +} + +// Load a preset in binary form +void opl2instrument::loadPatch(unsigned char inst[14]) { + const unsigned int adlib_opadd[] = {0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12}; + // Set all voices + printf("%02x %02x %02x %02x %02x ",inst[0],inst[1],inst[2],inst[3],inst[4]); + printf("%02x %02x %02x %02x %02x %02x\n",inst[5],inst[6],inst[7],inst[8],inst[9],inst[10]); + + emulatorMutex.lock(); + for(int v=0; v<9; ++v) { + theEmulator->write(0x20+adlib_opadd[v],inst[0]); // op1 AM/VIB/EG/KSR/Multiplier + theEmulator->write(0x23+adlib_opadd[v],inst[1]); // op2 + theEmulator->write(0x40+adlib_opadd[v],inst[2]); // op1 KSL/Output Level + theEmulator->write(0x43+adlib_opadd[v],inst[3]); // op2 + theEmulator->write(0x60+adlib_opadd[v],inst[4]); // op1 A/D + theEmulator->write(0x63+adlib_opadd[v],inst[5]); // op2 + theEmulator->write(0x80+adlib_opadd[v],inst[6]); // op1 S/R + theEmulator->write(0x83+adlib_opadd[v],inst[7]); // op2 + theEmulator->write(0xe0+adlib_opadd[v],inst[8]); // op1 waveform + theEmulator->write(0xe3+adlib_opadd[v],inst[9]); // op2 + theEmulator->write(0xc0+v,inst[10]); // feedback/algorithm + } + emulatorMutex.unlock(); +} + +void opl2instrument::tuneEqual(int center, float Hz) { + for(int n=0; n<128; ++n) { + float tmp = Hz*pow(2, (n-center)/12.0); + fnums[n] = Hz2fnum( tmp ); + //printf("%d: %d %d %f\n", n, (fnums[n] & 0x1c00) >> 10, fnums[n] & 0x3ff,tmp); + } +} + +// Find suitable F number in lowest possible block +int opl2instrument::Hz2fnum(float Hz) { + for(int block=0; block<8; ++block) { + unsigned int fnum = Hz * pow(2, 20-block) / 49716; + if(fnum<1023) { + return fnum + (block << 10); + } + } + return 0; +} + +// Load one of the default patches +void opl2instrument::loadGMPatch() { + unsigned char *inst = midi_fm_instruments[m_patchModel.value()]; + // printf("loadGMPatch: %d ", m_patchModel.value()); + loadPatch(inst); +} + +// +/* void opl2instrument::loadSBIFile() { + + } */ + +// Update patch from the models to the chip emulation +void opl2instrument::updatePatch() { + printf("updatePatch()\n"); + unsigned char *inst = midi_fm_instruments[0]; + inst[0] = ( op1_trem_mdl.value() ? 128 : 0 ) + + ( op1_vib_mdl.value() ? 64 : 0 ) + + ( op1_perc_mdl.value() ? 0 : 32 ) + // NB. This envelope mode is "perc", not "sus" + ( op1_ksr_mdl.value() ? 16 : 0 ) + + ((int)op1_mul_mdl.value() & 0x0f); + inst[1] = ( op2_trem_mdl.value() ? 128 : 0 ) + + ( op2_vib_mdl.value() ? 64 : 0 ) + + ( op2_perc_mdl.value() ? 0 : 32 ) + // NB. This envelope mode is "perc", not "sus" + ( op2_ksr_mdl.value() ? 16 : 0 ) + + ((int)op2_mul_mdl.value() & 0x0f); + inst[2] = ( (int)op1_scale_mdl.value() & 0x03 << 6 ) + + (63 - ( (int)op1_lvl_mdl.value() & 0x3f ) ); + inst[3] = ( (int)op2_scale_mdl.value() & 0x03 << 6 ) + + (63 - ( (int)op2_lvl_mdl.value() & 0x3f ) ); + inst[4] = ((15 - ((int)op1_a_mdl.value() & 0x0f ) ) << 4 )+ + (15 - ( (int)op1_d_mdl.value() & 0x0f ) ); + inst[5] = ((15 - ( (int)op2_a_mdl.value() & 0x0f ) ) << 4 )+ + (15 - ( (int)op2_d_mdl.value() & 0x0f ) ); + inst[6] = ((15 - ( (int)op1_s_mdl.value() & 0x0f ) ) << 4 ) + + (15 - ( (int)op1_r_mdl.value() & 0x0f ) ); + inst[7] = ((15 - ( (int)op2_s_mdl.value() & 0x0f ) ) << 4 ) + + (15 - ( (int)op2_r_mdl.value() & 0x0f ) ); + inst[8] = (int)op1_waveform_mdl.value() & 0x03; + inst[9] = (int)op2_waveform_mdl.value() & 0x03; + inst[10] = (fm_mdl.value() ? 0 : 1 ) + + (((int)feedback_mdl.value() & 0x07 )<< 1); + // These are always 0 in the list I had? + inst[11] = 0; + inst[12] = 0; + inst[13] = 0; + + // Not part of the patch per se + theEmulator->write(0xBD, (trem_depth_mdl.value() ? 128 : 0 ) + + (vib_depth_mdl.value() ? 64 : 0 )); + + loadPatch(inst); +} + + + +opl2instrumentView::opl2instrumentView( Instrument * _instrument, + QWidget * _parent ) : + InstrumentView( _instrument, _parent ) +{ + /* Unnecessary? + m_patch = new lcdSpinBox( 3, this , "PRESET"); + m_patch->setLabel( "PRESET" ); + m_patch->move( 100, 1 ); + m_patch->setEnabled( true ); + */ + +#define KNOB_GEN(knobname, hinttext, hintunit,xpos,ypos) \ + knobname = new knob( knobStyled, this );\ + knobname->setHintText( tr(hinttext) + "", hintunit );\ + knobname->setFixedSize(22,22);\ + knobname->setCenterPointX(11.0);\ + knobname->setCenterPointY(11.0);\ + knobname->setTotalAngle(270.0);\ + knobname->move(xpos,ypos); + +#define BUTTON_GEN(buttname, tooltip, xpos, ypos) \ + buttname = new pixmapButton( this, NULL );\ + buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( "opl2_led_on" ) );\ + buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( "opl2_led_off" ) );\ + buttname->setCheckable( true );\ + toolTip::add( buttname, tr( tooltip ) );\ + buttname->move( xpos, ypos ); + +#define WAVEBUTTON_GEN(buttname, tooltip, xpos, ypos, icon_on, icon_off, buttgroup) \ + buttname = new pixmapButton( this, NULL );\ + buttname->setActiveGraphic( PLUGIN_NAME::getIconPixmap( icon_on ) ); \ + buttname->setInactiveGraphic( PLUGIN_NAME::getIconPixmap( icon_off ) ); \ + toolTip::add( buttname, tr( tooltip ) );\ + buttname->move( xpos, ypos );\ + buttgroup->addButton(buttname); + + + // OP1 knobs & buttons... + KNOB_GEN(op1_a_kn, "Attack", "", 6, 48); + KNOB_GEN(op1_d_kn, "Decay", "", 34, 48); + KNOB_GEN(op1_s_kn, "Sustain", "", 62, 48); + KNOB_GEN(op1_r_kn, "Release", "", 90, 48); + KNOB_GEN(op1_lvl_kn, "Level", "", 166, 48); + KNOB_GEN(op1_scale_kn, "Scale", "", 194, 48); + KNOB_GEN(op1_mul_kn, "Frequency multiplier", "", 222, 48); + BUTTON_GEN(op1_ksr_btn, "Keyboard scaling rate", 9, 87); + BUTTON_GEN(op1_perc_btn, "Percussive envelope", 36, 87); + BUTTON_GEN(op1_trem_btn, "Tremolo", 65, 87); + BUTTON_GEN(op1_vib_btn, "Vibrato", 93, 87); + KNOB_GEN(feedback_kn, "Feedback", "", 128, 48); + + op1_waveform = new automatableButtonGroup( this ); + WAVEBUTTON_GEN(op1_w0_btn,"Sine", 154, 86, "wave1_on", "wave1_off", op1_waveform); + WAVEBUTTON_GEN(op1_w1_btn,"Half sine", 178, 86, "wave2_on", "wave2_off", op1_waveform); + WAVEBUTTON_GEN(op1_w2_btn,"Absolute sine", 199, 86, "wave3_on", "wave3_off", op1_waveform); + WAVEBUTTON_GEN(op1_w3_btn,"Quarter sine", 220, 86, "wave4_on", "wave4_off", op1_waveform); + + + // And the same for OP2 + KNOB_GEN(op2_a_kn, "Attack", "", 6, 138); + KNOB_GEN(op2_d_kn, "Decay", "", 34, 138); + KNOB_GEN(op2_s_kn, "Sustain", "", 62, 138); + KNOB_GEN(op2_r_kn, "Release", "", 90, 138); + KNOB_GEN(op2_lvl_kn, "Level", "", 166, 138); + KNOB_GEN(op2_scale_kn, "Scale", "", 194, 138); + KNOB_GEN(op2_mul_kn, "Frequency multiplier", "", 222, 138); + BUTTON_GEN(op2_ksr_btn, "Keyboard scaling rate", 9, 177); + BUTTON_GEN(op2_perc_btn, "Percussive envelope", 36, 177); + BUTTON_GEN(op2_trem_btn, "Tremolo", 65, 177); + BUTTON_GEN(op2_vib_btn, "Vibrato", 93, 177); + + op2_waveform = new automatableButtonGroup( this ); + WAVEBUTTON_GEN(op2_w0_btn,"Sine", 154, 176, "wave1_on", "wave1_off", op2_waveform); + WAVEBUTTON_GEN(op2_w1_btn,"Half sine", 178, 176, "wave2_on", "wave2_off", op2_waveform); + WAVEBUTTON_GEN(op2_w2_btn,"Absolute sine", 199, 176, "wave3_on", "wave3_off", op2_waveform); + WAVEBUTTON_GEN(op2_w3_btn,"Quarter Sine", 220, 176, "wave4_on", "wave4_off", op2_waveform); + + BUTTON_GEN(fm_btn, "FM", 9, 220); + BUTTON_GEN(vib_depth_btn, "Vibrato depth", 65, 220); + BUTTON_GEN(trem_depth_btn, "Tremolo depth", 93, 220); + + + setAutoFillBackground( true ); + QPalette pal; + pal.setBrush( backgroundRole(), PLUGIN_NAME::getIconPixmap( + "artwork" ) ); + setPalette( pal ); + + + +} + +void opl2instrumentView::modelChanged() +{ + opl2instrument * m = castModel(); + // m_patch->setModel( &m->m_patchModel ); + + op1_a_kn->setModel( &m->op1_a_mdl ); + op1_d_kn->setModel( &m->op1_d_mdl ); + op1_s_kn->setModel( &m->op1_s_mdl ); + op1_r_kn->setModel( &m->op1_r_mdl ); + op1_lvl_kn->setModel( &m->op1_lvl_mdl ); + op1_scale_kn->setModel( &m->op1_scale_mdl ); + op1_mul_kn->setModel( &m->op1_mul_mdl ); + feedback_kn->setModel( &m->feedback_mdl ); + op1_ksr_btn->setModel( &m->op1_ksr_mdl ); + op1_perc_btn->setModel( &m->op1_perc_mdl ); + op1_trem_btn->setModel( &m->op1_trem_mdl ); + op1_vib_btn->setModel( &m->op1_vib_mdl ); + /* op1_w0_btn->setModel( &m->op1_w0_mdl ); + op1_w1_btn->setModel( &m->op1_w1_mdl ); + op1_w2_btn->setModel( &m->op1_w2_mdl ); + op1_w3_btn->setModel( &m->op1_w3_mdl ); */ + op1_waveform->setModel( &m->op1_waveform_mdl ); + + + op2_a_kn->setModel( &m->op2_a_mdl ); + op2_d_kn->setModel( &m->op2_d_mdl ); + op2_s_kn->setModel( &m->op2_s_mdl ); + op2_r_kn->setModel( &m->op2_r_mdl ); + op2_lvl_kn->setModel( &m->op2_lvl_mdl ); + op2_scale_kn->setModel( &m->op2_scale_mdl ); + op2_mul_kn->setModel( &m->op2_mul_mdl ); + op2_ksr_btn->setModel( &m->op2_ksr_mdl ); + op2_perc_btn->setModel( &m->op2_perc_mdl ); + op2_trem_btn->setModel( &m->op2_trem_mdl ); + op2_vib_btn->setModel( &m->op2_vib_mdl ); + /* op2_w0_btn->setModel( &m->op2_w0_mdl ); + op2_w1_btn->setModel( &m->op2_w1_mdl ); + op2_w2_btn->setModel( &m->op2_w2_mdl ); + op2_w3_btn->setModel( &m->op2_w3_mdl ); */ + op2_waveform->setModel( &m->op2_waveform_mdl ); + + fm_btn->setModel( &m->fm_mdl ); + vib_depth_btn->setModel( &m->vib_depth_mdl ); + trem_depth_btn->setModel( &m->trem_depth_mdl ); + +} + +#include "moc_opl2instrument.cxx" diff --git a/plugins/opl2/opl2instrument.h b/plugins/opl2/opl2instrument.h new file mode 100644 index 000000000..6933d7479 --- /dev/null +++ b/plugins/opl2/opl2instrument.h @@ -0,0 +1,173 @@ +/* + * OPL2 FM synth + * + * Copyright (c) 2013 Raine M. Ekman + * + * 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. + * + */ + +#ifndef _OPL2_H +#define _OPL2_H + +#include "Instrument.h" +#include "InstrumentView.h" +#include "opl.h" + +#include "lcd_spinbox.h" +#include "knob.h" +#include "pixmap_button.h" + +#define OPL2_VOICE_FREE 255 + +class opl2instrument : public Instrument +{ + Q_OBJECT +public: + opl2instrument( InstrumentTrack * _instrument_track ); + virtual QString nodeName() const; + virtual PluginView * instantiateView( QWidget * _parent ); + + inline virtual bool isMidiBased() const { return true; } + + virtual bool handleMidiEvent( const midiEvent & _me, + const midiTime & _time ); + virtual void play( sampleFrame * _working_buffer ); + + void saveSettings( QDomDocument & _doc, QDomElement & _this ); + void loadSettings( const QDomElement & _this ); + void loadPatch(unsigned char inst[14]); + void tuneEqual(int center, float Hz); + + IntModel m_patchModel; + + FloatModel op1_a_mdl; + FloatModel op1_d_mdl; + FloatModel op1_s_mdl; + FloatModel op1_r_mdl; + FloatModel op1_lvl_mdl; + FloatModel op1_scale_mdl; + FloatModel op1_mul_mdl; + FloatModel feedback_mdl; + BoolModel op1_ksr_mdl; + BoolModel op1_perc_mdl; + BoolModel op1_trem_mdl; + BoolModel op1_vib_mdl; + BoolModel op1_w0_mdl; + BoolModel op1_w1_mdl; + BoolModel op1_w2_mdl; + BoolModel op1_w3_mdl; + IntModel op1_waveform_mdl; + + + FloatModel op2_a_mdl; + FloatModel op2_d_mdl; + FloatModel op2_s_mdl; + FloatModel op2_r_mdl; + FloatModel op2_lvl_mdl; + FloatModel op2_scale_mdl; + FloatModel op2_mul_mdl; + BoolModel op2_ksr_mdl; + BoolModel op2_perc_mdl; + BoolModel op2_trem_mdl; + BoolModel op2_vib_mdl; + BoolModel op2_w0_mdl; + BoolModel op2_w1_mdl; + BoolModel op2_w2_mdl; + BoolModel op2_w3_mdl; + IntModel op2_waveform_mdl; + + BoolModel fm_mdl; + BoolModel vib_depth_mdl; + BoolModel trem_depth_mdl; + + +private slots: + void updatePatch(); + void reloadEmulator(); + void loadGMPatch(); + +private: + Copl *theEmulator; + fpp_t frameCount; + short *renderbuffer; + int voiceNote[9]; + int heldNotes[128]; + // These include both octave and Fnumber + int fnums[128]; + + int Hz2fnum(float Hz); + static QMutex emulatorMutex; +}; + + +class opl2instrumentView : public InstrumentView +{ + Q_OBJECT +public: + opl2instrumentView( Instrument * _instrument, QWidget * _parent ); + lcdSpinBox *m_patch; + void modelChanged(); + + knob *op1_a_kn; + knob *op1_d_kn; + knob *op1_s_kn; + knob *op1_r_kn; + knob *op1_lvl_kn; + knob *op1_scale_kn; + knob *op1_mul_kn; + knob *feedback_kn; + pixmapButton *op1_ksr_btn; + pixmapButton *op1_perc_btn; + pixmapButton *op1_trem_btn; + pixmapButton *op1_vib_btn; + pixmapButton *op1_w0_btn; + pixmapButton *op1_w1_btn; + pixmapButton *op1_w2_btn; + pixmapButton *op1_w3_btn; + automatableButtonGroup *op1_waveform; + + + knob *op2_a_kn; + knob *op2_d_kn; + knob *op2_s_kn; + knob *op2_r_kn; + knob *op2_lvl_kn; + knob *op2_scale_kn; + knob *op2_mul_kn; + pixmapButton *op2_ksr_btn; + pixmapButton *op2_perc_btn; + pixmapButton *op2_trem_btn; + pixmapButton *op2_vib_btn; + pixmapButton *op2_w0_btn; + pixmapButton *op2_w1_btn; + pixmapButton *op2_w2_btn; + pixmapButton *op2_w3_btn; + automatableButtonGroup *op2_waveform; + + + pixmapButton *fm_btn; + pixmapButton *vib_depth_btn; + pixmapButton *trem_depth_btn; + + + + +}; + +#endif diff --git a/plugins/opl2/temuopl.cpp b/plugins/opl2/temuopl.cpp new file mode 100644 index 000000000..13691d782 --- /dev/null +++ b/plugins/opl2/temuopl.cpp @@ -0,0 +1,75 @@ +/* + * AdPlug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter , et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * temuopl.cpp - Tatsuyuki Satoh's OPL2 emulator, by Simon Peter + */ + +#include "temuopl.h" + +CTemuopl::CTemuopl(int rate, bool bit16, bool usestereo) + : use16bit(bit16), stereo(usestereo) +{ + opl = OPLCreate(OPL_TYPE_YM3812, 3579545, rate); +} + +CTemuopl::~CTemuopl() +{ + OPLDestroy(opl); +} + +void CTemuopl::update(short *buf, int samples) +{ + int i; + + if(use16bit) { + YM3812UpdateOne(opl,buf,samples); + + if(stereo) + for(i=samples-1;i>=0;i--) { + buf[i*2] = buf[i]; + buf[i*2+1] = buf[i]; + } + } else { + short *tempbuf = new short[stereo ? samples*2 : samples]; + int i; + + YM3812UpdateOne(opl,tempbuf,samples); + + if(stereo) + for(i=samples-1;i>=0;i--) { + tempbuf[i*2] = tempbuf[i]; + tempbuf[i*2+1] = tempbuf[i]; + } + + for(i=0;i<(stereo ? samples*2 : samples);i++) + ((char *)buf)[i] = (tempbuf[i] >> 8) ^ 0x80; + + delete [] tempbuf; + } +} + +void CTemuopl::write(int reg, int val) +{ + OPLWrite(opl,0,reg); + OPLWrite(opl,1,val); +} + +void CTemuopl::init() +{ + OPLResetChip(opl); +} diff --git a/plugins/opl2/temuopl.h b/plugins/opl2/temuopl.h new file mode 100644 index 000000000..564fe3d8d --- /dev/null +++ b/plugins/opl2/temuopl.h @@ -0,0 +1,47 @@ +/* + * Adplug - Replayer for many OPL2/OPL3 audio file formats. + * Copyright (C) 1999 - 2004 Simon Peter, , et al. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * temuopl.h - Tatsuyuki Satoh's OPL2 emulator, by Simon Peter + */ + +#ifndef H_ADPLUG_TEMUOPL +#define H_ADPLUG_TEMUOPL + +#include "opl.h" +extern "C" { +#include "fmopl.h" +} + +class CTemuopl: public Copl +{ + public: + CTemuopl(int rate, bool bit16, bool usestereo); // rate = sample rate + virtual ~CTemuopl(); + + void update(short *buf, int samples); // fill buffer + + // template methods + void write(int reg, int val); + void init(); + + private: + bool use16bit,stereo; + FM_OPL *opl; // holds emulator data +}; + +#endif diff --git a/plugins/opl2/wave1_off.png b/plugins/opl2/wave1_off.png new file mode 100644 index 0000000000000000000000000000000000000000..c8a8f87acab304d654ff483536421887b0d6d5fa GIT binary patch literal 749 zcmVe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00LD>L_t(I%e9riYg17a$3N%( zXkK2@HX1?b@&t>hqaFMg+O>mIMJI7l#NlmqF(MR%jv|PHPA(1w`zLfMwsldekq&hd zs*)x)dGEgKkTfx^l&I-}1NWZG`F{DHbI-jJMUet>KpxP5FhnyQ@CzLu1Jtky@*VIf zj^m0kMnnXq6e4Xg&N)PcBuTo7S(cHeX&+dcriWstQO=8@RjZncwlvP6>BZc%fiTlBKKAw&YXpBiwRtV6LUcQ?{#)a?CQY4hVGYN&p4Nrcho!?U+V67zx0>Z}%EJx14>6@0XZ(7tJ z3dQFdvnizKJoW32T+I@Gv|xYhfdIxBjA=6uVyA8g{8(_*AN5ewPs3QO(OUYOT-&V? z1VNWzkSVs?VD4u$u4bf@9`i{EUnuN$y8``lkT@nLCfM28K|}_ACiJEzm_HcoEmh|N z`DSy^PfblxE|Y00000NkvXXu0mjfTKYKt literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave1_on.png b/plugins/opl2/wave1_on.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb87e118878b51e112a106e4a7080297617d3d5 GIT binary patch literal 786 zcmV+t1MU2YP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00MeRL_t(I%e9riYg9oH$3HV~ z_uacY^5YK07=$MQ3rmUSPmrXrN-2n-pomq9Yl7Is!X`qBGT`)+p{0gUIS#? z#=}ynG-Her5kXZEX$6xc2_ixiMSB<1G$oGXea7NA{_UG-(=t#hm1eB9vaqng%E}70 zT5S*De^WHbo0Z5aS zd_GTX4N(qqFBQ2A+mk7-0I~_$SIXJP9^aov`(o%`cSQi}YX<-ka=9F_F(jLitt<6A zDOsOD4~1%1QHcXpMMd!%3g?tRjoP%su6q=wd^A3wjg|g+pX3s#Xa~RvSZnb;pJdWt z#U43pb3*<@hT?2MX2W8gC9`2E&IWix9^R0}s_jrDfH4M9gKM{ob3Vn2hn22>P^nf3f`F*1=;u})5FCS0fchNdo_W|Mh{m9P15BYsjMc6*jiZF& z;bGR7z7c!iHl&kx-Su-wow+e+jh($ literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave2_off.png b/plugins/opl2/wave2_off.png new file mode 100644 index 0000000000000000000000000000000000000000..836c438e134d8bad6a213c5f46d9fc0e5e6038e7 GIT binary patch literal 435 zcmV;k0ZjghP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00A3GL_t(I%eB-$O2j}E$MNr* zOcImb5OmQhY=lU31#jUA1P>rvYN4JY9>7wxvk<(AXe(IQ3M&dOqUO)o=%S(+VVr6n z{P-~M@fbQF{4$Y`k@vg2)dqB@ z)A^LI78)Mc8mf7Nz81n$g}zdMdub2^lcG)x&)YS=4?aK+^$z&Q3h%5vEJ>0~dcEH( zF&=ae9Q377EgHtdnrg)`yXv(^`~AKFD3==^wrZw#HS#2rfSEGf?Uw+|4BNqyWtjmO zZ&Y~a3h(0kHgAt!engxbL5&18g6^S4@;tY{{I2WT-&4nN?4wF4`{-b>WF3_>O*jAJ dlRmMHz5+}>LbgL1&$j>o002ovPDHLkV1m!5tV{p^ literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave2_on.png b/plugins/opl2/wave2_on.png new file mode 100644 index 0000000000000000000000000000000000000000..9a99d9d9b97df56d2acc9a2ce2a70396beb1ec42 GIT binary patch literal 520 zcmV+j0{8uiP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00D4GL_t(I%e9n0PFq0`hrgMf zeZ)4#2n&#clvlU`MM9i_mIELfjsQ)HL|nlVWg^jV1H=sw1suQ>)aemPV`1~h@9k1J z5hgg6i)FSM&1gT(Z!|NK=lK$e&48WMP-(Z@F*&r^Y$k_VqtT%MEitrSuOnH>U~@Da z0U6`}&;E)P`~_IGS|wV^lcwoHW`fmfHDXp^f1vssDyNFS5(XQQ`|Ut%j;ra#XGDw7 z1glgk1S|0pkuJ;~I;1bdVY`OL&&b_=p!_tQ5^sWaIvu)O1&`L8$4I#}+);DR;U)681rCRM`v-vD(Zn5kk$(>6y~nE%NHbDi zWmu^>!7eAvwM>Zrn^!PZrT$&huIbv^D#@=@p6C1TaimXDL!SZD?_uXzotSO_0000< KMNUMnLSTZJ_Sdlh literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave3_off.png b/plugins/opl2/wave3_off.png new file mode 100644 index 0000000000000000000000000000000000000000..1bb117a06e11a9ce179a1c4d41616f5936d36657 GIT binary patch literal 497 zcmVe zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00CJ^L_t(I%e9m}O9L?&fS=@Y zr(Ao~a_Cfgg-W%Ep!gSDy9f?$iendX@h{XxTsn$w4uZeHzaR?YAUHU~B_XB2tj2T`R`X-pT-r;IjI;)X^&3EWGGdX$As($H81jOcVac|l1{GuTUD>X+dY z+;fR@oh=I2Qbt?p1mhiBSkEGp)@rp}`hFOp9Vnkzs0Ts3pc(Ea#LJr5bq9c#s}OZ3 zCKwUI%z>Rr>-D+@h?Y_Y-Gu7CLVSq42tBQN>qleTuXgQ8<|xMNTiEv!(QGy~z;GwV zxsf=xvhYS<&q4tFZKp6FuB3#kDdkgzXti4Ao8Pu=^L=VrmU*d^(!8|YUi`B}YfTtM nBuSFJv>*t&|M5v*n3jG4?x=LNq!ksV00000NkvXXu0mjfeC5iO literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave3_on.png b/plugins/opl2/wave3_on.png new file mode 100644 index 0000000000000000000000000000000000000000..c79eb0e517f008a7933bda60f5a22d3fc526931d GIT binary patch literal 580 zcmV-K0=xZ*P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00FE?L_t(I%e9ojY7{{bhQF$= zp18Z&U2%yX9Gt^m3?ldhf)MZl#5eF14}vHP9t2N&5HF%$d;{?ff`Yz)2Xl=ecnFCg zLpCux)7>6MFsy4lIQFHX>+g@M{;OJwqBu><%Yf@`LuE3Vq}HLM(WrH(4TnSa@3su> z_xnf|s4fE}Kz@df79fAY{JQ7gfpxoGk_Bek&MRalSg+S3WdW~|1n5`jyjJ`Np}Lef zxE4vHIU^Qm;;6~hXNq+?9ijy)Nk|EU#}1iF(`+AM_$jfs9th_ep!*EV&m6LUM6;DB z9|Y0>ya|%$c`EPM9Ii!{pD1!5Om76@3g`~>UOG;^b?j`+09aYe_hwjkd;DTS4l#$NH(=g-yb6(aTFR?5 znVJ*q{V{#b+)Nxd3y#L9{!?vtOjlRW|1%_|#H^|aAwU8tisI(~_@rO84gCRSUyNVn SRF+f#0000P000vR1^@s6Wpbtk00006VoOIv0RI5$ z0KFoD=Cl9+010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3$u}6(j0~W4HhS02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00JFJL_t(I%e9riYZO5g$3HVW zo6F{YkvqIth-+~qV4K#)`~en%pp96F5ET(yA+=y@dkB{1A2{p}Vs#L(wGjd$%IRUu z-tNq^*xMiH1|e`ayTk0`y?Nj7cjrAug<;rZHNyg@KxBYBD+2ig90Q-s{{H@(i|&r& zm?TNC*5=bWm;0yFsSJn117HSxR^4t_mO)!2O|!kd&CbpaaU9ENGD3Ma?zaY*zqA1Fh0ho=A4HO{fCIk_MLMr~$S2aWgYb{zA zWK6wYU)Y@V9jCV)_L{Ky*guzX7L)Xa&Le-}Oq_FR5l9In2!f)puL#j|ORbjLL0~EX zvt37Y&rz#EtrR0ACC<48?Pfe4=f>o2j2uH_;4yt>>5QvNzP+Adjunl8$K+MQrSV!t ziM5u!y_*Gy);g{I>kitQ&I^M&Q~XBAeA)OhnBjlXY`*kSX9|tWW6B3DB1q9T7~fCX zMmG{R-}i`kwfaTbfi8@00000NkvXXu0mjfi$^iJ literal 0 HcmV?d00001 diff --git a/plugins/opl2/wave4_on.png b/plugins/opl2/wave4_on.png new file mode 100644 index 0000000000000000000000000000000000000000..7af2f083b7ddee7c729a6fe9c3b9f1a883da5099 GIT binary patch literal 742 zcmVP000vR1^@s6Wpbtk00006VoOIv0RI5$ z0KFoD=Cl9+010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3$u}6)KZj+Z+G@02y>e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00K@)L_t(I%e9rgYZOrw#eetB zefyPN*AR@UG9WSO{1b!(B#5<*wFpTBE3pe$i;YAZA^(8b38t4|R3s7zSP3c!K_YSU zwVV047PHx%&8AS?ykUxY_q=z`y>rid5{BU@XEPGu0B9+2^NdIifem0&=jP^?{&jT} zMZ|GTk|dosP18<%cX!uTR#p~)BjAgSjEvYIWSeE%OioTRH8n*PMYg)SdKY-1^?IFy z`vWSF8Q)+1Al{6bn3xbi0MS~lh7=N_s{q!X?^$_b?zHc0xX+;1gw}GalNo?kDiur< zj@$sMb_x71p;R)Q>k84Kp&_i0;9d6PWE;va9sZNB^DOCTK9(SP%m-oTc^5+kycdcU z1Iv+=%Vknch{TWvoO=`?2LcL_#C>9eZ%}>O#+3UpUZGe4%au{r*Vk#x#SLwR>?gT!t3Y!)VSheh_>QuD0#n!$${&rHndvTxa}FmC-2&ZK8V_SohYK7I z*R#R-drEu-_8(Y=as3!4f^&v-ZG(F6@lNm?&{|0G-UCqjsg!PeTm!5C@*9$I*m)A; z$uRy%8N1=30u~wZWoTYc@!l}HEE)s>UX0c(1Ox=_z~1ud4RlNZ(0HAX2pmG;t;2gr z^DTUwNy`7XaPSp$4~oSgZ=Wm^b%MU>^H$ccf8)=bi_@%07*qoM6N<$g8TD7(f|Me literal 0 HcmV?d00001