added basic support for recording sound into sample tracks

git-svn-id: https://lmms.svn.sf.net/svnroot/lmms/trunk/lmms@1508 0778d3d1-df1d-0410-868b-ea421aaaa00d
This commit is contained in:
Tobias Doerffel
2008-08-27 13:52:21 +00:00
parent d1283bc54e
commit 1410f2bc92
12 changed files with 509 additions and 12 deletions

View File

@@ -1,3 +1,26 @@
2008-08-27 Csaba Hruska <csaba.hruska/at/gmail.com>
* cmake/modules/FindPortaudio.cmake:
* include/audio_portaudio.h:
* src/core/audio/audio_portaudio.cpp:
* src/gui/setup_dialog.cpp:
* lmmsconfig.h.in:
* CMakeLists.txt:
added support for PortAudio
* include/mixer.h:
* include/sample_record_handle.h:
* include/song_editor.h:
* include/sample_track.h:
* include/audio_device.h:
* src/gui/song_editor.cpp:
* src/tracks/sample_track.cpp:
* src/core/audio/audio_device.cpp:
* src/core/mixer.cpp:
* src/core/engine.cpp:
* src/core/sample_record_handle.cpp:
added basic support for recording sound into sample tracks
2008-08-27 Paul Giblock <drfaygo/at/gmail/dot/com>
* include/controller_view.h:
* src/gui/widgets/controller_view.cpp:

View File

@@ -64,6 +64,11 @@ public:
virtual void renamePort( audioPort * _port );
inline bool supportsCapture( void ) const
{
return( m_supportsCapture );
}
inline sample_rate_t sampleRate( void ) const
{
return( m_sampleRate );
@@ -156,6 +161,8 @@ protected:
bool hqAudio( void ) const;
protected:
bool m_supportsCapture;
private:
sample_rate_t m_sampleRate;

View File

@@ -288,6 +288,7 @@ public:
sample_rate_t baseSampleRate( void ) const;
sample_rate_t outputSampleRate( void ) const;
sample_rate_t inputSampleRate( void ) const;
sample_rate_t processingSampleRate( void ) const;
@@ -347,6 +348,16 @@ public:
m_playHandlesToRemoveMutex.unlock();
}
void lockInputFrames( void )
{
m_inputFramesMutex.lock();
}
void unlockInputFrames( void )
{
m_inputFramesMutex.unlock();
}
// audio-buffer-mgm
void bufferToPort( const sampleFrame * _buf,
const fpp_t _frames,
@@ -374,6 +385,18 @@ public:
return( m_fifoWriter != NULL );
}
void pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames );
inline const sampleFrame * inputBuffer( void )
{
return m_inputBuffer[ m_inputBufferRead ];
}
inline const f_cnt_t inputBufferFrames( void )
{
return m_inputBufferFrames[ m_inputBufferRead ];
}
inline const surroundSampleFrame * nextBuffer( void )
{
return( hasFifoWriter() ? m_fifo->read() : renderNextBuffer() );
@@ -430,6 +453,12 @@ private:
sampleFrame * m_workingBuf;
sampleFrame * m_inputBuffer[2];
f_cnt_t m_inputBufferFrames[2];
f_cnt_t m_inputBufferSize[2];
int m_inputBufferRead;
int m_inputBufferWrite;
surroundSampleFrame * m_readBuf;
surroundSampleFrame * m_writeBuf;
@@ -472,6 +501,7 @@ private:
QMutex m_globalMutex;
QMutex m_playHandlesMutex;
QMutex m_playHandlesToRemoveMutex;
QMutex m_inputFramesMutex;
fifo * m_fifo;

View File

@@ -0,0 +1,74 @@
/*
* sample_record_handle.h - play-handle for recording a sample
*
* Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
*
* 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 _SAMPLE_RECORD_HANDLE_H
#define _SAMPLE_RECORD_HANDLE_H
#include <QtCore/QList>
#include <QtCore/QPair>
#include <qobject.h>
#include "mixer.h"
#include "sample_buffer.h"
class bbTrack;
class pattern;
class sampleTCO;
class track;
class sampleRecordHandle : public playHandle
{
public:
sampleRecordHandle( sampleTCO * _tco );
virtual ~sampleRecordHandle();
virtual void play( bool _try_parallelizing,
sampleFrame * _working_buffer );
virtual bool done( void ) const;
virtual bool isFromTrack( const track * _track ) const;
f_cnt_t framesRecorded( void ) const;
void createSampleBuffer( sampleBuffer * * _sample_buf );
private:
virtual void writeBuffer( const sampleFrame * _ab,
const fpp_t _frames );
typedef QList<QPair<sampleFrame *, fpp_t> > bufferList;
bufferList m_buffers;
f_cnt_t m_framesRecorded;
midiTime m_minLength;
track * m_track;
bbTrack * m_bbTrack;
sampleTCO * m_tco;
} ;
#endif

View File

@@ -40,6 +40,7 @@ class sampleBuffer;
class sampleTCO : public trackContentObject
{
Q_OBJECT
mapPropertyFromModel(bool,isRecord,setRecord,m_recordModel);
public:
sampleTCO( track * _track );
virtual ~sampleTCO();
@@ -65,12 +66,15 @@ public:
public slots:
void setSampleBuffer( sampleBuffer * _sb );
void setSampleFile( const QString & _sf );
void updateLength( bpm_t = 0 );
void toggleRecord( void );
private:
sampleBuffer * m_sampleBuffer;
boolModel m_recordModel;
friend class sampleTCOView;
@@ -96,6 +100,8 @@ public slots:
protected:
virtual void contextMenuEvent( QContextMenuEvent * _cme );
virtual void mousePressEvent( QMouseEvent * _me );
virtual void dragEnterEvent( QDragEnterEvent * _dee );
virtual void dropEvent( QDropEvent * _de );
virtual void mouseDoubleClickEvent( QMouseEvent * );

View File

@@ -65,6 +65,8 @@ private slots:
void setHighQuality( bool );
void play( void );
void record( void );
void recordAccompany( void );
void stop( void );
void masterVolumeChanged( int _new_val );
@@ -101,6 +103,8 @@ private:
QWidget * m_toolBar;
toolButton * m_playButton;
toolButton * m_recordButton;
toolButton * m_recordAccompanyButton;
toolButton * m_stopButton;
lcdSpinBox * m_tempoSpinBox;

View File

@@ -38,7 +38,8 @@ audioDevice::audioDevice( const ch_cnt_t _channels, mixer * _mixer ) :
m_sampleRate( _mixer->processingSampleRate() ),
m_channels( _channels ),
m_mixer( _mixer ),
m_buffer( new surroundSampleFrame[getMixer()->framesPerPeriod()] )
m_buffer( new surroundSampleFrame[getMixer()->framesPerPeriod()] ),
m_supportsCapture( false )
{
int error;
if( ( m_srcState = src_new(

View File

@@ -79,6 +79,12 @@ void engine::init( const bool _has_gui )
s_fxMixer = new fxMixer;
s_bbTrackContainer = new bbTrackContainer;
s_ladspaManager = new ladspa2LMMS;
s_projectJournal->setJournalling( TRUE );
s_mixer->initDevices();
if( s_hasGUI )
{
s_mainWindow = new mainWindow;
@@ -89,15 +95,7 @@ void engine::init( const bool _has_gui )
s_bbEditor = new bbEditor( s_bbTrackContainer );
s_pianoRoll = new pianoRoll;
s_automationEditor = new automationEditor;
}
s_ladspaManager = new ladspa2LMMS;
s_projectJournal->setJournalling( TRUE );
s_mixer->initDevices();
if( s_hasGUI )
{
s_mainWindow->finalize();
}

View File

@@ -54,6 +54,7 @@
#include "audio_alsa.h"
#include "audio_jack.h"
#include "audio_oss.h"
#include "audio_portaudio.h"
#include "audio_pulseaudio.h"
#include "audio_sdl.h"
#include "audio_dummy.h"
@@ -287,8 +288,18 @@ mixer::mixer( void ) :
m_masterGain( 1.0f ),
m_audioDev( NULL ),
m_oldAudioDev( NULL ),
m_globalMutex( QMutex::Recursive )
m_globalMutex( QMutex::Recursive ),
m_inputBufferRead( 0 ),
m_inputBufferWrite( 1 )
{
for( int i = 0; i < 2; ++i )
{
m_inputBufferFrames[i] = 0;
m_inputBufferSize[i] = DEFAULT_BUFFER_SIZE * 100;
m_inputBuffer[i] = new sampleFrame[ DEFAULT_BUFFER_SIZE * 100 ];
clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] );
}
if( configManager::inst()->value( "mixer", "framesperaudiobuffer"
).toInt() >= 32 )
{
@@ -453,6 +464,15 @@ sample_rate_t mixer::outputSampleRate( void ) const
sample_rate_t mixer::inputSampleRate( void ) const
{
return( m_audioDev != NULL ? m_audioDev->sampleRate() :
baseSampleRate() );
}
sample_rate_t mixer::processingSampleRate( void ) const
{
return( outputSampleRate() * m_qualitySettings.sampleRateMultiplier() );
@@ -470,6 +490,36 @@ bool mixer::criticalXRuns( void ) const
void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames )
{
lockInputFrames();
f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ];
int size = m_inputBufferSize[ m_inputBufferWrite ];
sampleFrame * buf = m_inputBuffer[ m_inputBufferWrite ];
if( frames + _frames > size )
{
size = tMax( size * 2, frames + _frames );
sampleFrame * ab = new sampleFrame[ size ];
memcpy( ab, buf, frames * sizeof( sampleFrame ) );
delete [] buf;
m_inputBufferSize[ m_inputBufferWrite ] = size;
m_inputBuffer[ m_inputBufferWrite ] = ab;
buf = ab;
}
memcpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) );
m_inputBufferFrames[ m_inputBufferWrite ] += _frames;
unlockInputFrames();
}
const surroundSampleFrame * mixer::renderNextBuffer( void )
{
microTimer timer;
@@ -486,6 +536,16 @@ const surroundSampleFrame * mixer::renderNextBuffer( void )
last_metro_pos = p;
}
lockInputFrames();
// swap buffer
m_inputBufferWrite++;
m_inputBufferWrite %= 2;
m_inputBufferRead++;
m_inputBufferRead %= 2;
// clear new write buffer
m_inputBufferFrames[ m_inputBufferWrite ] = 0;
unlockInputFrames();
// now we have to make sure no other thread does anything bad
// while we're acting...
lock();
@@ -929,6 +989,20 @@ audioDevice * mixer::tryAudioDevices( void )
#endif
#ifdef LMMS_HAVE_PORTAUDIO
if( dev_name == audioPortAudio::name() || dev_name == "" )
{
dev = new audioPortAudio( success_ful, this );
if( success_ful )
{
m_audioDevName = audioPortAudio::name();
return( dev );
}
delete dev;
}
#endif
#ifdef LMMS_HAVE_PULSEAUDIO
if( dev_name == audioPulseAudio::name() || dev_name == "" )
{

View File

@@ -0,0 +1,158 @@
#ifndef SINGLE_SOURCE_COMPILE
/*
* sample_record_handle.cpp - implementation of class sampleRecordHandle
*
* Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
*
* 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 "sample_record_handle.h"
#include "bb_track.h"
#include "engine.h"
#include "instrument_track.h"
#include "pattern.h"
#include "sample_buffer.h"
#include "sample_track.h"
sampleRecordHandle::sampleRecordHandle( sampleTCO * _tco ) :
playHandle( SamplePlayHandle ),
m_track( _tco->getTrack() ),
m_bbTrack( NULL ),
m_tco( _tco ),
m_framesRecorded( 0 ),
m_minLength( _tco->length() )
{
}
sampleRecordHandle::~sampleRecordHandle()
{
if( !m_buffers.empty() )
{
sampleBuffer * sb;
createSampleBuffer( &sb );
m_tco->setSampleBuffer( sb );
}
while( !m_buffers.empty() )
{
delete[] m_buffers.front().first;
m_buffers.erase( m_buffers.begin() );
}
m_tco->setRecord( false );
}
void sampleRecordHandle::play( bool /* _try_parallelizing */,
sampleFrame * _working_buffer )
{
const sampleFrame * recbuf = engine::getMixer()->inputBuffer();
const fpp_t frames = engine::getMixer()->inputBufferFrames();
writeBuffer( recbuf, frames );
m_framesRecorded += frames;
midiTime len = m_framesRecorded / engine::framesPerTick();
if( len > m_minLength )
{
// m_tco->changeLength( len );
m_minLength = len;
}
}
bool sampleRecordHandle::done( void ) const
{
return( FALSE );
}
bool sampleRecordHandle::isFromTrack( const track * _track ) const
{
return( m_track == _track || m_bbTrack == _track );
}
f_cnt_t sampleRecordHandle::framesRecorded( void ) const
{
return( m_framesRecorded );
}
void sampleRecordHandle::createSampleBuffer( sampleBuffer * * _sample_buf )
{
const f_cnt_t frames = framesRecorded();
// create buffer to store all recorded buffers in
sampleFrame * data = new sampleFrame[frames];
// make sure buffer is cleaned up properly at the end...
sampleFrame * data_ptr = data;
#ifdef LMMS_DEBUG
assert( data != NULL );
#endif
// now copy all buffers into big buffer
for( bufferList::const_iterator it = m_buffers.begin();
it != m_buffers.end(); ++it )
{
memcpy( data_ptr, ( *it ).first, ( *it ).second *
sizeof( sampleFrame ) );
data_ptr += ( *it ).second;
}
// create according sample-buffer out of big buffer
*_sample_buf = new sampleBuffer( data, frames );
( *_sample_buf )->setSampleRate( engine::getMixer()->inputSampleRate() );
delete[] data;
}
void sampleRecordHandle::writeBuffer( const sampleFrame * _ab,
const fpp_t _frames )
{
sampleFrame * buf = new sampleFrame[_frames];
for( fpp_t frame = 0; frame < _frames; ++frame )
{
for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
{
buf[frame][chnl] = _ab[frame][chnl];
}
}
m_buffers.push_back( qMakePair( buf, _frames ) );
}
#endif

