diff --git a/ChangeLog b/ChangeLog index 8b504bcbd..926c7831d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,26 @@ +2008-08-27 Csaba Hruska + + * 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 * include/controller_view.h: * src/gui/widgets/controller_view.cpp: diff --git a/include/audio_device.h b/include/audio_device.h index 488fb1ff9..0541a90b6 100644 --- a/include/audio_device.h +++ b/include/audio_device.h @@ -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; diff --git a/include/mixer.h b/include/mixer.h index cb9b26950..6792a5651 100644 --- a/include/mixer.h +++ b/include/mixer.h @@ -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; diff --git a/include/sample_record_handle.h b/include/sample_record_handle.h new file mode 100644 index 000000000..27310f505 --- /dev/null +++ b/include/sample_record_handle.h @@ -0,0 +1,74 @@ +/* + * sample_record_handle.h - play-handle for recording a sample + * + * Copyright (c) 2008 Csaba Hruska + * + * 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 +#include +#include + +#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 > bufferList; + bufferList m_buffers; + f_cnt_t m_framesRecorded; + midiTime m_minLength; + + track * m_track; + bbTrack * m_bbTrack; + sampleTCO * m_tco; + +} ; + + +#endif diff --git a/include/sample_track.h b/include/sample_track.h index ce0768275..13c23f5bd 100644 --- a/include/sample_track.h +++ b/include/sample_track.h @@ -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 * ); diff --git a/include/song_editor.h b/include/song_editor.h index 2ce4a7c8d..84b893b78 100644 --- a/include/song_editor.h +++ b/include/song_editor.h @@ -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; diff --git a/src/core/audio/audio_device.cpp b/src/core/audio/audio_device.cpp index 0b68c110a..9f31614bd 100644 --- a/src/core/audio/audio_device.cpp +++ b/src/core/audio/audio_device.cpp @@ -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( diff --git a/src/core/engine.cpp b/src/core/engine.cpp index e6345bc4e..3a80a38a8 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -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(); } diff --git a/src/core/mixer.cpp b/src/core/mixer.cpp index 6982f603b..02810c181 100644 --- a/src/core/mixer.cpp +++ b/src/core/mixer.cpp @@ -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 == "" ) { diff --git a/src/core/sample_record_handle.cpp b/src/core/sample_record_handle.cpp new file mode 100644 index 000000000..b86d9e2a0 --- /dev/null +++ b/src/core/sample_record_handle.cpp @@ -0,0 +1,158 @@ +#ifndef SINGLE_SOURCE_COMPILE + +/* + * sample_record_handle.cpp - implementation of class sampleRecordHandle + * + * Copyright (c) 2008 Csaba Hruska + * + * 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 diff --git a/src/gui/song_editor.cpp b/src/gui/song_editor.cpp index 17384d060..611482444 100644 --- a/src/gui/song_editor.cpp +++ b/src/gui/song_editor.cpp @@ -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(); diff --git a/src/tracks/sample_track.cpp b/src/tracks/sample_track.cpp index 85698c362..94fff5c94 100644 --- a/src/tracks/sample_track.cpp +++ b/src/tracks/sample_track.cpp @@ -28,11 +28,13 @@ #include #include +#include #include #include #include #include +#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 ( + 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( 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 );