image thumbnail
from Forest Reverb Generator by Travis Wiens
Generates acoustic impulse response of a forest.

forest_reverb.m
%This script calculates the impulse response for a forest of 
%hard cylinders. There are a number of parameters at the top of the file
%that you can adjust.

%This is currently copyright Travis Wiens 2008 but I intend to GPL it. 
%Please contact me if you want me to hurry this process up or you want
%a commerical license. email: travis.mlfx@nutaksas.com

save_data=false;%should we save data?
save_wav=false;%should we write a wav file?
plot_data=true;%should we plot data?
zero_delay=true;%if true, chops pre-echo so that the direct path impulse arrives 
%   at sample 1, otherwise it will arrive at sample k_prewindow

N_trees=10;%number of trees in forest
N_sc_max=5;%maximum number of scatterings in each path (calculation time increases a lot)
X_limits=100*[-1 -1;1 1];%(m) limits of forest [x y]
a_tree_limits=[0.1 0.2];%(m) limtis to tree radius

x_sp=1/2;%exponential on sound propagation equation 
% (1 for spherical spreading, 1/2 for cylindrical, 0 for plane wave)
d0=1;%(m) distance for gain G0;
G0=1;%gain at d0


F_s=44100;%(Hz) Sampling frequency
N_fft=512;%number of points in fft and individual impulse responses
N_m=0;%number of terms in scattering equation series (or use 0 to set automatically)
f_f=17e3;%(Hz) filter cutoff frequency
minphase=false;%change phase to minimum phase filter form?
c=340;%(m/s) speed of sound
N_bits=24;%number of bits to save wav (some older software may require 16)

k_prewindow=100;%number of samples to take from end of impulse (pre echo)
N_window=300;%total number of samples to use from impulse

%end of parameters