View File

@@ -49,6 +49,7 @@
#include "tool_button.h"
#include "tooltip.h"
#include "visualization_widget.h"
#include "audio_device.h"
@@ -222,6 +223,21 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) :
tr( "Play song (Space)" ),
this, SLOT( play() ), m_toolBar );
m_recordButton = new toolButton( embed::getIconPixmap( "record" ),
tr( "Record samples from Audio-device" ),
this, SLOT( record() ), m_toolBar );
m_recordAccompanyButton = new toolButton(
embed::getIconPixmap( "record_accompany" ),
tr( "Record samples from Audio-device while playing song or BB track" ),
this, SLOT( recordAccompany() ), m_toolBar );
// disable record buttons if capturing is not supported
if( !engine::getMixer()->audioDev()->supportsCapture() )
{
m_recordButton->setDisabled( true );
m_recordAccompanyButton->setDisabled( true );
}
m_stopButton = new toolButton( embed::getIconPixmap( "stop", 24, 24 ),
tr( "Stop song (Space)" ),
this, SLOT( stop() ), m_toolBar );
@@ -305,6 +321,8 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) :
tb_layout->addSpacing( 5 );
tb_layout->addWidget( m_playButton );
tb_layout->addWidget( m_recordButton );
tb_layout->addWidget( m_recordAccompanyButton );
tb_layout->addWidget( m_stopButton );
tb_layout->addSpacing( 10 );
tb_layout->addWidget( m_addBBTrackButton );
@@ -386,6 +404,22 @@ void songEditor::play( void )
void songEditor::record( void )
{
play();
}
void songEditor::recordAccompany( void )
{
play();
}
void songEditor::stop( void )
{
m_s->stop();

View File

@@ -28,11 +28,13 @@
#include <QtXml/QDomElement>
#include <QtGui/QDropEvent>
#include <QtGui/QMenu>
#include <QtGui/QLayout>
#include <QtGui/QMdiArea>
#include <QtGui/QPainter>
#include <QtGui/QPushButton>
#include "gui_templates.h"
#include "sample_track.h"
#include "song.h"
#include "embed.h"
@@ -40,6 +42,7 @@
#include "tooltip.h"
#include "audio_port.h"
#include "sample_play_handle.h"
#include "sample_record_handle.h"
#include "string_pair_drag.h"
#include "knob.h"
#include "main_window.h"
@@ -89,6 +92,16 @@ const QString & sampleTCO::sampleFile( void ) const
void sampleTCO::setSampleBuffer( sampleBuffer * _sb )
{
sharedObject::unref( m_sampleBuffer );
m_sampleBuffer = _sb;
updateLength();
emit sampleChanged();
}
void sampleTCO::setSampleFile( const QString & _sf )
{
@@ -101,6 +114,15 @@ void sampleTCO::setSampleFile( const QString & _sf )
void sampleTCO::toggleRecord( void )
{
m_recordModel.setValue( !m_recordModel.value() );
emit dataChanged();
}
void sampleTCO::updateLength( bpm_t )
{
changeLength( sampleLength() );
@@ -204,6 +226,37 @@ void sampleTCOView::updateSample( void )
void sampleTCOView::contextMenuEvent( QContextMenuEvent * _cme )
{
QMenu contextMenu( this );
if( fixedTCOs() == FALSE )
{
contextMenu.addAction( embed::getIconPixmap( "cancel" ),
tr( "Delete (middle mousebutton)" ),
this, SLOT( remove() ) );
contextMenu.addSeparator();
contextMenu.addAction( embed::getIconPixmap( "edit_cut" ),
tr( "Cut" ), this, SLOT( cut() ) );
}
contextMenu.addAction( embed::getIconPixmap( "edit_copy" ),
tr( "Copy" ), m_tco, SLOT( copy() ) );
contextMenu.addAction( embed::getIconPixmap( "edit_paste" ),
tr( "Paste" ), m_tco, SLOT( paste() ) );
contextMenu.addSeparator();
contextMenu.addAction( embed::getIconPixmap( "muted" ),
tr( "Mute/unmute (<Ctrl> + middle click)" ),
m_tco, SLOT( toggleMute() ) );
contextMenu.addAction( embed::getIconPixmap( "record" ),
tr( "Set/clear record" ),
m_tco, SLOT( toggleRecord() ) );
constructContextMenu( &contextMenu );
contextMenu.exec( QCursor::pos() );
}
void sampleTCOView::dragEnterEvent( QDragEnterEvent * _dee )
{
if( stringPairDrag::processDragEnterEvent( _dee,
@@ -241,6 +294,23 @@ void sampleTCOView::dropEvent( QDropEvent * _de )
void sampleTCOView::mousePressEvent( QMouseEvent * _me )
{
if( _me->button() == Qt::LeftButton &&
engine::getMainWindow()->isCtrlPressed() &&
engine::getMainWindow()->isShiftPressed() )
{
m_tco->toggleRecord();
}
else
{
trackContentObjectView::mousePressEvent( _me );
}
}
void sampleTCOView::mouseDoubleClickEvent( QMouseEvent * )
{
QString af = m_tco->m_sampleBuffer->openAudioFile();
@@ -298,6 +368,14 @@ void sampleTCOView::paintEvent( QPaintEvent * _pe )
{
p.drawPixmap( 3, 8, embed::getIconPixmap( "muted", 16, 16 ) );
}
if( m_tco->isRecord() )
{
p.setFont( pointSize<6>( p.font() ) );
p.setPen( QColor( 224, 0, 0 ) );
p.drawText( 9, p.fontMetrics().height() - 1, "Rec" );
p.setBrush( QBrush( QColor( 224, 0, 0 ) ) );
p.drawEllipse( 4, 5, 4, 4 );
}
}
@@ -342,8 +420,18 @@ bool sampleTrack::play( const midiTime & _start, const fpp_t _frames,
sampleTCO * st = dynamic_cast<sampleTCO *>( tco );
if( !st->isMuted() )
{
samplePlayHandle * handle = new samplePlayHandle( st );
handle->setVolumeModel( &m_volumeModel );
playHandle * handle;
if( st->isRecord() )
{
sampleRecordHandle * smpHandle = new sampleRecordHandle( st );
handle = smpHandle;
}
else
{
samplePlayHandle * smpHandle = new samplePlayHandle( st );
smpHandle->setVolumeModel( &m_volumeModel );
handle = smpHandle;
}
//TODO: check whether this works
// handle->setBBTrack( _tco_num );
handle->setOffset( _offset );