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