samer@0: function [Sched,GetData]=playaudio_unfold(buflen,unfold_fn,S,Snk,varargin) samer@0: % playaudio_async - Play stream of audio data asynchronously samer@0: % samer@0: % playaudio :: samer@0: % N:natural ~'buflen', samer@0: % (S->[[C,N]],S) ~'unfolding function', samer@36: % sink(C,R) ~'sink for C channels', samer@0: % options { samer@0: % maxbuf :: natural/2*max(size(X)) ~'maximum buffer size'; samer@0: % hook :: (iterator(S)->iterator(T)) ~'fn to build iterator'; samer@0: % onstart :: A -> action ~'called BEFORE timer starts'; samer@0: % onstop :: A -> action ~'called AFTER timer stops'; samer@0: % onfinish:: A -> action ~'called when end of signal reached'; samer@0: % defer :: bool / 0 ~'if 1, don't start the timer' samer@0: % } samer@0: % -> sched(S) ~'scheduler api functions', samer@0: % (S -> [[N]]) ~'function to recover audio from iterator state'. samer@0: % samer@0: % iterator(S) ::= cell {(S->action S)~'state transformer', S~'initial state'}. samer@0: % samer@0: % sched(S) ::= struct { samer@0: % dispose :: unit -> action unit; samer@0: % isrunning :: unit -> action bool; samer@0: % startat :: real -> action unit; samer@0: % start :: unit -> action unit; samer@0: % stop :: unit -> action unit; samer@0: % rewind :: unit -> action unit; samer@0: % getstate :: unit -> action S; samer@0: % setstate :: S -> action unit samer@0: % }. samer@0: % samer@0: % The 'hook' option gives the caller an opportunity to elaborate on the samer@0: % 'iterator' used to drive the audio playback. The 'iterator' is a cell samer@0: % array contain a state transformer function and initial state. The caller samer@0: % can use this to build a more complex iterator the does other things samer@0: % for each buffer of samples. samer@0: % samer@0: % The third return value is a function which can be used in a state samer@0: % transformer function to recover a buffer of audio samples from the samer@0: % current state. samer@0: % samer@0: % NB: all the audio buffers must be the same size for this to work. samer@0: % NB: the rewind function should only be called when the timer is stopped. samer@0: samer@0: N=buflen; samer@37: opts=options('onstop',@nop,'onstart',@nop,'onfinish',@nop, ... samer@0: 'period_adjust',0.9,'preload',2,'sched',@iterate_timed,varargin{:}); samer@0: samer@0: it=feval(getparam(opts,'hook',@id),{@playbuf,S}); samer@0: samer@0: maxbuf=getparam(opts,'maxbuf',2*N); samer@0: sync_delta=getparam(opts,'sync_delta',0.02); samer@0: sync_offset=getparam(opts,'sync_offset',0); samer@0: samer@0: L=construct(Snk); samer@0: write=L.writer(maxbuf); samer@0: period=(N/rate(Snk))*opts.period_adjust; samer@0: samer@0: Sched=opts.sched(it{1},it{2},period, ... samer@0: 'exec_mode','fixedRate', 'busy_mode','drop', opts, ... samer@0: 'onstart',@onstart,'onstop',@onstop); samer@0: getstate=Sched.getstate; samer@0: odispose=Sched.dispose; samer@0: Sched.atend=@()isempty(getstate()); samer@0: Sched.dispose=@dispose; samer@0: Sched.setGain=L.setGain; samer@0: samer@0: GetData=@head; samer@0: samer@0: function dispose samer@0: odispose(); samer@0: L.dispose(); samer@0: end samer@0: samer@0: function onstart(S), samer@0: opts.onstart(S); samer@0: L.start(); samer@0: for i=1:opts.preload samer@0: write(zeros(maxbuf,1)); samer@0: end samer@0: end samer@0: samer@0: function onstop(S), samer@0: write(zeros(maxbuf,1)); samer@0: L.stop(); samer@0: opts.onstop(S); samer@0: end samer@0: samer@0: function S=playbuf(S) samer@0: [y,S]=unfold_fn(S); samer@0: n=size(y,2); samer@0: if n>0, write(y); end samer@0: end samer@0: end samer@0: