1 /************************************************************************
3 * Copyright 2010 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 ************************************************************************/
22 #include "QcSoundFileView.h"
23 #include "../QcWidgetFactory.h"
25 #include <QGridLayout>
27 #include <QApplication>
28 #include <QPaintEvent>
33 static QcWidgetFactory
<QcSoundFileView
> sfViewFactory
;
34 static QcWidgetFactory
<QcWaveform
> waveformFactory
;
36 const int kMaxRawFrames
= 300000;
37 const int kMaxFramesPerCacheUnit
= 128;
39 QcSoundFileView::QcSoundFileView() :
40 hScrollMultiplier( 0.f
)
42 waveform
= new QcWaveform();
44 timeScrollBar
= new QScrollBar( Qt::Horizontal
);
46 zoomScrollBar
= new QSlider();
47 zoomScrollBar
->setRange( 0, 1000 );
49 QGridLayout
*l
= new QGridLayout();
50 l
->addWidget( waveform
, 0, 0 );
51 l
->addWidget( zoomScrollBar
, 0, 1 );
52 l
->addWidget( timeScrollBar
, 1, 0, 1, 2 );
56 connect( timeScrollBar
, SIGNAL(valueChanged(int)), this, SLOT(onPosSliderChanged(int)) );
57 connect( zoomScrollBar
, SIGNAL(valueChanged(int)), this, SLOT(onZoomSliderChanged(int)) );
58 connect( waveform
, SIGNAL(loadingDone()), this, SLOT(updateTimeScrollBar()) );
59 connect( waveform
, SIGNAL(loadingDone()), this, SLOT(updateZoomScrollBar()) );
61 updateTimeScrollBar();
66 void QcSoundFileView::load( const QString
& filename
)
68 waveform
->load( filename
);
72 void QcSoundFileView::onPosSliderChanged( int value
)
74 waveform
->scrollTo( value
* hScrollMultiplier
);
77 void QcSoundFileView::onZoomSliderChanged( int z
)
79 float frac
= (float)(zoomScrollBar
->maximum() - z
) / zoomScrollBar
->maximum();
82 waveform
->zoomTo( frac
);
83 updateTimeScrollBar();
86 void QcSoundFileView::updateTimeScrollBar()
89 // view start moves weirdly when changing zoom. setSliderPosition() actually calls Waveform
90 // again, and the resulting position is not the same as Waveform's original.
93 max
= waveform
->frames() - waveform
->viewFrames();
95 hScrollMultiplier
= max
/ 1000.0;
98 hScrollMultiplier
= 1.0;
100 timeScrollBar
->setMaximum( max
);
101 //printf("before: %Li\n", waveform->scrollPos() );
102 timeScrollBar
->blockSignals( true );
103 timeScrollBar
->setSliderPosition( waveform
->scrollPos() / hScrollMultiplier
);
104 timeScrollBar
->blockSignals( false );
105 //printf("after: %Li\n", waveform->scrollPos() );
106 timeScrollBar
->setEnabled( max
> 0 );
109 void QcSoundFileView::updateZoomScrollBar()
111 int max
= zoomScrollBar
->maximum();
112 zoomScrollBar
->setSliderPosition( max
- (waveform
->zoom() * max
) );
115 QcWaveform::QcWaveform( QWidget
* parent
) : QWidget( parent
),
127 _cursorEditable(true),
129 _cursorColor(QColor(255,0,0)),
132 _gridResolution(1.0),
134 _gridColor(QColor(100,100,200)),
141 _peakColor( QColor(242,178,0) ),
142 _rmsColor( QColor(255,255,0) ),
146 memset( &sfInfo
, 0, sizeof(SF_INFO
) );
147 setSizePolicy( QSizePolicy::Expanding
, QSizePolicy::Expanding
);
148 setAttribute( Qt::WA_OpaquePaintEvent
, true );
151 QcWaveform::~QcWaveform()
155 if( sf
) sf_close( sf
);
158 void QcWaveform::load( const QString
& filename
)
160 qcDebugMsg( 1, "QcWaveform::load()" );
163 memset( &new_info
, 0, sizeof(SF_INFO
) );
165 SNDFILE
*new_sf
= sf_open( filename
.toStdString().c_str(), SFM_READ
, &new_info
);
168 qcErrorMsg("Could not open soundfile.");
172 doLoad( new_sf
, new_info
, 0, new_info
.frames
);
175 void QcWaveform::load( const QString
& filename
, int beg
, int dur
)
177 qcDebugMsg( 1, "QcWaveform::load( beg, dur )" );
180 memset( &new_info
, 0, sizeof(SF_INFO
) );
182 SNDFILE
*new_sf
= sf_open( filename
.toStdString().c_str(), SFM_READ
, &new_info
);
185 qcErrorMsg("Could not open soundfile.");
189 doLoad( new_sf
, new_info
, beg
, dur
);
192 void QcWaveform::doLoad( SNDFILE
*new_sf
, const SF_INFO
&new_info
, sf_count_t beg
, sf_count_t dur
)
194 // set up soundfile to scale data in range [-1,1] to int range
195 // when reading floating point data as int
196 sf_command( new_sf
, SFC_SET_SCALE_FLOAT_INT_READ
, NULL
, SF_TRUE
);
198 // check beginning and duration validity
200 if( beg
< 0 || dur
< 1 || beg
+ dur
> new_info
.frames
) {
201 qcErrorMsg("Invalid beginning and/or duration.");
206 // cleanup previous state
208 // NOTE we have to delete SoundCacheStream before closing the soundfile, as it might be still
210 // TODO: should SoundCacheStream open the soundfile on its own?
213 if( sf
) sf_close( sf
);
217 _beg
= _rangeBeg
= beg
;
218 _dur
= _rangeDur
= dur
;
219 _rangeEnd
= _rangeBeg
+ _rangeDur
;
223 _cache
= new SoundCacheStream();
224 connect( _cache
, SIGNAL(loadProgress(int)),
225 this, SIGNAL(loadProgress(int)) );
226 connect( _cache
, SIGNAL(loadProgress(int)),
227 this, SLOT(update()) );
228 connect( _cache
, SIGNAL(loadingDone()), this, SIGNAL(loadingDone()) );
229 connect( _cache
, SIGNAL(loadingDone()), this, SLOT(redraw()) );
231 _cache
->load( sf
, sfInfo
, beg
, dur
, kMaxFramesPerCacheUnit
, kMaxRawFrames
);
236 float QcWaveform::loadProgress()
238 return _cache
? _cache
->loadProgress() : 1.f
;
241 float QcWaveform::zoom()
243 // NOTE We have limited _rangeDur to 1 minimum.
244 return _dur
/ _rangeDur
;
247 float QcWaveform::xZoom()
249 return ( sfInfo
.samplerate
? _dur
/ sfInfo
.samplerate
: 0 );
252 float QcWaveform::yZoom()
257 VariantList
QcWaveform::selections() const
260 for( int i
= 0; i
< 64; ++i
) {
261 slist
.data
<< QVariant::fromValue
<VariantList
>( selection(i
) );
266 void QcWaveform::setCurrentSelection( int i
) {
267 if( i
< 0 || i
> 63 ) return;
272 VariantList
QcWaveform::selection( int i
) const
275 if( i
< 0 || i
> 63 ) return l
;
276 const Selection
&s
= _selections
[i
];
277 l
.data
<< QVariant(static_cast<int>(s
.start
- _rangeBeg
));
278 l
.data
<< QVariant(static_cast<int>(s
.size
));
282 void QcWaveform::setSelection( int i
, sf_count_t a
, sf_count_t b
)
284 if( i
< 0 || i
> 63 ) return;
285 Selection
& s
= _selections
[i
];
286 s
.start
= qMin( a
, b
);
287 s
.size
= qMax( a
, b
) - s
.start
;
291 void QcWaveform::setSelection( int i
, VariantList l
)
293 if( l
.data
.count() < 2 ) return;
294 sf_count_t start
= l
.data
[0].toInt() + _rangeBeg
;
295 sf_count_t end
= start
+ l
.data
[1].toInt();
296 setSelection( i
, start
, end
);
299 void QcWaveform::setSelectionStart( int i
, sf_count_t frame
)
301 if( i
< 0 || i
> 63 ) return;
302 Selection
& s
= _selections
[i
];
303 sf_count_t frame2
= s
.start
+ s
.size
;
304 s
.start
= qMin( frame
, frame2
);
305 s
.size
= qMax( frame
, frame2
) - s
.start
;
309 void QcWaveform::setSelectionEnd( int i
, sf_count_t frame
)
311 if( i
< 0 || i
> 63 ) return;
312 Selection
& s
= _selections
[i
];
313 sf_count_t frame2
= s
.start
;
314 s
.start
= qMin( frame
, frame2
);
315 s
.size
= qMax( frame
, frame2
) - s
.start
;
319 void QcWaveform::setSelectionEditable( int i
, bool editable
)
321 if( i
< 0 || i
> 63 ) return;
322 _selections
[i
].editable
= editable
;
326 void QcWaveform::setSelectionColor( int i
, const QColor
&c
)
328 if( i
< 0 || i
> 63 ) return;
329 _selections
[i
].color
= c
;
333 VariantList
QcWaveform::waveColors() const
337 // FIXME there is no way to return an Array of Colors to SC
338 // as the Color objects should have been preallocated by the
339 // user, but we don't have an interface for that.
341 Q_FOREACH( QColor clr
, _waveColors
) {
342 clist
.data
<< QVariant(clr
);
348 void QcWaveform::setWaveColors( const VariantList
&list
)
351 Q_FOREACH( QVariant var
, list
.data
) {
352 _waveColors
<< var
.value
<QColor
>();
357 void QcWaveform::zoomTo( double z
)
359 z
= qMax( 0.0, qMin( 1.0, z
) );
361 _dur
= qMax( _rangeDur
* z
, 1.0 );
363 //printf("dur: %Li view: %Li\n", sfInfo.frames, _dur);
364 if( _beg
+ _dur
> _rangeEnd
) _beg
= _rangeEnd
- _dur
;
370 void QcWaveform::zoomBy( double factor
)
372 zoomTo( zoom() * factor
);
375 void QcWaveform::zoomAllOut()
383 void QcWaveform::scrollTo( double startFrame
)
385 _beg
= qBound( (double)_rangeBeg
, startFrame
, _rangeEnd
- _dur
);
389 void QcWaveform::scrollBy( double f
)
391 scrollTo( _beg
+ f
);
394 float QcWaveform::scrollPos()
396 double scrollRange
= _rangeDur
- _dur
;
397 return scrollRange
> 0.0 ? (_beg
- _rangeBeg
) / scrollRange
: 0.f
;
399 void QcWaveform::setScrollPos( double fraction
)
401 scrollTo( fraction
* (_rangeDur
- _dur
) + _rangeBeg
);
404 void QcWaveform::scrollToStart()
406 scrollTo( _rangeBeg
);
409 void QcWaveform::scrollToEnd()
411 scrollTo( _rangeEnd
- _dur
);
414 void QcWaveform::setXZoom( double seconds
)
416 // NOTE We have limited _rangeDur to 1 minimum.
417 double frac
= seconds
* sfInfo
.samplerate
/ _rangeDur
;
421 void QcWaveform::setYZoom( double factor
)
427 void QcWaveform::zoomSelection( int i
)
429 if( i
< 0 || i
> 63 ) return;
431 Selection
&s
= _selections
[i
];
433 if( s
.start
>= _rangeEnd
|| s
.size
< 1 || s
.start
+ s
.size
<= _rangeBeg
)
436 _beg
= qMax( s
.start
, _rangeBeg
);
437 double end
= qMin( s
.start
+ s
.size
, _rangeEnd
);
440 // clear the selection
447 void QcWaveform::resizeEvent( QResizeEvent
* )
450 pixmap
= new QPixmap( size() );
455 void QcWaveform::paintEvent( QPaintEvent
*ev
)
457 Q_ASSERT( pixmap
!= 0 );
461 // if loading draw progress
462 if( _cache
&& _cache
->loading() ) {
464 p
.fillRect( r
, QColor(100,100,100) );
465 r
.setRight( r
.right() * _cache
->loadProgress() / 100 );
466 p
.fillRect( r
, QColor( 0, 0, 0 ) );
467 p
.setPen( QColor(255,255,255) );
469 opt
.setAlignment( Qt::AlignCenter
);
470 p
.drawText( rect(), "loading...", opt
);
474 // draw waveform on pixmap
476 if( _drawWaveform
&& dirty
) {
477 draw( pixmap
, 0, width(), _beg
, _dur
);
483 p
.fillRect( rect(), QColor( 0, 0, 0 ) );
489 p
.scale( (double) width() / _dur
, 1.0 );
490 p
.translate( _beg
* -1.0, 0.0 );
491 p
.setPen( Qt::NoPen
);
494 for( i
= 0; i
< 64; ++i
) {
495 const Selection
&s
= _selections
[i
];
497 QRectF
r ( s
.start
, 0, s
.size
, height() );
498 p
.setBrush( s
.color
);
507 if( _showGrid
&& sfInfo
.samplerate
> 0 && _gridResolution
> 0.f
) {
510 double durSecs
= (double) _dur
/ sfInfo
.samplerate
;
511 double begSecs
= (double) _beg
/ sfInfo
.samplerate
;
513 p
.scale( width() / durSecs
, 1.0 );
514 p
.setPen( _gridColor
);
516 double offset
= _gridOffset
- begSecs
;
517 offset
-= ( floor( offset
/ _gridResolution
) * _gridResolution
);
518 while( offset
< durSecs
) {
519 p
.drawLine( QLineF( offset
, 0.0, offset
, height() ) );
520 offset
+= _gridResolution
;
526 // paste the waveform pixmap on screen
529 p
.drawPixmap( ev
->rect(), *pixmap
, ev
->rect() );
533 if( _showCursor
&& _cursorPos
>= _beg
&& _cursorPos
< _beg
+ _dur
) {
534 double cursorX
= (_cursorPos
- _beg
) / _fpp
;
535 p
.setPen( _cursorColor
);
536 p
.drawLine( QLineF( cursorX
, 0, cursorX
, height() ) );
540 void QcWaveform::mousePressEvent( QMouseEvent
*ev
)
542 _dragAction
= NoDragAction
;
543 _dragPoint
= ev
->pos();
544 _dragFrame
= ev
->pos().x() * _fpp
+ _beg
;
546 Qt::KeyboardModifiers mods
= ev
->modifiers();
547 Qt::MouseButton btn
= ev
->button();
549 Qt::KeyboardModifier CTRL
= Qt::MetaModifier
;
551 Qt::KeyboardModifier CTRL
= Qt::ControlModifier
;
554 if( btn
== Qt::LeftButton
) {
556 if( (mods
& Qt::ShiftModifier
) && ( mods
& CTRL
) ) {
557 _dragFrame
= _selections
[_curSel
].start
;
558 _dragAction
= MoveSelection
;
560 else if( mods
& Qt::ShiftModifier
) {
561 _dragAction
= Select
;
562 const Selection
&s
= _selections
[_curSel
];
563 if( _dragFrame
< s
.start
+ (s
.size
*0.5) ) {
564 setSelectionStart( _curSel
, _dragFrame
);
565 _dragFrame
= s
.start
+ s
.size
;
568 setSelectionEnd( _curSel
, _dragFrame
);
569 _dragFrame
= s
.start
;
574 if( !(mods
& CTRL
) ) {
575 _dragAction
= Select
;
576 _selections
[_curSel
].start
= _dragFrame
;
577 _selections
[_curSel
].size
= 0;
581 if( _cursorEditable
) {
582 _cursorPos
= _dragFrame
;
583 if( mods
& CTRL
) _dragAction
= MoveCursor
;
585 Q_EMIT( metaAction() );
590 else if( btn
== Qt::RightButton
) {
592 if( mods
& Qt::ShiftModifier
) {
595 _dragData2
= ev
->pos().x() * _fpp
+ _beg
;
598 _dragAction
= Scroll
;
605 void QcWaveform::mouseDoubleClickEvent ( QMouseEvent
* )
607 setSelection( _curSel
, _rangeBeg
, _rangeEnd
);
611 void QcWaveform::mouseMoveEvent( QMouseEvent
*ev
)
613 if( _dragAction
== Scroll
) {
614 double dpos
= _dragPoint
.x() - ev
->pos().x();
615 scrollTo( dpos
* _fpp
+ _dragData
);
617 else if( _dragAction
== Zoom
) {
618 double factor
= pow( 2, (ev
->pos().y() - _dragPoint
.y()) * 0.008 );
619 double zoom_0
= _dragData
;
620 zoomTo( zoom_0
* factor
);
621 scrollTo( _dragData2
- (_dragPoint
.x() * _fpp
) );
623 else if( _dragAction
== Select
) {
624 sf_count_t frame
= qBound( 0, ev
->pos().x(), width() ) * _fpp
+ _beg
;
625 setSelection( _curSel
, _dragFrame
, frame
);
629 else if( _dragAction
== MoveSelection
) {
630 double dpos
= ev
->pos().x() - _dragPoint
.x();
631 Selection
&s
= _selections
[_curSel
];
632 s
.start
= _dragFrame
+ (dpos
* _fpp
);
633 s
.start
= qBound( _rangeBeg
, s
.start
, _rangeEnd
- s
.size
);
637 else if( _dragAction
== MoveCursor
) {
638 _cursorPos
= qBound( 0, ev
->pos().x(), width() ) * _fpp
+ _beg
;
640 Q_EMIT( metaAction() );
644 void QcWaveform::rebuildCache ( int maxFPU
, int maxRawFrames
)
648 void QcWaveform::draw( QPixmap
*pix
, int x
, int width
, double f_beg
, double f_dur
)
650 // FIXME anomaly: when _fpp reaching 1.0 rms can go outside min-max!
652 pix
->fill( QColor( 0, 0, 0, 0 ) );
656 if( !sf
|| !_cache
|| !_cache
->ready() ) return;
658 // check for sane situation:
659 if( f_beg
< _rangeBeg
|| f_beg
+ f_dur
> _rangeEnd
) return;
662 sf_count_t i_beg
= floor(f_beg
); // data beginning;
663 sf_count_t i_count
= ceil( f_beg
+ f_dur
) - i_beg
; // data count;
665 if( i_beg
+ i_count
< _rangeEnd
) {
673 // data source - choose according to horiz. zoom (data-display resolution)
674 SoundStream
*soundStream
;
675 SoundFileStream sfStream
;
677 if( _fpp
> 1.0 ? (_fpp
< _cache
->fpu()) : _cache
->fpu() > 1.0 ) {
678 qcDebugMsg( 1, QString("use file") );
679 soundStream
= &sfStream
;
680 sfStream
.load( sf
, sfInfo
, i_beg
, i_count
);
683 qcDebugMsg( 1, QString("use cache") );
684 soundStream
= _cache
;
688 float spacing
= pix
->height() * 0.15f
/ (sfInfo
.channels
+ 1);
689 float chHeight
= pix
->height() * 0.85f
/ (float) sfInfo
.channels
;
690 float yScale
= -chHeight
/ 65535.f
* _yZoom
;
693 // initial painter setup
697 float halfChH
= chHeight
* 0.5;
698 p
.translate( 0.f
, halfChH
+ spacing
);
700 int waveColorN
= _waveColors
.count();
702 for( ch
= 0; ch
< soundStream
->channels(); ++ch
) {
704 if( ch
< waveColorN
&& _waveColors
[ch
].isValid() ) {
705 QColor
clr( _waveColors
[ch
] );
706 rmsPen
.setColor( clr
);
707 minMaxPen
.setColor( clr
.darker( 140 ) );
710 rmsPen
.setColor( _rmsColor
);
711 minMaxPen
.setColor( _peakColor
);
715 p
.setPen( QColor(90,90,90) );
716 p
.drawLine( x
, 0, x
+ width
, 0 );
718 // draw bounding lines
719 p
.setPen( QColor(100,100,100) );
720 p
.drawLine( x
, halfChH
, x
+width
, halfChH
);
721 p
.drawLine( x
, -halfChH
, x
+width
, -halfChH
);
726 p
.setClipRect( x
, -halfChH
, x
+width
, chHeight
);
727 p
.scale( 1.f
, yScale
);
731 // draw min-max regions and RMS
733 short minBuffer
[width
];
734 short maxBuffer
[width
];
738 bool ok
= soundStream
->displayData( ch
, f_beg
, f_dur
,
739 minBuffer
, maxBuffer
,
742 // printf("integration ok: %i\n", ok);
746 for( i
= 0; i
< width
; ++i
) {
747 short min
= minBuffer
[i
];
748 short max
= maxBuffer
[i
];
750 p
.setPen( minMaxPen
);
751 p
.drawLine( x
+ i
, min
, x
+ i
, max
);
754 p
.setPen( minMaxPen
);
755 p
.drawPoint( x
+ i
, min
);
758 p
.drawLine( x
+ i
, minRMS
[i
], x
+ i
, maxRMS
[i
] );
763 // draw lines between actual values
765 qreal ppf
= 1.0 / _fpp
;
766 qreal dx
= (i_beg
- f_beg
) * ppf
;
768 bool interleaved
= false;
769 short *data
= soundStream
->rawFrames( ch
, i_beg
, i_count
, &interleaved
);
770 //printf("got raw frames ok: %i\n", data != 0 );
771 Q_ASSERT( data
!= 0 );
772 int step
= interleaved
? soundStream
->channels() : 1;
777 QPointF
pt( dx
, (qreal
) *data
);
781 for( f
= 1; f
< i_count
; ++f
) {
783 QPointF
pt( f
* ppf
+ dx
, (qreal
) *data
);
786 if( i_count
&& !haveOneMore
) {
787 path
.lineTo( QPointF( f
* ppf
+ dx
, (qreal
)*data
) );
796 p
.translate( 0.f
, chHeight
+ spacing
);
800 SoundFileStream::SoundFileStream() : _data(0), _dataSize(0), _dataOffset(0)
803 SoundFileStream::SoundFileStream( SNDFILE
*sf
, const SF_INFO
&info
, sf_count_t b
, sf_count_t d
)
806 load( sf
, info
, b
, d
);
809 SoundFileStream::~SoundFileStream()
814 void SoundFileStream::load( SNDFILE
*sf
, const SF_INFO
&info
, sf_count_t beg
, sf_count_t dur
)
821 _data
= new short [_dataSize
* info
.channels
];
822 sf_seek( sf
, _dataOffset
, SEEK_SET
);
823 _dataSize
= sf_readf_short( sf
, _data
, _dataSize
);
830 bool SoundFileStream::integrate
831 ( int ch
, double f_beg
, double f_dur
,
832 short *minBuffer
, short *maxBuffer
, float *sumBuf
, float *sum2Buf
, int bufferSize
)
836 && ( f_beg
>= beginning() )
837 && ( f_beg
+ f_dur
<= beginning() + duration() );
838 if( !ok
) return false;
840 double fpu
= f_dur
/ bufferSize
;
841 double f_pos
= f_beg
- _dataOffset
;
842 double f_pos_max
= _dataSize
;
845 for( i
= 0; i
< bufferSize
; ++i
) {
846 int data_pos
= floor(f_pos
);
848 // increment position
850 // slower, but error-proof:
851 // f_pos = (double)(i+1) / width() * f_dur + f_beg;
853 // the following is a faster variant, but floating point operations are fallible,
854 // so we need to make sure we stay within the constraints of f_dur;
855 double f_pos1
= f_pos
+ fpu
;
856 if( f_pos1
> f_pos_max
) f_pos1
= f_pos_max
;
858 int frame_count
= ceil(f_pos1
) - data_pos
;
860 float frac0
= data_pos
+ 1.f
- f_pos
;
861 float frac1
= f_pos1
+ 1.f
- ceil(f_pos1
);
863 // get min, max and sum
864 short *samples
= _data
+ (data_pos
* channels()) + ch
;
865 short min
= SHRT_MAX
;
866 short max
= SHRT_MIN
;
870 for( f
= 0; f
< frame_count
; ++f
, samples
+= channels() ){
871 // TODO should we overlap min-max or not here?
872 float sample
= *samples
;
874 if( f
== 0 ) frac
= frac0
;
875 else if( f
== frame_count
- 1 ) frac
= frac1
;
878 if( sample
< min
) min
= sample
;
879 if( sample
> max
) max
= sample
;
881 sum
+= sample
* frac
;
882 sum2
+= sample
* sample
* frac
;
896 bool SoundFileStream::displayData
897 ( int ch
, double f_beg
, double f_dur
,
898 short *minBuffer
, short *maxBuffer
, short *minRMS
, short *maxRMS
, int bufferSize
)
902 && ( f_beg
>= beginning() )
903 && ( f_beg
+ f_dur
<= beginning() + duration() );
904 if( !ok
) return false;
906 double fpu
= f_dur
/ bufferSize
;
907 double f_pos
= f_beg
- _dataOffset
;
908 double f_pos_max
= _dataSize
;
910 short min
= SHRT_MAX
;
911 short max
= SHRT_MIN
;
913 for( i
= 0; i
< bufferSize
; ++i
) {
914 int data_pos
= floor(f_pos
);
916 // increment position
918 // slower, but error-proof:
919 // f_pos = (double)(i+1) / width() * f_dur + f_beg;
921 // the following is a faster variant, but floating point operations are fallible,
922 // so we need to make sure we stay within the constraints of f_dur;
923 double f_pos1
= f_pos
+ fpu
;
924 if( f_pos1
> f_pos_max
) f_pos1
= f_pos_max
;
926 int frame_count
= ceil(f_pos1
) - data_pos
;
928 float frac0
= data_pos
+ 1.f
- f_pos
;
929 float frac1
= f_pos1
+ 1.f
- ceil(f_pos1
);
931 // get min, max and sum
932 short *samples
= _data
+ (data_pos
* channels()) + ch
;
937 for( f
= 0; f
< frame_count
; ++f
, samples
+= channels() ){
938 // TODO should we overlap min-max or not here?
939 float sample
= *samples
;
941 if( f
== 0 ) frac
= frac0
;
942 else if( f
== frame_count
- 1 ) frac
= frac1
;
945 if( sample
< min
) min
= sample
;
946 if( sample
> max
) max
= sample
;
948 sum
+= sample
* frac
;
949 sum2
+= sample
* sample
* frac
;
953 double avg
= sum
/ n
;
954 double stdDev
= sqrt( abs((sum2
- (sum
*avg
) ) / n
) );
958 minRMS
[i
] = avg
- stdDev
;
959 maxRMS
[i
] = avg
+ stdDev
;
969 short *SoundFileStream::rawFrames( int ch
, sf_count_t b
, sf_count_t d
, bool *interleaved
)
971 if( ch
> channels() || b
< _dataOffset
|| b
+ d
> _dataOffset
+ _dataSize
) return 0;
973 sf_count_t offset
= (b
- _dataOffset
) * channels() + ch
;
974 return ( _data
+ offset
);
978 SoundCacheStream::SoundCacheStream()
979 : SoundStream ( 0, 0.0, 0.0 ),
988 _loader
= new SoundCacheLoader( this );
989 connect( _loader
, SIGNAL(loadProgress(int)),
990 this, SLOT(onLoadProgress(int)),
991 Qt::QueuedConnection
);
992 connect( _loader
, SIGNAL(loadingDone()), this, SLOT(onLoadingDone()), Qt::QueuedConnection
);
995 void SoundCacheStream::load( SNDFILE
*sf
, const SF_INFO
&info
, sf_count_t beg
, sf_count_t dur
,
996 int maxFramesPerUnit
, int maxRawFrames
)
998 Q_ASSERT( maxRawFrames
> 0 && maxFramesPerUnit
> 0 );
1003 if( _loader
->isRunning() ) {
1004 _loader
->terminate();
1009 _ch
= info
.channels
;
1010 _beg
= _dataOffset
= beg
;
1013 // adjust data size for data amount limit
1014 if( _dur
<= maxRawFrames
) {
1015 // ok, not crossing the limit
1020 _dataSize
= maxRawFrames
;
1021 _fpu
= (double) _dur
/ _dataSize
;
1022 // re-adjust for data resolution limit
1023 if( _fpu
> maxFramesPerUnit
) {
1024 _dataSize
= (double) _dur
/ maxFramesPerUnit
;
1025 _fpu
= (double) _dur
/ _dataSize
;
1029 _caches
= new SoundCache
[info
.channels
];
1031 for( ch
= 0; ch
< info
.channels
; ++ch
) {
1032 _caches
[ch
].min
= new short [_dataSize
];
1033 _caches
[ch
].max
= new short [_dataSize
];
1034 _caches
[ch
].sum
= new float [_dataSize
];
1035 _caches
[ch
].sum2
= new float [_dataSize
];
1039 _loader
->load( sf
, info
);
1042 SoundCacheStream::~SoundCacheStream()
1044 if( _loader
->isRunning() ) {
1045 _loader
->terminate();
1051 bool SoundCacheStream::displayData
1052 ( int ch
, double f_beg
, double f_dur
,
1053 short *minBuffer
, short *maxBuffer
, short *minRMS
, short *maxRMS
, int bufferSize
)
1057 && ( f_beg
>= beginning() )
1058 && ( f_beg
+ f_dur
<= beginning() + duration() )
1059 && bufferSize
<= f_dur
* _fpu
;
1060 if( !ok
) return false;
1062 double ratio
= f_dur
/ _fpu
/ bufferSize
;
1063 double cache_pos
= (f_beg
- _dataOffset
) / _fpu
;
1065 short min
= SHRT_MAX
;
1066 short max
= SHRT_MIN
;
1068 for( i
= 0; i
< bufferSize
; ++i
) {
1069 int f
= floor(cache_pos
); // first frame
1070 bool no_overlap
= f
== ceil(cache_pos
);
1071 float frac0
= f
+ 1.f
- cache_pos
;
1074 // Due to possibility of floating point operation failures.
1075 if( cache_pos
> _dataSize
) cache_pos
= _dataSize
;
1076 int frame_count
= ceil(cache_pos
) - f
;
1077 float frac1
= cache_pos
+ 1.f
- ceil(cache_pos
);
1079 //short min = SHRT_MAX;
1080 //short max = SHRT_MIN;
1084 short *p_min
= _caches
[ch
].min
;
1085 short *p_max
= _caches
[ch
].max
;
1086 float *p_sum
= _caches
[ch
].sum
;
1087 float *p_sum2
= _caches
[ch
].sum2
;
1088 int countdown
= frame_count
;
1089 while( countdown
-- ) {
1091 // NOTE for min-max, behave as if first frame was ceil(cache_pos) instead of floor(),
1092 // to not smudge too much at large scale
1093 if( countdown
< frame_count
- 1 || no_overlap
) {
1094 if( p_min
[f
] < min
) min
= p_min
[f
];
1095 if( p_max
[f
] > max
) max
= p_max
[f
];
1099 if( countdown
== frame_count
- 1 ) frac
= frac0
;
1100 else if( countdown
== 0 ) frac
= frac1
;
1103 sum
+= p_sum
[f
] * frac
;
1104 sum2
+= p_sum2
[f
] * frac
;
1109 double n
= f_dur
/ bufferSize
;
1110 double avg
= sum
/ n
;
1111 double stdDev
= sqrt( abs((sum2
- (sum
*avg
) ) / n
) );
1115 minRMS
[i
] = avg
- stdDev
;
1116 maxRMS
[i
] = avg
+ stdDev
;
1118 // assure continuity from pixel to pixel
1126 short *SoundCacheStream::rawFrames( int ch
, sf_count_t b
, sf_count_t d
, bool *interleaved
)
1128 if( !_ready
|| _fpu
!= 1.0 || ch
> channels() ||
1129 b
< _dataOffset
|| b
+ d
> _dataOffset
+ _dataSize
)
1132 *interleaved
= false;
1133 return _caches
[ch
].min
+ b
- _dataOffset
;
1136 /*SoundCacheLoader::SoundCacheLoader( SNDFILE *sf, const SF_INFO &info,
1137 int maxFPU, int maxRawFrames )
1141 maxRawFrames( maxRawFrames )
1144 void SoundCacheStream::onLoadProgress( int progress
)
1146 _loadProgress
= progress
;
1147 Q_EMIT( loadProgress(progress
) );
1150 void SoundCacheStream::onLoadingDone()
1152 // FIXME what if the signal is received just after starting another load?
1155 Q_EMIT( loadingDone() );
1158 void SoundCacheLoader::load( SNDFILE
*sf
, const SF_INFO
&info
)
1160 Q_ASSERT( !isRunning() );
1166 void SoundCacheLoader::run()
1170 int channels
= _cache
->channels();
1171 double fpu
= _cache
->_fpu
;
1172 int size
= _cache
->_dataSize
;
1173 double offset
= _cache
->_dataOffset
;
1174 SoundCache
*chanCaches
= _cache
->_caches
;
1178 int chunkSize
= qMin( 1000, size
- i
);
1180 double beg
= i
* fpu
+ offset
;
1181 double dur
= chunkSize
* fpu
;
1183 sf_count_t i_beg
= floor(beg
);
1184 sf_count_t i_dur
= ceil(beg
+dur
) - i_beg
;
1186 SoundFileStream
sfStream( _sf
, _info
, i_beg
, i_dur
);
1189 for( ch
= 0; ch
< channels
; ++ch
) {
1190 sfStream
.integrate( ch
, beg
, dur
,
1191 chanCaches
[ch
].min
+ i
, chanCaches
[ch
].max
+ i
,
1192 chanCaches
[ch
].sum
+ i
, chanCaches
[ch
].sum2
+ i
,
1198 Q_EMIT( loadProgress( i
* 100 / size
) );
1201 Q_EMIT( loadingDone() );