I am writing a Direction of Arrival (DOA) estimator using the MUSIC algorithm in python and am having trouble getting the correct spectrogram output.

Specifically, my resulting graph comes out aliased as such:

On the left is the SYNTHESIZED signal, using the same source azimuth and elevation as the real dataset. On the right is the real-world signal.


These graphs looks like this when I am looking for 2 signals. If I am looking for only 1, they look like this:


My guess is that the reason this is happening is that I am trying to use a Hermitian, which should be reserved exclusively for complex numbers, when my signals are represented using only real values, without an imaginary one. The the real world dataset is presented as an NxM .wav where N is the number of channels (8) and M is the length of the signals (174800 or so) sampled at 44.1KHz. The data arrives in int16 format, which I then convert into floating point.

The eigenvectors I obtain from the covariance matrix look realistic, with 2 peaks, 1 twice as high as the other. The synthesized signal has only 1 peak. As seen in the graphs, if I limit to using all but the highest peak eigenvector, the spectrogram doesn't really show anything, while taking M-2 does provide peaks, but theyre aliased. I am certain this is aliasing and not a reverberation of some sort, because setting an extremely low wavelength variable for the steering matrix makes the real-world signal spectrogram perfectly symmetrical. If I shuffle microphone positions, I also never get more or less than 2 peaks.

The following are some code snippets to get an idea of how I implemented the algorithm:

The synthesized signal is created using these two functions as the core:

    def emit(shape,C):
        x = np.random.randn(shape)
        x += np.random.randn(shape)
        x *= np.sqrt(0.5)
        x *= sqrt(C)
        return x
    A = array.steering_matrix(sources, wavelength)
    S = source_signal.emit((channel_count,n_snapshots),1.0)
    Y = A @ S
    N = noise_signal.emit((channel_count,n_snapshots),1.0)
    Y += N
    return Y

Once Y is returned, I do not tamper with it in any other way.

The full music function is as follows:

def music(A,x,k):
    R = (x @ x.conj().T) / x.shape[1]
    Ev, E = np.linalg.eigh(R)
    En = E[:,:-k]
    v = En.T.conj() @ A
    return np.reciprocal(np.sum(v * v.conj(), axis=0).real),En

Steering matrix:

def steering_matrix(sensors,sources,wavelength):
    sources = np.deg2rad(sources)

    s = 2 * np.pi / wavelength
    cos_el = np.cos(sources[:, 1])
    cc = cos_el * np.cos(sources[:, 0])
    cs = cos_el * np.sin(sources[:, 0])

    D = s * (np.outer(sensors[:, 0], cc) +
            np.outer(sensors[:, 1], cs) +
            np.outer(sensors[:, 2], np.sin(sources[:, 1])))
    A = np.exp(1j * D)

    return A

I am nearly certain that this is related to complex numbers. If I synthesize a signal using the steering matrix, I get a signal that has mixed values on both the real and the imag portions, but my real world data has no imag portion. So if the implementation I am using for MUSIC requires some of the information to be retained on the imag portion, everything would mess up. I am specifically questioning my use of conj().

  • $\begingroup$ Not sure why you're comparing "complex numbers" and "floats". A float is simply a data type, which consequently, can be used to create complex numbers. Are you sure that your data is not a stream of complex numbers with interleaved real and imaginary parts? $\endgroup$
    – Envidia
    Dec 8, 2020 at 21:38
  • $\begingroup$ link they definitely look like floats to me. The original project from which I fetched the dataset also parses them as such. $\endgroup$
    – Felkin
    Dec 8, 2020 at 21:44
  • $\begingroup$ So what data set did you use where it was working? This data set might be an interleaved set, where two of those numbers make one complex number. $\endgroup$
    – Envidia
    Dec 8, 2020 at 21:49
  • 1
    $\begingroup$ You should ask whoever (or whatever) is providing this input data to provide you its format. Only then will you know how to parse it correctly. Mind you, the steering matrix and the sampled return data per channel should be complex. Is the data you linked the image of the sampled data per channel? $\endgroup$
    – Envidia
    Dec 8, 2020 at 22:14
  • 1
    $\begingroup$ DoA = Direction of Arrival. AoA = Angle of Arrival. $\endgroup$
    – Felkin
    Dec 9, 2020 at 2:05

1 Answer 1


I guess what is missing here is the Hilbert transform. The MUSIC algorithm expects an analytic signal, consisting of in-phase (real) and quadrature (imaginary) components. Hilbert transform recovers the quadrature component form the real signal. More info here: DSPRelated - Blogs - Rick Lyons - A Quadrature Signals Tutorial: Complex, But Not Complicated.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.