DEV Community

Anthony Slater
Anthony Slater

Posted on

Using Numpy to build WAV files

I found a fun little tool that converts text into Morse Code with audio.

GitHub logo nimit2801 / MorseCodeMessenger

This Project will currently let you convert your text into Encrypted Morse Code with audio!

It works by first declaring a dictionary of letters, numbers and symbols with their corresponding definitions of dots and dashes. The program asks the user for input; iterates through the provided string one character at a time; looks up the character's definition; and saves the result to a new encoded string. The program includes two wav files: a dot and a dash. The program iterates through the saved encoded string of dots and dashes and plays the appropriate wav file. So it can encode text into sound, but the issue was can it decode sound into text?

I have a little bit of experience making command line tools, but I have never worked with sound before. My plan to tackle this issue was:

  • figure out how to read a wav file
  • create a wav file from multiple smaller ones
  • figure out how to find patterns in a wav file

After a little bit of research I found that a straightforward way to read a wav file was to use scipy.io.wavfile.read. The method takes a file location to a wav file and returns a sample rate as an integer and a numpy array. The opposite method scipy.io.wavfile.write takes a file location, a sample rate and a numpy array and creates a wav file. After playing around with these functions for a while, I was able to read the dot and dash wav files and create a large numpy array by appending copies of the dot and dash arrays.

dt = np.dtype('uint8')
blank = np.array([0] * 800, dt)   # 0.1s
padding = np.array([0] * 240, dt) # 0.03s
samplerate, dot = wavfile.read(DOT)
samplerate, dash = wavfile.read(DASH)
dot = np.append(dot, padding.copy())
dash = np.append(dash, padding.copy())
wav_data = np.empty((0,0), dt)
for tune in self.encoded:
  np_data = dot.copy() if tune == '.' else dash.copy() if tune == '-' else blank.copy()
  wav_data = np.append(wav_data.copy(), np_data) 
location = f'{self.filename}.wav'
wavfile.write(location, samplerate, wav_data)
Enter fullscreen mode Exit fullscreen mode

The last thing I needed the tool to do was read a wav file and recognize patterns, specifically a dot, a dash, or blank silence. Perhaps there is a better method, but I decided that I would have the program read the wav file and look for specific values in the returned array at specific locations. This isn't a particularly elegant solution as it mainly involved checking an arbitrary location.

samplerate, wav = wavfile.read(f'{self.filename}.wav')
end = wav.shape[0]
self.encoded = ''
ptr = 0
while ptr < end: 
  if wav[ptr] == 0:
    ptr += 800      # size of blank array
    self.encoded += ' '
  elif wav[ptr + 800] == 128:
    ptr += 1120     # size of dot array
    self.encoded += '.'
  else:
    ptr += 2080     # size of dash array
    self.encoded += '-'
Enter fullscreen mode Exit fullscreen mode

The result is a string of dots, dashes and spaces which could be passed to the original functions of the tool.
The last thing I wanted to do, was give the user a way to specify which functions to call from the command line. So I turned to argparse and implemented the following flags:

Optional flags Purpose Example
-w
--wav
Create a Morse Code audio file At the prompts, enter a message Hello World and a name hi. An audio file will be created and saved as hi.wav
-p
--play
Play a Morse Code audio file At the prompt, enter hi and if hi.wav exists, the audio will be played
-r
--read
Decipher a Morse Code audio file At the prompt, enter hi and if hi.wav exists, the text Hello World will be displayed

This was a fun little project and I was really happy to have my PR merged.

Top comments (0)