This practical will guide you through taking some basic propagation measurements. Generally speaking, underwater acoustic propagation is affected by:

For this exercise, you should select a few locations which you think have variable acoustic propagation (i.e. Canals, ditches, small ponds, etc). At each site, drop the speaker in (about mid-water depth if possible) and move the hydrophone along a transect trying to make measurements at the given dinstances from the speaker:

If you can’t make 8 meters at a given site, that’s fine. The point of this exercise is to give you an idea how propagation is different depandant upon the surrounding environment, rather than precise data collection.

For the playback tone, you can use a 10 second sweep ranging from 100-5000Hz (broadly covering the hearing range of fish). We’ve already made this for you and you can download it here.

After you’ve made the recordings, you should clip the recordings to a length of exactly 10 seconds using Audacity. By using the spectrogram view, it’ll be much easier to see the sweep in audacity.

Spectrogram View

Spectrogram View

Its a good idea to preview your measurements in audacity as you make them. Also, make sure you name your files in a clear and concise manner. For example:

When you return from taking the measurements, you can follow the instructions in the next section to run a quick propagation analysis.

Analysis in R

Here we’ll run your data through some quick scripts which will let you compare the acoustic propagation in the different environments you sampled.

To start, lets open R studio, create a new script, and install and load the packages we’ll need for this exercise. To install packages, use the command install.packages('signal'). After you’ve installed them (or if you have already previously installed some of them, theres no need to do this again), you can load them using require(signal). Just replace signal with the name of whatever pacakge you need. The following code block should install all your pacakges.

install.pacakges(c('signal', 'devtools', 'ggplot2', 'tuneR'))

## ezPSD requires a slightly different installation command, as this is not on the official R repositories.

Now we can load our packages for use. You will have to load your packages (as seen below) each time you restart R, but you will only have to install a pacakge a single time for each machine your using.


Propagation Models

For your analysis, you want to know how quickly the sound will attenuate over distance. To make sense of your measurements, it’ll be usefull to plot the results against a reference value. In underwater acoustics, two simple models for propagation loss are:

  • Spherical Spreading: This models a omnidirectional sound source (emits in all directions equally) in a theoretical free-field environment (no water surface, bottom, or reflective surfaces are nearby). Here, the attenuation is a result of only the spherical expansion of the sound, thus it is a usefull reference point.
  • Cylinderical spreading: This is slightly more realistic for shallow water af further distances from a sound source. This is essentially spherical spreading, but only modelling the sound traveling along a two-dimentional plane. Imagine the acoustic enegy as a cylinder the same height as the water column, which expands accross the horizontal plane.
In this figure, close to the source, sound propagates via spherical spreading, and cylindrical farther from the source.

In this figure, close to the source, sound propagates via spherical spreading, and cylindrical farther from the source.

In reality, shallow water propagation is much more complex than this (for the curious, search the keywords: Pekeris’ shallow water and normal mode propagation to get a better understanding of shallow water acoustics), but they should at least give you a simple theoretical reference point from which you can gain insight into real propagation measurements.

Analysis of Field Data

