NO-OP; Backport changes from Mixbus branch
[ardour.git] / libs / ardour / diskstream.cc
1 /*
2     Copyright (C) 2000-2006 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cassert>
21 #include <cstdio>
22 #include <unistd.h>
23 #include <cmath>
24 #include <cerrno>
25 #include <string>
26 #include <climits>
27 #include <fcntl.h>
28 #include <cstdlib>
29 #include <ctime>
30 #include <sys/stat.h>
31
32 #include <glibmm/threads.h>
33
34 #include "pbd/error.h"
35 #include "pbd/basename.h"
36 #include "pbd/memento_command.h"
37 #include "pbd/xml++.h"
38 #include "pbd/stacktrace.h"
39
40 #include "ardour/debug.h"
41 #include "ardour/diskstream.h"
42 #include "ardour/io.h"
43 #include "ardour/pannable.h"
44 #include "ardour/profile.h"
45 #include "ardour/playlist.h"
46 #include "ardour/session.h"
47 #include "ardour/track.h"
48
49 #include "pbd/i18n.h"
50 #include <locale.h>
51
52 using namespace std;
53 using namespace ARDOUR;
54 using namespace PBD;
55
56 ARDOUR::framecnt_t Diskstream::disk_read_chunk_frames = default_disk_read_chunk_frames ();
57 ARDOUR::framecnt_t Diskstream::disk_write_chunk_frames = default_disk_write_chunk_frames ();
58
59 PBD::Signal0<void>                Diskstream::DiskOverrun;
60 PBD::Signal0<void>                Diskstream::DiskUnderrun;
61
62 Diskstream::Diskstream (Session &sess, const string &name, Flag flag)
63         : SessionObject(sess, name)
64         , i_am_the_modifier (0)
65         , _track (0)
66         , _record_enabled (0)
67             , _record_safe (0)
68         , _visible_speed (1.0f)
69         , _actual_speed (1.0f)
70         , _buffer_reallocation_required (false)
71         , _seek_required (false)
72         , capture_start_frame (0)
73         , capture_captured (0)
74         , was_recording (false)
75         , adjust_capture_position (0)
76         , _capture_offset (0)
77         , _roll_delay (0)
78         , first_recordable_frame (max_framepos)
79         , last_recordable_frame (max_framepos)
80         , last_possibly_recording (0)
81         , _alignment_style (ExistingMaterial)
82         , _alignment_choice (Automatic)
83         , _slaved (false)
84         , loop_location (0)
85         , overwrite_frame (0)
86         , overwrite_offset (0)
87         , _pending_overwrite (false)
88         , overwrite_queued (false)
89         , wrap_buffer_size (0)
90         , speed_buffer_size (0)
91         , _speed (1.0)
92         , _target_speed (_speed)
93         , file_frame (0)
94         , playback_sample (0)
95         , in_set_state (false)
96         , _flags (flag)
97         , deprecated_io_node (0)
98 {
99 }
100
101 Diskstream::Diskstream (Session& sess, const XMLNode& /*node*/)
102         : SessionObject(sess, "unnamed diskstream")
103         , i_am_the_modifier (0)
104         , _track (0)
105         , _record_enabled (0)
106             , _record_safe (0)
107         , _visible_speed (1.0f)
108         , _actual_speed (1.0f)
109         , _buffer_reallocation_required (false)
110         , _seek_required (false)
111         , capture_start_frame (0)
112         , capture_captured (0)
113         , was_recording (false)
114         , adjust_capture_position (0)
115         , _capture_offset (0)
116         , _roll_delay (0)
117         , first_recordable_frame (max_framepos)
118         , last_recordable_frame (max_framepos)
119         , last_possibly_recording (0)
120         , _alignment_style (ExistingMaterial)
121         , _alignment_choice (Automatic)
122         , _slaved (false)
123         , loop_location (0)
124         , overwrite_frame (0)
125         , overwrite_offset (0)
126         , _pending_overwrite (false)
127         , overwrite_queued (false)
128         , wrap_buffer_size (0)
129         , speed_buffer_size (0)
130         , _speed (1.0)
131         , _target_speed (_speed)
132         , file_frame (0)
133         , playback_sample (0)
134         , in_set_state (false)
135         , _flags (Recordable)
136         , deprecated_io_node (0)
137 {
138 }
139
140 Diskstream::~Diskstream ()
141 {
142         DEBUG_TRACE (DEBUG::Destruction, string_compose ("Diskstream %1 deleted\n", _name));
143
144         if (_playlist) {
145                 _playlist->release ();
146         }
147
148         delete deprecated_io_node;
149 }
150
151 void
152 Diskstream::set_track (Track* t)
153 {
154         _track = t;
155         _io = _track->input();
156
157         ic_connection.disconnect();
158         _io->changed.connect_same_thread (ic_connection, boost::bind (&Diskstream::handle_input_change, this, _1, _2));
159
160         if (_io->n_ports() != ChanCount::ZERO) {
161                 input_change_pending.type = IOChange::Type (IOChange::ConfigurationChanged|IOChange::ConnectionsChanged);
162                 non_realtime_input_change ();
163         }
164
165         _track->Destroyed.connect_same_thread (*this, boost::bind (&Diskstream::route_going_away, this));
166 }
167
168 void
169 Diskstream::handle_input_change (IOChange change, void * /*src*/)
170 {
171         Glib::Threads::Mutex::Lock lm (state_lock);
172
173         if (change.type & (IOChange::ConfigurationChanged|IOChange::ConnectionsChanged)) {
174
175                 /* rather than handle this here on a DS-by-DS basis we defer to the
176                    session transport/butler thread, and let it tackle
177                    as many diskstreams as need it in one shot. this avoids many repeated
178                    takings of the audioengine process lock.
179                 */
180
181                 if (!(input_change_pending.type & change.type)) {
182                         input_change_pending.type = IOChange::Type (input_change_pending.type | change.type);
183                         _session.request_input_change_handling ();
184                 }
185         }
186 }
187
188 void
189 Diskstream::non_realtime_set_speed ()
190 {
191         if (_buffer_reallocation_required)
192         {
193                 Glib::Threads::Mutex::Lock lm (state_lock);
194                 allocate_temporary_buffers ();
195
196                 _buffer_reallocation_required = false;
197         }
198
199         if (_seek_required) {
200                 if (speed() != 1.0f || speed() != -1.0f) {
201                         seek ((framepos_t) (_session.transport_frame() * (double) speed()), true);
202                 }
203                 else {
204                         seek (_session.transport_frame(), true);
205                 }
206
207                 _seek_required = false;
208         }
209 }
210
211 bool
212 Diskstream::realtime_set_speed (double sp, bool global)
213 {
214         bool changed = false;
215         double new_speed = sp * _session.transport_speed();
216
217         if (_visible_speed != sp) {
218                 _visible_speed = sp;
219                 changed = true;
220         }
221
222         if (new_speed != _actual_speed) {
223
224                 framecnt_t required_wrap_size = (framecnt_t) ceil (_session.get_block_size() *
225                                                                   fabs (new_speed)) + 2;
226
227                 if (required_wrap_size > wrap_buffer_size) {
228                         _buffer_reallocation_required = true;
229                 }
230
231                 _actual_speed = new_speed;
232                 _target_speed = fabs(_actual_speed);
233         }
234
235         if (changed) {
236                 if (!global) {
237                         _seek_required = true;
238                 }
239                 SpeedChanged (); /* EMIT SIGNAL */
240         }
241
242         return _buffer_reallocation_required || _seek_required;
243 }
244
245 void
246 Diskstream::set_capture_offset ()
247 {
248         if (_io == 0) {
249                 /* can't capture, so forget it */
250                 return;
251         }
252
253         switch (_alignment_style) {
254         case ExistingMaterial:
255                 _capture_offset = _io->latency();
256                 break;
257
258         case CaptureTime:
259         default:
260                 _capture_offset = 0;
261                 break;
262         }
263 #ifdef MIXBUS
264         framecnt_t port_offset;
265         if (_track->mixbus_internal_bounce (port_offset)) {
266                 /* _capture_offset may become negative, but the sum
267                  * _capture_offset + existing_material_offset
268                  * will be postive.
269                  */
270                 _capture_offset -= port_offset;
271         }
272 #endif
273
274         DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: using IO latency, capture offset set to %2 with style = %3\n", name(), _capture_offset, enum_2_string (_alignment_style)));
275 }
276
277
278 void
279 Diskstream::set_align_style (AlignStyle a, bool force)
280 {
281         if (record_enabled() && _session.actively_recording()) {
282                 return;
283         }
284
285         if ((a != _alignment_style) || force) {
286                 _alignment_style = a;
287                 set_capture_offset ();
288                 AlignmentStyleChanged ();
289         }
290 }
291
292 void
293 Diskstream::set_align_choice (AlignChoice a, bool force)
294 {
295         if (record_enabled() && _session.actively_recording()) {
296                 return;
297         }
298
299         if ((a != _alignment_choice) || force) {
300                 _alignment_choice = a;
301
302                 switch (_alignment_choice) {
303                         case Automatic:
304                                 set_align_style_from_io ();
305                                 break;
306                         case UseExistingMaterial:
307                                 set_align_style (ExistingMaterial);
308                                 break;
309                         case UseCaptureTime:
310                                 set_align_style (CaptureTime);
311                                 break;
312                 }
313         }
314 }
315
316 int
317 Diskstream::set_loop (Location *location)
318 {
319         if (location) {
320                 if (location->start() >= location->end()) {
321                         error << string_compose(_("Location \"%1\" not valid for track loop (start >= end)"), location->name()) << endl;
322                         return -1;
323                 }
324         }
325
326         loop_location = location;
327
328         LoopSet (location); /* EMIT SIGNAL */
329         return 0;
330 }
331
332 /** Get the start position (in session frames) of the nth capture in the current pass */
333 ARDOUR::framepos_t
334 Diskstream::get_capture_start_frame (uint32_t n) const
335 {
336         Glib::Threads::Mutex::Lock lm (capture_info_lock);
337
338         if (capture_info.size() > n) {
339                 /* this is a completed capture */
340                 return capture_info[n]->start;
341         } else {
342                 /* this is the currently in-progress capture */
343                 return capture_start_frame;
344         }
345 }
346
347 ARDOUR::framecnt_t
348 Diskstream::get_captured_frames (uint32_t n) const
349 {
350         Glib::Threads::Mutex::Lock lm (capture_info_lock);
351
352         if (capture_info.size() > n) {
353                 /* this is a completed capture */
354                 return capture_info[n]->frames;
355         } else {
356                 /* this is the currently in-progress capture */
357                 return capture_captured;
358         }
359 }
360
361 void
362 Diskstream::set_roll_delay (ARDOUR::framecnt_t nframes)
363 {
364         _roll_delay = nframes;
365 }
366
367 int
368 Diskstream::use_playlist (boost::shared_ptr<Playlist> playlist)
369 {
370         if (!playlist) {
371                 return 0;
372         }
373
374         bool prior_playlist = false;
375
376         {
377                 Glib::Threads::Mutex::Lock lm (state_lock);
378
379                 if (playlist == _playlist) {
380                         return 0;
381                 }
382
383                 playlist_connections.drop_connections ();
384
385                 if (_playlist) {
386                         _playlist->release();
387                         prior_playlist = true;
388                 }
389
390                 _playlist = playlist;
391                 _playlist->use();
392
393                 if (!in_set_state && destructive() && recordable()) {
394                         reset_write_sources (false);
395                 }
396
397                 _playlist->ContentsChanged.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_modified, this));
398                 _playlist->LayeringChanged.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_modified, this));
399                 _playlist->DropReferences.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_deleted, this, boost::weak_ptr<Playlist>(_playlist)));
400                 _playlist->RangesMoved.connect_same_thread (playlist_connections, boost::bind (&Diskstream::playlist_ranges_moved, this, _1, _2));
401         }
402
403         /* don't do this if we've already asked for it *or* if we are setting up
404            the diskstream for the very first time - the input changed handling will
405            take care of the buffer refill.
406         */
407
408         if (!overwrite_queued && prior_playlist) {
409                 _session.request_overwrite_buffer (_track);
410                 overwrite_queued = true;
411         }
412
413         PlaylistChanged (); /* EMIT SIGNAL */
414         _session.set_dirty ();
415
416         return 0;
417 }
418
419 void
420 Diskstream::playlist_changed (const PropertyChange&)
421 {
422         playlist_modified ();
423 }
424
425 void
426 Diskstream::playlist_modified ()
427 {
428         if (!i_am_the_modifier && !overwrite_queued) {
429                 _session.request_overwrite_buffer (_track);
430                 overwrite_queued = true;
431         }
432 }
433
434 void
435 Diskstream::playlist_deleted (boost::weak_ptr<Playlist> wpl)
436 {
437         boost::shared_ptr<Playlist> pl (wpl.lock());
438
439         if (pl == _playlist) {
440
441                 /* this catches an ordering issue with session destruction. playlists
442                    are destroyed before diskstreams. we have to invalidate any handles
443                    we have to the playlist.
444                 */
445
446                 if (_playlist) {
447                         _playlist.reset ();
448                 }
449         }
450 }
451
452 bool
453 Diskstream::set_name (const string& str)
454 {
455         if (_name != str) {
456                 assert(playlist());
457                 playlist()->set_name (str);
458                 SessionObject::set_name(str);
459         }
460         return true;
461 }
462
463 bool
464 Diskstream::set_write_source_name (const std::string& str) {
465         _write_source_name = str;
466         return true;
467 }
468
469 XMLNode&
470 Diskstream::get_state ()
471 {
472         XMLNode* node = new XMLNode ("Diskstream");
473         char buf[64];
474         LocaleGuard lg;
475
476         node->add_property ("flags", enum_2_string (_flags));
477         node->add_property ("playlist", _playlist->name());
478         node->add_property("name", _name);
479         id().print (buf, sizeof (buf));
480         node->add_property("id", buf);
481         snprintf (buf, sizeof(buf), "%f", _visible_speed);
482         node->add_property ("speed", buf);
483         node->add_property ("capture-alignment", enum_2_string (_alignment_choice));
484         node->add_property ("record-safe", _record_safe ? "yes" : "no");
485
486         if (_extra_xml) {
487                 node->add_child_copy (*_extra_xml);
488         }
489
490         return *node;
491 }
492
493 int
494 Diskstream::set_state (const XMLNode& node, int /*version*/)
495 {
496         XMLProperty const * prop;
497
498         if ((prop = node.property ("name")) != 0) {
499                 _name = prop->value();
500         }
501
502         if (deprecated_io_node) {
503                 set_id (*deprecated_io_node);
504         } else {
505                 set_id (node);
506         }
507
508         if ((prop = node.property ("flags")) != 0) {
509                 _flags = Flag (string_2_enum (prop->value(), _flags));
510         }
511
512         if (Profile->get_trx() && (_flags & Destructive)) {
513                 error << string_compose (_("%1: this session uses destructive tracks, which are not supported"), PROGRAM_NAME) << endmsg;
514                 return -1;
515         }
516
517         if ((prop = node.property (X_("capture-alignment"))) != 0) {
518                 set_align_choice (AlignChoice (string_2_enum (prop->value(), _alignment_choice)), true);
519         } else {
520                 set_align_choice (Automatic, true);
521         }
522
523         if ((prop = node.property ("playlist")) == 0) {
524                 return -1;
525         }
526
527         if (find_and_use_playlist (prop->value())) {
528                 return -1;
529         }
530
531         if ((prop = node.property ("speed")) != 0) {
532                 double sp = atof (prop->value().c_str());
533
534                 if (realtime_set_speed (sp, false)) {
535                         non_realtime_set_speed ();
536                 }
537         }
538
539         if ((prop = node.property ("record-safe")) != 0) {
540             _record_safe = PBD::string_is_affirmative (prop->value()) ? 1 : 0;
541         }
542
543         return 0;
544 }
545
546 void
547 Diskstream::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const & movements_frames, bool from_undo)
548 {
549         /* If we're coming from an undo, it will have handled
550            automation undo (it must, since automation-follows-regions
551            can lose automation data).  Hence we can do nothing here.
552         */
553
554         if (from_undo) {
555                 return;
556         }
557
558         if (!_track || Config->get_automation_follows_regions () == false) {
559                 return;
560         }
561
562         list< Evoral::RangeMove<double> > movements;
563
564         for (list< Evoral::RangeMove<framepos_t> >::const_iterator i = movements_frames.begin();
565              i != movements_frames.end();
566              ++i) {
567
568                 movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
569         }
570
571         /* move panner automation */
572         boost::shared_ptr<Pannable> pannable = _track->pannable();
573         Evoral::ControlSet::Controls& c (pannable->controls());
574
575         for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) {
576                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl>(ci->second);
577                 if (!ac) {
578                         continue;
579                 }
580                 boost::shared_ptr<AutomationList> alist = ac->alist();
581                 if (!alist->size()) {
582                         continue;
583                 }
584                 XMLNode & before = alist->get_state ();
585                 bool const things_moved = alist->move_ranges (movements);
586                 if (things_moved) {
587                         _session.add_command (new MementoCommand<AutomationList> (
588                                                       *alist.get(), &before, &alist->get_state ()));
589                 }
590         }
591
592         /* move processor automation */
593         _track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames));
594 }
595
596 void
597 Diskstream::move_processor_automation (boost::weak_ptr<Processor> p, list< Evoral::RangeMove<framepos_t> > const & movements_frames)
598 {
599         boost::shared_ptr<Processor> processor (p.lock ());
600         if (!processor) {
601                 return;
602         }
603
604         list< Evoral::RangeMove<double> > movements;
605         for (list< Evoral::RangeMove<framepos_t> >::const_iterator i = movements_frames.begin(); i != movements_frames.end(); ++i) {
606                 movements.push_back(Evoral::RangeMove<double>(i->from, i->length, i->to));
607         }
608
609         set<Evoral::Parameter> const a = processor->what_can_be_automated ();
610
611         for (set<Evoral::Parameter>::const_iterator i = a.begin (); i != a.end (); ++i) {
612                 boost::shared_ptr<AutomationList> al = processor->automation_control(*i)->alist();
613                 if (!al->size()) {
614                         continue;
615                 }
616                 XMLNode & before = al->get_state ();
617                 bool const things_moved = al->move_ranges (movements);
618                 if (things_moved) {
619                         _session.add_command (
620                                 new MementoCommand<AutomationList> (
621                                         *al.get(), &before, &al->get_state ()
622                                         )
623                                 );
624                 }
625         }
626 }
627
628 void
629 Diskstream::check_record_status (framepos_t transport_frame, bool can_record)
630 {
631         int possibly_recording;
632         int rolling;
633         int change;
634         const int transport_rolling = 0x4;
635         const int track_rec_enabled = 0x2;
636         const int global_rec_enabled = 0x1;
637         const int fully_rec_enabled = (transport_rolling|track_rec_enabled|global_rec_enabled);
638
639         /* merge together the 3 factors that affect record status, and compute
640          * what has changed.
641          */
642
643         rolling = _session.transport_speed() != 0.0f;
644         possibly_recording = (rolling << 2) | ((int)record_enabled() << 1) | (int)can_record;
645         change = possibly_recording ^ last_possibly_recording;
646
647         if (possibly_recording == last_possibly_recording) {
648                 return;
649         }
650
651         const framecnt_t existing_material_offset = _session.worst_playback_latency();
652
653         if (possibly_recording == fully_rec_enabled) {
654
655                 if (last_possibly_recording == fully_rec_enabled) {
656                         return;
657                 }
658
659                 capture_start_frame = _session.transport_frame();
660                 first_recordable_frame = capture_start_frame + _capture_offset;
661                 last_recordable_frame = max_framepos;
662
663                 DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1: @ %7 (%9) FRF = %2 CSF = %4 CO = %5, EMO = %6 RD = %8 WOL %10 WTL %11\n",
664                                                                       name(), first_recordable_frame, last_recordable_frame, capture_start_frame,
665                                                                       _capture_offset,
666                                                                       existing_material_offset,
667                                                                       transport_frame,
668                                                                       _roll_delay,
669                                                                       _session.transport_frame(),
670                                                                       _session.worst_output_latency(),
671                                                                       _session.worst_track_latency()));
672
673
674                 if (_alignment_style == ExistingMaterial) {
675                         first_recordable_frame += existing_material_offset;
676                         DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("\tshift FRF by EMO %1\n",
677                                                                               first_recordable_frame));
678                 }
679
680                 prepare_record_status (capture_start_frame);
681
682         } else {
683
684                 if (last_possibly_recording == fully_rec_enabled) {
685
686                         /* we were recording last time */
687
688                         if (change & transport_rolling) {
689
690                                 /* transport-change (stopped rolling): last_recordable_frame was set in ::prepare_to_stop(). We
691                                  * had to set it there because we likely rolled past the stopping point to declick out,
692                                  * and then backed up.
693                                  */
694
695                         } else {
696                                 /* punch out */
697
698                                 last_recordable_frame = _session.transport_frame() + _capture_offset;
699
700                                 if (_alignment_style == ExistingMaterial) {
701                                         last_recordable_frame += existing_material_offset;
702                                 }
703                         }
704                 }
705         }
706
707         last_possibly_recording = possibly_recording;
708 }
709
710 void
711 Diskstream::route_going_away ()
712 {
713         _io.reset ();
714 }
715
716 void
717 Diskstream::calculate_record_range (Evoral::OverlapType ot, framepos_t transport_frame, framecnt_t nframes,
718                                     framecnt_t & rec_nframes, framecnt_t & rec_offset)
719 {
720         switch (ot) {
721         case Evoral::OverlapNone:
722                 rec_nframes = 0;
723                 break;
724
725         case Evoral::OverlapInternal:
726                 /*     ----------    recrange
727                  *       |---|       transrange
728                  */
729                 rec_nframes = nframes;
730                 rec_offset = 0;
731                 break;
732
733         case Evoral::OverlapStart:
734                 /*    |--------|    recrange
735                  *  -----|          transrange
736                  */
737                 rec_nframes = transport_frame + nframes - first_recordable_frame;
738                 if (rec_nframes) {
739                         rec_offset = first_recordable_frame - transport_frame;
740                 }
741                 break;
742
743         case Evoral::OverlapEnd:
744                 /*    |--------|    recrange
745                  *       |--------  transrange
746                  */
747                 rec_nframes = last_recordable_frame - transport_frame;
748                 rec_offset = 0;
749                 break;
750
751         case Evoral::OverlapExternal:
752                 /*    |--------|    recrange
753                  *  --------------  transrange
754                  */
755                 rec_nframes = last_recordable_frame - first_recordable_frame;
756                 rec_offset = first_recordable_frame - transport_frame;
757                 break;
758         }
759
760         DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose ("%1 rec? %2 @ %3 (for %4) FRF %5 LRF %6 : rf %7 @ %8\n",
761                                                               _name, enum_2_string (ot), transport_frame, nframes,
762                                                               first_recordable_frame, last_recordable_frame, rec_nframes, rec_offset));
763 }
764
765 void
766 Diskstream::prepare_to_stop (framepos_t transport_frame, framepos_t audible_frame)
767 {
768         switch (_alignment_style) {
769         case ExistingMaterial:
770                 last_recordable_frame = transport_frame + _capture_offset;
771                 DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose("%1: prepare to stop sets last recordable frame to %2 + %3 = %4\n", _name, transport_frame, _capture_offset, last_recordable_frame));
772                 break;
773
774         case CaptureTime:
775                 last_recordable_frame = audible_frame; // note that capture_offset is zero
776                 /* we may already have captured audio before the last_recordable_frame (audible frame),
777                    so deal with this.
778                 */
779                 if (last_recordable_frame > capture_start_frame) {
780                         capture_captured = min (capture_captured, last_recordable_frame - capture_start_frame);
781                 }
782                 DEBUG_TRACE (DEBUG::CaptureAlignment, string_compose("%1: prepare to stop sets last recordable frame to audible frame @ %2\n", _name, audible_frame));
783                 break;
784         }
785
786 }
787
788 void
789 Diskstream::engage_record_enable ()
790 {
791         g_atomic_int_set (&_record_enabled, 1);
792 }
793
794 void
795 Diskstream::disengage_record_enable ()
796 {
797         g_atomic_int_set (&_record_enabled, 0);
798 }
799
800 void
801 Diskstream::engage_record_safe ()
802 {
803         g_atomic_int_set (&_record_safe, 1);
804 }
805
806 void
807 Diskstream::disengage_record_safe ()
808 {
809         g_atomic_int_set (&_record_safe, 0);
810 }
811
812 framecnt_t
813 Diskstream::default_disk_read_chunk_frames()
814 {
815         return 65536;
816 }
817
818 framecnt_t
819 Diskstream::default_disk_write_chunk_frames ()
820 {
821         return 65536;
822 }
823
824 void
825 Diskstream::set_buffering_parameters (BufferingPreset bp)
826 {
827         framecnt_t read_chunk_size;
828         framecnt_t read_buffer_size;
829         framecnt_t write_chunk_size;
830         framecnt_t write_buffer_size;
831
832         if (!get_buffering_presets (bp, read_chunk_size, read_buffer_size, write_chunk_size, write_buffer_size)) {
833                 return;
834         }
835
836         disk_read_chunk_frames = read_chunk_size;
837         disk_write_chunk_frames = write_chunk_size;
838         Config->set_audio_capture_buffer_seconds (write_buffer_size);
839         Config->set_audio_playback_buffer_seconds (read_buffer_size);
840
841         cerr << "Set buffering params to " << disk_read_chunk_frames << '|' << disk_write_chunk_frames << '|'
842              << Config->get_audio_playback_buffer_seconds() << '|'
843              << Config->get_audio_capture_buffer_seconds ()
844              << endl;
845 }
846
847 bool
848 Diskstream::get_buffering_presets (BufferingPreset bp,
849                                    framecnt_t& read_chunk_size,
850                                    framecnt_t& read_buffer_size,
851                                    framecnt_t& write_chunk_size,
852                                    framecnt_t& write_buffer_size)
853 {
854         switch (bp) {
855         case Small:
856                 read_chunk_size = 65536;  /* samples */
857                 write_chunk_size = 65536; /* samples */
858                 read_buffer_size = 5;  /* seconds */
859                 write_buffer_size = 5; /* seconds */
860                 break;
861
862         case Medium:
863                 read_chunk_size = 262144;  /* samples */
864                 write_chunk_size = 131072; /* samples */
865                 read_buffer_size = 10;  /* seconds */
866                 write_buffer_size = 10; /* seconds */
867                 break;
868
869         case Large:
870                 read_chunk_size = 524288; /* samples */
871                 write_chunk_size = 131072; /* samples */
872                 read_buffer_size = 20; /* seconds */
873                 write_buffer_size = 20; /* seconds */
874                 break;
875
876         default:
877                 return false;
878         }
879
880         return true;
881 }