-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathTEST_SCRIPTS.m
More file actions
1449 lines (1325 loc) · 64 KB
/
TEST_SCRIPTS.m
File metadata and controls
1449 lines (1325 loc) · 64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%% ACOUSTICAL SPHERICAL ARRAY PROCESSING LIBRARY
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Archontis Politis, 2016
% Department of Signal Processing and Acoustics, Aalto University, Finland
% archontis.politis@aalto.fi
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%
% This is a collection MATLAB routines that perform array processing
% techniques on spatially transformed signals, commonly captured with
% a spherical microphone array. The routines fall into four main
% categories:
%
% a) obtain spherical harmonic (SH) signals with broadband characteristics,
% as much as possible,
%
% b) generate beamforming weights in the spherical harmonic domain (SHD)
% for common signal-independent beampatterns,
%
% c) demonstrate some adaptive beamforming methods in the SHD,
%
% d) demonstrate some direction-of-arrival (DoA) estimation methods in the
% SHD,
%
% e) demonstrate methods for analysis of diffuseness in the sound-field
%
% f) demonstrate flexible diffuse-field coherence modeling of arrays
%
% The latest version of the library can be found at
%
% https://github.com/polarch/Spherical-Array-Processing
%
% Detailed demonstration of the routines is given in TEST_SCRIPTS.m and at
%
% http://research.spa.aalto.fi/projects/spharrayproc-lib/spharrayproc.html
%
% The library relies in the other two libraries of the author related to
% acoustical array processing found at:
%
% https://github.com/polarch/Array-Response-Simulator
% https://github.com/polarch/Spherical-Harmonic-Transform
%
% They need to be added to the MATLAB path for most functions to
% work.
%
% For any questions, comments, corrections, or general feedback, please
% contact archontis.politis@aalto.fi
%% MICROPHONE SIGNALS TO SH SIGNALS
%
% The first operation is to obtain the SH signals from the microphone
% signals. That corresponds to two operations: a matrixing of the signals
% that performs a discrete spherical harmonic transform (SHT) on the
% sound pressure over the spherical array, followed by an equalization step
% of the SH signals that extrapolates them from the finite array radius to
% array-independent sound-field coefficients. This operation is limited by
% physical considerations, and the inversion should be limited to avoid
% excessive noise amplification in the SH signals. A few approaches are
% demonstrated below.
%%% ---Theory based-filters
%
% The simplest approach avoids the effect of spatial aliasing and takes
% into account only the array radius [ref1]. An amplification limit
% has to be specified for the filters realizing this single-channel
% regularized inversion, which determines the amount of regularization
% needed in order not to exceed this threshold.
clear all; close all;
% Simulate a nearly uniform array of 32 microphones on a rigid baffle, with
% the specifications of the Eigenmike array [ref2].
mic_dirs_deg = ...
[0 21;
32 0;
0 -21;
328 0;
0 58;
45 35;
69 0;
45 -35;
0 -58;
315 -35;
291 0;
315 35;
91 69;
90 32;
90 -31;
89 -69;
180 21;
212 0;
180 -21;
148 0;
180 58;
225 35;
249 0;
225 -35;
180 -58;
135 -35;
111 0;
135 35;
269 69;
270 32;
270 -32;
271 -69];
mic_dirs_rad = mic_dirs_deg*pi/180;
nMics = size(mic_dirs_deg,1);
% Eigenmike radius
R = 0.042;
% Plot microphone array
plotMicArray(mic_dirs_deg, R); view(65,20);
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Type and order of expansion for modeling the array response
arrayType = 'rigid';
c = 343;
f_max = 20000;
kR_max = 2*pi*f_max*R/c;
array_order = ceil(2*kR_max);
% frequency vector
fs = 48000;
Lfilt = 1024;
f = (0:Lfilt/2)'*fs/Lfilt;
kR = 2*pi*f*R/c;
nBins = Lfilt/2+1;
% Do some array analysis of noise and spatial aliasing
sht_order = floor(sqrt(nMics)-1); % approximate for uniformly arranged mics
[ampf, ampf_lin] = sphArrayNoise(R, nMics, sht_order, arrayType, f);
% plot noise power responses
figure
semilogx(f, 10*log10(abs(ampf)));
hold on, semilogx(f, 10*log10(abs(ampf_lin)),'--k')
grid, xlabel('Frequency (Hz)'), ylabel('10log_{10}(G_n^2)'), set(gca, 'xlim', [50 20000])
legend(num2str((0:sht_order)'))
% find limiting frequencies for the specified threshold
maxG_db = 10;
f_lim = sphArrayNoiseThreshold(R, nMics, maxG_db, sht_order, arrayType);
% plot frequencies
semilogx([f(2) f_lim(end)]', ones(2,1)*maxG_db, 'color','k')
for n=1:sht_order
semilogx([f_lim(n) f_lim(n)], [0 maxG_db], 'k')
end
ht = title(['Noise amplification curves of equalized SH components, ' char(10) 'and respective frequencies for max. noise maplification maxG = ' num2str(maxG_db) 'dB']);
ht.FontSize = 14; h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% get spatial aliasing estimates, by SHT order, number of microphone or condition number
f_alias = sphArrayAliasLim(R, nMics, sht_order, mic_dirs_rad);
% plot orthogonality matrix of the microphone arrangement
aziElev2aziPolar = @(dirs) [dirs(:,1) pi/2-dirs(:,2)]; % function to convert from azimuth-inclination to azimuth-elevation
Y_mics = sqrt(4*pi) * getSH(sht_order, aziElev2aziPolar(mic_dirs_rad), 'real'); % real SH matrix for microphones
YY_mics = (1/nMics)*(Y_mics'*Y_mics);
figure
imagesc(YY_mics), colorbar
ht = title( 'Orthogonality of array SHT $\mathbf{Y}_{mic}^H \mathbf{Y}_{mic}$','Interpreter','latex');
ht.FontSize = 14; h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Obtain responses for a dense grid of directions
[grid_azi, grid_elev] = meshgrid(-180:5:180, -85:5:85);
grid_dirs_deg = [grid_azi(:) grid_elev(:)];
grid_dirs_rad = grid_dirs_deg*pi/180;
[~, H_array_sim] = simulateSphArray(Lfilt, mic_dirs_rad, grid_dirs_rad, arrayType, R, array_order, fs);
% Define an inline function for super-titles in subplots
sgtitle = @(title_string) annotation('textbox', [0 0.9 1 0.1],'String', title_string,'EdgeColor', 'none','HorizontalAlignment', 'center','FontSize', 16);
% Apply a plain SHT on the microphone responses without equalization.
M_mic2sh_sht = (1/nMics)*Y_mics';
Y_grid = sqrt(4*pi) * getSH(sht_order, aziElev2aziPolar(grid_dirs_rad), 'real'); % SH matrix for grid directions
%w_grid = getVoronoiWeights(grid_dirs_rad); % get approximate integration weights for grid points
%evaluateSHTfilters(repmat(M_mic2sh_sht, [1 1 nBins]), H_array_sim, fs, Y_grid, w_grid);
evaluateSHTfilters(repmat(M_mic2sh_sht, [1 1 nBins]), H_array_sim, fs, Y_grid);
sgtitle('Ideal array - Plain SHT'); h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Apply single channel regularized inversion, as found e.g. in [ref1]
maxG_dB = 15; % maximum allowed amplification
H_filt = arraySHTfiltersTheory_radInverse(R, nMics, sht_order, Lfilt, fs, maxG_dB);
% combine the per-order filters with the SHT matrix for evaluation of full filter matrix
for kk=1:nBins
M_mic2sh_radinv(:,:,kk) = diag(replicatePerOrder(H_filt(kk,:),2))*M_mic2sh_sht;
end
evaluateSHTfilters(M_mic2sh_radinv, H_array_sim, fs, Y_grid);
sgtitle('Ideal array - Regularized inversion of radial response');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Apply single channel inversion with soft-limiting, as proposed in [ref3]
H_filt = arraySHTfiltersTheory_softLim(R, nMics, sht_order, Lfilt, fs, maxG_dB);
% combine the per-order filters with the SHT matrix for evaluation of full filter matrix
for kk=1:nBins
M_mic2sh_softlim(:,:,kk) = diag(replicatePerOrder(H_filt(kk,:),2))*M_mic2sh_sht;
end
evaluateSHTfilters(M_mic2sh_softlim, H_array_sim, fs, Y_grid);
sgtitle('Ideal array - Soft-limited inversion of radial response');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Invert the full theoretical array response matrix, as proposed in [ref4]
M_mic2sh_regLS = arraySHTfiltersTheory_regLS(R, mic_dirs_rad, sht_order, Lfilt, fs, maxG_dB);
evaluateSHTfilters(M_mic2sh_regLS, H_array_sim, fs, Y_grid);
sgtitle('Ideal array - Regularized array response matrix inversion');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Measurement based-filters
%
% When calibration measurements of the array response exist, it is
% advantageous to create the filters based on them, to take into account
% any deviations of the actual array from the theoretical model. In the
% example below measured responses of an Egenmike array are used, and the
% filters are evaluated with respect to that.
load('Eigenmike_IRs.mat', 'fs', 'h_mics', 'measurement_dirs_aziElev', 'measurement_area_weights');
grid_dirs_rad = measurement_dirs_aziElev; clear measurement_dirs_aziElev
w_grid = measurement_area_weights; clear measurement_area_weights
Y_grid = sqrt(4*pi) * getSH(sht_order, aziElev2aziPolar(grid_dirs_rad), 'real'); % SH matrix for grid directions
nGrid = length(w_grid);
H_array_meas = fft(h_mics,Lfilt);
H_array_meas = H_array_meas(1:nBins,:,:);
% normalize measured responses close to unity using the mean of responses
% (equivalent to the diffuse omni power)
for kk=1:nBins
H_kk = squeeze(H_array_meas(kk,:,:));
tempH = (1/nMics)*sum(H_kk,1).';
H_mean(kk) = sqrt( real((tempH.*w_grid)'*tempH) );
end
% normalize responses
norm_diff = max(H_mean);
H_array_meas = H_array_meas/norm_diff;
% first show results when applying the theory-devised filters to the
% real Eigenmike array
evaluateSHTfilters(M_mic2sh_radinv, H_array_meas, fs, Y_grid, w_grid);
sgtitle('Measured array - Theoretical regularized inversion of radial response');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
evaluateSHTfilters(M_mic2sh_softlim, H_array_meas, fs, Y_grid, w_grid);
sgtitle('Measured array - Theoretical soft-limited inversion of radial response');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
evaluateSHTfilters(M_mic2sh_regLS, H_array_meas, fs, Y_grid, w_grid);
sgtitle('Measured array - Theoretical regularized array response matrix inversion');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
% Compute measurement-based filters and show results
% Invert the measured array response matrix, as proposed in [ref1]
E_mic2sh_regLS = arraySHTfiltersMeas_regLS(H_array_meas, sht_order, grid_dirs_rad, w_grid, Lfilt, maxG_dB);
evaluateSHTfilters(E_mic2sh_regLS, H_array_meas, fs, Y_grid, w_grid);
sgtitle('Measured array - Regularized inversion of measured array response matrix');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
% Invert the SH coefficients of the array response matrix, as proposed in [ref4]
E_mic2sh_regLSHD = arraySHTfiltersMeas_regLSHD(H_array_meas, sht_order, grid_dirs_rad, w_grid, Lfilt, maxG_dB);
evaluateSHTfilters(E_mic2sh_regLSHD, H_array_meas, fs, Y_grid, w_grid);
sgtitle('Measured array - Regularized inversion of SH transformed measured array response matrix');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Diffuse-field Equalization above aliasing
%
% Since it is impossible to linearly approximate the ideal spherical
% harmonics through the encoding matrix above aliasing, a practical
% approach is to equalize the output of the filter matrix to be flat under
% a diffuse field excitation. That means that even though the spatial response
% of the channels is not ideal at those frequencies, at least it produces a
% flat power spectrum on average. This is also the approach favoured by Gerzon
% in the pioneering work on the tetrahedral microphone array and, we assume,
% what is implemented in practice in the Sound-field microphones.
% SH values for the simulation grid of directions
[grid_azi, grid_elev] = meshgrid(-180:5:180, -85:5:85);
grid_dirs_deg = [grid_azi(:) grid_elev(:)];
grid_dirs_rad = grid_dirs_deg*pi/180;
Y_grid = sqrt(4*pi) * getSH(sht_order, aziElev2aziPolar(grid_dirs_rad), 'real');
% Apply single channel regularized inversion, as found e.g. in [ref1]
maxG_dB = 15; % maximum allowed amplification
H_filt = arraySHTfiltersTheory_radInverse(R, nMics, sht_order, Lfilt, fs, maxG_dB);
% combine the per-order filters with the SHT matrix for evaluation of full filter matrix
for kk=1:nBins
M_mic2sh_radinv(:,:,kk) = diag(replicatePerOrder(H_filt(kk,:),2))*M_mic2sh_sht;
end
evaluateSHTfilters(M_mic2sh_radinv, H_array_sim, fs, Y_grid);
sgtitle('Theoretical encoder without diffuse-field equalization (radinv)');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
% We can observe that the inversion works well up to aliasing, but since it
% ignores it, the diffuse-field repsonse of the array starts rise
% unnaturally at high frequencies.
% Get theoretical diffuse coherence matrix of the array
M_diffcoh = getDiffCohMtxTheory(mic_dirs_rad, arrayType, R, array_order, f, []);
% Apply diffuse-field equalization to the encoding filter matrix
M_mic2sh_diffeq = arraySHTfilters_diffEQ(M_mic2sh_radinv, M_diffcoh, f_alias, fs);
% Plots
evaluateSHTfilters(M_mic2sh_diffeq, H_array_sim, fs, Y_grid);
sgtitle('Theoretical encoder with diffuse-field equalization (radinv)');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
% We can observe that the diffuse-field response of all components have
% been equalized above whatever freqeuncy limit is detected as aliasing
% limit.
%% SIGNAL-INDEPENDENT BEAMFORMING IN THE SPHERICAL HARMONIC DOMAIN
%
% After the SH signals have been obtained, it is possible to perform
% beamforming on the SHD. In the frequency band that the SH signals are
% close to the ideal ones, beamforming is frequency-independent and it
% corresponds to a weight-and-sum operation of the SH signals. The
% beamforming weights can be derived analytically for various common
% beampatterns and for the available order of the SH signals. Beampatterns
% maintain their directivity for all directions in the SHD, and if they are
% axisymmetric their rotation to an arbitrary direction becomes very
% simple.
%
% The following axisymmetric patterns are included in the library:
%
% * cardioid [single null at opposite of the look-direction]
% * supercardioid (up to 4th-order) [ref5]
% [maximum front-to-back rejection ratio]
% * hypercardioid/superdirective/regular/plane-wave decomposition beamformer
% [maximum directivity factor]
% * max-energy vector (almost super-cardioid) [ref6]
% [maximum intensity vector under isotropic diffuse conditions]
% * Dolph-Chebyshev [ref7]
% [sidelobe level control]
% * arbitrary patterns of differential form [ref8]
% [weighted cosine power series]
% * patterns corresponding to real- and symmetrically-weighted linear array [ref9]
%
% and some non-axisymmetric cases:
%
% * closest beamformer to a given directional function
% [best least-squares approximation]
% * acoustic velocity beamformers for a given spatial filter [ref10]
% [capture the acoustic velocity of a directionally-weighted soundfield]
%
% The following code examples illustrate most of these patterns.
clear all; close all;
% Define an inline function for super-titles in subplots
sgtitle = @(title_string) annotation('textbox', [0 0.9 1 0.1],'String', title_string,'EdgeColor', 'none','HorizontalAlignment', 'center','FontSize', 16);
%%% ---Cardioids
% get beamforming weights for orders 1-4 and plot pattern
figure
for n=1:4
h = subplot(1,4,n);
w_n = beamWeightsCardioid2Spherical(n);
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n)])
end
sgtitle('Cardioid patterns of various orders');
h = gcf; h.Position(3) = 1.5*h.Position(3);
% get the beamforming weights for a rotated 3rd-order cardioid to 120deg azi
% and 60deg elevation
w_n = beamWeightsCardioid2Spherical(3);
azi = 2*pi/3;
incl = pi/2-pi/6; % inclination instead of elevation is used in this function
w_nm = rotateAxisCoeffs(w_n, incl, azi, 'real');
plotSphFunctionCoeffs(w_nm, 'real', 5, 5, 'real'), axis([-1 1 -1 1 -1 1]), view(70,25); title('cardioid rotated at 120deg-30deg')
%%
%%% ---Hypercardioids (regular spherical beamformer, plane-wave decomposition, superdirective, or max-DI)
% get beamforming weights for orders 1-4 and plot pattern
figure
for n=1:4
h = subplot(1,4,n);
w_n = beamWeightsHypercardioid2Spherical(n);
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n)])
end
sgtitle('Hypercardioid patterns of various orders');
h = gcf; h.Position(3) = 1.5*h.Position(3);
%%
%%% ---Supercardioids (max front-to-back power ratio)
% Note that the coefficients for these are hard-coded and defined up to
% order 4. The coefficients have been converted from differential array
% coefficients found in [ref]. An analytical formula for supercardioids of
% any order directly in the SHD is not known to the author.
% get beamforming weights for orders 1-4 and plot pattern
figure
for n=1:4
h = subplot(1,4,n);
w_n = beamWeightsSupercardioid2Spherical(n);
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n)])
end
sgtitle('Supercardioid patterns of various orders');
h = gcf; h.Position(3) = 1.5*h.Position(3);
%%
%%% ---Max-EV (energy vector) patterns
% These patterns originate from literature on Ambisonics [ref]. They do not
% optimize any of the standard array processing metrics. Instead they
% maximize the length of the resulting vector, if a unit vector is weighted
% with the squared pattern and integrated over all directions. From an
% acoustic standpoint, this could be the pattern that maximizes the acoustic
% intensity vector in an isotropic diffuse field. They are relevant in the
% design of panning functions. In practice they are very similar (but
% not equal) to supercardioids, and they can easily be generated analytically
% for any order.
% get beamforming weights for orders 1-4 and plot pattern
figure
for n=1:4
h = subplot(1,4,n);
w_n = beamWeightsMaxEV(n);
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n)])
end
sgtitle('Max-EV patterns of various orders');
h = gcf; h.Position(3) = 1.5*h.Position(3);
%%
%%% ---Dolph-Chebyshev beampatterns
% Here the pattern is determined, apart from
% the order, from a specification of the sidelobe level, or the
% null-to-null mainlobe width. The generating formula is as found in [ref7].
% get beamforming weights for orders 2-4 and plot pattern
figure
bwidths = [3*pi/4; 3*pi/5; pi/2]; % beamwidths for plotting
slobes = [0.4, 0.3, 0.2]; % sidelobe leves for plotting
for n=2:4
h = subplot(2,3,n-1);
w_n = beamWeightsDolphChebyshev2Spherical(n, 'width', bwidths(n-1));
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n) ', beamwidth = ' num2str(rad2deg(bwidths(n-1)))])
end
for n=2:4
h = subplot(2,3,3+(n-1));
w_n = beamWeightsDolphChebyshev2Spherical(n, 'sidelobe', slobes(n-1));
plotAxisymPatternFromCoeffs(w_n, h)
title(['order = ' num2str(n) ', sidelobe = ' num2str(slobes(n-1))])
end
sgtitle('Dolph-Chebyshev patterns of various orders');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Differential arrays
% Differential arrays of N+1 microphones can generate any axisymmetric
% pattern $d(\theta)=\sum_{n=0}^N a_n \cos(\theta)$ of order N, with
% differential weights a_n. The following function transforms the
% differential weights to SH weights suitable for spherical processing,
% with the transformation done as in [ref8]. This is useful for obtaining
% weights for patterns that are defined using the differential form, see
% e.g. [ref5].
% Generate cardioid beamformers directly in the SHD, and compare with
% cardioids defined as differential weights first, and then transformed to
% spherical.
figure
for n=1:3
h = subplot(2,3,n);
w_n = beamWeightsCardioid2Spherical(n);
plotAxisymPatternFromCoeffs(w_n, h)
title(['SHD weights: order = ' num2str(n)])
h = subplot(2,3,3+n);
a_n = beamWeightsCardioid2Differential(n); % generate weights for cardioids using the differential form
b_n = beamWeightsDifferential2Spherical(a_n); % convert from differential to spherical
plotAxisymPatternFromCoeffs(b_n, h)
title(['Diff. to SHD weights: order = ' num2str(n)])
end
sgtitle('Comparison between spherical and differential-to-spherical weights');
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Arbitrary beampatterns
% It is possible to obtain spherical beamforming weights for an arbitrary
% beamforming pattern, as a best least-squares approximation to it for a
% certain order. This can be done by performing a discrete SHT on the
% target pattern. The example below obtains the weights for axisymmetric
% toroidal patterns.
figure
for n=1:4
h = subplot(1,4,n);
% Define a function describing the target pattern: toroidal pattern raised
% to the n-th power
fPattern = @(azi,elev) ones(size(azi)).*abs(cos(elev)).^n;
% Get beamforming weights for order 4 patterns
order = 4;
w_nm = beamWeightsFromFunction(fPattern, order);
% keep only the m=0 weights, due to axisymmetry
w_n = extractAxisCoeffs(w_nm);
plotAxisymPatternFromCoeffs(w_n, h)
title(['Torus d(\theta)=|\sin(\theta)|^' num2str(n)])
end
sgtitle('4th-order toroidal patterns');
h = gcf; h.Position(3) = 1.5*h.Position(3);
% rotate a toroidal pattern to 30deg azi and 60deg elevation
fPattern = @(azi,elev) ones(size(azi)).*abs(cos(elev));
w_n = extractAxisCoeffs(beamWeightsFromFunction(fPattern, order));
azi = pi/6;
elev = pi/3;
w_nm = rotateAxisCoeffs(w_n, pi/2-elev, azi, 'real'); % inclination instead of elevation is used in this function
plotSphFunctionCoeffs(w_nm, 'real', 5, 5, 'real'), axis([-1 1 -1 1 -1 1]), view(70,25); title('toroidal rotated at 30deg-60deg')
%%
%%% ---Weighted velocity beamformers
% A continuous amplitude distribution of plane waves incident from all
% directions results in a certain acoustic pressure and velocity at the
% origin. The pressure corresponds to omnidirectional pickup, while the
% components of the velocity vector can be captured by three dipole
% patterns oriented along the Certesian axis.
% If the sound-field distribution has been weighted by some directional
% pattern, e.g. some beamformer, then the resulting velocity components
% can be captured by specific patterns, products of the beamformer and the
% dipoles, as shown in [ref10]. These velocity beamformers have some
% applications to estimation of acoustic velocity or intensity at
% directionally constrained regions. Note that the velocity weights
% are one order greater than the beamfomrer that generates them.
% Define a 2nd-order cardioid as the directional weighting, oriented at
% 30deg azi and 60deg elevation.
order_sec = 2;
w_n = beamWeightsCardioid2Spherical(order_sec); % sector order cardioid
A_xyz = computeVelCoeffsMtx(order_sec); % compute transformation matrices for velocity patterns
v_nm = beamWeightsVelocityPatterns(w_n, [azi elev], A_xyz, 'real'); % compute velocity weights for rotated cardioid
% plot sector and velocity patterns
figure
h_ax = subplot(141);
plotSphFunctionCoeffs(rotateAxisCoeffs(w_n,pi/2-elev,azi,'real'), 'real', 5, 5, 'real', h_ax)
title('2nd-order sector pattern'), view(65,30), axis([-1 1 -1 1 -1 1])
vel_labels = {'x','y','z'};
for n=1:3
h_ax = subplot(1,4,n+1);
plotSphFunctionCoeffs(v_nm(:,n), 'real', 5, 5, 'real', h_ax)
title(['3rd-order velocity patterns: ' vel_labels{n}]), view(65,30), axis([-1 1 -1 1 -1 1])
end
sgtitle('Sector and resulting velocity patterns example')
h = gcf; h.Position(3) = 2*h.Position(3);
%% SIGNAL-DEPENDENT AND PARAMETRIC BEAMFORMING
%
% Contrary to the fixed beamformers of the previous section, parametric and
% signal-dependent beamformers use information about the signals of
% interest, given either in terms of acoustical parameters, such as
% DoAs of the sources, or extracted through the second-order statistics of
% the array signals given through their spatial covariance matrix (or a
% combination of the two)
% The following examples are included in the library:
%
% * plane-wave decomposition (PWD) beamformer at desired DoA, with
% nulls at other specified DoAs
% * null-steering beamformer at specified DoAs with constraint on
% omnidirectional response otherwise
% * minimum-variance distortioneless response (MVDR) in the SHD
% * linearly-constrained minimum variance (LCMV) beamformer in the SHD
% * informed parametric multi-wave multi-channel Wiener spatial filter
% (iPMMW) in the SHD [ref]
%
% The following code examples illustrates these methods.
clear all; close all;
% Define an inline function for super-titles in subplots
sgtitle = @(title_string) annotation('textbox', [0 0.9 1 0.1],'String', title_string,'EdgeColor', 'none','HorizontalAlignment', 'center','FontSize', 16);
% function to convert from azimuth-inclination to azimuth-elevation
aziElev2aziPolar = @(dirs) [dirs(:,1) pi/2-dirs(:,2)];
%%% ---Plane-wave decomposition beamformer with null-steering
%
% This simple beamformer produces the beamforming weight vectors for a
% number of specified directions, where each vector corresponds to a PWD
% beamformer at the specific direction, with nulls at the rest.
% signal modeling
order = 3;
src_dirs = [0 0; pi/2 pi/4; pi -pi/4];
src_xyz = unitSph2cart(src_dirs);
% compute beamformer weights and plot results
W_nullpwd = sphNullformer_pwd(order, src_dirs);
figure
h_ax = subplot(131); plotSphFunctionCoeffs(W_nullpwd(:,1), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(132); plotSphFunctionCoeffs(W_nullpwd(:,2), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(133); plotSphFunctionCoeffs(W_nullpwd(:,3), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
sgtitle('PWD beamformer on first, second, and third DoA, with nulls at the rest');
h = gcf; h.Position(3) = 2*h.Position(3);
%%
%%% ---Nullformer for diffuse sound extraction
%
% This beamformer puts nulls at the directions of the sources, while aiming
% to preserve as much omnidirectional response as possible. It is similar to
% [ref11], but in the SHD the diffuse coherence vector simplifies to unity
% for the omni channel (first SH signal) and zeros for the rest, due to the
% orthogonality of the SHs.
% signal modeling
order = 3;
src_dirs = [0 0; pi/2 pi/4; pi -pi/4];
src_xyz = unitSph2cart(src_dirs);
% compute beamformer weights and plot results
w_nulldiff = sphNullformer_diff(order, src_dirs);
plotSphFunctionCoeffs(w_nulldiff, 'real', 5, 5, 'real'); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
title('Null-steering on specified DoAs, with omnidirectional constraint');
h = gcf; h.Position(3) = 2*h.Position(3);
%%
%%% ---MVDR
%
% A classic adaptive beamformer. In the example below the source powers and
% directions are known and the SH covariance matrix is constructed and
% passed to the MVDR. Multiple sets of weights are computed if multiple
% directions are passed to the MVDR, one for each distortionless response.
% The method is exactly the same as the MVDR in the space (sensor) domain,
% with the array steering vectors replaced by SHs.
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; 0 pi/4];
src_xyz = unitSph2cart(src_dirs);
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]);
P_diff = 1;
sphCOV = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% compute beamformer weights and plot results
W_mvdr = sphMVDR(sphCOV, src_dirs);
figure
h_ax = subplot(131); plotSphFunctionCoeffs(W_mvdr(:,1), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(132); plotSphFunctionCoeffs(W_mvdr(:,2), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(133); plotSphFunctionCoeffs(W_mvdr(:,3), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
sgtitle('MVDR beamformer on first, second, and third DoA');
h = gcf; h.Position(3) = 2*h.Position(3);
%%
%%% ---LCMV
%
% The LCMV, or linearly-constrained minimum variance beamformer is a
% generalization of the MVDR, where multiple directional constraints can be
% specified. Th example below constructs and serves directly the SH
% covariance matrix as in the MVDR example. The method is exactly the same
% as the LCMV in the space (sensor) domain, with the array steering vectors
% replaced by SHs.
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
src_xyz = unitSph2cart(src_dirs);
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]); % unit powers for the three sources
P_diff = 1; % unit power for the diffuse sound
sphCOV = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% compute beamformer weights and plot results
constraints = [1 0.5 1]';
w_lcmv = sphLCMV(sphCOV, src_dirs, constraints);
plotSphFunctionCoeffs(w_lcmv, 'real', 5, 5, 'real'); view(135,35), axis(1.2*[-1 1 -1 1 -1 1])
line([0 0 0; 1.2*src_xyz(:,1)'],[0 0 0; 1.2*src_xyz(:,2)'],[0 0 0; 1.2*src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
title('LCMV beamformer with [1 0.5 1] constraints on three DoAs');
h = gcf; h.Position(3) = 2*h.Position(3);
%%
%%% ---iPMMW
%
% The iPMMW, as has been proposed in [ref12], is an optimal beamformer for
% extraction of multiple plane-wave sounds, with a trade-off between signal
% distortion and diffuse and spatially-white noise suppression. It combines
% an LCMV beamformer with a Multi-channel Wiener filter. It relies
% on estimation of sensor noise power, diffuse signal power, source signal
% powers, and their DoAs. In the example below we assume that the
% respective quantities have been estimated perfectly and they are served
% to the beamformer.
% SIGNAL MODELING
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
src_xyz = unitSph2cart(src_dirs);
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
% source powers
P_src = [1 2 0.7];
% diffuse power at -10dB
P_diff = 0.1;
% noise power at SH signals at -30dB
P_noise = 0.001;
% SIGNAL STATISTICS
% signal covariance matrix (assuming uncorrelated sources)
Ss = stVec*diag(P_src)*stVec';
% diffuse field coherence matrix in the SHD (identity due to orthogonality)
Gamma_d = eye(nSH)/(4*pi);
% diffuse sound covariance matrix
Sd = P_diff*Gamma_d;
% Noise covariance matrix. Identity for simplicity now. Commonly this is
% an identity matrix for sensor noise modeling only. For a more realistic
% SH noise, the transformation of the microphone signals and the equalization
% has to be taken into account. In general this is still diagonal below aliasing,
% but not white.
Sn = P_noise*eye(nSH);
% SH signal statistics
sphCOV = Ss + Sd + Sn;
% compute beamformer weights and plot results
[W_pmmw, Pd_est, Ps_est] = sphiPMMW(sphCOV, Sn, src_dirs);
figure
h_ax = subplot(131); plotSphFunctionCoeffs(W_mvdr(:,1), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(132); plotSphFunctionCoeffs(W_mvdr(:,2), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
h_ax = subplot(133); plotSphFunctionCoeffs(W_mvdr(:,3), 'real', 5, 5, 'real', h_ax); view(3), axis([-1 1 -1 1 -1 1])
line([0 0 0; src_xyz(:,1)'],[0 0 0; src_xyz(:,2)'],[0 0 0; src_xyz(:,3)'],'color','k','linewidth',3,'linestyle','--')
sgtitle('iPMMW beamformer for first, second, and third DoA');
h = gcf; h.Position(3) = 2*h.Position(3);
%% DIRECTION-OF-ARRIVAL (DoA) ESTIMATION IN THE SHD
%
% Direction of arrival (DoA) estimation can be done by a steered-response
% power approach, steering a beamformer on a grid and checking for peaks on
% the power output, or by a subspace approach such as MUSIC. Another
% alternative is to utilize the acoustic intensity vector, obtained from
% the first-order signals, which its temporal and spatial statistics reveal
% information about presence and distribution of sound sources.
%
% A few examples of DoA estimation in the SHD are included in the library:
%
% * Steered-response power DoA estimation, based on plane-wave
% decomposition (regular) beamforming
% * Steered-response power DoA estimation, based on MVDR beamforming
% * Acoustic intensity vector DoA estimation
% * Eigenbeam-MUSIC DoA estimation
% * Eigenbeam-Esprit DoA estimation
%
% The following code examples illustrates use of these methods.
aziElev2aziPolar = @(dirs) [dirs(:,1) pi/2-dirs(:,2)]; % function to convert from azimuth-inclination to azimuth-elevation
grid_dirs = grid2dirs(5,5,0,0); % Grid of directions to evaluate DoA estimation
%%% ---Plane-wave decomposition power map
%
% This is the simplest approach, in which a (regular) plane-wave decomposition
% beamformer is steered to multiple directions on a grid, forming a power
% map. Peak finding then on the map determines potential source DoAs. The
% spatial resolution of this approach is limited by the available order of
% the SH signals, and for low orders is also low.
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
nSrc = size(src_dirs,1);
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]); % unit powers for the three sources
P_diff = 1; % unit power for the diffuse sound
sphCOV = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% DoA estimation
[P_pwd, est_dirs_pwd] = sphPWDmap(sphCOV, grid_dirs, nSrc);
est_dirs_pwd = est_dirs_pwd*180/pi;
% plots results
plotDirectionalMapFromGrid(P_pwd, 5, 5, [], 0, 0);
src_dirs_deg = src_dirs*180/pi;
line_args = {'linestyle','none','marker','o','color','r', 'linewidth',1.5,'markersize',12};
line(src_dirs_deg(:,1), src_dirs_deg(:,2), line_args{:});
line_args = {'linestyle','none','marker','x','color','r', 'linewidth',1.5,'markersize',12};
line(est_dirs_pwd(:,1), est_dirs_pwd(:,2), line_args{:});
xlabel('Azimuth (deg)'), ylabel('Elevation (deg)'), title('PWD DoA, o: true directions, x: estimated')
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---MVDR power map
%
% This is similar to the PWD estimation, but the power map is formed with a
% MVDR beamformer. Since statistics of the signals are taken into account in
% this case, the spatial resolution can be significantly higher. However,
% due to an incoherence assumption on the source signals, MVDR estimates
% can be biased by presence of coherent source (e.g. reflections).
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
nSrc = size(src_dirs,1);
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]); % unit powers for the three sources
P_diff = 1; % unit power for the diffuse sound
sphCOV = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% DoA estimation
[P_mvdr, est_dirs_mvdr] = sphMVDRmap(sphCOV, grid_dirs, nSrc);
est_dirs_mvdr = est_dirs_mvdr*180/pi;
% plots results
plotDirectionalMapFromGrid(P_mvdr, 5, 5, [], 0, 0);
src_dirs_deg = src_dirs*180/pi;
line_args = {'linestyle','none','marker','o','color','r', 'linewidth',1.5,'markersize',12};
line(src_dirs_deg(:,1), src_dirs_deg(:,2), line_args{:});
line_args = {'linestyle','none','marker','x','color','r', 'linewidth',1.5,'markersize',12};
line(est_dirs_mvdr(:,1), est_dirs_mvdr(:,2), line_args{:});
xlabel('Azimuth (deg)'), ylabel('Elevation (deg)'), title('MVDR DoA, o: true directions, x: estimated')
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---MUSIC spectrum
%
% The MUSIC method decomposes the spatial correlation matrix of the Sh
% signals into a signal dominated-subspace, and a noise-dmoniated subspace.
% Assuming orthogonality between the two, the MUSIC spectrum gives in a
% very fine resolution view of source DoAs in the sound-field. However that
% assumption can be violated again in the presence of correlated sources
% (e.g. early relfections), in which case additional processing should be
% employed to decorrelate these components.
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
nSrc = 3;
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]); % unit powers for the three sources
P_diff = 1; % unit power for the diffuse sound
sphCOV = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% DoA estimation
[P_music, est_dirs_music] = sphMUSIC(sphCOV, grid_dirs, nSrc);
est_dirs_music = est_dirs_music*180/pi;
% plots results
plotDirectionalMapFromGrid(P_music, 5, 5, [], 0, 0);
src_dirs_deg = src_dirs*180/pi;
line_args = {'linestyle','none','marker','o','color','r', 'linewidth',1.5,'markersize',12};
line(src_dirs_deg(:,1), src_dirs_deg(:,2), line_args{:});
line_args = {'linestyle','none','marker','x','color','r', 'linewidth',1.5,'markersize',12};
line(est_dirs_music(:,1), est_dirs_music(:,2), line_args{:});
xlabel('Azimuth (deg)'), ylabel('Elevation (deg)'), title('MUSIC DoA, o: true directions, x: estimated')
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---ESPRIT
%
% The Esprit method is another subspace method like MUSIC, however based on
% rotation-invariance properties of subsets of the array channels, it can
% estimate the DoAs in a gridless search-free way. Otherwise, it has the
% same properties and limitations as MUSIC, high-resolution, and
% sensitivity to correlation between directional signals. Since ESPRIT for
% SH signals is formulated with complex SHs, we transform the spatial
% correlation matrix from the one based on real SHs to the one based on
% complex ones, before passing to the method.
% signal modeling
order = 3;
nSH = (order+1)^2;
src_dirs = [0 0; pi/2 0; pi pi/4];
nSrc = 3;
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
stVec = Y_src';
P_src = diag([1 1 1]); % unit powers for the three sources
P_diff = 1; % unit power for the diffuse sound
sphCOV_real = stVec*P_src*stVec' + P_diff*eye(nSH)/(4*pi);
% Convert SCM to the complex SH basis
T_r2c = conj(real2complexSHMtx(order));
sphCOV_complex = T_r2c*sphCOV_real*T_r2c';
% Build signal subspace
[U,~] = eig(sphCOV_complex);
U = U(:,end:-1:1);
Us = U(:,1:nSrc);
% DoA estimation
est_dirs_esprit = sphESPRIT(Us);
est_dirs_esprit = est_dirs_esprit*180/pi;
% plots results
plotDirectionalMapFromGrid(zeros(size(grid_dirs,1),1), 5, 5, [], 0, 0);
src_dirs_deg = src_dirs*180/pi;
line_args = {'linestyle','none','marker','o','color','r', 'linewidth',1.5,'markersize',12};
line(src_dirs_deg(:,1), src_dirs_deg(:,2), line_args{:});
line_args = {'linestyle','none','marker','x','color','b', 'linewidth',1.5,'markersize',12};
line(est_dirs_esprit(:,1), est_dirs_esprit(:,2), line_args{:});
xlabel('Azimuth (deg)'), ylabel('Elevation (deg)'), title('ESPRIT DoA, o: true directions, x: estimated')
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Sparse recovery based DoA estimation
%
% This example demonstrates DoA estimation based on sparse recovery
% (compressed sensing principles), where the microphone signals are seen as
% a combination of a dense set of plane-wave signals with unknown
% amplitudes, form which only a few are active (sparse). If the recording is
% indeed sparse in that sense (a few dry directional sources) the sparse
% solution returns both the sparse signals themselves, and the entries in
% the solution with significant power returns directly their DoAs.
% Build dense (overcomplete) matrix of SH vectors (dictionary) for the sparse solution
order = 3; % SH order
Y_grid = getSH(order, aziElev2aziPolar(grid_dirs), 'real')*sqrt(4*pi);
A_grid = Y_grid'; % steering vector matrix
% Source signal modeling
% three unit power white noise sources
lSig = 10000; % samples
nSrc = 5;
src_dirs = (2*rand(nSrc,2)-1)*pi * diag([1, 1/2]);
Psrc = [1 1 1]; % pw signal powers
srcsig = randn(nSrc, lSig);
nSH = (order+1)^2;
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
A_dir = Y_src';
sh_dirsig = A_dir*srcsig; % SH signals for sources
% Diffuse signal modeling
[~, diff_dirs] = getTdesign(21); % get dense uniform distribution of directions (240-point t-design)
nDiff = size(diff_dirs,1);
Pdiff = 0.1; % diffuse sound power at 0dB
diffsig = sqrt(Pdiff/nDiff)*randn(nDiff, lSig);
Y_diff = getSH(order, aziElev2aziPolar(diff_dirs), 'real');
A_diff = Y_diff';
sh_diffsig = A_diff*diffsig; % SH signals for diffuse sound
% Total SH signals
shsig = sh_dirsig+sh_diffsig;
% Sparse recovery parameters
% Diffuse-to-total ratio (DTR) used as regularization (Epain & Jin)
diffuseness = Pdiff/(sum(Psrc) + Pdiff); % use any diffuseness estimator here in practice
p = 0.9; % sparsifying exponent
stopValue = 10^-10; % error value to stop the iterations
maxIter = 50; % maximum number of iterations before stopping, if the stopValue is not reached
% compute powers at grid points returned by the sparse solution
[P_sr, est_dirs_sr] = sphSRmap(shsig, p, A_grid, diffuseness, stopValue, maxIter, grid_dirs, nSrc);
est_dirs_sr = est_dirs_sr*180/pi;
% plots results
plotDirectionalMapFromGrid(P_sr, 5, 5, [], 0, 0);
src_dirs_deg = src_dirs*180/pi;
line_args = {'linestyle','none','marker','o','color','r', 'linewidth',1.5,'markersize',12};
line(src_dirs_deg(:,1), src_dirs_deg(:,2), line_args{:});
line_args = {'linestyle','none','marker','x','color','r', 'linewidth',1.5,'markersize',12};
line(est_dirs_sr(:,1), est_dirs_sr(:,2), line_args{:});
xlabel('Azimuth (deg)'), ylabel('Elevation (deg)'), title('Sparse Recovery DoA, o: true directions, x: estimated')
h = gcf; h.Position(3) = 1.5*h.Position(3); h.Position(4) = 1.5*h.Position(4);
%%
%%% ---Intensity vector DoA estimation
%
% The acoustic intensity vector points to the opposite of the source DoA,
% for a single source, and its time average does the same in presence of a
% single source and uncorrelated diffuse sound. In presence of multiple
% active sources, the intensity DoA estimates fluctuate between and around
% the true DoAs. In this case intensity histograms, and subsequent fitting
% of distributions can be used to give multiple DoA estimates.
% Source signal modeling
% three unit power white noise sources
lSig = 100000; % samples
src_dirs = [0 pi/6; pi -pi/6];
nSrc = size(src_dirs,1);
srcsig = randn(nSrc, lSig);
order = 1;
nSH = (order+1)^2;
Y_src = getSH(order, aziElev2aziPolar(src_dirs), 'real');
A_dir = Y_src';
sh_dirsig = A_dir*srcsig; % SH signals for sources
% Diffuse signal modeling
[~, diff_dirs] = getTdesign(21); % get dense uniform distribution of directions
nDiff = size(diff_dirs,1);
Pdiff = 1; % diffuse sound power at 0dB
diffsig = sqrt(Pdiff/nDiff)*randn(nDiff, lSig);
Y_diff = getSH(order, aziElev2aziPolar(diff_dirs), 'real');
A_diff = Y_diff';
sh_diffsig = A_diff*diffsig; % SH signals for diffuse sound
% SH signals
shsig = sh_dirsig+sh_diffsig;
% acoustic intensity estimation
% convert to pressure velocity
M_sh2pv = beamWeightsPressureVelocity('real');
pvsig = M_sh2pv*shsig(1:4,:);
% partition the signal into 100 sample buffers with 50% overlap
lBuf = 100;