To analyse your field data, we’ve made some functions for you to use, so you can focus on interpreting your results rather than learning R. To use these functions, copy and paste the code block into your own R script then select the line where the function is defined: loadWave <- function(filename){ and run the selected line [Ctrl Enter]. You’ll notice the function has now been saved in your workspace environment as in the image below.

Save function to workspace

Save function to workspace

The following function can be used to load your sound files.

loadWave <- function(filename){
    wav <- readWave(filename = filename)

# Example usage
TestSound_1m <- loadWave('../../../../paPAM/Test Sounds/H.wav')
## Loading required package: tuneR
# An example PSD to visualize the sound we just loaded
ggpsd(ezWelch(TestSound_1m, fs = 44100, wl = 1024))

Now you can use the following function to plot your 10 second wave files you’ve loaded. Don’t be put off by the volume of code here. You don’t need to understand how this function works, you only have to load it (press [Ctrl Enter] with your cursor on the line where plotPropagation <- function(...) is found).

plotPropagation <- function(wavs, distances, fs = 44100, wl = 2048){
    require(ggplot2); require(ezPSD)
    n <- length(wavs)
    # Initialize results data.frame
    results <- data.frame()
    # Define frequency bins for 1/3 octaves
    octave.third.bins <- data.frame(
            start = 100*(2^(0:16/3)),
            end = 100*(2^(1:17/3)))
    octave.third.bins$center = apply(octave.third.bins, MARGIN = 1, FUN = 'mean')
    ## Loop through each file
    for(i in 1:n){
        ## Calculate Power Spectral Density
        psd <- ezWelch(wavs[[i]], fs = fs, wl = wl)
        ## Loop through all 1/3 octave bins and save summed PSD values
        for(j in 1:nrow(octave.third.bins)){
            idx <- which(psd$Frequency > octave.third.bins$start[j] &
                                        psd$Frequency < octave.third.bins$end[j])
            spl <- 10*log10(mean(psd$Power[idx]))
            results <- rbind(
                    dB = spl,
                    Range = distances[i],
                    Frequency = octave.third.bins$center[j]))
    # Normalize values
 results.normalized <- by(data = results, INDICES = results$Frequency, 
                                                 FUN = function(x){
                                                    x$dB = x$dB - max(x$dB)
 results.norm.combine <- do.call(results.normalized, what = 'rbind')
    # Add Spherical Spreading   
    dist.unique <- unique(distances)
    results.spherical <- data.frame(
                                        dB = -20*log10(distances),
                                        Range = distances)
    # Add Cylindrical Spreading 
    results.cylindrical <- data.frame(
                                        dB = -10*log10(dist.unique),
                                        Range = distances)
 # define jet colormap
 jet.colors <- colorRampPalette( c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F",
                                                                    "yellow", "#FF7F00", "red", "#7F0000"))
    ggplot(results.norm.combine) +
        geom_line(aes(x = Range, y = dB, color = Frequency, group = Frequency,
                                    linetype = "1")) +
         ylab("dB (re max)") + xlab("Range (meters)") + 
        geom_line(data = results.spherical, 
                            aes(x = Range, y = dB, linetype = "2")) +
        geom_line(data = results.cylindrical, 
                            aes(x = Range, y = dB, linetype = "3")) +
        scale_linetype_manual(name =  "Type",
                                                    labels = c("Observed", "Spherical", "Cylindrical"),
                                                    values = c(`1` = 1,
                                                                         `2` = 2,
                                                                         `3` = 3)) +
        scale_color_gradientn(colours = jet.colors(7), trans = 'log',
                                                    breaks = 100*2^(0:5)) +

Now we’ll use this function to plot our propatation measurements. Heres an example plot made with simulated data (modeled from spherical spreading). Pay attention to the syntax used here, as it can be a little tricky.

#Loaded wav files (from the loadWave function)
# Canal_1.wav <- loadWave('simulatedCanal.wav')
wavs <- list(

# The distances for each measuement listed above (in matching order)
distances = c(1,2,4,8)

# Now we just drop these two variables we made into the plotting function
plotPropagation(wavs = wavs, distances = distances)

This plot allows you to examine the propagation over distance as compared to the theoretical cylindrical and spherical spreading. I’ve also divided up your signal into 1/3 octave bands so we can see how different frequency ranges propagate over distance. Note that each frequency band is normalized so there is a propagation loss of 0 at the distance of 1m. Thus, this plot is only usefull for examining the relative changes in dB over distance with respect to particular frequency bands. In our example plot, as spherical spreading is not frequency dependant, you’ll notice that all our 1/3 octave bands are overlayed on a single line in this plot.

While this is interesting, it’s also useful to examine the frequency spectrum in more detail. For this, we can use the following function to plot the power spectral density plot of each measurement.

# fs = sampling rate (likely 44100Hz)
# fft = window length.  Increase this to get higher resolution plots
plotPSDs <- function(wavs, distances, fft = 512, fs = 40000){
    n <- length(wavs)
    results <- data.frame()
    for(i in 1:n){
        psd <- ezWelch(wavs[[i]], wl = fft, fs = fs)
        results <- rbind(
                dB = 10*log10(psd$Power),
                Frequency = psd$Frequency,
                Range = distances[i]))
    #Normalize values
    results$dB <- results$dB - max(results$dB)

    ggplot(results) +
        geom_line(aes(x = Frequency, y = dB, color = factor(Range))) +
        theme_bw() + ylab("dB (re max)") + xlab("Frequency (Hz)") + xlim(0, 5000) + 
        scale_color_discrete(name = "Range")

# Example usage
    wavs = list(
    distances = c(1,2,4,8),
    fs = 10000, fft = 512)