--
-- Copyright (C) 2020  <fastrgv@gmail.com>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You may read the full text of the GNU General Public License
-- at <http://www.gnu.org/licenses/>.
--





-- Now automatically uses #channels and sampleRate of input file.
-- Must have format = 16bit signed int:

with text_io;
with ada.direct_io;
with interfaces.C;
with interfaces.C.strings;
with system;
with pcm_h;

with ada.strings.unbounded;
use  ada.strings.unbounded;


package body sndloop is


task body iplaytask is

	use text_io;

	procedure myassert( 
		condition : boolean;  
		flag: integer:=0;
		msg: string := ""
		) is
	begin
	  if condition=false then
			put("ASSERTION Failed!  ");
			if flag /= 0 then
				put( "@ " & integer'image(flag) &" : " );
			end if;
			put_line(msg);
			new_line;
			raise program_error;
	  end if;
	end myassert;


	use interfaces.C;
	use interfaces.C.strings;
	use pcm_h;



----------------------------------------------------
	package wio is new ada.direct_io(char);
	use wio;
	ifid: wio.file_type;
	sz: wio.count;
	ch, ch1,ch2,ch3,ch4: char;

	formoffset : constant wio.positive_count := 20;--(short=2)
	chanoffset : constant wio.positive_count := 22;--(short=2)
	rateoffset : constant wio.positive_count := 24;--sampRate(int=4)
	byteoffset : constant wio.positive_count := 28;--bytesPerSec(int=4)
	bitsoffset : constant wio.positive_count := 34;--bitsPerSample(short=2)
	dataoffset : constant wio.positive_count := 44;
	nchan, sampRate, bitsPerSample, bytePerSec: natural;
-----------------------------------------------
	bytesPerFrame, nch: interfaces.c.size_t;
	uchan : unsigned;

	rc: int;

	pcm: integer;

	sampSize, bytesPerBuf : size_t; -- bytes/samp
	sampSizeMx : constant size_t := 4; -- bytes/samp

	-- if this is too big, then quitting a sound loop
	-- has significant latency.
	chunkSize : constant size_t := 64; --Ok
	--chunkSize : constant size_t := 32; --staticNoise

	params, handle : system.address;


	val:  aliased unsigned;
	dir: aliased int;

	remframes,frames: aliased snd_pcm_uframes_t;

	defaultstr: chars_ptr := new_string( "default" );

	sft: snd_pcm_sframes_t := 0;
	epipe: constant snd_pcm_sframes_t := 32;
	estrpipe: constant snd_pcm_sframes_t := 86;
	eagain: constant snd_pcm_sframes_t := 11;


	type buffertype is new char_array(1..chunkSize*sampSizeMx*2);
	bufmax: constant integer := 100_000;
	subtype bufrng is integer range 1..bufmax;
	bufptr : array(bufrng) of access buffertype;

	bi: size_t;
	kk, nbuf: integer;

	-- memory space for "params":
	junk: array(1..1_000) of integer; 
	-- 1_000 works; 100 causes storage_error

	--convert char to integer:
	function c2i( ch: char ) return integer is
	begin
		return character'pos( character(ch) );
	end;

	--convert integer to unsigned:
	function i2u( i: integer ) return unsigned is
	begin
		return unsigned(i);
	end;




usfil: unbounded_string;

begin --iplaytask

select -- outer : init or terminate

accept init(sfil: string; dur: gldouble) do

	usfil:=to_unbounded_string(sfil);
	wio.open(ifid, wio.in_file, sfil);
	sz := wio.size(ifid);


	-- get/check format flag
	wio.set_index(ifid, formoffset+1);
	wio.read( ifid, ch ); --first byte is 1 or ?
	pcm := c2i(ch);
	if 
		(pcm /= 1)   --wav_fmt_pcm
		and
		(pcm /= 254) --wav_fmt_extensible
	then --?compression used?
		put(", pcm="&integer'image(pcm));
		put_line(" : cannot decode compressed files");
		raise program_error;
	end if;



	-- determine nchan [1 or 2]:
	wio.set_index(ifid, chanoffset+1);
	wio.read( ifid, ch ); --first byte is 1 or 2
	nchan := c2i(ch);
	nch:=interfaces.c.size_t(nchan);
	uchan := i2u(nchan);
	myassert( uchan>=1, 99 );
	myassert( uchan<=2, 98 );


	-- determine sample rate:
	wio.set_index(ifid, rateoffset+1);
	wio.read( ifid, ch1 ); -- byte1
	wio.read( ifid, ch2 ); -- byte2
	wio.read( ifid, ch3 ); -- byte3
	wio.read( ifid, ch4 ); -- byte4

	--now, convert LittleEndian to integer
	sampRate := c2i(ch1)+256*( c2i(ch2) + 256*( c2i(ch3) + 256*c2i(ch4)));


	-- get BytesPerSecond:
	wio.set_index(ifid, byteoffset+1);
	wio.read( ifid, ch1 ); -- byte1
	wio.read( ifid, ch2 ); -- byte2
	wio.read( ifid, ch3 ); -- byte3
	wio.read( ifid, ch4 ); -- byte4

	--now, convert LittleEndian to integer
	bytePerSec := c2i(ch1)+256*( c2i(ch2) + 256*( c2i(ch3) + 256*c2i(ch4)));


	-- get bitsPerSample:
	wio.set_index(ifid, bitsoffset+1);
	wio.read( ifid, ch1 ); -- byte1
	wio.read( ifid, ch2 ); -- byte2
	bitsPerSample := c2i(ch1); -- 8, 16, 24 ...
	sampSize := size_t(bitsPerSample/8);
	myassert( sampSize<=sampSizeMx, 888);

	bytesPerFrame := sampSize*nch;



--New paradigm: read entire file here:
  	frames := snd_pcm_uframes_t(chunkSize); 
	bytesPerBuf := size_t(frames)*bytesPerFrame;
	nbuf := integer( size_t(sz-dataoffset) / bytesPerBuf );
	myassert( nbuf <= bufmax, 999);


	wio.set_index(ifid, dataoffset+1);
	for j in 1..nbuf loop
		bufptr(j) := new buffertype;

		for i in size_t range 1..size_t(frames)*bytesPerFrame loop
			wio.read( ifid, bufptr(j)(i) ); --Load Data Buffer from Soundfile
		end loop;

	end loop;
	wio.close(ifid);



------------------------------------------------------------------------
------------------------------------------------------------------------

-- also do the following 2 key steps here, simply because code
-- within an accept statement is "protected"

	-- this next proc seems to allocate what "handle" needs:
	rc := snd_pcm_open(handle'address, defaultstr, snd_pcm_stream_playback, 0);
	myassert( rc>=0, 1);

	-- I need to manually allocate some space for "params":
	params := junk'address; --YES !!! This works fine.
	rc := snd_pcm_hw_params_any(handle, params);
	myassert( rc>=0, 2);


end init; -- end  accept init block



  	rc:=snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
	myassert( rc>=0, 3);



	-- set format; [uncompressed] WAV files are likely one of these:
	if     bitsPerSample=8 then
  		rc := snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_U8);
	elsif bitsPerSample=16 then
  		rc := snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
	elsif bitsPerSample=24 then
		if sampSize=3 then
  			rc := snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24_3LE);
		elsif sampSize=4 then
  			rc := snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S24_LE);
		end if;
	elsif bitsPerSample=32 then
  		rc := snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S32_LE);
	end if;
	myassert( rc>=0, 4);




	-- set #chan:
  	rc := snd_pcm_hw_params_set_channels(handle, params, uchan );
	myassert( rc>=0, 5);


	-- set sampleRate
  	val := i2u(sampRate);
  	rc := snd_pcm_hw_params_set_rate_near(handle, params, val'access, dir'access);
	myassert( rc>=0, 6);


  	rc:=snd_pcm_hw_params_set_period_size_near(handle, params, frames'access, dir'access);
	myassert( rc>=0, 7);


	-- install these parameter values:
  	rc := snd_pcm_hw_params(handle, params);
	myassert( rc>=0, 8);


  	rc:=snd_pcm_hw_params_get_period_size(params, frames'access, dir'access);
	myassert( rc>=0, 9);
	myassert(frames=snd_pcm_uframes_t(chunkSize), 99); --our buffer size assumed this!




	outer:loop
------------------------outerloop----------------------------------------------

		select -- middle : start or quit

			accept Start; --middle select choice 1

			mid:loop -- replays loop from start
--------------------------------------------------------midLoop-----------

				kk:=0;
				inner:loop -- plays entire sound file, unless stopped.
					kk:=kk+1;
					exit inner when kk>nbuf;
--=======================================================================innerLoop

					select -- inner : stop else continue
						accept Stop;
						exit mid; --then wait for soundloop [re]start or quit
					else
						delay 0.0;
					end select; --inner

					remframes:=frames;
					bi:=1;
					while remframes>0 loop
						-- Pipe data to soundcard:
						sft := snd_pcm_writei(handle, bufptr(kk)(bi)'address, remframes);

						-- try to handle problems as per aplay.c:
						if sft=-eagain or ( sft>=0 and int(sft)<int(remframes) ) then
							--pipe took only part of sound: "short write"
							rc:=snd_pcm_wait(handle,100); --wait up to 100 ms for pcm to be ready
						elsif sft=-epipe then
							rc:=snd_pcm_prepare(handle); -- (xrun: under or over run)
						elsif sft=-estrpipe then --suspended
							loop
								rc:=snd_pcm_resume(handle); --try to resume (no samples lost)
								exit when long(rc) /= -eagain;
								delay 0.001; --wait until suspend flag is released
							end loop;
							if rc<0 then rc:=snd_pcm_prepare(handle); end if;
						end if;
						myassert( rc>=0, 12 );

						if sft>0 then -- if sft<remframes => short-write
							remframes:=remframes - snd_pcm_uframes_t(sft);
							bi := bi+size_t(sft)*bytesPerFrame;
						end if;
					end loop;

--=========================================================innerLoop
				end loop inner;
-----------------------------------------------midLoop-----------------------
			end loop mid;

		or -- middle select

			accept Quit; --middle select choice 2
				exit outer;

		end select; --middle

------------------------outerloop----------------------------------------------
	end loop outer;





	--wio.close(ifid);


  	rc:=snd_pcm_drain(handle);
	myassert( rc>=0, 13);


  	rc:=snd_pcm_close(handle);
	myassert( rc>=0, 14);

or -- outer select
	terminate; -- (in case we never init)

end select; --outer

exception
	when others =>
		put_line("iplaytask error");
		raise;

end iplaytask; -- task body

end sndloop; -- package body

