diff --git a/TODO b/TODO index 3b81df73d..9cd0a167d 100644 --- a/TODO +++ b/TODO @@ -52,6 +52,7 @@ - add FLAC as export-format? Andrew Kelley's todo: +- make knobs easier to tune (less sensitive) - add a Modulator class and a Humanizer tool to the piano roll - when a song goes past the end of the song, make it stop or loop - when looking at a piano roll, if the song is playing that pattern, move the position ticker to where it should be @@ -75,8 +76,6 @@ Andrew Kelley's todo: - don't end mouse selection when control is released - mouse cursor isn't updated correctly in selection mode (from resizing note edit area) -- recording automation -- make knobs easier to tune (less sensitive) - make the menu for a channel happen when you right click, instead of renaming, and make the midi input a top-level menu item - add a "Set exact value" to a right clicked automation menu - enable "auto detect" by default when you bring up the "connect to controller" window diff --git a/include/automation_recorder.h b/include/automation_recorder.h new file mode 100644 index 000000000..da3ea42da --- /dev/null +++ b/include/automation_recorder.h @@ -0,0 +1,78 @@ +/* + * automation_recorder.h - declaration of class AutomationRecorder + * which accepts a controllerEvent call from midi + * controllers and creates automation TCOs if automation + * recording is on. + * + * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2009-2009 Andrew Kelley + * + * 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 _AUTOMATION_RECORDER_H +#define _AUTOMATION_RECORDER_H + +#include + +#include "controller.h" +#include "automation_track.h" +#include "automation_pattern.h" + + +class AutomationRecorder : public QObject +{ + Q_OBJECT +public: + typedef struct + { + // during this recording, have we seen this controller change? + bool seen; + // the track that contains the tco + automationTrack* auto_track; + // the tco that we're putting this automation in + automationPattern* pat; + } ControllerMetaData; + typedef QMap ControllerMap; + + AutomationRecorder(); + ~AutomationRecorder(); + + // midi controllers call this + void controllerEvent( const controller * _controller, float _val ); + + // must be called at some point between a recording ending and a new + // one beginning + void initRecord( void ); + + inline bool recording( void ) const { return m_recording; } + inline void setRecording( bool _recording ){ m_recording = _recording; } + +private: + + bool m_recording; // while the song is playing, should we record automation? + ControllerMap m_controllers; // remember state during recording + + friend class engine; +} ; + + +#endif + diff --git a/include/engine.h b/include/engine.h index bf083b4a6..c3da8429b 100644 --- a/include/engine.h +++ b/include/engine.h @@ -34,6 +34,7 @@ #include "lmms_style.h" class automationEditor; +class AutomationRecorder; class bbEditor; class bbTrackContainer; class dummyTrackContainer; @@ -139,6 +140,11 @@ public: return s_automationEditor; } + static AutomationRecorder * getAutomationRecorder( void ) + { + return s_automationRecorder; + } + static ladspa2LMMS * getLADSPAManager( void ) { return s_ladspaManager; @@ -195,6 +201,7 @@ private: static fxMixerView * s_fxMixerView; static songEditor * s_songEditor; static automationEditor * s_automationEditor; + static AutomationRecorder * s_automationRecorder; static bbEditor * s_bbEditor; static pianoRoll * s_pianoRoll; static projectNotes * s_projectNotes; diff --git a/include/main_window.h b/include/main_window.h index 2bd032cae..20de79695 100644 --- a/include/main_window.h +++ b/include/main_window.h @@ -136,6 +136,7 @@ public slots: void undo( void ); void redo( void ); + void toggleRecordAutomation( bool ); protected: virtual void closeEvent( QCloseEvent * _ce ); diff --git a/include/midi_controller.h b/include/midi_controller.h index d61a367de..5c7e2bac9 100644 --- a/include/midi_controller.h +++ b/include/midi_controller.h @@ -49,7 +49,7 @@ public: virtual void processOutEvent( const midiEvent& _me, const midiTime & _time) { - // No output yet + // No output yet (TODO?) } virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); diff --git a/src/core/automation_recorder.cpp b/src/core/automation_recorder.cpp new file mode 100644 index 000000000..492160149 --- /dev/null +++ b/src/core/automation_recorder.cpp @@ -0,0 +1,139 @@ +/* + * automation_recorder.cpp - declaration of class AutomationRecorder + * which handles the valueChanged signal of every + * controller and records automation if automation + * recording is on. + * + * Copyright (c) 2008-2009 Tobias Doerffel + * Copyright (c) 2009-2009 Andrew Kelley + * + * 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 "automation_recorder.h" +#include "controller.h" +#include "song.h" + + +AutomationRecorder::AutomationRecorder() : + m_recording( false ), + m_controllers( ControllerMap() ) +{ +} + +AutomationRecorder::~AutomationRecorder() +{ +} + +void AutomationRecorder::controllerEvent( + const controller * _controller, float _val ) +{ + if( engine::getSong()->isRecording() && + engine::getSong()->isPlaying() && + m_recording ) + { + // record this controller position at the current tick + // determine the current tick + song * s = engine::getSong(); + midiTime & song_pos = s->getPlayPos( song::Mode_PlaySong ); + /* + // if the tick is within an existing automation TCO + // for the automatable model that this controller controls + track * inside_track = NULL; + for( int i=0; i < s->trackList.size(); ++i){ + track * t = s->trackList.at(i); + if( t->type == track::AutomationTrack && + song_pos >= t->startPosition() && + song_pos <= t->endPosition() ) + { + inside_track = t; + break; + } + } + + if( inside_track != NULL ) + { + // edit the value of the automation TCO at this tick + + } + else + { + // create a new automation TCO for this automatable model + // and set the value + + } + */ + + // check if we've seen this controller change yet + if( m_controllers.contains( _controller ) && + m_controllers[_controller].seen ) + { + ControllerMetaData data = m_controllers[_controller]; + // we've seen this controller, add automation to the TCO we added + // first make the TCO bigger + data.pat->changeLength( song_pos - data.pat->startPosition() ); + // now draw a line from the last one to this one + // TODO: make it smooth (draw line instead of insert value) + data.pat->putValue( song_pos, _val ); + + } + else + { + // new entry in controller map + ControllerMetaData data; + + // create a new automation track in the song + engine::getMixer()->lock(); + data.auto_track = (automationTrack *) + track::create( track::AutomationTrack, engine::getSong() ); + engine::getMixer()->unlock(); + + // put a tco in the automation track which contains the automation + // turn the tco into an automation pattern + data.pat = dynamic_cast( + data.auto_track->createTCO( song_pos ) ); + data.pat->movePosition( song_pos ); + + // add each automatableModel that the controller controls + // to the automation pattern + QObjectList kids = _controller->children(); + for( int i = 0; i < kids.size(); ++i ) + { + data.pat->addObject( + qobject_cast( kids.at(i) ) ); + } + + // add first value TODO: make sure this is absolute + data.pat->putValue( song_pos, _val ); + + // insert into map + data.seen = true; + m_controllers.insert(_controller, data); + } + } +} + +void AutomationRecorder::initRecord( void ) +{ + // starting a new recording, clear map + m_controllers.clear(); +} + +#include "moc_automation_recorder.cxx" + diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 1e52d38c4..77fc2c2a5 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -28,6 +28,7 @@ #include "engine.h" #include "automation_editor.h" +#include "automation_recorder.h" #include "bb_editor.h" #include "bb_track_container.h" #include "config_mgr.h" @@ -63,6 +64,7 @@ song * engine::s_song = NULL; UnifiedResourcesProvider * engine::s_resourcesProvider = NULL; songEditor * engine::s_songEditor = NULL; automationEditor * engine::s_automationEditor = NULL; +AutomationRecorder * engine::s_automationRecorder = NULL; bbEditor * engine::s_bbEditor = NULL; pianoRoll * engine::s_pianoRoll = NULL; projectNotes * engine::s_projectNotes = NULL; @@ -125,6 +127,7 @@ void engine::init( const bool _has_gui ) s_bbEditor = new bbEditor( s_bbTrackContainer ); s_pianoRoll = new pianoRoll; s_automationEditor = new automationEditor; + s_automationRecorder = new AutomationRecorder; s_mainWindow->finalize(); } @@ -152,6 +155,8 @@ void engine::destroy( void ) s_pianoRoll = NULL; delete s_automationEditor; s_automationEditor = NULL; + delete s_automationRecorder; + s_automationRecorder = NULL; delete s_fxMixerView; s_fxMixerView = NULL; diff --git a/src/core/midi/midi_controller.cpp b/src/core/midi/midi_controller.cpp index 1c65416ca..ef6044c79 100644 --- a/src/core/midi/midi_controller.cpp +++ b/src/core/midi/midi_controller.cpp @@ -35,7 +35,7 @@ #include "mixer.h" #include "midi_client.h" #include "midi_controller.h" - +#include "automation_recorder.h" midiController::midiController( model * _parent ) : @@ -96,6 +96,10 @@ void midiController::processInEvent( const midiEvent & _me, Uint8 val = _me.m_data.m_bytes[2] & 0x7F; m_lastValue = (float)( val ) / 127.0f; emit valueChanged(); + + // send this event to the automation recorder + engine::getAutomationRecorder()->controllerEvent( + this, m_lastValue ); } break; diff --git a/src/core/song.cpp b/src/core/song.cpp index 82fbbcda3..d67d96e13 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -33,6 +33,7 @@ #include "song.h" #include "automation_track.h" #include "automation_editor.h" +#include "automation_recorder.h" #include "bb_editor.h" #include "bb_track.h" #include "bb_track_container.h" @@ -594,6 +595,8 @@ void song::setPlayPos( tick_t _ticks, PlayModes _play_mode ) void song::stop( void ) { m_actions.push_back( ActionStop ); + + engine::getAutomationRecorder()->initRecord(); } diff --git a/src/gui/cusis_style.cpp b/src/gui/cusis_style.cpp index 0fa62b71c..9cc7e8bd0 100644 --- a/src/gui/cusis_style.cpp +++ b/src/gui/cusis_style.cpp @@ -304,7 +304,6 @@ QPalette CusisStyle::standardPalette( void ) const void CusisStyle::hoverColors( bool sunken, bool hover, bool active, QColor & color, QColor & blend ) const { - printf("SB: %d %d %d\n", sunken, hover, active ); if( active ) { if( sunken ) diff --git a/src/gui/main_window.cpp b/src/gui/main_window.cpp index a4f162687..9b03f376b 100644 --- a/src/gui/main_window.cpp +++ b/src/gui/main_window.cpp @@ -48,6 +48,7 @@ #include "main_window.h" #include "bb_editor.h" #include "song_editor.h" +#include "automation_recorder.h" #include "song.h" #include "piano_roll.h" #include "embed.h" @@ -759,6 +760,8 @@ void mainWindow::finalize( void ) m_chkrAutomation = new QCheckBox( tr( "Automation" ), gr_w ); m_chkrMidi = new QCheckBox( tr( "MIDI" ), gr_w ); + connect(m_chkrAutomation, SIGNAL(toggled(bool)), SLOT(toggleRecordAutomation(bool))); + grw_layout->addWidget( new QLabel( tr( "RECORD" ), gr_w ) ); grw_layout->addWidget( m_chkrAudio ); grw_layout->addWidget( m_chkrAutomation ); @@ -1554,6 +1557,13 @@ void mainWindow::masterPitchReleased( void ) } + +void mainWindow::toggleRecordAutomation( bool _recording ) +{ + engine::getAutomationRecorder()->setRecording( _recording ); +} + + #include "moc_main_window.cxx"