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:
4
TODO
4
TODO
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -104,7 +104,8 @@ public slots:
|
||||
void execConnectionDialog( void );
|
||||
void removeConnection( void );
|
||||
void editSongGlobalAutomation( void );
|
||||
|
||||
void arm( void );
|
||||
void deArm( void );
|
||||
|
||||
protected:
|
||||
automatableModelView * amv;
|
||||
|
||||
@@ -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;
|
||||
} ;
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user