Modifying Sound Finder to find high frequencies?

Hi,
I’d like to find sounds in my file that have strong high frequency content. In other words, I’d like to be able to say, “mark areas where the peaks are less than N samples apart.”

Looking at Peak Finder ( http://audacityteam.org/nyquist/peakfinder.ny ), setting it so that it looked for peaks less than a “Maximum Distance” apart – instead of “Minimum Distance” – would do the trick, only the way the plugin is written,I suspect that would mark only the first such sound.

Ditto for Sound Finder (http://forum.audacityteam.org/viewtopic.php?f=42&t=40450&start=10)…

Any thoughts on how to find sounds above a certain frequency?

Given the number of elves galloping to your rescue, I’d say no. Do you want to program it and submit the work for inclusion in Audacity?

Koz

I’d love to, if I knew how to program. Sure, I can find a basic tutorial on doing things in Nyquist, but I’ve no clue what would need to happen structurally / overall.

For example:
Peak Finder almost does what I want, but it would need to a) look for peaks a minimum distance apart instead of a maximum one, b) continue looking for a 2nd, 3rd, …Nth set of peaks after finding the first two, and c) identify that two or more peaks are part of a cluster while some later two or more peaks are part of a different cluster.

Now it’s easy enough to find out how to do things on the order of assigning things to variables and making for loops in Nyquist. But I’ve no idea how to go from that lowest-level to the conceptual level of a) b) and c) above.

if I knew how to program.

You could learn. Nyquist is a good language to start out and there are three sections of the forum just about programming.

Koz

What about Audacity’s spectrogram view …

“mark areas where the peaks are less than N samples apart.”

That’s the identify part, but not the mark mart of the job. Koz



That’s not quite the same task.

Addressing the first proposition - finding sounds that have a strong high frequency content would be quite a simple modification to “Sound Finder”.
All you would need to do is to apply a high pass filter to the audio that is being analysed.

Open “soundfinder.ny” in a plain text editor (if you use Windows I’d recommend Notepad++ )
At lines 16 to 18 you’ll see the following:

;Create a function to make the sum the two channels if they are stereo
(defun mono-s (s-in) (if (arrayp s-in) (snd-add (aref s-in 0) (aref s-in 1))
s-in))

This function provides the sound to be analysed and ensures that the audio being analysed is mono.
The function is not actually correct as it causes stereo tracks to be summed which makes the threshold level wrong for stereo tracks.
Better would be the maximum of left and right channels:

;;; Make signal mono
(defun mono-s (s-in)
  (if (arrayp s-in) 
    (s-max 
      (snd-abs (aref s-in 0))
      (snd-abs  (aref s-in 1)))
    s-in))

To detect high frequencies only, all we need to do is to filter this signal appropriately, for example:

;;; Make signal mono
(defun mono-s (s-in)
  (let ((s-in (highpass8 s-in freq)))
    (if (arrayp s-in) 
      (s-max 
        (snd-abs (aref s-in 0))
        (snd-abs  (aref s-in 1)))
      s-in)))

Which will pre-filter the input signal with a steep (48 dB per octave) high pass filter at a frequency set by “freq”.
The variable “freq” needs to be global as we have not included it as an argument of the function, but that works fine if we want to make it adjustable.

We can add a frequency control with our other controls:
(lines 8 to 12)

;control sil-lev "Treat audio below this level as silence [ -dB]" real "" 26 0 100
;control sil-dur "Minimum duration of silence between sounds [seconds]" real "" 1.0 0.1 5.0
;control labelbeforedur "Label starting point [seconds before sound starts]" real "" 0.1 0.0 1.0
;control labelafterdur "Label ending point [seconds after sound ends]" real "" 0.1 0.0 1.0
;control finallabel "Add a label at the end of the track? [No=0, Yes=1]" int "" 0 0 1

Add the new control:

;control sil-lev "Treat audio below this level as silence [ -dB]" real "" 26 0 100
;control sil-dur "Minimum duration of silence between sounds [seconds]" real "" 1.0 0.1 5.0
;control labelbeforedur "Label starting point [seconds before sound starts]" real "" 0.1 0.0 1.0
;control labelafterdur "Label ending point [seconds after sound ends]" real "" 0.1 0.0 1.0
;control finallabel "Add a label at the end of the track? [No=0, Yes=1]" int "" 0 0 1
;control freq "Detects sounds above (Hz)" int "" 0 0 10000

A good place to start for learning how to write/modify Nyquist plug-ins is here: http://wiki.audacityteam.org/wiki/Nyquist_Audio_Programming

Apparently the email notification function didn’t notify me of all these wonderful replies… in the meantime I wrote the following incomplete plugin. The reason I visited this thread is because I can’t get the do loop to work… it errors out with “bad binding”.

Now to read through that which people wrote…

;nyquist plug-in
;version 2
;type analyze
;name "FFtesting"
;action "hi"

; no garbage collector messages please
(setf *gc-flag* nil)

(print "RUNNING FFTEST")

(setf s1 (if (arrayp s) (snd-add (aref s 0) (aref s1)) s))
(defun signal () s1)
(setq l NIL)
(setq result-fft NIL)
(setq fft-length 96)
(setq result-fft (snd-fft (snd-copy (signal)) fft-length 0 NIL))

; return the fft
  (format t "result-fft: ~A" result-fft)

;rconvert result-fft to absolute values
;(setq abs-fft (abs result-fft))
; so it looks like (abs) can't handle arrays
    
(setf abs-fft (make-array fft-length))

(setf argument 1)
(format t "made abs-fft" argument)


(do (n 0 (setq n (1+ n))) 
    (= n fft-length "done converting") 
    (setf (aref abs-fft n) (abs (aref result-fft n))))

