---------------------------------------------------------------------------
-- (c) 2020 mark watson
-- I am happy for anyone to use this for non-commercial use.
-- If my vhdl files are used commercially or otherwise sold,
-- please contact me for explicit permission at scrameta (gmail).
-- This applies for source and binary form and derived works.
---------------------------------------------------------------------------

LIBRARY ieee;
USE ieee.std_logic_1164.all; 
use ieee.numeric_std.all;
use IEEE.STD_LOGIC_MISC.all;
use work.AudioTypes.all;

LIBRARY work;

ENTITY mixer IS 
PORT
(
	CLK : IN STD_LOGIC;
	RESET_N : IN STD_LOGIC;

	ENABLE_CYCLE : IN STD_LOGIC;

	DETECT_RIGHT : IN STD_LOGIC;
	POST_DIVIDE : IN STD_LOGIC_VECTOR(7 downto 0);
	FANCY_ENABLE : IN STD_LOGIC;
	B_CH0_EN : IN STD_LOGIC_VECTOR(3 downto 0);	
	B_CH1_EN : IN STD_LOGIC_VECTOR(3 downto 0);	

	L_CH0 : IN SIGNED(15 downto 0);
	L_CH1 : IN SIGNED(15 downto 0);
	L_CH2 : IN SIGNED(15 downto 0);
	L_CH3 : IN SIGNED(15 downto 0);
	L_CH4 : IN SIGNED(15 downto 0);
	R_CH0 : IN SIGNED(15 downto 0);
	R_CH1 : IN SIGNED(15 downto 0);
	R_CH2 : IN SIGNED(15 downto 0);
	R_CH3 : IN SIGNED(15 downto 0);
	R_CH4 : IN SIGNED(15 downto 0);
	B_CH0 : IN SIGNED(15 downto 0);
	B_CH1 : IN SIGNED(15 downto 0);

	AUDIO_0_SIGNED : out signed(15 downto 0);
	AUDIO_1_SIGNED : out signed(15 downto 0);
	AUDIO_2_SIGNED : out signed(15 downto 0);
	AUDIO_3_SIGNED : out signed(15 downto 0)
);
END mixer;		
		
ARCHITECTURE vhdl OF mixer IS

	-- DC blocker constants
	constant DC_EXTRA_BITS : integer := 4;
	constant DC_ACC_WIDTH  : integer := 16 + DC_EXTRA_BITS; -- 20 bits
	constant DC_K          : integer := 10;

	subtype dc_acc_t is signed(DC_ACC_WIDTH-1 downto 0);
	type dc_arr_t is array (0 to 3) of dc_acc_t;

	-- DETECT RIGHT PLAYING
	signal RIGHT_PLAYING_RECENTLY : std_logic;
	signal RIGHT_NEXT : std_logic;
	signal RIGHT_REG : std_logic;
	signal RIGHT_PLAYING_COUNT_NEXT : unsigned(23 downto 0);
	signal RIGHT_PLAYING_COUNT_REG : unsigned(23 downto 0);

	-- sums
	signal audio0_reg : signed(15 downto 0);
	signal audio0_next : signed(15 downto 0);
	signal audio1_reg : signed(15 downto 0);
	signal audio1_next : signed(15 downto 0);
	signal audio2_reg : signed(15 downto 0);
	signal audio2_next : signed(15 downto 0);
	signal audio3_reg : signed(15 downto 0);
	signal audio3_next : signed(15 downto 0);

	signal acc_reg : signed(19 downto 0);
	signal acc_next : signed(19 downto 0);

	-- DC blocker per-channel state
	signal dc_reg  : dc_arr_t;
	signal dc_next : dc_arr_t;

	-- Pipeline register: holds dc-corrected divided value between state_dc and state_clear
	signal dc_corrected_reg  : signed(19 downto 0);
	signal dc_corrected_next : signed(19 downto 0);

	signal out_ch_reg : std_logic_vector(1 downto 0);
	signal out_ch_next : std_logic_vector(1 downto 0);
	
	signal state_reg : unsigned(3 downto 0);
	signal state_next : unsigned(3 downto 0);
	constant state_CH0    : unsigned(3 downto 0) := "0000";
	constant state_CH1    : unsigned(3 downto 0) := "0001";
	constant state_CH2    : unsigned(3 downto 0) := "0010";
	constant state_CH3    : unsigned(3 downto 0) := "0011";
	constant state_CH4    : unsigned(3 downto 0) := "0100";
	constant state_BCH0   : unsigned(3 downto 0) := "0101";
	constant state_BCH1   : unsigned(3 downto 0) := "0110";
	constant state_dc     : unsigned(3 downto 0) := "0111";  -- divide + dc block
	constant state_clear  : unsigned(3 downto 0) := "1000";  -- saturate + write output

	signal channelsel : std_logic_vector(3 downto 0);
	signal include_in_output : std_logic_vector(3 downto 0);
	signal left_on_right : std_logic;
	
	signal volume : signed(15 downto 0);
	signal saturated : signed(15 downto 0);
	
	signal write : std_logic;

