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:
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 == "" )
|
||||
{
|
||||
|
||||
158
src/core/sample_record_handle.cpp
Normal file
158
src/core/sample_record_handle.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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 );
|
||||
|
||||
Reference in New Issue
Block a user