% sndstream - file reader implementation using samer.audio.alt.FileSource
%
% sndstream ::
%    (unit -> (AudioInputStream, unit -> unit)) ~'function to open stream',
%    (unit -> string) ~'function to create character representation',
%    options {
%       channels :: natural/nan ~'desired number of channels';
%       rate     :: nonneg/nan ~'desired sampling rate';
%       bits     :: natural/16 ~'desired bits per sample';
%    }
% -> signal(C,R).
%
% If channels or rate are not nan, audio format will be converted to match.
% If either of them are nan, the corresponding value from the audio file will
% be left unchanged.

classdef sndstream < signal
	properties (GetAccess=private, SetAccess=immutable)
		streamfn
		stringfn
		format
	end
	methods
		function s=sndstream(streamfn,varargin)
			opts=prefs('channels',nan,'rate',nan,'bits',16, ...
						  'stringfn',@()sprintf('sndstream(%s)',tostring(streamfn)), ...
						  varargin{:});

			if any(isnan([opts.channels,opts.rate,opts.bits]))
				fmt=peek(streamfn);
				if isnan(opts.channels), opts.channels=fmt.getChannels(); end
				if isnan(opts.rate), opts.rate=fmt.getSampleRate(); end
				if isnan(opts.bits), opts.bits=fmt.getSampleSizeInBits(); end
			end
			if opts.bits<0
				error('Cannot determine bits per sample');
			end
			
			s.streamfn=streamfn;
			s.stringfn=opts.stringfn;
			s.format=audio_format(opts.channels,opts.rate,opts.bits);
		end

		function s=tostring(sig), s=sig.stringfn(); end
		function c=channels(s), c=s.format.getChannels(); end
		function r=rate(s), r=s.format.getSampleRate(); end

		function s=construct(sig)
			[str,cleanup]=sig.streamfn(false);
			src=samer.audio.alt.StreamSource(str,sig.format);
			ref=disposables('reg',src);
			s.start = @()src.start();
			s.stop = @()src.stop();
			s.reader = @reader;
			s.dispose = @dispose;

			function r=reader(n)
				ch=src.getFormat.getChannels();
				rdr=src.reader(n*ch);
				r=@next;
				function [x,rem]=next
					x=reshape(rdr.next(),ch,n);
					rem=rdr.unread()/ch;
				end
			end
			function dispose
				disposables('dereg',ref);
				src.dispose();
				cleanup();
			end
		end
	end
end

function fmt=peek(streamfn)
	[str,cleanup]=streamfn(true);
	f1=str.getFormat();
	str.close(); cleanup();
	ss=f1.getSampleSizeInBits();
	if ss<0, ss=16; end
	fmt=audio_format(f1.getChannels(),f1.getSampleRate(),ss);
end