BEGIN
-- DETECT IF RIGHT CHANNEL PLAYING
-- TODO: into another entity
process(clk,reset_n)
begin
	if (reset_n='0') then
		RIGHT_REG <= '0';
		RIGHT_PLAYING_COUNT_REG <= (others=>'0');
		audio0_reg <= (others=>'0');
		audio1_reg <= (others=>'0');
		audio2_reg <= (others=>'0');
		audio3_reg <= (others=>'0');
		acc_reg <= (others=>'0');
		out_ch_reg <= (others=>'0');
		dc_corrected_reg <= (others=>'0');
		state_reg <= state_CH0;
		for i in 0 to 3 loop
			dc_reg(i) <= (others=>'0');
		end loop;
	elsif (clk'event and clk='1') then
		RIGHT_REG <= RIGHT_NEXT;
		RIGHT_PLAYING_COUNT_REG <= RIGHT_PLAYING_COUNT_NEXT;
		audio0_reg <= audio0_next;
		audio1_reg <= audio1_next;
		audio2_reg <= audio2_next;
		audio3_reg <= audio3_next;
		acc_reg <= acc_next;
		out_ch_reg <= out_ch_next;
		dc_reg <= dc_next;
		dc_corrected_reg <= dc_corrected_next;
		state_reg <= state_next;
	end if;
end process;

process(RIGHT_NEXT,RIGHT_REG,ENABLE_CYCLE,RIGHT_PLAYING_RECENTLY,RIGHT_PLAYING_COUNT_REG)
begin
	RIGHT_PLAYING_COUNT_NEXT <= RIGHT_PLAYING_COUNT_REG;

	if (ENABLE_CYCLE='1' and RIGHT_PLAYING_RECENTLY='1') then
		RIGHT_PLAYING_COUNT_NEXT <= RIGHT_PLAYING_COUNT_REG-1;
	end if;

	if (RIGHT_NEXT/=RIGHT_REG) then
		RIGHT_PLAYING_COUNT_NEXT <= (others=>'1');
	end if;
end process;
RIGHT_PLAYING_RECENTLY <= or_reduce(std_logic_vector(RIGHT_PLAYING_COUNT_REG));


	process(state_reg,RIGHT_REG,out_ch_reg,acc_reg,volume,dc_reg,dc_corrected_reg,
	POST_DIVIDE,SATURATED,include_in_output)
		variable postdivide  : std_logic_vector(1 downto 0);
		variable presaturate : signed(19 downto 0);
		variable addAcc      : std_logic;
		variable clearAcc    : std_logic;
		-- DC blocker datapath variables
		variable ch_idx   : integer range 0 to 3;
		variable x_ext    : dc_acc_t;
		variable dc_cur   : dc_acc_t;
		variable err      : dc_acc_t;
		variable adj      : dc_acc_t;
		variable dc_new_v : dc_acc_t;
		variable y_new    : dc_acc_t;
	begin
		state_next        <= state_reg;
		out_ch_next       <= out_ch_reg;
		acc_next          <= acc_reg;
		RIGHT_NEXT        <= RIGHT_REG;
		dc_next           <= dc_reg;
		dc_corrected_next <= dc_corrected_reg;

		write      <= '0';
		channelsel <= (others=>'0');
		saturated  <= (others=>'0');
		addAcc     := '0';
		clearAcc   := '0';
		postdivide := "00";

		case out_ch_reg is 
		when "00" =>
			postdivide := POST_DIVIDE(1 downto 0);
			addAcc     := include_in_output(0);
		when "01" =>
			postdivide := POST_DIVIDE(3 downto 2);
			addAcc     := include_in_output(1);
		when "10" =>
			postdivide := POST_DIVIDE(5 downto 4);
			addAcc     := include_in_output(2);
		when "11" =>
			postdivide := POST_DIVIDE(7 downto 6);
			addAcc     := include_in_output(3);
		when others =>
		end case;

		case state_reg is
			when state_CH0 =>
				channelsel <= x"0";					
				state_next <= state_CH1;				
			when state_CH1 =>
				channelsel <= x"1";
				state_next <= state_CH2;
			when state_CH2 =>
				channelsel <= x"2";
				state_next <= state_CH3;	
			when state_CH3 =>
				channelsel <= x"3";
				state_next <= state_CH4;
			when state_CH4 =>
				channelsel <= x"4";
				state_next <= state_BCH0;
			when state_BCH0 =>
				channelsel <= x"6";
				state_next <= state_BCH1;
				-- NEEDS DOING WITHOUT BCH* mixed, since those plays on all channels!!
				RIGHT_NEXT <= (xor_reduce(std_logic_vector(acc_reg)) and out_ch_reg(0)) or (RIGHT_REG and not(out_ch_reg(0)));	
			when state_BCH1 =>
				channelsel <= x"7";
				state_next <= state_dc;

			when state_dc =>
				-- Divide accumulator and run dc blocker.
				-- Result registered into dc_corrected_reg, accumulator cleared.
				-- Critical path: shift + subtract + shift_right(K) + add + subtract
				case postdivide is
					when "00" => presaturate := resize(acc_reg(19 downto 0), 20);
					when "01" => presaturate := resize(acc_reg(19 downto 1), 20);
					when "10" => presaturate := resize(acc_reg(19 downto 2), 20);
					when "11" => presaturate := resize(acc_reg(19 downto 3), 20);
					when others =>
				end case;

				ch_idx   := to_integer(unsigned(out_ch_reg));
				x_ext    := resize(presaturate, DC_ACC_WIDTH);
				dc_cur   := dc_reg(ch_idx);
				err      := x_ext - dc_cur;
				adj      := shift_right(err, DC_K);
				dc_new_v := dc_cur + adj;
				y_new    := x_ext - dc_new_v;

				dc_next(ch_idx)   <= dc_new_v;
				dc_corrected_next <= resize(y_new, 20);
				clearAcc          := '1';
				state_next        <= state_clear;

			when state_clear =>
				-- Saturate the registered dc-corrected value and write to output.
				-- Critical path: just the saturation check + mux
				write       <= '1';
				out_ch_next <= std_logic_vector(unsigned(out_ch_reg)+1);
				state_next  <= state_CH0;

			when others =>
				state_next <= state_CH0;
		end case;

		channelsel(3) <= out_ch_reg(0);

		-- Saturation reads from the pipeline register, so only the
		-- saturation check itself is on the state_clear critical path
		if dc_corrected_reg(19 downto 15) /= "00000" and
		   dc_corrected_reg(19 downto 15) /= "11111" then
			saturated(14 downto 0) <= (others => not dc_corrected_reg(19));
			saturated(15)          <= dc_corrected_reg(19);
		else
			saturated <= dc_corrected_reg(15 downto 0);
		end if;

		-- Accumulator update: clear takes priority over add
		if clearAcc = '1' then
			acc_next <= (others=>'0');
		elsif addAcc = '1' then
			acc_next <= acc_reg + resize(volume, 20);
		end if;

	end process;		

	process(state_reg,channelsel,
		L_CH0,L_CH1,L_CH2,L_CH3,L_CH4,
		R_CH0,R_CH1,R_CH2,R_CH3,R_CH4,
		B_CH0,B_CH1,
		B_CH0_EN,B_CH1_EN
		)
	begin
		volume <= (others=>'0');
			--left
		include_in_output(0) <= not(channelsel(3)); 
		include_in_output(2) <= not(channelsel(3));
			--right
		include_in_output(1) <= channelsel(3);
		include_in_output(3) <= channelsel(3);
		case channelsel is
		when x"0" =>
		        volume <= L_CH0;
		when x"1" =>
		        volume <= L_CH1;
		when x"2" =>
		        volume <= L_CH2;
		when x"3" =>
		        volume <= L_CH3;
		when x"4" =>
		        volume <= L_CH4;
		when x"8" =>
		        volume <= R_CH0;
		when x"9" =>
			volume <= R_CH1;
		when x"a" =>
			volume <= R_CH2;
		when x"b" =>
			volume <= R_CH3;
		when x"c" =>
			volume <= R_CH4;
		when x"6"|x"e" =>
			include_in_output <= B_CH0_EN;
		        volume <= B_CH0;
		when x"7"|x"f" =>
			include_in_output <= B_CH1_EN;
		        volume <= B_CH1;
		when others =>
		end case;
	end process;
	
	left_on_right <= not(FANCY_ENABLE) or (not(RIGHT_PLAYING_RECENTLY) AND DETECT_RIGHT);		

	process(write,saturated,out_ch_reg,left_on_right,audio0_reg,audio1_reg,audio2_reg,audio3_reg)
		variable out_ch_adj : std_logic_vector(2 downto 0);
		variable wr : std_logic_vector(3 downto 0);
	begin
		audio0_next <= audio0_reg;
		audio1_next <= audio1_reg;
		audio2_next <= audio2_reg;
		audio3_next <= audio3_reg;

		out_ch_adj(1 downto 0) := out_ch_reg;
		out_ch_adj(2) := left_on_right;
		
		wr := (others=>'0');
		case out_ch_adj is 	
		when "000" => 
			wr(0) := write;
		when "001" => 
			wr(1) := write;
		when "010" => 
			wr(2) := write;
		when "011" => 
			wr(3) := write;
		when "100" => 
			wr(0) := write;
			wr(1) := write;
		when "110" => 
			wr(2) := write;
			wr(3) := write;
		when others =>
			-- 101 -> write to right, dropped since we are playing ONLY left on right
			-- 111 -> write to right, dropped since we are playing ONLY left on right
			-- Deliberate! We accumulate right still for the right detect logic but do not output it
		end case;

		if (wr(0)='1') then
			audio0_next <= saturated;
		end if;
		if (wr(1)='1') then
			audio1_next <= saturated;
		end if;
		if (wr(2)='1') then
			audio2_next <= saturated;
		end if;
		if (wr(3)='1') then
			audio3_next <= saturated;
		end if;
	end process;	
	
-- output
	AUDIO_0_SIGNED <= audio0_reg;
	AUDIO_1_SIGNED <= audio1_reg;
	AUDIO_2_SIGNED <= audio2_reg;
	AUDIO_3_SIGNED <= audio3_reg;
end vhdl;
