### Audible playback of ultrasound using GNURadio Companion

Inspired by the preamp and hydrophone post yesterday by Mikko Syrjäsuo, I put together some gnuradio scripts for processing infra or ultrasound recordings. There are three different flowgraphs: The first one allows you to playback a recording with a faster or slower speed. The second one allows you to shift signal in frequency by multiplying it with a complex sinusoid up or down in frequency to an audible band. The third one is something similar to what Mikko was describing. It uses a custom python block to compress a wide bandwidth into a more narrow bandwidth by using something called a vocoder (at least that is what I think it is called). I've attached the code.

The audio downconversion script and the vocoder script can also be operated in realtime, by replacing the wav-source block with an audio card block. It sounds very weird talking to a microphone and hearing your own voice shifted to a higher or lower frequency.

For more serious applications, you could also use the USRP N2x0 or USRP1 together with the LFRX daughterboard to record ultrasound signals.

I managed to find nice high frequency (250 kHz) ultrasound recordings of bat sounds on this web site: http://www.batcalls.com/. I have used these for testing. Specifically this wav file.

Flowgraph to adjust the speed of the audio recording, resulting in sped up or slowed down playback of the wav file. |

Audio frequency shifting using mixing and filtering. This allows "mixing" the ultrasound onto an audible frequency band, retaining the temporal characteristics of the signal. |

The vocoder flowgraph. The vocode block is a custom block that I wrote in Python. The code is shown below. |

Bat call signal between 10 and 75 kHz is converted into a 1 to 14 kHz signal. |

The code for the vocoder is shown below:

```
```

```
```

`# `

Simple vocoder gnuradio block, (c) 2017 Juha Vierinen

#

```
import numpy as np
from gnuradio import gr
class vocode(gr.sync_block):
"""
docstring for block vocode
"""
def __init__(self, n,n0,n1,dec):
self.buflen=4*n+8192
self.tmp_in = np.zeros(self.buflen,dtype=np.float32) # buffering
self.tmp_out = np.zeros(self.buflen,dtype=np.float32)
```

# buffering

```
self.fin = np.zeros(n,dtype=np.float32)
self.n = n # fft size
self.n0 = n0 # first bin (mapped to dc freq)
self.n1 = n1 # last bin (mapped to dc+(n1-n0) freq)
self.w=np.zeros(n,dtype=np.float32) # window function
l2=int(n/2)
self.w[0:l2]=np.linspace(0,1.0,num=l2)
self.w[l2:n]=np.linspace(1.0,0.0,num=l2)
```

```
# how much do we integrate and decimate spectral components toghether
self.dec = dec
```

```
# some buffer indices
self.idx_bufin = 0 # location on input buffer
self.idx_proc = 0 # location on processing buffer
self.idx_out = 0 # location on output buffer
gr.sync_block.__init__(self,
name="vocode",
in_sig=[np.float32],
out_sig=[np.float32])
def work(self, input_items, output_items):
in0 = input_items[0]
out = output_items[0]
L=len(in0)
```

```
# insert data into input buffer
self.tmp_in[np.mod(self.idx_bufin+np.arange(L),self.buflen)]=in0
```

```
```

` # number of windows we can process`

` tail = self.idx_bufin+L`

n_win = int(np.floor((tail-self.n-self.idx_proc)/(self.n/2)))

```
# process all available FFT windows (half overlapping)
for i in range(n_win):
# window
i0=self.idx_proc
fin=self.tmp_in[np.mod(i0+np.arange(self.n),self.buflen)]
# compress and shift spectrum
F=np.fft.rfft(self.w*fin)
Fc=np.convolve(F,np.repeat(1.0/float(self.dec),self.dec),mode="same")
idxs=np.arange(self.n0,self.n1,self.dec)
Fc2=np.copy(F) ; Fc2[:]=0.0
Fc2[np.arange(len(idxs))]=Fc[idxs]
self.tmp_out[np.mod(i0+2*self.n+np.arange(self.n),self.buflen)]+=self.w*np.fft.irfft(Fc2)
# empty tail of buffer
self.tmp_out[np.mod(i0-self.n+np.arange(self.n),self.buflen)]=0.0
self.idx_proc+=self.n/2
```

```
self.idx_bufin+=L
out[:]=self.tmp_out[np.mod(self.idx_out+np.arange(L),self.buflen)]
self.idx_out+=L
return L
```

```
```

## Comments

## Post a Comment