class library: PriorityQueue - implement removeValue, hide array
[supercollider.git] / QtCollider / widgets / QcSoundFileView.cpp
blobf5c8d69d2ad491c55a4436a8c6c59d16d32b5fc6
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>
26 #include <QPainter>
27 #include <QApplication>
28 #include <QPaintEvent>
30 #include <climits>
31 #include <cmath>
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 );
54 setLayout( l );
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();
80 frac *= frac;
81 frac *= frac;
82 waveform->zoomTo( frac );
83 updateTimeScrollBar();
86 void QcSoundFileView::updateTimeScrollBar()
88 // FIXME:
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.
92 quint64 max;
93 max = waveform->frames() - waveform->viewFrames();
94 if( max > 1000 ) {
95 hScrollMultiplier = max / 1000.0;
96 max = 1000;
97 } else {
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 ),
116 sf(0),
118 _rangeBeg(0),
119 _rangeDur(0),
120 _rangeEnd(0),
122 _cache(0),
124 _curSel(0),
126 _showCursor(false),
127 _cursorEditable(true),
128 _cursorPos(0),
129 _cursorColor(QColor(255,0,0)),
131 _showGrid(true),
132 _gridResolution(1.0),
133 _gridOffset(0.0),
134 _gridColor(QColor(100,100,200)),
136 _beg(0.0),
137 _dur(0.0),
138 _yZoom(1.f),
140 pixmap(0),
141 _peakColor( QColor(242,178,0) ),
142 _rmsColor( QColor(255,255,0) ),
143 dirty(false),
144 _drawWaveform(true)
146 memset( &sfInfo, 0, sizeof(SF_INFO) );
147 setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
148 setAttribute( Qt::WA_OpaquePaintEvent, true );
151 QcWaveform::~QcWaveform()
153 delete _cache;
154 delete pixmap;
155 if( sf ) sf_close( sf );
158 void QcWaveform::load( const QString& filename )
160 qcDebugMsg( 1, "QcWaveform::load()" );
162 SF_INFO new_info;
163 memset( &new_info, 0, sizeof(SF_INFO) );
165 SNDFILE *new_sf = sf_open( filename.toStdString().c_str(), SFM_READ, &new_info );
167 if( !new_sf ) {
168 qcErrorMsg("Could not open soundfile.");
169 return;
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 )" );
179 SF_INFO new_info;
180 memset( &new_info, 0, sizeof(SF_INFO) );
182 SNDFILE *new_sf = sf_open( filename.toStdString().c_str(), SFM_READ, &new_info );
184 if( !new_sf ) {
185 qcErrorMsg("Could not open soundfile.");
186 return;
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.");
202 sf_close( new_sf );
203 return;
206 // cleanup previous state
208 // NOTE we have to delete SoundCacheStream before closing the soundfile, as it might be still
209 // loading it
210 // TODO: should SoundCacheStream open the soundfile on its own?
212 delete _cache;
213 if( sf ) sf_close( sf );
215 sf = new_sf;
216 sfInfo = new_info;
217 _beg = _rangeBeg = beg;
218 _dur = _rangeDur = dur;
219 _rangeEnd = _rangeBeg + _rangeDur;
221 updateFPP();
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 );
233 redraw();
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()
254 return _yZoom;
257 VariantList QcWaveform::selections() const
259 VariantList slist;
260 for( int i = 0; i < 64; ++i ) {
261 slist.data << QVariant::fromValue<VariantList>( selection(i) );
263 return slist;
266 void QcWaveform::setCurrentSelection( int i ) {
267 if( i < 0 || i > 63 ) return;
268 _curSel = i;
269 update();
272 VariantList QcWaveform::selection( int i ) const
274 VariantList l;
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));
279 return l;
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;
288 update();
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;
306 update();
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;
316 update();
319 void QcWaveform::setSelectionEditable( int i, bool editable )
321 if( i < 0 || i > 63 ) return;
322 _selections[i].editable = editable;
323 update();
326 void QcWaveform::setSelectionColor( int i, const QColor &c )
328 if( i < 0 || i > 63 ) return;
329 _selections[i].color = c;
330 update();
333 VariantList QcWaveform::waveColors() const
335 VariantList clist;
336 #if 0
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);
344 #endif
345 return clist;
348 void QcWaveform::setWaveColors( const VariantList &list )
350 _waveColors.clear();
351 Q_FOREACH( QVariant var, list.data ) {
352 _waveColors << var.value<QColor>();
354 redraw();
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;
366 updateFPP();
367 redraw();
370 void QcWaveform::zoomBy( double factor )
372 zoomTo( zoom() * factor );
375 void QcWaveform::zoomAllOut()
377 _beg = _rangeBeg;
378 _dur = _rangeDur;
379 updateFPP();
380 redraw();
383 void QcWaveform::scrollTo( double startFrame )
385 _beg = qBound( (double)_rangeBeg, startFrame, _rangeEnd - _dur );
386 redraw();
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;
418 zoomTo( frac );
421 void QcWaveform::setYZoom( double factor )
423 _yZoom = factor;
424 redraw();
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 )
434 return;
436 _beg = qMax( s.start, _rangeBeg );
437 double end = qMin( s.start + s.size, _rangeEnd );
438 _dur = end - _beg;
440 // clear the selection
441 s.size = 0;
443 updateFPP();
444 redraw();
447 void QcWaveform::resizeEvent( QResizeEvent * )
449 delete pixmap;
450 pixmap = new QPixmap( size() );
451 updateFPP();
452 redraw();
455 void QcWaveform::paintEvent( QPaintEvent *ev )
457 Q_ASSERT( pixmap != 0 );
459 QPainter p( this );
461 // if loading draw progress
462 if( _cache && _cache->loading() ) {
463 QRect r( rect() );
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) );
468 QTextOption opt;
469 opt.setAlignment( Qt::AlignCenter );
470 p.drawText( rect(), "loading...", opt );
471 return;
474 // draw waveform on pixmap
476 if( _drawWaveform && dirty ) {
477 draw( pixmap, 0, width(), _beg, _dur );
478 dirty = false;
481 // clear background
483 p.fillRect( rect(), QColor( 0, 0, 0 ) );
485 // draw selections
487 p.save();
489 p.scale( (double) width() / _dur, 1.0 );
490 p.translate( _beg * -1.0, 0.0 );
491 p.setPen( Qt::NoPen );
493 int i;
494 for( i = 0; i < 64; ++i ) {
495 const Selection &s = _selections[i];
496 if( s.size > 0 ) {
497 QRectF r ( s.start, 0, s.size, height() );
498 p.setBrush( s.color );
499 p.drawRect( r );
503 p.restore();
505 // draw time grid
507 if( _showGrid && sfInfo.samplerate > 0 && _gridResolution > 0.f ) {
508 p.save();
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;
523 p.restore();
526 // paste the waveform pixmap on screen
528 if( _drawWaveform )
529 p.drawPixmap( ev->rect(), *pixmap, ev->rect() );
531 // draw cursor
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();
548 #ifdef Q_OS_MAC
549 Qt::KeyboardModifier CTRL = Qt::MetaModifier;
550 #else
551 Qt::KeyboardModifier CTRL = Qt::ControlModifier;
552 #endif
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;
567 else {
568 setSelectionEnd( _curSel, _dragFrame );
569 _dragFrame = s.start;
571 Q_EMIT( action() );
573 else {
574 if( !(mods & CTRL) ) {
575 _dragAction = Select;
576 _selections[_curSel].start = _dragFrame;
577 _selections[_curSel].size = 0;
578 update();
579 Q_EMIT( action() );
581 if( _cursorEditable ) {
582 _cursorPos = _dragFrame;
583 if( mods & CTRL ) _dragAction = MoveCursor;
584 update();
585 Q_EMIT( metaAction() );
590 else if( btn == Qt::RightButton ) {
592 if( mods & Qt::ShiftModifier ) {
593 _dragAction = Zoom;
594 _dragData = zoom();
595 _dragData2 = ev->pos().x() * _fpp + _beg;
597 else {
598 _dragAction = Scroll;
599 _dragData = _beg;
605 void QcWaveform::mouseDoubleClickEvent ( QMouseEvent * )
607 setSelection( _curSel, _rangeBeg, _rangeEnd );
608 Q_EMIT( action() );
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 );
626 update();
627 Q_EMIT( action() );
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 );
634 update();
635 Q_EMIT( action() );
637 else if( _dragAction == MoveCursor ) {
638 _cursorPos = qBound( 0, ev->pos().x(), width() ) * _fpp + _beg;
639 update();
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 ) );
654 QPainter p( pix );
656 if( !sf || !_cache || !_cache->ready() ) return;
658 // check for sane situation:
659 if( f_beg < _rangeBeg || f_beg + f_dur > _rangeEnd ) return;
661 // data indexes
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;
664 bool haveOneMore;
665 if( i_beg + i_count < _rangeEnd ) {
666 ++i_count;
667 haveOneMore = true;
669 else {
670 haveOneMore = false;
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 );
682 else {
683 qcDebugMsg( 1, QString("use cache") );
684 soundStream = _cache;
687 // geometry
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;
691 //spacing /= yscale;
693 // initial painter setup
694 QPen minMaxPen;
695 QPen rmsPen;
697 float halfChH = chHeight * 0.5;
698 p.translate( 0.f, halfChH + spacing );
700 int waveColorN = _waveColors.count();
701 int ch;
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 ) );
709 else {
710 rmsPen.setColor( _rmsColor );
711 minMaxPen.setColor( _peakColor );
714 // draw center line
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 );
723 p.save();
725 p.setClipping(true);
726 p.setClipRect( x, -halfChH, x+width, chHeight );
727 p.scale( 1.f, yScale );
729 if( _fpp > 1.0 ) {
731 // draw min-max regions and RMS
733 short minBuffer[width];
734 short maxBuffer[width];
735 short minRMS[width];
736 short maxRMS[width];
738 bool ok = soundStream->displayData( ch, f_beg, f_dur,
739 minBuffer, maxBuffer,
740 minRMS, maxRMS,
741 width );
742 // printf("integration ok: %i\n", ok);
743 Q_ASSERT( ok );
745 int i;
746 for( i = 0; i < width; ++i ) {
747 short min = minBuffer[i];
748 short max = maxBuffer[i];
749 if( max != min ) {
750 p.setPen( minMaxPen );
751 p.drawLine( x + i, min, x + i, max );
753 else {
754 p.setPen( minMaxPen );
755 p.drawPoint( x + i, min );
757 p.setPen( rmsPen );
758 p.drawLine( x + i, minRMS[i], x + i, maxRMS[i] );
761 else {
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;
774 QPainterPath path;
776 if( i_count ) {
777 QPointF pt( dx, (qreal) *data );
778 path.moveTo( pt );
780 int f; // frame
781 for( f = 1; f < i_count; ++f ) {
782 data += step;
783 QPointF pt( f * ppf + dx, (qreal) *data );
784 path.lineTo( pt );
786 if( i_count && !haveOneMore ) {
787 path.lineTo( QPointF( f * ppf + dx, (qreal)*data ) );
790 p.setPen( rmsPen );
791 p.drawPath( path );
794 p.restore();
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 )
804 : _data(0)
806 load( sf, info, b, d );
809 SoundFileStream::~SoundFileStream()
811 delete[] _data;
814 void SoundFileStream::load( SNDFILE *sf, const SF_INFO &info, sf_count_t beg, sf_count_t dur )
816 delete[] _data;
818 _dataOffset = beg;
819 _dataSize = dur;
821 _data = new short [_dataSize * info.channels];
822 sf_seek( sf, _dataOffset, SEEK_SET);
823 _dataSize = sf_readf_short( sf, _data, _dataSize );
825 _ch = info.channels;
826 _beg = _dataOffset;
827 _dur = _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 )
834 bool ok = _data != 0
835 && ch < channels()
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;
844 int i;
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;
867 float sum = 0.f;
868 float sum2 = 0.f;
869 int f; // frame
870 for( f = 0; f < frame_count; ++f, samples += channels() ){
871 // TODO should we overlap min-max or not here?
872 float sample = *samples;
873 float frac;
874 if( f == 0 ) frac = frac0;
875 else if( f == frame_count - 1 ) frac = frac1;
876 else frac = 1.0;
878 if( sample < min ) min = sample;
879 if( sample > max ) max = sample;
881 sum += sample * frac;
882 sum2 += sample * sample * frac;
885 minBuffer[i] = min;
886 maxBuffer[i] = max;
887 sumBuf[i] = sum;
888 sum2Buf[i] = sum2;
890 f_pos = f_pos1;
893 return true;
896 bool SoundFileStream::displayData
897 ( int ch, double f_beg, double f_dur,
898 short *minBuffer, short *maxBuffer, short *minRMS, short *maxRMS, int bufferSize )
900 bool ok = _data != 0
901 && ch < channels()
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;
912 int i;
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;
934 float sum = 0.f;
935 float sum2 = 0.f;
936 int f; // frame
937 for( f = 0; f < frame_count; ++f, samples += channels() ){
938 // TODO should we overlap min-max or not here?
939 float sample = *samples;
940 float frac;
941 if( f == 0 ) frac = frac0;
942 else if( f == frame_count - 1 ) frac = frac1;
943 else frac = 1.0;
945 if( sample < min ) min = sample;
946 if( sample > max ) max = sample;
948 sum += sample * frac;
949 sum2 += sample * sample * frac;
952 double n = fpu;
953 double avg = sum / n;
954 double stdDev = sqrt( abs((sum2 - (sum*avg) ) / n) );
956 minBuffer[i] = min;
957 maxBuffer[i] = max;
958 minRMS[i] = avg - stdDev;
959 maxRMS[i] = avg + stdDev;
961 f_pos = f_pos1;
962 min = maxBuffer[i];
963 max = minBuffer[i];
966 return true;
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;
972 *interleaved = true;
973 sf_count_t offset = (b - _dataOffset) * channels() + ch;
974 return ( _data + offset );
978 SoundCacheStream::SoundCacheStream()
979 : SoundStream ( 0, 0.0, 0.0 ),
980 _caches(0),
981 _fpu(0.0),
982 _dataOffset(0),
983 _dataSize(0),
984 _ready(false),
985 _loading(false),
986 _loadProgress(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 );
1000 _ready = false;
1001 _loadProgress = 0;
1003 if( _loader->isRunning() ) {
1004 _loader->terminate();
1005 _loader->wait();
1007 delete [] _caches;
1009 _ch = info.channels;
1010 _beg = _dataOffset = beg;
1011 _dur = dur;
1013 // adjust data size for data amount limit
1014 if( _dur <= maxRawFrames ) {
1015 // ok, not crossing the limit
1016 _dataSize = _dur;
1017 _fpu = 1.0;
1019 else {
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];
1030 int ch;
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];
1038 _loading = true;
1039 _loader->load( sf, info );
1042 SoundCacheStream::~SoundCacheStream()
1044 if( _loader->isRunning() ) {
1045 _loader->terminate();
1046 _loader->wait();
1048 delete [] _caches;
1051 bool SoundCacheStream::displayData
1052 ( int ch, double f_beg, double f_dur,
1053 short *minBuffer, short *maxBuffer, short *minRMS, short *maxRMS, int bufferSize )
1055 bool ok = _ready
1056 && ch < channels()
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;
1067 int i;
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;
1073 cache_pos += ratio;
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;
1081 double sum = 0.0;
1082 double sum2 = 0.0;
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];
1098 float frac;
1099 if( countdown == frame_count - 1 ) frac = frac0;
1100 else if( countdown == 0 ) frac = frac1;
1101 else frac = 1.0;
1103 sum += p_sum[f] * frac;
1104 sum2 += p_sum2[f] * frac;
1106 ++f;
1109 double n = f_dur / bufferSize;
1110 double avg = sum / n;
1111 double stdDev = sqrt( abs((sum2 - (sum*avg) ) / n) );
1113 minBuffer[i] = min;
1114 maxBuffer[i] = max;
1115 minRMS[i] = avg - stdDev;
1116 maxRMS[i] = avg + stdDev;
1118 // assure continuity from pixel to pixel
1119 min = maxBuffer[i];
1120 max = minBuffer[i];
1123 return true;
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 )
1130 return 0;
1132 *interleaved = false;
1133 return _caches[ch].min + b - _dataOffset;
1136 /*SoundCacheLoader::SoundCacheLoader( SNDFILE *sf, const SF_INFO &info,
1137 int maxFPU, int maxRawFrames )
1138 : _sf( sf ),
1139 _sfInfo( info ),
1140 _maxFPU( maxFPU ),
1141 maxRawFrames( maxRawFrames )
1142 {}*/
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?
1153 _ready = true;
1154 _loading = false;
1155 Q_EMIT( loadingDone() );
1158 void SoundCacheLoader::load( SNDFILE *sf, const SF_INFO &info )
1160 Q_ASSERT( !isRunning() );
1161 _sf = sf;
1162 _info = info;
1163 start();
1166 void SoundCacheLoader::run()
1168 Q_ASSERT( _sf );
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;
1176 int i = 0;
1177 while( i < size ) {
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 );
1188 int ch;
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,
1193 chunkSize );
1196 i += chunkSize;
1198 Q_EMIT( loadProgress( i * 100 / size ) );
1201 Q_EMIT( loadingDone() );