1 /************************************************************************
3 * Copyright 2010-2012 Jakob Leben (jakob.leben@gmail.com)
5 * This file is part of SuperCollider Qt GUI.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ************************************************************************/
23 #include "../../QcWidgetFactory.h"
25 #include <QGridLayout>
27 #include <QApplication>
28 #include <QPaintEvent>
35 QC_DECLARE_QWIDGET_FACTORY(QcWaveform
);
37 const int kMaxRawFrames
= 300000;
38 const int kMaxFramesPerCacheUnit
= 128;
40 QcWaveform::QcWaveform( QWidget
* parent
) : QWidget( parent
),
52 _cursorEditable(true),
64 _bkgColor( QColor(0,0,0) ),
65 _cursorColor( QColor(255,0,0) ),
66 _gridColor( QColor(100,100,200) ),
67 _peakColor( QColor(242,178,0) ),
68 _rmsColor( QColor(255,255,0) ),
72 memset( &sfInfo
, 0, sizeof(SF_INFO
) );
74 setFocusPolicy( Qt::StrongFocus
);
75 setSizePolicy( QSizePolicy::Expanding
, QSizePolicy::Expanding
);
76 setAttribute( Qt::WA_OpaquePaintEvent
, true );
79 QcWaveform::~QcWaveform()
83 if( sf
) sf_close( sf
);
86 void QcWaveform::load( const QString
& filename
)
88 qcDebugMsg( 1, "QcWaveform::load( filename )" );
91 memset( &new_info
, 0, sizeof(SF_INFO
) );
93 SNDFILE
*new_sf
= sf_open( filename
.toStdString().c_str(), SFM_READ
, &new_info
);
96 qcErrorMsg(QString("Could not open soundfile: ") + filename
);
100 doLoad( new_sf
, new_info
, 0, new_info
.frames
);
103 void QcWaveform::load( const QString
& filename
, int beg
, int dur
)
105 qcDebugMsg( 1, "QcWaveform::load( filename, beg, dur )" );
108 memset( &new_info
, 0, sizeof(SF_INFO
) );
110 SNDFILE
*new_sf
= sf_open( filename
.toStdString().c_str(), SFM_READ
, &new_info
);
113 qcErrorMsg(QString("Could not open soundfile: ") + filename
);
117 doLoad( new_sf
, new_info
, beg
, dur
);
120 void QcWaveform::load( const QVector
<double> & data
, int offset
, int ch
, int sr
)
122 qcDebugMsg( 1, "QcWaveform::load( data, offset, channels )" );
125 qcWarningMsg( "QSoundFileView: invalid number of channels!" );
129 int ns
= data
.count();
132 if( nf
* ch
!= ns
) {
133 qcWarningMsg( "QSoundFileView: size of data not a multiple of channel count!" );
137 if( offset
< 0 || nf
- offset
< 1 ) {
138 qcWarningMsg( "QSoundFileView: invalid range of data!" );
143 if( sf
) sf_close( sf
);
147 memset( &new_info
, 0, sizeof(SF_INFO
) );
148 new_info
.channels
= ch
;
149 new_info
.samplerate
= sr
;
152 _beg
= _rangeBeg
= 0;
153 _dur
= _rangeDur
= _rangeEnd
= nf
- offset
;
157 _cache
= new SoundCacheStream();
158 connect( _cache
, SIGNAL(loadingDone()), this, SIGNAL(loadingDone()) );
159 connect( _cache
, SIGNAL(loadingDone()), this, SLOT(redraw()) );
161 _cache
->load( data
, _rangeDur
, offset
, ch
);
164 void QcWaveform::allocate ( int frames
, int ch
, int sr
)
167 qcWarningMsg( "QSoundFileView: invalid number of channels!" );
172 if( sf
) sf_close( sf
);
176 memset( &new_info
, 0, sizeof(SF_INFO
) );
177 new_info
.channels
= ch
;
178 new_info
.samplerate
= sr
;
181 _beg
= _rangeBeg
= 0;
182 _dur
= _rangeDur
= _rangeEnd
= frames
;
186 _cache
= new SoundCacheStream();
187 _cache
->allocate( frames
, ch
);
192 void QcWaveform::write( const QVector
<double> & data
, int offset
)
195 qcWarningMsg( "QSoundFileView: can not write data while displaying a sound file!" );
199 if( !_cache
|| !_cache
->ready() ) {
200 qcWarningMsg( "QSoundFileView: can not write data; memory has not been allocated yet!" );
204 int ch
= sfInfo
.channels
;
205 int ns
= data
.size();
208 if( nf
* ch
!= ns
) {
209 qcWarningMsg( "QSoundFileView: can not write data; size not a multiple of channels!" );
213 if( offset
< 0 || offset
+ nf
> _rangeEnd
) {
214 qcWarningMsg( "QSoundFileView: can not write data; either offset is wrong or data size is too large." );
218 _cache
->write( data
, offset
, nf
);
223 void QcWaveform::doLoad( SNDFILE
*new_sf
, const SF_INFO
&new_info
, sf_count_t beg
, sf_count_t dur
)
225 // set up soundfile to scale data in range [-1,1] to int range
226 // when reading floating point data as int
227 sf_command( new_sf
, SFC_SET_SCALE_FLOAT_INT_READ
, NULL
, SF_TRUE
);
229 // check beginning and duration validity
231 if( beg
< 0 || dur
< 1 || beg
+ dur
> new_info
.frames
) {
232 qcErrorMsg("Invalid beginning and/or duration.");
237 // cleanup previous state
239 // NOTE we have to delete SoundCacheStream before closing the soundfile, as it might be still
241 // TODO: should SoundCacheStream open the soundfile on its own?
244 if( sf
) sf_close( sf
);
248 _beg
= _rangeBeg
= beg
;
249 _dur
= _rangeDur
= dur
;
250 _rangeEnd
= _rangeBeg
+ _rangeDur
;
254 _cache
= new SoundCacheStream();
255 connect( _cache
, SIGNAL(loadProgress(int)),
256 this, SIGNAL(loadProgress(int)) );
257 connect( _cache
, SIGNAL(loadProgress(int)),
258 this, SLOT(update()) );
259 connect( _cache
, SIGNAL(loadingDone()), this, SIGNAL(loadingDone()) );
260 connect( _cache
, SIGNAL(loadingDone()), this, SLOT(redraw()) );
262 _cache
->load( sf
, sfInfo
, beg
, dur
, kMaxFramesPerCacheUnit
, kMaxRawFrames
);
267 float QcWaveform::loadProgress()
269 return _cache
? _cache
->loadProgress() : 1.f
;
272 float QcWaveform::zoom()
274 // NOTE We have limited _rangeDur to 1 minimum.
275 return _dur
/ _rangeDur
;
278 float QcWaveform::xZoom()
280 return ( sfInfo
.samplerate
? _dur
/ sfInfo
.samplerate
: 0 );
283 float QcWaveform::yZoom()
288 VariantList
QcWaveform::selections() const
291 for( int i
= 0; i
< 64; ++i
) {
292 slist
.data
<< QVariant::fromValue
<VariantList
>( selection(i
) );
297 void QcWaveform::setCurrentSelection( int i
) {
298 if( i
< 0 || i
> 63 ) return;
303 VariantList
QcWaveform::selection( int i
) const
306 if( i
< 0 || i
> 63 ) return l
;
307 const Selection
&s
= _selections
[i
];
308 l
.data
<< QVariant(static_cast<int>(s
.start
- _rangeBeg
));
309 l
.data
<< QVariant(static_cast<int>(s
.size
));
313 void QcWaveform::setSelection( int i
, sf_count_t a
, sf_count_t b
)
315 if( i
< 0 || i
> 63 ) return;
316 Selection
& s
= _selections
[i
];
317 s
.start
= qMin( a
, b
);
318 s
.size
= qMax( a
, b
) - s
.start
;
322 void QcWaveform::setSelection( int i
, VariantList l
)
324 if( l
.data
.count() < 2 ) return;
325 sf_count_t start
= l
.data
[0].toInt() + _rangeBeg
;
326 sf_count_t end
= start
+ l
.data
[1].toInt();
327 setSelection( i
, start
, end
);
330 void QcWaveform::setSelectionStart( int i
, sf_count_t frame
)
332 if( i
< 0 || i
> 63 ) return;
333 Selection
& s
= _selections
[i
];
334 sf_count_t frame2
= s
.start
+ s
.size
;
335 s
.start
= qMin( frame
, frame2
);
336 s
.size
= qMax( frame
, frame2
) - s
.start
;
340 void QcWaveform::setSelectionEnd( int i
, sf_count_t frame
)
342 if( i
< 0 || i
> 63 ) return;
343 Selection
& s
= _selections
[i
];
344 sf_count_t frame2
= s
.start
;
345 s
.start
= qMin( frame
, frame2
);
346 s
.size
= qMax( frame
, frame2
) - s
.start
;
350 void QcWaveform::setSelectionEditable( int i
, bool editable
)
352 if( i
< 0 || i
> 63 ) return;
353 _selections
[i
].editable
= editable
;
357 void QcWaveform::setSelectionColor( int i
, const QColor
&c
)
359 if( i
< 0 || i
> 63 ) return;
360 _selections
[i
].color
= c
;
364 VariantList
QcWaveform::waveColors() const
367 Q_FOREACH( QColor clr
, _waveColors
)
368 clist
.data
<< QVariant(clr
);
372 void QcWaveform::setWaveColors( const VariantList
&list
)
375 Q_FOREACH( QVariant var
, list
.data
)
376 _waveColors
<< var
.value
<QColor
>();
380 void QcWaveform::zoomTo( double z
)
382 z
= qMax( 0.0, qMin( 1.0, z
) );
384 _dur
= qMax( _rangeDur
* z
, 1.0 );
386 //printf("dur: %Li view: %Li\n", sfInfo.frames, _dur);
387 if( _beg
+ _dur
> _rangeEnd
) _beg
= _rangeEnd
- _dur
;
393 void QcWaveform::zoomBy( double factor
)
395 zoomTo( zoom() * factor
);
398 void QcWaveform::zoomAllOut()
406 void QcWaveform::scrollTo( double startFrame
)
408 _beg
= qBound( (double)_rangeBeg
, startFrame
, _rangeEnd
- _dur
);
412 void QcWaveform::scrollBy( double f
)
414 scrollTo( _beg
+ f
);
417 float QcWaveform::scrollPos()
419 double scrollRange
= _rangeDur
- _dur
;
420 return scrollRange
> 0.0 ? (_beg
- _rangeBeg
) / scrollRange
: 0.f
;
422 void QcWaveform::setScrollPos( double fraction
)
424 scrollTo( fraction
* (_rangeDur
- _dur
) + _rangeBeg
);
427 void QcWaveform::scrollToStart()
429 scrollTo( _rangeBeg
);
432 void QcWaveform::scrollToEnd()
434 scrollTo( _rangeEnd
- _dur
);
437 void QcWaveform::setXZoom( double seconds
)
439 // NOTE We have limited _rangeDur to 1 minimum.
440 double frac
= seconds
* sfInfo
.samplerate
/ _rangeDur
;
444 void QcWaveform::setYZoom( double factor
)
450 void QcWaveform::zoomSelection( int i
)
452 if( i
< 0 || i
> 63 ) return;
454 Selection
&s
= _selections
[i
];
456 if( s
.start
>= _rangeEnd
|| s
.size
< 1 || s
.start
+ s
.size
<= _rangeBeg
)
459 _beg
= qMax( s
.start
, _rangeBeg
);
460 double end
= qMin( s
.start
+ s
.size
, _rangeEnd
);
463 // clear the selection
470 void QcWaveform::resizeEvent( QResizeEvent
* )
473 pixmap
= new QPixmap( size() );
478 void QcWaveform::paintEvent( QPaintEvent
*ev
)
480 Q_ASSERT( pixmap
!= 0 );
484 // if loading draw progress
485 if( _cache
&& _cache
->loading() ) {
487 p
.fillRect( r
, QColor(100,100,100) );
488 r
.setRight( r
.right() * _cache
->loadProgress() / 100 );
489 p
.fillRect( r
, QColor( 0, 0, 0 ) );
490 p
.setPen( QColor(255,255,255) );
492 opt
.setAlignment( Qt::AlignCenter
);
493 p
.drawText( rect(), "loading...", opt
);
497 p
.fillRect( rect(), _bkgColor
);
499 // draw waveform on pixmap
501 if( _drawWaveform
&& dirty
) {
502 draw( pixmap
, 0, width(), _beg
, _dur
);
510 p
.scale( (double) width() / _dur
, 1.0 );
511 p
.translate( _beg
* -1.0, 0.0 );
512 p
.setPen( Qt::NoPen
);
515 for( i
= 0; i
< 64; ++i
) {
516 const Selection
&s
= _selections
[i
];
518 QRectF
r ( s
.start
, 0, s
.size
, height() );
519 p
.setBrush( s
.color
);
528 if( _showGrid
&& sfInfo
.samplerate
> 0 && _gridResolution
> 0.f
) {
531 double durSecs
= (double) _dur
/ sfInfo
.samplerate
;
532 double begSecs
= (double) _beg
/ sfInfo
.samplerate
;
534 p
.scale( width() / durSecs
, 1.0 );
535 p
.setPen( _gridColor
);
537 double offset
= _gridOffset
- begSecs
;
538 offset
-= ( floor( offset
/ _gridResolution
) * _gridResolution
);
539 while( offset
< durSecs
) {
540 p
.drawLine( QLineF( offset
, 0.0, offset
, height() ) );
541 offset
+= _gridResolution
;
547 // paste the waveform pixmap on screen
550 p
.drawPixmap( ev
->rect(), *pixmap
, ev
->rect() );
554 if( _showCursor
&& _cursorPos
>= _beg
&& _cursorPos
< _beg
+ _dur
) {
555 double cursorX
= (_cursorPos
- _beg
) / _fpp
;
556 p
.setPen( _cursorColor
);
557 p
.drawLine( QLineF( cursorX
, 0, cursorX
, height() ) );
561 void QcWaveform::keyPressEvent( QKeyEvent
*ev
)
563 if( ev
->key() == Qt::Key_Shift
&& _dragAction
== Navigate
)
565 _dragPoint
= mapFromGlobal(QCursor::pos());
572 void QcWaveform::mousePressEvent( QMouseEvent
*ev
)
574 _dragAction
= NoDragAction
;
575 _dragPoint
= ev
->pos();
576 _dragFrame
= ev
->pos().x() * _fpp
+ _beg
;
578 Qt::KeyboardModifiers mods
= ev
->modifiers();
579 Qt::MouseButton btn
= ev
->button();
581 Qt::KeyboardModifier CTRL
= Qt::MetaModifier
;
583 Qt::KeyboardModifier CTRL
= Qt::ControlModifier
;
586 if( btn
== Qt::LeftButton
) {
588 if( (mods
& Qt::ShiftModifier
) && ( mods
& CTRL
) ) {
589 _dragFrame
= _selections
[_curSel
].start
;
590 _dragAction
= MoveSelection
;
592 else if( mods
& Qt::ShiftModifier
) {
593 _dragAction
= Select
;
594 const Selection
&s
= _selections
[_curSel
];
595 if( _dragFrame
< s
.start
+ (s
.size
*0.5) ) {
596 setSelectionStart( _curSel
, _dragFrame
);
597 _dragFrame
= s
.start
+ s
.size
;
600 setSelectionEnd( _curSel
, _dragFrame
);
601 _dragFrame
= s
.start
;
606 if( !(mods
& CTRL
) ) {
607 _dragAction
= Select
;
608 _selections
[_curSel
].start
= _dragFrame
;
609 _selections
[_curSel
].size
= 0;
613 if( _cursorEditable
) {
614 _cursorPos
= _dragFrame
;
615 if( mods
& CTRL
) _dragAction
= MoveCursor
;
617 Q_EMIT( metaAction() );
622 else if( btn
== Qt::RightButton
) {
623 _dragAction
= Navigate
;
624 _dragData
= ev
->pos().x() * _fpp
+ _beg
;
629 void QcWaveform::mouseDoubleClickEvent ( QMouseEvent
* )
631 setSelection( _curSel
, _rangeBeg
, _rangeEnd
);
635 void QcWaveform::mouseMoveEvent( QMouseEvent
*ev
)
637 if( !ev
->buttons() ) return;
639 if( _dragAction
== Navigate
) {
640 Qt::KeyboardModifiers mods
= ev
->modifiers();
641 if( mods
& Qt::ShiftModifier
) {
642 double factor
= pow( 2, (ev
->pos().y() - _dragPoint
.y()) * 0.008 );
643 // zoom to the initial zoom times the factor based on distance from initial position
644 zoomTo( _dragData2
* factor
);
646 // scroll to the clicked frame minus the current mouse position in frames
647 // _fpp has been adjusted by zooming
648 scrollTo( _dragData
- (ev
->pos().x() * _fpp
) );
650 else if( _dragAction
== Select
) {
651 sf_count_t frame
= qBound( 0, ev
->pos().x(), width() ) * _fpp
+ _beg
;
652 setSelection( _curSel
, _dragFrame
, frame
);
656 else if( _dragAction
== MoveSelection
) {
657 double dpos
= ev
->pos().x() - _dragPoint
.x();
658 Selection
&s
= _selections
[_curSel
];
659 s
.start
= _dragFrame
+ (dpos
* _fpp
);
660 s
.start
= qBound( _rangeBeg
, s
.start
, _rangeEnd
- s
.size
);
664 else if( _dragAction
== MoveCursor
) {
665 _cursorPos
= qBound( 0, ev
->pos().x(), width() ) * _fpp
+ _beg
;
667 Q_EMIT( metaAction() );
671 void QcWaveform::rebuildCache ( int maxFPU
, int maxRawFrames
)
675 void QcWaveform::draw( QPixmap
*pix
, int x
, int width
, double f_beg
, double f_dur
)
677 // FIXME anomaly: when _fpp reaching 1.0 rms can go outside min-max!
679 pix
->fill( QColor( 0, 0, 0, 0 ) );
683 if( !_cache
|| !_cache
->ready() ) return;
685 // check for sane situation:
686 if( f_beg
< _rangeBeg
|| f_beg
+ f_dur
> _rangeEnd
) return;
689 sf_count_t i_beg
= floor(f_beg
); // data beginning;
690 sf_count_t i_count
= ceil( f_beg
+ f_dur
) - i_beg
; // data count;
692 if( i_beg
+ i_count
< _rangeEnd
) {
700 // data source - choose according to horiz. zoom (data-display resolution)
701 bool canUseCache
= _fpp
< 1.0 ? _cache
->fpu() == 1.0 : _fpp
>= _cache
->fpu();
703 SoundStream
*soundStream
;
704 SoundFileStream sfStream
;
707 qcDebugMsg( 2, QString("using cache") );
708 soundStream
= _cache
;
711 qcDebugMsg( 2, QString("using file") );
712 soundStream
= &sfStream
;
713 sfStream
.load( sf
, sfInfo
, i_beg
, i_count
);
716 qcWarningMsg( "QSoundFileView: can't paint waveform: view resolution exceeds data cache resolution,"
717 " and soundfile is not given." );
722 float spacing
= pix
->height() * 0.15f
/ (sfInfo
.channels
+ 1);
723 float chHeight
= pix
->height() * 0.85f
/ (float) sfInfo
.channels
;
724 float yScale
= -chHeight
/ 65535.f
* _yZoom
;
727 // initial painter setup
731 float halfChH
= chHeight
* 0.5;
732 p
.translate( 0.f
, halfChH
+ spacing
);
734 int waveColorN
= _waveColors
.count();
736 for( ch
= 0; ch
< soundStream
->channels(); ++ch
) {
738 if( ch
< waveColorN
&& _waveColors
[ch
].isValid() ) {
739 QColor
clr( _waveColors
[ch
] );
740 rmsPen
.setColor( clr
);
741 minMaxPen
.setColor( clr
.darker( 140 ) );
744 rmsPen
.setColor( _rmsColor
);
745 minMaxPen
.setColor( _peakColor
);
749 p
.setPen( QColor(90,90,90) );
750 p
.drawLine( x
, 0, x
+ width
, 0 );
752 // draw bounding lines
753 p
.setPen( QColor(100,100,100) );
754 p
.drawLine( x
, halfChH
, x
+width
, halfChH
);
755 p
.drawLine( x
, -halfChH
, x
+width
, -halfChH
);
760 p
.setClipRect( x
, -halfChH
, x
+width
, chHeight
);
761 p
.scale( 1.f
, yScale
);
765 // draw min-max regions and RMS
767 short minBuffer
[width
];
768 short maxBuffer
[width
];
772 bool ok
= soundStream
->displayData( ch
, f_beg
, f_dur
,
773 minBuffer
, maxBuffer
,
776 // printf("integration ok: %i\n", ok);
780 for( i
= 0; i
< width
; ++i
) {
781 short min
= minBuffer
[i
];
782 short max
= maxBuffer
[i
];
784 p
.setPen( minMaxPen
);
785 p
.drawLine( x
+ i
, min
, x
+ i
, max
);
788 p
.setPen( minMaxPen
);
789 p
.drawPoint( x
+ i
, min
);
792 p
.drawLine( x
+ i
, minRMS
[i
], x
+ i
, maxRMS
[i
] );
797 // draw lines between actual values
799 qreal ppf
= 1.0 / _fpp
;
800 qreal dx
= (i_beg
- f_beg
) * ppf
;
802 bool interleaved
= false;
803 short *data
= soundStream
->rawFrames( ch
, i_beg
, i_count
, &interleaved
);
804 //printf("got raw frames ok: %i\n", data != 0 );
805 Q_ASSERT( data
!= 0 );
806 int step
= interleaved
? soundStream
->channels() : 1;
811 QPointF
pt( dx
, (qreal
) *data
);
815 for( f
= 1; f
< i_count
; ++f
) {
817 QPointF
pt( f
* ppf
+ dx
, (qreal
) *data
);
820 if( i_count
&& !haveOneMore
) {
821 path
.lineTo( QPointF( f
* ppf
+ dx
, (qreal
)*data
) );
830 p
.translate( 0.f
, chHeight
+ spacing
);