Repository: librosa/librosa

Issue:
Where does the formula in `window_bandwidth` come from?
**Is your feature request related to a problem? Please describe.**
Here's our formula for `window_bandwidth`

```python
        win = get_window(window, n)
        WINDOW_BANDWIDTHS[key] = n * np.sum(win ** 2) / np.sum(np.abs(win)) ** 2
```

This formula is (proportional to) the ratio between the square of the L2 norm and the square of the L1 norm.
I see how this relates to the (inverse) "peakiness" of the window vector, but i'd like some mathematical context around it.

**Describe the solution you'd like**
Some explanation in the docstring as to why we use this formula + where it comes from. Maybe a bibliographical /textbook reference.

**Describe alternatives you've considered**
* Knowledge of the First Kind
* Knowledge of the Third Kind

https://www.gutenberg.org/files/3800/3800-h/3800-h.htm

**Additional context**
Found while discussing #745 

Where does the formula in `window_bandwidth` come from?
**Is your feature request related to a problem? Please describe.**
Here's our formula for `window_bandwidth`

```python
        win = get_window(window, n)
        WINDOW_BANDWIDTHS[key] = n * np.sum(win ** 2) / np.sum(np.abs(win)) ** 2
```

This formula is (proportional to) the ratio between the square of the L2 norm and the square of the L1 norm.
I see how this relates to the (inverse) "peakiness" of the window vector, but i'd like some mathematical context around it.

**Describe the solution you'd like**
Some explanation in the docstring as to why we use this formula + where it comes from. Maybe a bibliographical /textbook reference.

**Describe alternatives you've considered**
* Knowledge of the First Kind
* Knowledge of the Third Kind

https://www.gutenberg.org/files/3800/3800-h/3800-h.htm

**Additional context**
Found while discussing #745 



Discussion:
I believe I pulled this formula from the following reference:
```
Poularikas A. D. “Windows ”
The Handbook of Formulas and Tables for Signal Processing.
Ed. Alexander D. Poularikas
Boca Raton: CRC Press LLC, 1999
```
section 7.2 (eq 7.2.1).
O_o upon closer inspection, I think we've fallen to [Jenkins' Inequality](https://twitter.com/PaulMineiro/status/1560991328556658688) and interchanged the sum and abs operators in the denominator:

![image](https://user-images.githubusercontent.com/1190540/186170781-01d6eb6a-3544-4580-ae86-00a74e99c8ef.png)

Since all of our cached windows (and I believe almost everything used commonly) are non-negative, correcting this should be NBD.  However, some of the more exotic ones (eg [HFT90D](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.general_cosine.html#scipy.signal.windows.general_cosine)) would change.

(*EDIT*: https://www.gaussianwaves.com/2020/09/equivalent-noise-bandwidth-enbw-of-window-functions/ gives a more direct derivation of the ENBW calculation.)
I believe I pulled this formula from the following reference:
```
Poularikas A. D. “Windows ”
The Handbook of Formulas and Tables for Signal Processing.
Ed. Alexander D. Poularikas
Boca Raton: CRC Press LLC, 1999
```
section 7.2 (eq 7.2.1).
O_o upon closer inspection, I think we've fallen to [Jenkins' Inequality](https://twitter.com/PaulMineiro/status/1560991328556658688) and interchanged the sum and abs operators in the denominator:

![image](https://user-images.githubusercontent.com/1190540/186170781-01d6eb6a-3544-4580-ae86-00a74e99c8ef.png)

Since all of our cached windows (and I believe almost everything used commonly) are non-negative, correcting this should be NBD.  However, some of the more exotic ones (eg [HFT90D](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.windows.general_cosine.html#scipy.signal.windows.general_cosine)) would change.

(*EDIT*: https://www.gaussianwaves.com/2020/09/equivalent-noise-bandwidth-enbw-of-window-functions/ gives a more direct derivation of the ENBW calculation.)

Resolving patch:
diff --git a/librosa/filters.py b/librosa/filters.py
--- a/librosa/filters.py
+++ b/librosa/filters.py
@@ -1080,7 +1080,16 @@ def cq_to_chroma(
 
 @cache(level=10)
 def window_bandwidth(window, n=1000):
-    """Get the equivalent noise bandwidth of a window function.
+    """Get the equivalent noise bandwidth (ENBW) of a window function.
+
+    The ENBW of a window is defined by [#]_ (equation 11) as the normalized
+    ratio of the sum of squares to the square of sums::
+
+        enbw = n * sum(window**2) / sum(window)**2
+
+    .. [#] Harris, F. J.
+        "On the use of windows for harmonic analysis with the discrete Fourier transform."
+        Proceedings of the IEEE, 66(1), 51-83.  1978.
 
     Parameters
     ----------
@@ -1115,7 +1124,7 @@ def window_bandwidth(window, n=1000):
 
     if key not in WINDOW_BANDWIDTHS:
         win = get_window(window, n)
-        WINDOW_BANDWIDTHS[key] = n * np.sum(win ** 2) / np.sum(np.abs(win)) ** 2
+        WINDOW_BANDWIDTHS[key] = n * np.sum(win ** 2) / (np.sum(win) ** 2 + util.tiny(win))
 
     return WINDOW_BANDWIDTHS[key]
 


Test patch: