%Real-time IIR & FFT-FIR filtering based on the playrec.dll
%Prototype 1 channel in -> 1 channels out
%B and A are IIR filter coeff. FIR is the FIR filter coeff. 
%NFFT is the FFT size used by the convolver. NFFT should be a multiple of 2
%and at least twice as long as the length of FIR.
%ASIO_INCHANNEL is the channel that real-time filter listens to.
%ASIO_OUTCHANNELS is the ASIO output channel that the filtered result will be sent
%to.
%FS is the sampling frequency
%BLOCKMETHOD has two choices, either 'FRY_IT' or 'CHEAP'. 
%There is no guarantee that the filtering 
%has finished before the data must be sent to the soundcard. 
%The choice 'FRY_IT' will use all CPU time on one CPU-core and block the prompt. 
%This choice minimise the probability for lost pages on output. 
%The choice 'CHEAP' will minimize the computational burden on the cost of
%higher probability for lost pages.
%QUESIZE is the amount of pages in the queue. A high number will introduce a
%long delay but will lower the probability for lost pages.
%The pagesize for IIR filters is 512 samples by default.
%When using B and A without the FFT convolver a QUESIZE of 100 can be
%useful in order to not loose pages, if other processes is multitasking at the same time. 
%
% REALTIMEFILTER(B,A,NFFT,FIR,ASIO_INCHANNEL,ASIO_OUTCHANNELS,FS,'BLOCKMETHOD',QUESIZE)
%[] can be used for emtpy fields in B,A,FIR and NFFT
%Mikael Bohman

function realtimefilter(B,A,fir,NFFT,ASIO_INCHANNEL,ASIO_OUTCHANNELS,fs,BLOCKMETHOD,QUESIZE)
error(nargchk(8,9,nargin))
if(nargin==8)
    QUESIZE=1;
end
METHOD=0;
if(isempty(fir)==0 && isempty(NFFT)==0)
    %MINST FIR
    if(NFFT<=length(fir))
        error('NFFT must be greater than the length of FIR');
    end
    if(NFFT<=2*length(fir))
        warning('NFFT should at least be twice as big as the length of FIR for efficient calculation');
    end
    if((log(NFFT)/log(2)-round(log(NFFT)/log(2)))~=0) 
        warning('NFFT should be a multiple of 2 for efficient filtering');
    end
    if(isprime(NFFT))
        warning('The NFFT was choosen to a prime number. Prime numbers will result in poor efficiency and is not well suited to work with FFT');
    end
    %Beräkna FIR koef. i frekvensdomänen                   
    FIR=fft(fir(:),NFFT);
    Length_FIR=length(fir);
    ZFFT=zeros(Length_FIR,1);    
    METHOD=1;
    PageSize=NFFT-Length_FIR;
end
if(isempty(A)==0 && isempty(B)==0)
    Zi=[];
    METHOD=2;
    PageSize=512;
end
if(isempty(A)==0 && isempty(B)==0 && isempty(fir)==0 && isempty(NFFT)==0)
    METHOD=3;
    PageSize=NFFT-Length_FIR;    
end
if(METHOD==0)
    PageSize=512;
    warning('No filters will be applied')
end

%Hitta ASIO kort i datorn
if(playrec('isInitialised'))
    playrec('reset');
    
end

S=playrec('getDevices');
disp(S);
playrec('init',fs,S.deviceID,S.deviceID); %Initiera ljudkortet
[pageNumList] = playrec('playrec',0, ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL);  %Spela in en page
for i=1:QUESIZE;
    pageNumList = [pageNumList playrec('playrec', 0, ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL)];
end

k=1;
CHEAP=strcmp(lower(BLOCKMETHOD),'cheap');
FRY_IT=strcmp(lower(BLOCKMETHOD),'fry_it');
disp('Press Ctrl+c to stop realtime filtering'); 
while(1)
    if(CHEAP)
        playrec('block',pageNumList(1));
    elseif(FRY_IT)
        while(playrec('isFinished', pageNumList(1)) == 0)
        end    
    else
        error('Incorrect BLOCKMETHOD was chosen')
    end
    
    
    data=double(playrec('getRec',pageNumList(1)));
    MAX=max(data);
    if(MAX>0.85)
        warning('Input clipped');
    end
    
    if(METHOD==0) %Ingen filtrering
        pageNumList = [pageNumList playrec('playrec', data, ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL)]; %Spela in page 3 samt spela upp första proc. page
        playrec('delPage',pageNumList(1));
        pageNumList=pageNumList(2:end);
    end
    
    
    if(METHOD==1) %FIR-FFT filtrering
        DATA=fft(data(:),NFFT);
        ut=real(ifft(FIR.*DATA));
        MAX=max(ut);
        if(MAX>0.99)
            warning('Output clipped')
        end
        ut(1:Length_FIR)=ut(1:Length_FIR)+ZFFT;
        ZFFT=ut(end-Length_FIR+1:end);
        pageNumList = [pageNumList playrec('playrec', ut(1:NFFT-Length_FIR), ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL)]; %Spela in page 3 samt spela upp första proc. page
    end
    
    if(METHOD==2) %FIR-FFT filtrering
        [ut,Zi]=filter(B,A,data,Zi);
        MAX=max(ut);
        if(MAX>0.99)
            warning('Output clipped')
        end
        pageNumList = [pageNumList playrec('playrec', ut, ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL)]; %Spela in page 3 samt spela upp första proc. page
    end
    
    if(METHOD==3) %FIR-FFT filtrering
        [data,Zi]=filter(B,A,data,Zi);
        DATA=fft(data(:),NFFT);
        ut=real(ifft(FIR.*DATA));
        ut(1:Length_FIR)=ut(1:Length_FIR)+ZFFT;
        ZFFT=ut(end-Length_FIR+1:end);
        MAX=max(ut);
        if(MAX>0.99)
            warning('Output clipped')
        end
        pageNumList = [pageNumList playrec('playrec', ut(1:NFFT-Length_FIR), ASIO_OUTCHANNELS, PageSize, ASIO_INCHANNEL)]; %Spela in page 3 samt spela upp första proc. page
    end    
    playrec('delPage',pageNumList(1));
    pageNumList=pageNumList(2:end);    
    
end
