Experimental automation recording support

Allows you to record automation by arming an automatable model
and then recording with the RecordAndPlay button.

Known bugs:
	* when you record and there is already an auto clip, it repeats it
	* it freezes when you try to do it with the Volume or Panning slider
	* crashes when you try to do it with a solo/mute button
This commit is contained in:
Andrew Kelley
2009-04-28 17:52:40 -07:00
parent 198cfe96b4
commit c12b716458
8 changed files with 76 additions and 36 deletions

4
TODO
View File

@@ -52,6 +52,10 @@
- add FLAC as export-format?
Andrew Kelley's todo:
- automation recording:
* when you record and there is already an auto clip, it repeats it
* it freezes when you try to do it with the Volume or Panning slider
* crashes when you try to do it with a solo/mute button
- when a song goes past the end of the song, make it stop or loop
- don't end mouse selection when control is released
- add a tools menu to piano roll

View File

@@ -212,13 +212,16 @@ public:
return "0";
}
inline bool armed( void ){ return m_armed; }
inline void setArmed( bool _armed ){ m_armed = _armed; }
public slots:
virtual void reset( void );
virtual void copyValue( void );
virtual void pasteValue( void );
void unlinkControllerConnection( void );
void handleDataChanged( void );
protected:
virtual void redoStep( journalEntry & _je );
@@ -252,6 +255,7 @@ private:
controllerConnection * m_controllerConnection;
bool m_armed; // record this model during automation recording?
static float __copiedValue;

View File

@@ -104,7 +104,8 @@ public slots:
void execConnectionDialog( void );
void removeConnection( void );
void editSongGlobalAutomation( void );
void arm( void );
void deArm( void );
protected:
automatableModelView * amv;

View File

@@ -48,14 +48,14 @@ public:
automationTrack* auto_track;
// the tco that we're putting this automation in
automationPattern* pat;
} ControllerMetaData;
typedef QMap<const controller *, ControllerMetaData> ControllerMap;
} ClipData;
typedef QMap<const automatableModel *, ClipData> AutoClipMap;
AutomationRecorder();
~AutomationRecorder();
// midi controllers call this
void controllerEvent( const controller * _controller, float _val );
// automatable models call this when their data changes
void modelDataEvent( automatableModel * _model );
// must be called at some point between a recording ending and a new
// one beginning
@@ -67,7 +67,7 @@ public:
private:
bool m_recording; // while the song is playing, should we record automation?
ControllerMap m_controllers; // remember state during recording
AutoClipMap m_clips; // remember state during recording
friend class engine;
} ;

View File

@@ -27,6 +27,7 @@
#include "automatable_model.h"
#include "automation_recorder.h"
#include "automation_pattern.h"
#include "controller_connection.h"
@@ -55,8 +56,13 @@ automatableModel::automatableModel( DataType _type,
m_journalEntryReady( false ),
m_setValueDepth( 0 ),
m_hasLinkedModels( false ),
m_controllerConnection( NULL )
m_controllerConnection( NULL ),
m_armed( false )
{
// we need to handle our own dataChanged signal so we can
// alert AutomationRecorder and pass a pointer to this
QObject::connect( this, SIGNAL( dataChanged() ),
this, SLOT( handleDataChanged() ) );
}
@@ -464,6 +470,13 @@ void automatableModel::unlinkControllerConnection( void )
void automatableModel::handleDataChanged( void )
{
// report the data changed to AutomationRecorder
engine::getAutomationRecorder()->modelDataEvent( this );
}
void automatableModel::setInitValue( const float _value )
{

View File

@@ -1,7 +1,7 @@
/*
* automation_recorder.cpp - declaration of class AutomationRecorder
* which handles the valueChanged signal of every
* controller and records automation if automation
* which handles the dataChanged event of every
* automatableModel and records it if automation
* recording is on.
*
* Copyright (c) 2009-2009 Andrew Kelley <superjoe30/at/gmail.com>
@@ -32,7 +32,7 @@
AutomationRecorder::AutomationRecorder() :
m_recording( false ),
m_controllers( ControllerMap() )
m_clips( AutoClipMap() )
{
}
@@ -40,17 +40,17 @@ AutomationRecorder::~AutomationRecorder()
{
}
void AutomationRecorder::controllerEvent(
const controller * _controller, float _val )
void AutomationRecorder::modelDataEvent( automatableModel * _model )
{
if( engine::getSong()->isRecording() &&
if( _model->armed() &&
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
@@ -79,23 +79,25 @@ void AutomationRecorder::controllerEvent(
}
*/
// check if we've seen this controller change yet
if( m_controllers.contains( _controller ) &&
m_controllers[_controller].seen )
// check if we've seen this model change yet
float val = _model->value<float>();
if( m_clips.contains( _model ) &&
m_clips[_model].seen )
{
ControllerMetaData data = m_controllers[_controller];
ClipData data = m_clips[_model];
// 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 - data.pat->startPosition(), _val, false );
data.pat->putValue(
song_pos - data.pat->startPosition(), val, false );
}
else
{
// new entry in controller map
ControllerMetaData data;
ClipData data;
// create a new automation track in the song
engine::getMixer()->lock();
@@ -109,22 +111,16 @@ void AutomationRecorder::controllerEvent(
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<automatableModel*>( kids.at(i) ) );
}
// connect the model to the automation pattern
data.pat->addObject( _model );
// add first value TODO: make sure this is absolute
data.pat->putValue(
song_pos - data.pat->startPosition(), _val, false );
song_pos - data.pat->startPosition(), val, false );
// insert into map
data.seen = true;
m_controllers.insert(_controller, data);
m_clips.insert(_model, data);
}
}
}
@@ -132,7 +128,7 @@ void AutomationRecorder::controllerEvent(
void AutomationRecorder::initRecord( void )
{
// starting a new recording, clear map
m_controllers.clear();
m_clips.clear();
}
#include "moc_automation_recorder.cxx"

View File

@@ -96,10 +96,6 @@ 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;

View File

@@ -25,6 +25,7 @@
#include <QtGui/QMenu>
#include <QtGui/QMouseEvent>
#include "automatable_model_view.h"
#include "automation_pattern.h"
#include "controller_connection_dialog.h"
@@ -127,6 +128,21 @@ void automatableModelView::addDefaultActions( QMenu * _menu )
amvSlots,
SLOT( execConnectionDialog() ) );
}
if( _model->armed() )
{
_menu->addAction( //embed::getIconPixmap( "controller" ),
automatableModel::tr( "de-arm" ),
amvSlots,
SLOT( deArm() ) );
}
else
{
_menu->addAction( //embed::getIconPixmap( "controller" ),
automatableModel::tr( "Arm for recording" ),
amvSlots,
SLOT( arm() ) );
}
}
@@ -241,6 +257,16 @@ void automatableModelViewSlots::editSongGlobalAutomation( void )
}
void automatableModelViewSlots::arm( void )
{
amv->modelUntyped()->setArmed( true );
}
void automatableModelViewSlots::deArm( void )
{
amv->modelUntyped()->setArmed( false );
}
#include "moc_automatable_model_view.cxx"