1 /*
2 Copyright (C) 2005-2007 Remon Sijrier
3
4 This file is part of Traverso
5
6 Traverso is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 */
21
22 #include <QTextStream>
23 #include <QString>
24 #include <QFile>
25 #include <QFileInfo>
26 #include <QList>
27 #include <QMap>
28 #include <QRegExp>
29 #include <QDebug>
30
31 #include <commands.h>
32
33 #include "AbstractAudioReader.h"
34 #include <AudioDevice.h>
35 #include <AudioBus.h>
36 #include <Client.h>
37 #include "ProjectManager.h"
38 #include "ContextPointer.h"
39 #include "Information.h"
40 #include "Sheet.h"
41 #include "Project.h"
42 #include "Track.h"
43 #include "Mixer.h"
44 #include "AudioSource.h"
45 #include "AudioClip.h"
46 #include "Peak.h"
47 #include "Export.h"
48 #include "DiskIO.h"
49 #include "WriteSource.h"
50 #include "AudioClipManager.h"
51 #include "Tsar.h"
52 #include "SnapList.h"
53 #include "Config.h"
54 #include "Utils.h"
55 #include "ContextItem.h"
56 #include "TimeLine.h"
57 #include "Marker.h"
58 #include "InputEngine.h"
59
60 #include <Plugin.h>
61 #include <PluginChain.h>
62
63 // Always put me below _all_ includes, this is needed
64 // in case we run with memory leak detection enabled!
65 #include "Debugger.h"
66
67
68 /** \class Sheet
69 \brief The 'work space' (as in WorkSheet) holding the Track 's and the Master Out AudioBus
70
71 A Sheet processes each Track, and mixes the result into it's Master Out AudioBus.
72 Sheet connects it's Client to the AudioDevice, to become part of audio processing
73 chain. The connection is instantiated by Project, who owns the Sheet objects.
74
75
76 */
77
Sheet(Project * project)78 Sheet::Sheet(Project* project)
79 : ContextItem()
80 , m_project(project)
81 {
82 PENTERCONS;
83 title = tr("Untitled");
84 m_id = create_id();
85 artists = tr("No artists name set");
86
87 init();
88 }
89
Sheet(Project * project,int numtracks)90 Sheet::Sheet(Project* project, int numtracks)
91 : ContextItem()
92 , m_project(project)
93 {
94 PENTERCONS;
95 title = tr("Untitled");
96 m_id = create_id();
97 artists = tr("No artists name set");
98 m_hzoom = config().get_property("Sheet", "hzoomLevel", 8192).toInt();
99
100 init();
101
102 for (int i=1; i <= numtracks; i++) {
103 Track* track = create_track();
104 private_add_track(track);
105 }
106 }
107
Sheet(Project * project,const QDomNode node)108 Sheet::Sheet(Project* project, const QDomNode node)
109 : ContextItem(), m_project(project)
110 {
111 PENTERCONS;
112
113 QDomNode propertiesNode = node.firstChildElement("Properties");
114 m_id = node.toElement().attribute("id", "0").toLongLong();
115
116 if (m_id == 0) {
117 m_id = create_id();
118 }
119
120 init();
121 set_state( node );
122 }
123
~Sheet()124 Sheet::~Sheet()
125 {
126 PENTERDES;
127
128 delete [] mixdown;
129 delete [] gainbuffer;
130
131 delete m_diskio;
132 delete m_masterOut;
133 delete m_renderBus;
134 delete m_clipRenderBus;
135 delete m_hs;
136 delete m_audiodeviceClient;
137 delete snaplist;
138 delete workSnap;
139 }
140
init()141 void Sheet::init()
142 {
143 PENTER2;
144 #if defined (THREAD_CHECK)
145 threadId = QThread::currentThreadId ();
146 #endif
147
148 QObject::tr("Sheet");
149
150 m_diskio = new DiskIO(this);
151 m_currentSampleRate = audiodevice().get_sample_rate();
152 m_diskio->output_rate_changed(m_currentSampleRate);
153 int converter_type = config().get_property("Conversion", "RTResamplingConverterType", DEFAULT_RESAMPLE_QUALITY).toInt();
154 m_diskio->set_resample_quality(converter_type);
155
156
157 connect(this, SIGNAL(seekStart()), m_diskio, SLOT(seek()), Qt::QueuedConnection);
158 connect(this, SIGNAL(prepareRecording()), this, SLOT(prepare_recording()));
159 connect(&audiodevice(), SIGNAL(clientRemoved(TAudioDeviceClient*)), this, SLOT (audiodevice_client_removed(TAudioDeviceClient*)));
160 connect(&audiodevice(), SIGNAL(started()), this, SLOT(audiodevice_started()));
161 connect(&audiodevice(), SIGNAL(driverParamsChanged()), this, SLOT(audiodevice_params_changed()), Qt::DirectConnection);
162 connect(m_diskio, SIGNAL(seekFinished()), this, SLOT(seek_finished()), Qt::QueuedConnection);
163 connect (m_diskio, SIGNAL(readSourceBufferUnderRun()), this, SLOT(handle_diskio_readbuffer_underrun()));
164 connect (m_diskio, SIGNAL(writeSourceBufferOverRun()), this, SLOT(handle_diskio_writebuffer_overrun()));
165 connect(&config(), SIGNAL(configChanged()), this, SLOT(config_changed()));
166 connect(this, SIGNAL(transportStarted()), m_diskio, SLOT(start_io()));
167 connect(this, SIGNAL(transportStopped()), m_diskio, SLOT(stop_io()));
168
169 mixdown = gainbuffer = 0;
170 m_masterOut = new AudioBus("Master Out", 2);
171 m_renderBus = new AudioBus("Render Bus", 2);
172 m_clipRenderBus = new AudioBus("Clip Render Bus", 2);
173
174 resize_buffer(false, audiodevice().get_buffer_size());
175 m_hs = new QUndoStack(pm().get_undogroup());
176 set_history_stack(m_hs);
177 m_acmanager = new AudioClipManager(this);
178
179 set_context_item( m_acmanager );
180
181 m_playBackBus = audiodevice().get_playback_bus("Playback 1");
182
183 m_transport = m_stopTransport = m_resumeTransport = m_readyToRecord = false;
184 snaplist = new SnapList(this);
185 workSnap = new Snappable();
186 workSnap->set_snap_list(snaplist);
187
188 m_realtimepath = false;
189 m_scheduledForDeletion = false;
190 m_isSnapOn=true;
191 changed = m_rendering = m_recording = m_prepareRecording = false;
192 firstVisibleFrame=0;
193 m_workLocation = TimeRef();
194 m_seeking = m_startSeek = 0;
195 // TODO seek to old position on project exit ?
196 m_transportLocation = TimeRef();
197 m_mode = EDIT;
198 m_sbx = m_sby = 0;
199 m_hzoom = config().get_property("Sheet", "hzoomLevel", 8192).toInt();
200
201 m_pluginChain = new PluginChain(this, this);
202 m_fader = m_pluginChain->get_fader();
203 m_fader->set_gain(0.5);
204 m_timeline = new TimeLine(this);
205
206 m_skipTimer.setSingleShot(true);
207
208 m_audiodeviceClient = new TAudioDeviceClient("sheet_" + QByteArray::number(get_id()));
209 m_audiodeviceClient->set_process_callback( MakeDelegate(this, &Sheet::process) );
210 m_audiodeviceClient->set_transport_control_callback( MakeDelegate(this, &Sheet::transport_control) );
211 }
212
set_state(const QDomNode & node)213 int Sheet::set_state( const QDomNode & node )
214 {
215 PENTER;
216
217 QDomNode propertiesNode = node.firstChildElement("Properties");
218 QDomElement e = propertiesNode.toElement();
219
220 title = e.attribute( "title", "" );
221 artists = e.attribute( "artists", "" );
222 set_gain(e.attribute( "mastergain", "1.0").toFloat() );
223 qreal zoom = e.attribute("hzoom", "4096").toDouble();
224 set_hzoom(zoom);
225 m_sbx = e.attribute("sbx", "0").toInt();
226 m_sby = e.attribute("sby", "0").toInt();
227 set_first_visible_frame(e.attribute( "firstVisibleFrame", "0" ).toUInt());
228
229 bool ok;
230 TimeRef location(e.attribute( "m_workLocation", "0").toLongLong(&ok));
231 set_work_at(location);
232 m_transportLocation = TimeRef(e.attribute( "transportlocation", "0").toLongLong(&ok));
233
234 // Start seeking to the 'old' transport pos
235 set_transport_pos(m_transportLocation);
236 set_snapping(e.attribute("snapping", "0").toInt());
237 m_mode = e.attribute("mode", "0").toInt();
238
239 m_timeline->set_state(node.firstChildElement("TimeLine"));
240
241 QDomNode tracksNode = node.firstChildElement("Tracks");
242 QDomNode trackNode = tracksNode.firstChild();
243
244 while(!trackNode.isNull()) {
245 Track* track = new Track(this, trackNode);
246 private_add_track(track);
247 track->set_state(trackNode);
248
249 trackNode = trackNode.nextSibling();
250 }
251
252 m_acmanager->set_state(node.firstChildElement("ClipManager"));
253
254 QDomNode pluginChainNode = node.firstChildElement("PluginChain");
255 m_pluginChain->set_state(pluginChainNode);
256
257 return 1;
258 }
259
get_state(QDomDocument doc,bool istemplate)260 QDomNode Sheet::get_state(QDomDocument doc, bool istemplate)
261 {
262 QDomElement sheetNode = doc.createElement("Sheet");
263
264 if (! istemplate) {
265 sheetNode.setAttribute("id", m_id);
266 }
267
268 QDomElement properties = doc.createElement("Properties");
269 properties.setAttribute("title", title);
270 properties.setAttribute("artists", artists);
271 properties.setAttribute("firstVisibleFrame", firstVisibleFrame);
272 properties.setAttribute("m_workLocation", m_workLocation.universal_frame());
273 properties.setAttribute("transportlocation", m_transportLocation.universal_frame());
274 properties.setAttribute("hzoom", m_hzoom);
275 properties.setAttribute("sbx", m_sbx);
276 properties.setAttribute("sby", m_sby);
277 properties.setAttribute("snapping", m_isSnapOn);
278 properties.setAttribute("mode", m_mode);
279 sheetNode.appendChild(properties);
280
281 doc.appendChild(sheetNode);
282
283 sheetNode.appendChild(m_acmanager->get_state(doc));
284
285 sheetNode.appendChild(m_timeline->get_state(doc));
286
287 QDomNode tracksNode = doc.createElement("Tracks");
288
289 apill_foreach(Track* track, Track, m_tracks) {
290 tracksNode.appendChild(track->get_state(doc, istemplate));
291 }
292
293 sheetNode.appendChild(tracksNode);
294
295 QDomNode pluginChainNode = doc.createElement("PluginChain");
296 pluginChainNode.appendChild(m_pluginChain->get_state(doc));
297 sheetNode.appendChild(pluginChainNode);
298
299
300 return sheetNode;
301 }
302
connect_to_audiodevice()303 void Sheet::connect_to_audiodevice( )
304 {
305 PENTER;
306 audiodevice().add_client(m_audiodeviceClient);
307 }
308
disconnect_from_audiodevice()309 void Sheet::disconnect_from_audiodevice()
310 {
311 PENTER;
312 if (is_transport_rolling()) {
313 m_transport = false;
314 emit transportStopped();
315 }
316 audiodevice().remove_client(m_audiodeviceClient);
317 }
318
schedule_for_deletion()319 void Sheet::schedule_for_deletion()
320 {
321 m_scheduledForDeletion = true;
322 pm().scheduled_for_deletion(this);
323 }
324
audiodevice_client_removed(TAudioDeviceClient * client)325 void Sheet::audiodevice_client_removed(TAudioDeviceClient* client )
326 {
327 PENTER;
328 if (m_audiodeviceClient == client) {
329 if (m_scheduledForDeletion) {
330 pm().delete_sheet(this);
331 }
332 }
333 }
334
add_track(Track * track,bool historable)335 Command* Sheet::add_track(Track* track, bool historable)
336 {
337 apill_foreach(Track* existing, Track, m_tracks) {
338 if (existing->is_solo()) {
339 track->set_muted_by_solo( true );
340 break;
341 }
342 }
343
344 return new AddRemove(this, track, historable, this,
345 "private_add_track(Track*)", "trackAdded(Track*)",
346 "private_remove_track(Track*)", "trackRemoved(Track*)",
347 tr("Add Track"));
348 }
349
350
remove_track(Track * track,bool historable)351 Command* Sheet::remove_track(Track* track, bool historable)
352 {
353 return new AddRemove(this, track, historable, this,
354 "private_remove_track(Track*)", "trackRemoved(Track*)",
355 "private_add_track(Track*)", "trackAdded(Track*)",
356 tr("Remove Track"));
357 }
358
any_track_armed()359 bool Sheet::any_track_armed()
360 {
361 apill_foreach(Track* track, Track, m_tracks) {
362 if (track->armed()) {
363 return true;
364 }
365 }
366 return false;
367 }
368
369
prepare_export(ExportSpecification * spec)370 int Sheet::prepare_export(ExportSpecification* spec)
371 {
372 PENTER;
373
374 if ( ! (spec->renderpass == ExportSpecification::CREATE_CDRDAO_TOC) ) {
375 if (is_transport_rolling()) {
376 spec->resumeTransport = true;
377 // When transport is rolling, this equals stopping the transport!
378 // prepare_export() is called from another thread, so use a queued connection
379 // to call the function in the correct thread!
380 if (!QMetaObject::invokeMethod(this, "start_transport", Qt::QueuedConnection)) {
381 printf("Invoking Sheet::start_transport() failed\n");
382 return -1;
383 }
384 int count = 0;
385 uint msecs = (audiodevice().get_buffer_size() * 1000) / audiodevice().get_sample_rate();
386 // wait a number (max 10) of process() cycles to be sure we really stopped transport
387 while (m_transport) {
388 spec->thread->sleep_for(msecs);
389 count++;
390 if (count > 10) {
391 break;
392 }
393 }
394 printf("Sheet::prepare_export: had to wait %d process cycles before the transport was stopped\n", count);
395 }
396
397 m_rendering = true;
398 }
399
400 spec->startLocation = LLONG_MAX;
401 spec->endLocation = TimeRef();
402
403 TimeRef endlocation, startlocation;
404
405 apill_foreach(Track* track, Track, m_tracks) {
406 track->get_render_range(startlocation, endlocation);
407
408 if (track->is_solo()) {
409 spec->endLocation = endlocation;
410 spec->startLocation = startlocation;
411 break;
412 }
413
414 if (endlocation > spec->endLocation) {
415 spec->endLocation = endlocation;
416 }
417
418 if (startlocation < spec->startLocation) {
419 spec->startLocation = startlocation;
420 }
421 }
422
423 if (spec->isCdExport) {
424 QList<Marker*> markers = m_timeline->get_markers();
425
426 if (m_timeline->get_start_location(startlocation)) {
427 PMESG2(" Start marker found at %s", QS_C(timeref_to_ms(startlocation)));
428 // round down to the start of the CD frame (75th of a sec)
429 startlocation = cd_to_timeref(timeref_to_cd(startlocation));
430 spec->startLocation = startlocation;
431 } else {
432 PMESG2(" No start marker found");
433 }
434
435 if (m_timeline->get_end_location(endlocation)) {
436 PMESG2(" End marker found at %s", QS_C(timeref_to_ms(endlocation)));
437 spec->endLocation = endlocation;
438 } else {
439 PMESG2(" No end marker found");
440 }
441 }
442
443 spec->totalTime = spec->endLocation - spec->startLocation;
444
445 // PWARN("Render length is: %s",timeref_to_ms_3(spec->totalTime).toLatin1().data() );
446
447 spec->pos = spec->startLocation;
448 spec->progress = 0;
449
450 spec->basename = "Sheet_" + QString::number(m_project->get_sheet_index(m_id)) +"-" + title;
451 spec->name = spec->basename;
452
453 if (spec->startLocation == spec->endLocation) {
454 info().warning(tr("No audio to export! (Is everything muted?)"));
455 return -1;
456 }
457 else if (spec->startLocation > spec->endLocation) {
458 info().warning(tr("Export start frame starts beyond export end frame!!"));
459 return -1;
460 }
461
462 if (spec->channels == 0) {
463 info().warning(tr("Export tries to render to 0 channels wav file??"));
464 return -1;
465 }
466
467 if (spec->renderpass == ExportSpecification::CREATE_CDRDAO_TOC) {
468 return 1;
469 }
470
471 if (spec->renderpass == ExportSpecification::WRITE_TO_HARDDISK) {
472 m_exportSource = new WriteSource(spec);
473 if (m_exportSource->prepare_export() == -1) {
474 delete m_exportSource;
475 return -1;
476 }
477 }
478
479 m_transportLocation = spec->startLocation;
480
481 resize_buffer(false, spec->blocksize);
482
483 renderDecodeBuffer = new DecodeBuffer;
484
485 return 1;
486 }
487
finish_audio_export()488 int Sheet::finish_audio_export()
489 {
490 m_exportSource->finish_export();
491 delete m_exportSource;
492 delete renderDecodeBuffer;
493 resize_buffer(false, audiodevice().get_buffer_size());
494 return 0;
495 }
496
render(ExportSpecification * spec)497 int Sheet::render(ExportSpecification* spec)
498 {
499 int chn;
500 uint32_t x;
501 int ret = -1;
502 int progress;
503
504 nframes_t diff = (spec->endLocation - spec->pos).to_frame(audiodevice().get_sample_rate());
505 nframes_t nframes = spec->blocksize;
506 nframes_t this_nframes = std::min(diff, nframes);
507
508 if (!spec->running || spec->stop || this_nframes == 0) {
509 process_export (nframes);
510 /* PWARN("Finished Rendering for this sheet");
511 PWARN("running is %d", spec->running);
512 PWARN("stop is %d", spec->stop);
513 PWARN("this_nframes is %d", this_nframes);*/
514 if (spec->renderpass == ExportSpecification::WRITE_TO_HARDDISK) {
515 return finish_audio_export();
516 } else {
517 return 0;
518 }
519 }
520
521 /* do the usual stuff */
522
523 process_export(nframes);
524
525 /* and now export the results */
526
527 nframes = this_nframes;
528
529 memset (spec->dataF, 0, sizeof (spec->dataF[0]) * nframes * spec->channels);
530
531 /* foreach output channel ... */
532
533 float* buf;
534
535 for (chn = 0; chn < spec->channels; ++chn) {
536 buf = m_masterOut->get_buffer(chn, nframes);
537
538 if (!buf) {
539 // Seem we are exporting at least to Stereo from an AudioBus with only one channel...
540 // Use the first channel..
541 buf = m_masterOut->get_buffer(0, nframes);
542 }
543
544 for (x = 0; x < nframes; ++x) {
545 spec->dataF[chn+(x*spec->channels)] = buf[x];
546 }
547 }
548
549
550 int bufsize = spec->blocksize * spec->channels;
551 if (spec->normalize) {
552 if (spec->renderpass == ExportSpecification::CALC_NORM_FACTOR) {
553 spec->peakvalue = Mixer::compute_peak(spec->dataF, bufsize, spec->peakvalue);
554 }
555 }
556
557 if (spec->renderpass == ExportSpecification::WRITE_TO_HARDDISK) {
558 if (spec->normalize) {
559 Mixer::apply_gain_to_buffer(spec->dataF, bufsize, spec->normvalue);
560 }
561 if (m_exportSource->process (nframes)) {
562 goto out;
563 }
564 }
565
566
567 spec->pos.add_frames(nframes, audiodevice().get_sample_rate());
568
569 if (! spec->normalize ) {
570 progress = int((double((spec->pos - spec->startLocation).universal_frame()) / spec->totalTime.universal_frame()) * 100);
571 } else {
572 progress = (int) (double( 100 * (spec->pos - spec->startLocation).universal_frame()) / (spec->totalTime.universal_frame() * 2));
573 if (spec->renderpass == ExportSpecification::WRITE_TO_HARDDISK) {
574 progress += 50;
575 }
576 }
577
578 if (progress > spec->progress) {
579 spec->progress = progress;
580 m_project->set_sheet_export_progress(progress);
581 }
582
583
584 /* and we're good to go */
585
586 ret = 1;
587
588 out:
589 if (!ret) {
590 spec->running = false;
591 spec->status = ret;
592 m_rendering = false;
593 }
594
595 return ret;
596 }
597
598
get_snap_list() const599 SnapList* Sheet::get_snap_list() const
600 {
601 return snaplist;
602 }
603
604
set_artists(const QString & pArtists)605 void Sheet::set_artists(const QString& pArtists)
606 {
607 artists = pArtists;
608 }
609
set_gain(float gain)610 void Sheet::set_gain(float gain)
611 {
612 if (gain < 0.0)
613 gain = 0.0;
614 if (gain > 2.0)
615 gain = 2.0;
616
617 m_fader->set_gain(gain);
618
619 emit masterGainChanged();
620 }
621
set_title(const QString & sTitle)622 void Sheet::set_title(const QString& sTitle)
623 {
624 title=sTitle;
625 emit propertyChanged();
626 }
627
set_first_visible_frame(nframes_t pos)628 void Sheet::set_first_visible_frame(nframes_t pos)
629 {
630 PENTER;
631 firstVisibleFrame = pos;
632 emit firstVisibleFrameChanged();
633 }
634
set_work_at(const TimeRef & location)635 void Sheet::set_work_at(const TimeRef& location)
636 {
637 m_workLocation = location;
638 if (workSnap->is_snappable()) {
639 snaplist->mark_dirty();
640 }
641 emit workingPosChanged();
642 }
643
toggle_snap()644 Command* Sheet::toggle_snap()
645 {
646 set_snapping( ! m_isSnapOn );
647 return 0;
648 }
649
650
set_snapping(bool snapping)651 void Sheet::set_snapping(bool snapping)
652 {
653 m_isSnapOn = snapping;
654 emit snapChanged();
655 }
656
657 /******************************** SLOTS *****************************/
658
create_track()659 Track* Sheet::create_track()
660 {
661 int height = Track::INITIAL_HEIGHT;
662
663 Track* track = new Track(this, "Unnamed", height);
664
665 return track;
666 }
667
solo_track(Track * t)668 void Sheet::solo_track(Track* t)
669 {
670 bool wasSolo = t->is_solo();
671
672 t->set_muted_by_solo(!wasSolo);
673 t->set_solo(!wasSolo);
674
675 bool hasSolo = false;
676 apill_foreach(Track* track, Track, m_tracks) {
677 track->set_muted_by_solo(!track->is_solo());
678 if (track->is_solo()) hasSolo = true;
679 }
680
681 if (!hasSolo) {
682 apill_foreach(Track* track, Track, m_tracks) {
683 track->set_muted_by_solo(false);
684 }
685 }
686 }
687
toggle_solo()688 Command* Sheet::toggle_solo()
689 {
690 bool hasSolo = false;
691 apill_foreach(Track* track, Track, m_tracks) {
692 if (track->is_solo()) hasSolo = true;
693 }
694
695 apill_foreach(Track* track, Track, m_tracks) {
696 track->set_solo(!hasSolo);
697 track->set_muted_by_solo(false);
698 }
699
700 return (Command*) 0;
701 }
702
toggle_mute()703 Command *Sheet::toggle_mute()
704 {
705 bool hasMute = false;
706 apill_foreach(Track* track, Track, m_tracks) {
707 if (track->is_muted()) hasMute = true;
708 }
709
710 apill_foreach(Track* track, Track, m_tracks) {
711 track->set_muted(!hasMute);
712 }
713
714 return (Command*) 0;
715 }
716
toggle_arm()717 Command *Sheet::toggle_arm()
718 {
719 bool hasArmed = false;
720 apill_foreach(Track* track, Track, m_tracks) {
721 if (track->armed()) hasArmed = true;
722 }
723
724 apill_foreach(Track* track, Track, m_tracks) {
725 if (hasArmed) {
726 track->disarm();
727 } else {
728 track->arm();
729 }
730 }
731
732 return (Command*) 0;
733 }
734
work_next_edge()735 Command* Sheet::work_next_edge()
736 {
737 /* nframes_t w = m_acmanager->get_last_frame();
738
739 foreach(Track* track, m_tracks) {
740 AudioClip* c=track->get_clip_after(m_workLocation);
741
742 if ((c) && (c->get_track_start_location() < w && c->get_track_start_location() > m_workLocation))
743 w = c->get_track_start_location();
744 }
745
746 set_work_at(w);
747
748 emit setCursorAtEdge();
749 */
750 return (Command*) 0;
751 }
752
work_previous_edge()753 Command* Sheet::work_previous_edge()
754 {
755 /* TimeRef w(0);
756 foreach(Track* track, m_tracks) {
757 AudioClip* c = track->get_clip_before(m_workLocation);
758 if ((c) && (c->get_track_end_location() >= w && c->get_track_end_location() < m_workLocation) )
759 w=c->get_track_end_location();
760 }
761
762 set_work_at(w);
763
764 emit setCursorAtEdge();
765 */
766 return (Command*) 0;
767 }
768
set_hzoom(qreal hzoom)769 void Sheet::set_hzoom( qreal hzoom )
770 {
771 // Traverso <= 0.42.0 doesn't store the real zoom factor, but an
772 // index. This currently causes problems as there is no real support
773 // (yet) for zoomlevels other then powers of 2, so we force that for now.
774 // NOTE: Remove those 2 lines when floating point zoomlevel is implemented!
775 int highbit;
776 hzoom = nearest_power_of_two(hzoom, highbit);
777
778
779 if (hzoom > Peak::max_zoom_value()) {
780 hzoom = Peak::max_zoom_value();
781 }
782
783 if (hzoom < 1.0) {
784 hzoom = 1.0;
785 }
786
787 if (m_hzoom == hzoom) {
788 return;
789 }
790
791 m_hzoom = hzoom;
792
793 emit hzoomChanged();
794 }
795
796 //
797 // Function called in RealTime AudioThread processing path
798 //
process(nframes_t nframes)799 int Sheet::process( nframes_t nframes )
800 {
801 if (m_startSeek) {
802 start_seek();
803 return 0;
804 }
805
806 // If no need for playback/record, return.
807 if (!is_transport_rolling()) {
808 return 0;
809 }
810
811 if (m_stopTransport) {
812 m_transport = false;
813 m_realtimepath = false;
814 m_stopTransport = false;
815
816 RT_THREAD_EMIT(this, 0, transportStopped());
817
818 return 0;
819 }
820
821 // zero the m_masterOut buffers
822 m_masterOut->silence_buffers(nframes);
823
824 int processResult = 0;
825
826
827 // Process all Tracks.
828 apill_foreach(Track* track, Track, m_tracks) {
829 processResult |= track->process(nframes);
830 }
831
832 // update the transport location
833 m_transportLocation.add_frames(nframes, audiodevice().get_sample_rate());
834
835 if (!processResult) {
836 return 0;
837 }
838
839 // Mix the result into the AudioDevice "physical" buffers
840 if (m_playBackBus) {
841 Mixer::mix_buffers_with_gain(m_playBackBus->get_buffer(0, nframes), m_masterOut->get_buffer(0, nframes), nframes, get_gain());
842 Mixer::mix_buffers_with_gain(m_playBackBus->get_buffer(1, nframes), m_masterOut->get_buffer(1, nframes), nframes, get_gain());
843
844 m_pluginChain->process_post_fader(m_masterOut, nframes);
845 }
846
847
848 return 1;
849 }
850
process_export(nframes_t nframes)851 int Sheet::process_export( nframes_t nframes )
852 {
853 // Get the masterout buffers, and fill with zero's
854 m_masterOut->silence_buffers(nframes);
855 memset (mixdown, 0, sizeof (audio_sample_t) * nframes);
856
857 // Process all Tracks.
858 apill_foreach(Track* track, Track, m_tracks) {
859 track->process(nframes);
860 }
861
862 Mixer::apply_gain_to_buffer(m_masterOut->get_buffer(0, nframes), nframes, get_gain());
863 Mixer::apply_gain_to_buffer(m_masterOut->get_buffer(1, nframes), nframes, get_gain());
864
865 // update the m_transportFrame
866 // m_transportFrame += nframes;
867 m_transportLocation.add_frames(nframes, audiodevice().get_sample_rate());
868
869 return 1;
870 }
871
get_cdrdao_tracklist(ExportSpecification * spec,bool pregap)872 QString Sheet::get_cdrdao_tracklist(ExportSpecification* spec, bool pregap)
873 {
874 QString output;
875
876 QList<Marker*> mlist = m_timeline->get_markers();
877 QList<Marker*> tempmarkers;
878
879 // Here we make the marker-stuff idiot-proof ;-). Traverso doesn't insist on having any
880 // marker at all, so we need to handle cases like:
881 // - no markers at all
882 // - one marker (doesn't make sense)
883 // - enough markers, but no end marker
884
885 Marker* temp;
886
887 if (mlist.size() < 2) {
888 switch (mlist.size()) {
889 case 0:
890 // no markers present. We add one at the beginning and one at the
891 // end of the render area.
892 temp = new Marker(m_timeline, spec->startLocation, Marker::CDTRACK);
893 tempmarkers.append(temp);
894 mlist.prepend(temp);
895 temp = new Marker(m_timeline, spec->endLocation, Marker::ENDMARKER);
896 tempmarkers.append(temp);
897 mlist.append(temp);
898 break;
899 case 1:
900 // one marker is present. We add two more, one at the beginning
901 // and one at the end of the render area. If the present marker
902 // happened to be at either the start or the end position,
903 // it will now be overwritten, so we will never end up with
904 // two markers at the same position.
905
906 // deactivate the next if-condition (only the first one) if you want the
907 // stuff before the first marker to go into the pre-gap
908 if (mlist.at(0)->get_when() != (spec->startLocation)) {
909 temp = new Marker(m_timeline, spec->startLocation, Marker::CDTRACK);
910 tempmarkers.append(temp);
911 mlist.prepend(temp);
912 }
913 if (mlist.at(0)->get_when() != (spec->startLocation)) {
914 temp = new Marker(m_timeline, spec->endLocation, Marker::ENDMARKER);
915 tempmarkers.append(temp);
916 mlist.append(temp);
917 }
918 break;
919 }
920 } else {
921 // would be ok, but let's check if there is an end marker present. If not,
922 // add one to spec->end_frame
923 if (!m_timeline->has_end_marker()) {
924 temp = new Marker(m_timeline, spec->endLocation, Marker::ENDMARKER);
925 tempmarkers.append(temp);
926 mlist.append(temp);
927 }
928 }
929
930 TimeRef start;
931
932 for(int i = 0; i < mlist.size()-1; ++i) {
933
934 Marker* startmarker = mlist.at(i);
935 Marker* endmarker = mlist.at(i+1);
936
937 output += "TRACK AUDIO\n";
938
939 if (startmarker->get_copyprotect()) {
940 output += " NO COPY\n";
941 } else {
942 output += " COPY\n";
943 }
944
945 if (startmarker->get_preemphasis()) {
946 output += " PRE_EMPHASIS\n";
947 }
948
949 output += " CD_TEXT {\n LANGUAGE 0 {\n";
950 output += " TITLE \"" + startmarker->get_description() + "\"\n";
951 output += " PERFORMER \"" + startmarker->get_performer() + "\"\n";
952 output += " ISRC \"" + startmarker->get_isrc() + "\"\n";
953 output += " ARRANGER \"" + startmarker->get_arranger() + "\"\n";
954 output += " SONGWRITER \"" + startmarker->get_songwriter() + "\"\n";
955 output += " MESSAGE \"" + startmarker->get_message() + "\"\n }\n }\n";
956
957 // add some stuff only required for the first track (e.g. pre-gap)
958 if ((i == 0) && pregap) {
959 //if (start == 0) {
960 // standard pregap, because we have a track marker at the beginning
961 output += " PREGAP 00:02:00\n";
962 //} else {
963 // // no track marker at the beginning, thus use the part from 0 to the first
964 // // track marker for the pregap
965 // output += " START " + frame_to_cd(start, m_project->get_rate()) + "\n";
966 // start = 0;
967 //}
968 }
969
970 TimeRef length = cd_to_timeref(timeref_to_cd(endmarker->get_when())) - cd_to_timeref(timeref_to_cd(startmarker->get_when()));
971
972 QString s_start = timeref_to_cd(start);
973 QString s_length = timeref_to_cd(length);
974
975 output += " FILE \"" + spec->name + "." + spec->extraFormat["filetype"] + "\" " + s_start + " " + s_length + "\n\n";
976 start += length;
977
978 // check if the second marker is of type "Endmarker"
979 if (endmarker->get_type() == Marker::ENDMARKER) {
980 break;
981 }
982 }
983
984 // delete all temporary markers
985 foreach(Marker* marker, tempmarkers) {
986 delete marker;
987 }
988
989 return output;
990 }
991
resize_buffer(bool updateArmStatus,nframes_t size)992 void Sheet::resize_buffer(bool updateArmStatus, nframes_t size)
993 {
994 if (mixdown)
995 delete [] mixdown;
996 if (gainbuffer)
997 delete [] gainbuffer;
998 mixdown = new audio_sample_t[size];
999 gainbuffer = new audio_sample_t[size];
1000 m_masterOut->set_buffer_size(size);
1001 m_renderBus->set_buffer_size(size);
1002 m_clipRenderBus->set_buffer_size(size);
1003
1004 if (updateArmStatus) {
1005 apill_foreach(Track* track, Track, m_tracks) {
1006 AudioBus* bus = audiodevice().get_capture_bus(track->get_bus_in().toLatin1());
1007 if (bus && track->armed()) {
1008 bus->set_monitor_peaks(true);
1009 }
1010 }
1011 }
1012 }
1013
audiodevice_params_changed()1014 void Sheet::audiodevice_params_changed()
1015 {
1016 resize_buffer(true, audiodevice().get_buffer_size());
1017
1018 // The samplerate possibly has been changed, this initiates
1019 // a seek in DiskIO, which clears the buffers and refills them
1020 // with the correct resampled audio data!
1021 // We need to seek to a different position then the current one,
1022 // else the seek won't happen at all :)
1023 if (m_currentSampleRate != audiodevice().get_sample_rate()) {
1024 m_currentSampleRate = audiodevice().get_sample_rate();
1025
1026 m_diskio->output_rate_changed(m_currentSampleRate);
1027
1028 TimeRef location = m_transportLocation;
1029 location.add_frames(1, audiodevice().get_sample_rate());
1030
1031 set_transport_pos(location);
1032 }
1033 }
1034
get_bitdepth()1035 int Sheet::get_bitdepth( )
1036 {
1037 return m_project->get_bitdepth();
1038 }
1039
get_rate()1040 int Sheet::get_rate( )
1041 {
1042 return m_project->get_rate();
1043 }
1044
get_first_visible_frame() const1045 nframes_t Sheet::get_first_visible_frame( ) const
1046 {
1047 return firstVisibleFrame;
1048 }
1049
get_diskio() const1050 DiskIO * Sheet::get_diskio( ) const
1051 {
1052 return m_diskio;
1053 }
1054
get_audioclip_manager() const1055 AudioClipManager * Sheet::get_audioclip_manager( ) const
1056 {
1057 return m_acmanager;
1058 }
1059
get_plugin_chain() const1060 PluginChain* Sheet::get_plugin_chain() const
1061 {
1062 return m_pluginChain;
1063 }
1064
get_track_index(qint64 id)1065 int Sheet::get_track_index(qint64 id)
1066 {
1067 int i=0;
1068 apill_foreach(Track* track, Track, m_tracks) {
1069 if (track->get_id() == id) {
1070 return i + 1;
1071 }
1072 ++i;
1073 }
1074 return 0;
1075 }
1076
handle_diskio_readbuffer_underrun()1077 void Sheet::handle_diskio_readbuffer_underrun( )
1078 {
1079 if (is_transport_rolling()) {
1080 printf("Sheet:: DiskIO ReadBuffer UnderRun signal received!\n");
1081 info().critical(tr("Hard Disk overload detected!"));
1082 info().critical(tr("Failed to fill ReadBuffer in time"));
1083 }
1084 }
1085
handle_diskio_writebuffer_overrun()1086 void Sheet::handle_diskio_writebuffer_overrun( )
1087 {
1088 if (is_transport_rolling()) {
1089 printf("Sheet:: DiskIO WriteBuffer OverRun signal received!\n");
1090 info().critical(tr("Hard Disk overload detected!"));
1091 info().critical(tr("Failed to empty WriteBuffer in time"));
1092 }
1093 }
1094
audiodevice_started()1095 void Sheet::audiodevice_started( )
1096 {
1097 m_playBackBus = audiodevice().get_playback_bus("Playback 1");
1098 }
1099
get_last_location() const1100 TimeRef Sheet::get_last_location() const
1101 {
1102 TimeRef lastAudio = m_acmanager->get_last_location();
1103
1104 if (m_timeline->get_markers().size()) {
1105 TimeRef lastMarker = m_timeline->get_markers().last()->get_when();
1106 return (lastAudio > lastMarker) ? lastAudio : lastMarker;
1107 }
1108
1109 return lastAudio;
1110 }
1111
private_add_track(Track * track)1112 void Sheet::private_add_track(Track* track)
1113 {
1114 m_tracks.append(track);
1115 }
1116
private_remove_track(Track * track)1117 void Sheet::private_remove_track(Track* track)
1118 {
1119 m_tracks.remove(track);
1120 }
1121
get_track(qint64 id)1122 Track* Sheet::get_track(qint64 id)
1123 {
1124 apill_foreach(Track* track, Track, m_tracks) {
1125 if (track->get_id() == id) {
1126 return track;
1127 }
1128 }
1129 return 0;
1130 }
1131
move_clip(Track * from,Track * too,AudioClip * clip,TimeRef location)1132 void Sheet::move_clip(Track * from, Track * too, AudioClip * clip, TimeRef location)
1133 {
1134 PENTER2;
1135
1136 if (from == too) {
1137 clip->set_track_start_location(location);
1138 return;
1139 }
1140
1141 // Remove has to be done BEFORE adding, else the APILinkedList logic
1142 // gets messed up for the Tracks AudioClipList, which is an APILinkedList :(
1143 Command::process_command(from->remove_clip(clip, false, true));
1144 Command::process_command(too->add_clip(clip, false, true));
1145
1146 if (clip->get_track_start_location() != location) {
1147 clip->set_track_start_location(location);
1148 }
1149 }
1150
set_editing_mode()1151 Command* Sheet::set_editing_mode( )
1152 {
1153 m_mode = EDIT;
1154 emit modeChanged();
1155 return 0;
1156 }
1157
set_effects_mode()1158 Command* Sheet::set_effects_mode( )
1159 {
1160 m_mode = EFFECTS;
1161 emit modeChanged();
1162 return 0;
1163 }
1164
set_temp_follow_state(bool state)1165 void Sheet::set_temp_follow_state(bool state)
1166 {
1167 emit tempFollowChanged(state);
1168 }
1169
1170 // Function is only to be called from GUI thread.
set_recordable()1171 Command * Sheet::set_recordable()
1172 {
1173 #if defined (THREAD_CHECK)
1174 Q_ASSERT(QThread::currentThreadId() == threadId);
1175 #endif
1176
1177 // Do nothing if transport is rolling!
1178 if (is_transport_rolling()) {
1179 return 0;
1180 }
1181
1182 // Transport is not rolling, it's save now to switch
1183 // recording state to on /off
1184 if (is_recording()) {
1185 set_recording(false, false);
1186 } else {
1187 if (!any_track_armed()) {
1188 info().critical(tr("No Tracks armed for recording!"));
1189 return 0;
1190 }
1191
1192 set_recording(true, false);
1193 }
1194
1195 return 0;
1196 }
1197
1198 // Function is only to be called from GUI thread.
set_recordable_and_start_transport()1199 Command* Sheet::set_recordable_and_start_transport()
1200 {
1201 if (!is_recording()) {
1202 set_recordable();
1203 }
1204
1205 start_transport();
1206
1207 return 0;
1208 }
1209
1210 // Function is only to be called from GUI thread.
start_transport()1211 Command* Sheet::start_transport()
1212 {
1213 #if defined (THREAD_CHECK)
1214 Q_ASSERT(QThread::currentThreadId() == threadId);
1215 #endif
1216 // Delegate the transport start (or if we are rolling stop)
1217 // request to the audiodevice. Depending on the driver in use
1218 // this call will return directly to us (by a call to transport_control),
1219 // or handled by the driver
1220 if (is_transport_rolling()) {
1221 audiodevice().transport_stop(m_audiodeviceClient);
1222 } else {
1223 audiodevice().transport_start(m_audiodeviceClient);
1224 }
1225
1226 return ie().succes();
1227 }
1228
1229 // Function can be called either from the GUI or RT thread.
1230 // So ALL functions called here need to be RT thread save!!
transport_control(transport_state_t state)1231 int Sheet::transport_control(transport_state_t state)
1232 {
1233 if (m_scheduledForDeletion) {
1234 return true;
1235 }
1236
1237 switch(state.tranport) {
1238 case TransportStopped:
1239 if (is_transport_rolling()) {
1240 stop_transport_rolling();
1241 if (is_recording()) {
1242 set_recording(false, state.realtime);
1243 }
1244 }
1245 return true;
1246
1247 case TransportStarting:
1248 if (state.location != m_transportLocation) {
1249 if ( ! m_seeking ) {
1250 m_newTransportLocation = state.location;
1251 m_startSeek = 1;
1252 m_seeking = 1;
1253
1254 PMESG("tranport starting: initiating seek");
1255 return false;
1256 }
1257 }
1258 if (! m_seeking) {
1259 if (is_recording()) {
1260 if (!m_prepareRecording) {
1261 m_prepareRecording = true;
1262 // prepare_recording() is only to be called from the GUI thread
1263 // so we delegate the prepare_recording() function call via a
1264 // RT thread save signal!
1265 Q_ASSERT(state.realtime);
1266 RT_THREAD_EMIT(this, 0, prepareRecording());
1267 PMESG("transport starting: initiating prepare for record");
1268 return false;
1269 }
1270 if (!m_readyToRecord) {
1271 PMESG("transport starting: still preparing for record");
1272 return false;
1273 }
1274 }
1275
1276
1277 PMESG("tranport starting: seek finished");
1278 return true;
1279 } else {
1280 PMESG("tranport starting: still seeking");
1281 return false;
1282 }
1283
1284 case TransportRolling:
1285 if (!is_transport_rolling()) {
1286 // When the transport rolling request came from a non slave
1287 // driver, we currently can assume it's comming from the GUI
1288 // thread, and TransportStarting never was called before!
1289 // So in case we are recording we have to prepare for recording now!
1290 if ( ! state.isSlave && is_recording() ) {
1291 Q_ASSERT(!state.realtime);
1292 prepare_recording();
1293 }
1294 start_transport_rolling(state.realtime);
1295 }
1296 return true;
1297 }
1298
1299 return false;
1300 }
1301
1302 // RT thread save function
start_transport_rolling(bool realtime)1303 void Sheet::start_transport_rolling(bool realtime)
1304 {
1305 m_realtimepath = true;
1306 m_transport = 1;
1307
1308 if (realtime) {
1309 RT_THREAD_EMIT(this, 0, transportStarted());
1310 } else {
1311 emit transportStarted();
1312 }
1313
1314 PMESG("transport rolling");
1315 }
1316
1317 // RT thread save function
stop_transport_rolling()1318 void Sheet::stop_transport_rolling()
1319 {
1320 m_stopTransport = 1;
1321 PMESG("transport stopped");
1322 }
1323
1324 // RT thread save function
set_recording(bool recording,bool realtime)1325 void Sheet::set_recording(bool recording, bool realtime)
1326 {
1327 m_recording = recording;
1328
1329 if (!m_recording) {
1330 m_readyToRecord = false;
1331 m_prepareRecording = false;
1332 }
1333
1334 if (realtime) {
1335 RT_THREAD_EMIT(this, 0, recordingStateChanged());
1336 } else {
1337 emit recordingStateChanged();
1338 }
1339 }
1340
1341
1342 // NON RT thread save function, should only be called from GUI thread!!
prepare_recording()1343 void Sheet::prepare_recording()
1344 {
1345 #if defined (THREAD_CHECK)
1346 Q_ASSERT(QThread::currentThreadId() == threadId);
1347 #endif
1348
1349 if (m_recording && any_track_armed()) {
1350 CommandGroup* group = new CommandGroup(this, "");
1351 int clipcount = 0;
1352 apill_foreach(Track* track, Track, m_tracks) {
1353 if (track->armed()) {
1354 AudioClip* clip = track->init_recording();
1355 if (clip) {
1356 // For autosave purposes, we connect the recordingfinished
1357 // signal to the clip_finished_recording() slot, and add this
1358 // clip to our recording clip list.
1359 // At the time the cliplist is empty, we're sure the recording
1360 // session is finished, at which time an autosave makes sense.
1361 connect(clip, SIGNAL(recordingFinished(AudioClip*)),
1362 this, SLOT(clip_finished_recording(AudioClip*)));
1363 m_recordingClips.append(clip);
1364
1365 group->add_command(new AddRemoveClip(clip, AddRemoveClip::ADD));
1366 clipcount++;
1367 }
1368 }
1369 }
1370 group->setText(tr("Recording to %n Clip(s)", "", clipcount));
1371 Command::process_command(group);
1372 }
1373
1374 m_readyToRecord = true;
1375 }
1376
clip_finished_recording(AudioClip * clip)1377 void Sheet::clip_finished_recording(AudioClip * clip)
1378 {
1379 if (!m_recordingClips.removeAll(clip)) {
1380 PERROR("clip %s was not in recording clip list, cannot remove it!", QS_C(clip->get_name()));
1381 }
1382
1383 if (m_recordingClips.isEmpty()) {
1384 // seems we finished recording completely now
1385 // all clips have set their resulting ReadSource
1386 // length and whatsoever, let's do an autosave:
1387 m_project->save(true);
1388 }
1389 }
1390
1391
set_transport_pos(TimeRef location)1392 void Sheet::set_transport_pos(TimeRef location)
1393 {
1394 #if defined (THREAD_CHECK)
1395 Q_ASSERT(QThread::currentThreadId() == threadId);
1396 #endif
1397 audiodevice().transport_seek_to(m_audiodeviceClient, location);
1398 }
1399
1400
1401 //
1402 // Function is ALWAYS called in RealTime AudioThread processing path
1403 // Be EXTREMELY carefull to not call functions() that have blocking behavior!!
1404 //
start_seek()1405 void Sheet::start_seek()
1406 {
1407 #if defined (THREAD_CHECK)
1408 Q_ASSERT(threadId != QThread::currentThreadId ());
1409 #endif
1410
1411 if (is_transport_rolling()) {
1412 m_realtimepath = false;
1413 m_resumeTransport = true;
1414 }
1415
1416 m_transport = false;
1417 m_startSeek = 0;
1418
1419 // only sets a boolean flag, save to call.
1420 m_diskio->prepare_for_seek();
1421
1422 // 'Tell' the diskio it should start a seek action.
1423 RT_THREAD_EMIT(this, NULL, seekStart());
1424 }
1425
seek_finished()1426 void Sheet::seek_finished()
1427 {
1428 #if defined (THREAD_CHECK)
1429 Q_ASSERT_X(threadId == QThread::currentThreadId (), "Sheet::seek_finished", "Called from other Thread!");
1430 #endif
1431 PMESG2("Sheet :: entering seek_finished");
1432 m_transportLocation = m_newTransportLocation;
1433 m_seeking = 0;
1434
1435 if (m_resumeTransport) {
1436 start_transport_rolling(false);
1437 m_resumeTransport = false;
1438 }
1439
1440 emit transportPosSet();
1441 PMESG2("Sheet :: leaving seek_finished");
1442 }
1443
config_changed()1444 void Sheet::config_changed()
1445 {
1446 int quality = config().get_property("Conversion", "RTResamplingConverterType", DEFAULT_RESAMPLE_QUALITY).toInt();
1447 if (m_diskio->get_resample_quality() != quality) {
1448 m_diskio->set_resample_quality(quality);
1449 }
1450 }
1451
1452
1453
get_tracks() const1454 QList< Track * > Sheet::get_tracks() const
1455 {
1456 QList<Track*> list;
1457 apill_foreach(Track* track, Track, m_tracks) {
1458 list.append(track);
1459 }
1460 return list;
1461 }
1462
get_track_for_index(int index)1463 Track * Sheet::get_track_for_index(int index)
1464 {
1465 apill_foreach(Track* track, Track, m_tracks) {
1466 if (track->get_sort_index() == index) {
1467 return track;
1468 }
1469 }
1470
1471 return 0;
1472 }
1473
1474
1475 // the timer is used to allow 'hopping' to the left from snap position to snap position
1476 // even during playback.
prev_skip_pos()1477 Command* Sheet::prev_skip_pos()
1478 {
1479 if (snaplist->was_dirty()) {
1480 update_skip_positions();
1481 }
1482
1483 TimeRef p = get_transport_location();
1484
1485 if (p < TimeRef()) {
1486 PERROR("pos < 0");
1487 set_transport_pos(TimeRef());
1488 return ie().failure();
1489 }
1490
1491 QListIterator<TimeRef> it(m_xposList);
1492
1493 it.toBack();
1494
1495 int steps = 1;
1496
1497 if (m_skipTimer.isActive())
1498 {
1499 ++steps;
1500 }
1501
1502 int i = 0;
1503 while (it.hasPrevious()) {
1504 TimeRef pos = it.previous();
1505 if (pos < p) {
1506 p = pos;
1507 ++i;
1508 }
1509 if (i >= steps) {
1510 break;
1511 }
1512 }
1513
1514 set_transport_pos(p);
1515
1516 m_skipTimer.start(500);
1517
1518 return ie().succes();
1519 }
1520
next_skip_pos()1521 Command* Sheet::next_skip_pos()
1522 {
1523 if (snaplist->was_dirty()) {
1524 update_skip_positions();
1525 }
1526
1527 TimeRef p = get_transport_location();
1528
1529 if (p > m_xposList.last()) {
1530 PERROR("pos > last snap position");
1531 return ie().failure();
1532 }
1533
1534 QListIterator<TimeRef> it(m_xposList);
1535
1536 int i = 0;
1537 int steps = 1;
1538
1539 while (it.hasNext()) {
1540 TimeRef pos = it.next();
1541 if (pos > p) {
1542 p = pos;
1543 ++i;
1544 }
1545 if (i >= steps) {
1546 break;
1547 }
1548 }
1549
1550 set_transport_pos(p);
1551
1552 return ie().succes();
1553 }
1554
update_skip_positions()1555 void Sheet::update_skip_positions()
1556 {
1557 m_xposList.clear();
1558
1559 // store the beginning of the sheet and the work cursor
1560 m_xposList << TimeRef();
1561 m_xposList << get_work_location();
1562
1563 // store all clip borders
1564 QList<AudioClip* > acList = get_audioclip_manager()->get_clip_list();
1565 for (int i = 0; i < acList.size(); ++i) {
1566 m_xposList << acList.at(i)->get_track_start_location();
1567 m_xposList << acList.at(i)->get_track_end_location();
1568 }
1569
1570 // store all marker positions
1571 QList<Marker*> markerList = get_timeline()->get_markers();
1572 for (int i = 0; i < markerList.size(); ++i) {
1573 m_xposList << markerList.at(i)->get_when();
1574 }
1575
1576 // remove duplicates
1577 QMutableListIterator<TimeRef> it(m_xposList);
1578 while (it.hasNext()) {
1579 TimeRef val = it.next();
1580 if (m_xposList.count(val) > 1) {
1581 it.remove();
1582 }
1583 }
1584
1585 qSort(m_xposList);
1586 }
1587
skip_to_start()1588 void Sheet::skip_to_start()
1589 {
1590 set_transport_pos((TimeRef()));
1591 set_work_at((TimeRef()));
1592 }
1593
skip_to_end()1594 void Sheet::skip_to_end()
1595 {
1596 // stop the transport, no need to play any further than the end of the sheet
1597 if (is_transport_rolling())
1598 {
1599 start_transport();
1600 }
1601 set_transport_pos(get_last_location());
1602 }
1603
1604 //eof
1605