Morse Code WAV 파일 생성 및 해석
과제 1: Morse code WAV file 만들기
목표
- “CNU CSE 202302627 CHOISOYEON” 텍스트를 WAV 파일로 변환하기
과정
- 텍스트를 먼저 모스 부호로 변환하고, 변환된 모스 부호를 WAV파일로 변환하고자 하였다.
- string(텍스트)에 text2morse 함수를 적용하여 모스 부호를 만들어내고, 만든 모스부호에 morse2audio 함수를 적용하여 오디오를 생성하고, audio2file로 최종 WAV 파일을 생성했다.
1. 라이브러리 및 mapping dictionary

- math: 사인파 생성(sin, pi 사용)을 위해 필요
- wave: WAV 파일 생성 및 저장을 위해 필요
- struct: audio data를 binary 형식으로 packing하기 위해 필요
- mapping dictionary: 영어 알파벳(A-Z)과 숫자(0-9)에 모스부호가 각각 key, value로 대응되도록 했다.
2. text2morse

- text2morse는 텍스트를 모스 부호로 변환해주는 함수로, 모스부호(string)를 반환한다.
- 모스부호는 대소문자를 구분하지 않고, mapping dictionary에도 영어 대문자만 정의해두었으므로 text에 소문자가 들어있을 경우를 대비해 .upper()를 통해 모두 대문자로 바꿔준다 (이번 과제의 경우 굳이 사용하지 않아도 됐다)
- 모스부호 변환 결과를 담을 변수 morse를 초기하하고, 텍스트를 한 글자씩 돌며 문자(key)를 모스부호(value)로 변환하여 morse에 붙인다. 이때, 모스부호의 공백은 ‘/’로 표시하므로 공백을 만나면 ‘/’로 변환한다.
- for문을 돌며 모든 텍스트의 변한이 끝나면 morse를 반환한다.
3. morse2audio

-
morse2audio는 모스 부호를 오디오 신호로 변환하는 함수이다.
- 상수 (*과제 조건)
- for문을 돌며 모스 부호 해석
4. audio2file

- wave.open으로 WAV 파일 생성 (쓰기 모드: ’wb’)
- 과제 조건에 맞도록 WAV 파일 속성 설정: mono 채널, 32bit(4bytes) sample, 48,000Hz sample rate
- struct.pack으로 각 샘플을 little-endian 32비트 정수(<l)로 변환 후 파일에 기록
결과


- 앞서 정의한 함수들을 순차적으로 적용하여 텍스트 → 모스부호 → WAV 파일 생성에 성공했다.
전체 코드
# asgn1.py
import math
import wave
import struct
english = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..'
}
number = {
'1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....',
'6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----'
}
def text2morse(text):
text = text.upper()
morse = ''
for t in text:
for key, value in english.items():
if t == key:
morse = morse + value + ' '
for key, value in number.items():
if t == key:
morse = morse + value + ' '
# morse 중간에 띄어쓰기 추가
if t==' ':
morse = morse + '/'
return morse
def morse2audio(morse):
INTMAX = 2**(32-1)-1
t = 0.1 # 100ms 0.1초로 수정
fs = 48000
f = 523.251 # 주파수: C5
audio = []
for i, m in enumerate(morse):
if m == '.':
for j in range(int(t * fs * 1)): # Dit: 1단위
audio.append(int(INTMAX * math.sin(2 * math.pi * f * (j / fs))))
elif m == '-':
for j in range(int(t * fs * 3)): # Dah: 3단위
audio.append(int(INTMAX * math.sin(2 * math.pi * f * (j / fs))))
elif m == '/':
for j in range(int(t * fs * 7)): # 단어 간 간격: 7단위
audio.append(int(0))
if m in ['.', '-']:
for j in range(int(t * fs * (1 if i + 1 < len(morse) and morse[i + 1] in ['.', '-'] else 3))):
audio.append(int(0))
return audio
def audio2file(audio, filename):
with wave.open(filename, 'wb') as w:
w.setnchannels(1)
w.setsampwidth(4)
w.setframerate(48000)
for a in audio:
w.writeframes(struct.pack('<l', a))
resMorse = text2morse("CNU CSE 202302627 CHOISOYEON")
print(resMorse)
resWav = morse2audio(resMorse)
audio2file(resWav, "202302627_최소연.wav")
과제 2: Morse code WAV file 해석하기
목표
주어진 WAV파일에서 텍스트를 추출하기
과정
과제1에서 했던 것의 반대 과정을 수행하면 되겠다. (WAV → 모스 부호 → 텍스트)
1. 라이브러리 및 딕셔너리

- wave: WAV 파일 생성 및 저장을 위해 필요
- struct: audio data를 binary 형식으로 packing하기 위해 필요
- mapping dictionary: 영어 알파벳(A-Z)과 숫자(0-9)에 모스부호가 각각 key, value로 대응되도록 했다.
2. file2audio