seed = rand('seed');%get seed from PRNG (in case we want to re-run something)
rand('seed',seed);%set PRNG to an old method (since we don't need security)

X_tree=[X_limits(1,1)+diff(X_limits(:,1))*rand(1,N_trees); X_limits(1,2)+diff(X_limits(:,2))*rand(1,N_trees)];
%(m) tree position vectors ([x;y])
X_t=[X_limits(1,1)+diff(X_limits(:,1))*rand(1,1); X_limits(1,2)+diff(X_limits(:,2))*rand(1,1)];
%(m) transmitter position [x;y]
X_r=[X_limits(1,1)+diff(X_limits(:,1))*rand(1,1); X_limits(1,2)+diff(X_limits(:,2))*rand(1,1)];
%(m) receiver position [x;y]

a_tree=a_tree_limits(1)*ones(1,N_trees)+diff(a_tree_limits)*rand(1,N_trees);%(m) tree radii

if plot_data
    figure(1);%plot forest layout
    h1=plot(X_t(1,:),X_t(2,:),'kx');
    hold on
    h2=plot(X_r(1,:),X_r(2,:),'k+');
    h3=plot(X_tree(1,:),X_tree(2,:),'go');
    legend([h1 h2 h3],'transmitter','reciever','tree','Location','Best')
    axis([X_limits(1,1) X_limits(2,1) X_limits(1,2) X_limits(2,2)])
    hold off
    xlabel('X_1 (m)')
    ylabel('X_2 (m)')
end



tic;

X_all=[X_t X_tree X_r];%aggregate all the location vectors

Y_sc=cell(N_trees+1,N_trees,N_trees+1);%cells to store scattering transfer functions

count=0;%counter
N_sc_calc=N_trees*((N_trees+1)+ (N_trees+3)*(N_trees)/2);%total number of scatterings to calculate
dt_i=.45;%(s) estimated calculation time per scattering (this is for a Core2Duo 6400 @ 2.13 GHz with 2 Gb RAM)
fprintf('Total impulses = %d, estimated t1 = %0.1f s = %0.1f m = %0.1f h\n',N_sc_calc,N_sc_calc*dt_i,N_sc_calc*dt_i/60,N_sc_calc*dt_i/3600)

N_sc_max_paths=sum(N_trees.^(1:N_sc_max).*(1:N_sc_max));
%maximum possible number of scatterings in the paths. This includes self
%reflection so it will be overestimated. If anyone knows an analytical
%expression for the proper number, I'd appreciate it.

dt_s=1e-4;%(s) estimated time to add an impulse
fprintf('Max scatterings = %d, t2 = %0.1f s = %0.1f m = %0.1f h (overestimate)\n',N_sc_max_paths,N_sc_max_paths*dt_s,N_sc_max_paths*dt_s/60,N_sc_max_paths*dt_s/3600)

for i=1:(N_trees+1);%select from transmitter plus trees
    X1=X_all(:,i);%incident ray origin
    t=toc;
    fprintf('Origin %d/%d. %d/%d impulses done. %0.1f s elapsed %0.1f s remaining\n',i,(N_trees+1),count,N_sc_calc, t, t*(N_sc_calc/count-1))
    for j=(1:(N_trees))+1;%select from trees only
        X2=X_all(:,j);%scattering point
        if i==1
            k_idx=2:(N_trees+2);
            %select from trees that haven't been starting points plus
            %receiver. Note that the path ABC has the same transfer
            %function as path CBA, so we can save a bunch of calculations.
        else
            k_idx=i:(N_trees+2);
        end
        for k=k_idx;
          X3=X_all(:,k); %scattered ray endpoint
          theta1=atan2(X2(2,:)-X1(2,:),X2(1,:)-X1(1,:));%angle of incident ray
          theta2=atan2(X3(2,:)-X2(2,:),X3(1,:)-X2(1,:));%angle of scattered ray
          phi=theta2-theta1;%scattering angle (0 for no scattering, pi for full reflection)
          [y_tmp Y_sc{i,j,k}]=scatter_impulse(a_tree(j-1),phi,F_s,N_fft,N_m,f_f, minphase);
          %calculate complex transfer function of scattering Y_sc
          count=count+1;%increment counter
        end
    end
end
fprintf('Total impulses generated = %d\n',count)
t1=toc;
fprintf('Impulse generation time=%f s (%f s/scatter)\n',t, t/count)

N_path_max=sum(N_trees.^(1:N_sc_max));%maximum possible number of paths (includes self reflection)




d1=sqrt(sum((X_t-X_r).^2));%(m) distance for direct path
k_d1=round(d1/c*F_s)+k_prewindow;%(samples) delay for direct path. 
%Note zero is actually at sample number k_prewindow.  This is to prevent any
%of the prewindowed impulses from falling outside the array if there any
%trees or near the line better transmitter and receiver.

G1=G0*(d0./d1).^x_sp;%gain for direct path

N_imp=N_fft+k_prewindow;%initial size of impulse response
y_imp=zeros(1,N_imp);
y_imp(k_prewindow)=G1;%perfect impulse response of direct path
count=1;%reset counter
count_ref=0;%reset counter
for N_r=1:N_sc_max
    fprintf('%d scatterer(s): ',N_r)
    idx_tree=ones(1,N_r);%this matrix includes the index of the trees in this path
    while idx_tree(end)<=N_trees %stop when all paths have been cycled through
        if all(diff(idx_tree)~=0) 
            %only calculate if there are no consecutive identical trees, 
            %ie ABCA and ABAB are ok but AABA and ABBB aren't
            X_path=[X_t X_tree(:,idx_tree) X_r];%locations of points on sound path
            Y_path=ones(1,N_fft);%path transfer function
            d_path=0;%(m) total distance along path
            for j=1:N_r
                d=sqrt(sum((X_path(:,j)-X_path(:,j+1)).^2));%(m) length of incident ray
                d_path=d_path+d;%add to cumulative path distance
                if j==1%sound source
                    idx1=1;%index of incident ray origin
                else
                    idx1=idx_tree(j-1)+1;
                end
                idx2=idx_tree(j)+1;%index of scattering point
                if j==N_r%receiver
                    idx3=N_trees+2;%index of scattered ray endpoint
                else
                    idx3=idx_tree(j+1)+1;
                end
                if (idx3<idx1);%switch direction because path ABC=CBA
                    idx_tmp=idx1;
                    idx1=idx3;
                    idx3=idx_tmp;
                end
                Y_path=Y_path.*Y_sc{idx1,idx2,idx3};%apply scattered TF
                count_ref=count_ref+1;%increment counter
            end
            d=sqrt(sum((X_path(:,j+1)-X_path(:,j+2)).^2));%(m) lenght of last scattered ray
            d_path=d_path+d;%total path length
            k_d=round(d_path/c*F_s)-k_d1+k_prewindow;%(samples) delay for each path
            y_path=real(ifft(Y_path));%get path's impulse response (use real part due to rounding errors)
            G_path=G0*(d0./d_path).^x_sp;%gain for path due to spreading
            
            y_path=circshift(y_path',k_prewindow)';%shift impulse to use preecho
            if numel(y_imp)<(k_d+N_window)%check if path impulse will fit into y_imp
                y_imp=[y_imp zeros(1,k_d+N_window-numel(y_imp))];%expand size
            end
            y_imp(k_d+(1:N_window))=y_imp(k_d+(1:N_window))+G_path*y_path(1:N_window);%add path impulse
            
            count=count+1;
        
        end
        %increment idx_tree (like it's an N_tree-base number) for next path
        idx_tree(1)=idx_tree(1)+1;
        for j=1:(N_r-1)
            if idx_tree(j)>N_trees
                idx_tree(j)=1;
                idx_tree(j+1)=idx_tree(j+1)+1;
            end
        end
        
    end
    fprintf('%d cumulative scatterings, %d paths\n',count_ref,count-1)
end

t2=toc;
fprintf('Inpulse addition time=%f s (%e s/scattering)\n',t2-t1,(t2-t1)/count_ref)
fprintf('Total time=%f s\n',t2)

N_sc=count_ref;%total number of scatterings
N_paths=count-1;%total number of paths

if zero_delay
    y_imp(1:(k_prewindow-1))=[];%remove all pre-echo (possible if there is 
    %a tree near the line between transmitter and receiver) and shift
    %response
end

N_imp=numel(y_imp);%size of impulse response

if plot_data
    figure(2)
    plot((1:N_imp)/F_s,y_imp)
    xlabel('t (s)')
    ylabel('y_{imp}')

    figure(3)
    semilogy((1:N_imp)/F_s,abs(y_imp))
    axis([0 N_imp/F_s  1e-10 1])
    xlabel('t (s)')
    ylabel('y_{imp}')
end

dstring=datestr(now,30);%date/time string
if save_wav
    fname=['forest_impulse_' dstring '.wav'];
    wavwrite(y_imp'./max(abs(y_imp))*(1-2^(-N_bits+1)),F_s,N_bits,fname);
 end
if save_data
    fname=['forest_impulse_' dstring '.mat'];
    save(fname,'y_imp','k_prewindow','N_window','X_r','X_tree','X_t','N_m','N_fft','N_trees','N_sc_max','a_tree','N_sc','N_paths','Y_sc','seed','N_sc','N_paths','t1','t2','f_f','c','minphase','F_s','x_sp','G0','d0','d1','G1')
end

Contact us at files@mathworks.com