(format t "abs-fft: ~A" abs-fft)


;gotta convert abs-fft into an array?
(setq array-fft NIL)
(setf array-fft (snd-samples abs-fft 1000))

(setf label-list nil)
; zeros out label-list

;drop a label function
(defun add-label (time text)
  (setf label-list (cons (list time text) label-list)))

;nth coefficient means fn = (n-1)*fundamental frequency
;n=fn/fundamental+1
;96 sample fft
;96000/96=1000 hz
;peak should be at 5000/1000 + 1 = 6
;add one and double due to notation, so n=14
;call the theshhold power value 15


;if n=14 is greater than 15, add a label
(if ((> (aref array-fft 14) 15) (add-label 0.0 test) nil)

  
;tell us about labels
; If no peaks were found, return a message
(if (null label-list)
    "No Peaks found."
  label-list)

Wow, this is a conceptually much simpler way than my idea of taking the FFT of the file bit by bit and looking for peaks in the higher-order terms. Thanks :slight_smile:

Probably quicker too. Bit-wise processing in Nyquist is pretty slow.

So it works, but:

  • Some of the high-frequency are quite short. The range labels end up selecting much more than I need, and make it hard to find the right ones inside the larger signal. It would be good to have it drop single labels much closer together, so tabbing through them shows me all the possible HF signals without having to change zoom level.

Before I do something horribly complicated out of lack of understanding – suggestions on modifying Sound Finder to drop single labels close together? I realize the label count might get quite high, that’s fine.

  • Some of the signals are quite low-amplitude, while others in a file are fairly high amplitude. Sound Finder’s “everything below ndB is silence” approach misses those. Is there any way to apply an AGC / normalizer / compressor to the sound before filtering?

The Nyquist manual mentions autonorm (probably not supported by Audacity) as well as computing a scale factor with peak.
More useful might be multiplying sound by the inverse of (follow).
Most useful looks like (agc), but again, not sure if Audacity does that…

It’s difficult to advise on the best approach without understanding the nature of the job.
Could you describe in more detail and more specifically what you are wanting to do?

I’m analyzing very long recordings for pulsed waveform signals produced by a (very) intermittent process as part of my research.
There might be five or six signals in eight hours’ worth of recordings, or there might be hundreds.
The signals fall into (rough) categories of very high, high, medium, low, and very low (<-70dB) amplitude. The ones of very high to medium are the most immediately useful. Detecting all would be nice, since I’m not yet sure just what the low and very-low amplitude signals mean.
The signals also fall into different categories of frequency content. The most interesting ones have very high frequency content, some have lower (due to natural low-pass filtering effects). But all can be fairly easily distinguished from background noise (primarily AC hum) by the high-frequency energy characteristic of a pulse.

Right now, finding and categorizing these signals by hand takes about two and a half to three hours per eight recorded hours, which is acutely painful. That’s what I’m trying to solve with this programming adventure…

The first thing to do is to make a copy of your original recording (don’t work on the original unless it does not matter if it gets destroyed).
Working with very long audio recordings has many difficulties, not least the amount of waiting time for processing and general computer stability. I’d recommend working on sections up to say an hour long, and when you have the process down to a routine you may be able to semi-automate the process using Chains (Audacity’s batch/macro type feature).

The first thing that I’d suggest is to use the notch filter (effect menu) to remove as much of the mains hum as you can. If you’re in the USA you’ll probably have harmonics at 60, 120, 180, 240 … Hz. In the UK it’s 50, 100. 150. 200. 250…
The main peaks will probably be easily visible in “Plot Spectrum” (Analyze menu).

When that is complete, Normalize the track to 0 dB with DC offset removal enabled (Effect menu) and Export this as a backup “working copy” (you may need this if you want to look at lower frequencies later).

Now, if you’re only interested in, say, frequencies in the range of 5 to 6 kHz, apply the Equalization effect with settings such as this:
eq.png
After Equalization, Normalize again.

Try this on a suitable sample - does this make things easier? If it does we can look at making it more automatic.

Fortunately, the recordings are already cut into 2GB chunks by the nature of the FAT filesystems they get recorded to. And processing power isn’t really an issue.

I was rather hoping to do the filtering/processing in the plugin: the filtering to extract the signal in a way that’s useful to the plugin makes it nearly impossible to analyze by eye later. If doing it the way you describe is the only practical one, is there way to copy the label track over to the original file?

We can possibly do that, but it it would help a lot if we know exactly what sort of filtering we need before we try to build it.

I was under the impression that you were trying to find the high frequency parts, so why does making them much bigger make them harder to see? :confused:

Before filtering, select the entire track and press Ctrl+D (Duplicate). You will then have two identical tracks, one above the other. You can filter one and leave the other untouched.

Ok, sounds like the next step is to design the right filter stack. Let me get back to you on that when I’m done.

Because there’s a low-frequency component too. It’s just that the high frequency component is what distinguishes them.

Just what I had in mind.

Oh, I see.
Using Audacity’s multi-track abilities may come in useful here - possibly filtering one copy one way to bring out the visual clues and another track another way to bring out the high frequency component.

Okay, so one thing that would be really useful as I’m testing filter combinations: getting Sound Finder to drop labels in a more useful way. This means dropping a label every time Sound Finder sees something it would classify as a sound, no matter how short that is or how close it is to the previous sound. Any suggestions on how I can do this?

edit:
If it’s not possible to tell it to drop “point” labels as opposed to “span” labels (is that the right terminology?), setting an upper bound on the length of a “span” label would be OK too.

I’ve already changed Sound Finder so that the label starting/ending points can be reduced to 0.0001, but that only helps so much when the signals are clustered together.I’ve also boosted the sample rate from 100 to 9600, but that hasn’t helped much.