- file2audio는 WAV파일에서 오디오 데이터를 읽어와 리스트로 변환한다.
- ‘rb’ 옵션으로 WAV파일을 binary 읽기 모드로 열고, 파일의 총 프레임 수(frame)와 초당 샘플 수(framerate)를 가져온다.
- 각 프레임을 1개씩 읽어 struct.unpack(‘<i’, frame)로 32비트 정수로 변환 후 리스트에 저장한다.
- 이렇게 만든 오디오 샘플 값 리스트(정수 저장) audio와 framerate를 반환한다.
3. audio2morse

- audio2morse는 오디오 데이터를 분석하여 모스부호로 변환한다.
- 여기서 threshold는 소리와 침묵을 구분하는 임계값이다. INTMAX는 가능한 최대 진폭을 나타내며 상대적으로 약한 신호도 감지할 수 있게끔 특정 값을 곱하여 진폭에 비례하는 임계값을 만들 것이다. 이 값을 1부터 1/10씩 순차적으로 작게 설정해봤고, 작을수록 분석 결과가 정밀해짐을 확인했다(결과 부분 참고).
- 오디오데이터 샘플을 처음부터 끝까지 하나씩 확인하며, 오디오 샘플 값의 절댓값을 비교한다. 모스부호는 소리의 존재 여부와 길이를 기준으로 판단하므로, 소리가 진동하는 방향과 상관없이 크기만 보면 된다.
3. morse2text

- morse2text는 모스부호를 받아 알파벳과 숫자 문자열로 변환해준다.
- 단어 사이 공백으로 약속된 ‘/’을 기준으로 문장을 단어들로 split하고, 단어를 for문으로 하나씩 처리한다. 단어 내에서 공백(’ ‘)을 기준으로 모스부호 문자를 분리하여 (strip사용으로 불필요 공백 제거) 모스부호를 하나씩 확인하고 원래의 문자와 매핑하여 text에 붙인다.
4. main

- 사이버캠퍼스에 배포된 WAV파일을 읽고 이를 모스부호로 변환하고, 다시 이를 텍스트로 변환하며 각 과정의 결과물을 출력하는 코드이다. 최종 텍스트는 utf-8로 인코딩하여 과제에 제출할 txt파일로 저장되게 했다.
결과

WAV file에서 추출한 문장을 txt파일에 저장되게 했고, 내용을 출력하게 했다.
여기서 아까 언급한 threshold 계산에서 INTMAX에 곱하는 값을 점점 작게 변화시켜봤더니, 잘 나오지 않던 문장이 정확히 나오는 것을 확인할 수 있었다.
분석 결과 음성 속에 있던 문장은 “HE WAS GOING IN THE FICTION DEPARTMENT”이다.
최종 코드
# asgn2.py
import wave
import struct
english = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..'
}
number = {
'1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....',
'6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----'
}
def file2audio(filename):
with wave.open(filename, 'rb') as w:
frames = w.getnframes()
framerate = w.getframerate()
audio = []
for i in range(frames):
frame = w.readframes(1)
audio.append(struct.unpack('<i', frame)[0])
return audio, framerate
def audio2morse(audio, framerate):
INTMAX = 2**(32-1)-1
threshold = INTMAX * 0.0000001
morse = ''
i = 0
while i < len(audio):
if abs(audio[i]) > threshold:
start = i
while i < len(audio) and abs(audio[i]) > threshold:
i += 1
duration = (i - start) / framerate
if 0.09 <= duration <= 0.11:
morse += '.'
elif 0.29 <= duration <= 0.31:
morse += '-'
else:
start = i
while i < len(audio) and abs(audio[i]) <= threshold:
i += 1
silence_duration = (i - start) / framerate
if 0.09 <= silence_duration <= 0.11:
morse += ''
elif 0.29 <= silence_duration <= 0.31:
morse += ' '
elif silence_duration >= 0.69:
morse += ' / '
i += 1
return morse.strip()
def morse2text(morse):
morse_words = morse.split('/')
text = ''
for word in morse_words:
morse_chars = word.strip().split(' ')
for char in morse_chars:
for key, value in english.items():
if value == char:
text += key
for key, value in number.items():
if value == char:
text += key
text += ' '
return text.strip()
audio_data, framerate = file2audio("output_202302627_최소연.wav")
extracted_morse = audio2morse(audio_data, framerate)
print("Extracted Morse:", extracted_morse)
extracted_text = morse2text(extracted_morse)
print("Extracted Text:", extracted_text)
with open("202302627_최소연.txt", "w", encoding="utf-8") as f:
f.write(extracted_text)
Leave a comment