詳しい説明は後程・・・
import numpy as np
import pyaudio
import time
import PySimpleGUI as sg
import threading
モールス信号辞書
MORSE_CODE_DICT = {
'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': '--..',
'0': '-----', '1': '.----', '2': '..---', '3': '…--', '4': '….-',
'5': '…..', '6': '-….', '7': '--…', '8': '---..', '9': '----.',
'?': '..--..', ',': '--..--', '.': '.-.-.-', '/': '-..-.', '@': '.--.-.',
'-': '-….-', '(': '-.--.', ')': '-.--.-'
}
PyAudioの初期化
p = pyaudio.PyAudio()
再生設定
VOLUME = 0.5 # 音量
SAMPLE_RATE = 44100 # サンプリングレート
playback_stopped = False # 再生停止フラグ
クリック音を減少させるエンベロープ
def apply_envelope(signal, fade_in_duration=0.01, fade_out_duration=0.01):
fade_in_samples = int(fade_in_duration * SAMPLE_RATE)
fade_out_samples = int(fade_out_duration * SAMPLE_RATE)
envelope = np.ones_like(signal)
envelope[:fade_in_samples] = np.linspace(0, 1, fade_in_samples)
envelope[-fade_out_samples:] = np.linspace(1, 0, fade_out_samples)
return signal * envelope
正弦波トーンの生成
def generate_tone(duration, frequency):
t = np.linspace(0, duration, int(SAMPLE_RATE * duration), False)
signal = np.sin(2 * np.pi * frequency * t)
signal = apply_envelope(signal)
return signal
音を再生
def play_tone(tone):
samples = (tone * VOLUME * np.iinfo(np.int16).max).astype(np.int16)
stream = p.open(format=pyaudio.paInt16,
channels=1,
rate=SAMPLE_RATE,
output=True)
stream.write(samples.tobytes())
stream.stop_stream()
stream.close()
ドットとダッシュの再生
def play_dot(frequency, dot_duration):
play_tone(generate_tone(dot_duration, frequency))
def play_dash(frequency, dot_duration):
play_tone(generate_tone(10 * dot_duration, frequency))
モールス信号の再生
def play_morse_code(input_text, wpm, frequency, window):
global playback_stopped
playback_stopped = False
# ドットの持続時間を計算(50単位=1単語を基準)
dot_duration = 60 / (50 * wpm) # WPMに基づく1つのドットの持続時間
# 文字間の間隔と単語間の間隔
char_gap = 3 * dot_duration # 文字間の間隔
word_gap = 7 * dot_duration # 単語間の間隔
sent_text = '' # 送信済みの文字列
# 各文字をモールス信号に変換して発声
for i, char in enumerate(input_text.upper()):
if playback_stopped:
break
if char in MORSE_CODE_DICT:
morse_code = MORSE_CODE_DICT[char]
for symbol in morse_code:
if playback_stopped:
break
if symbol == '.':
play_dot(frequency, dot_duration)
elif symbol == '-':
play_dash(frequency, dot_duration)
time.sleep(dot_duration) # ドット間の間隔
time.sleep(char_gap) # 文字間の間隔
elif char == ' ':
time.sleep(word_gap) # 単語間の間隔
# 送信済みのテキストを更新
sent_text += char
window['SENT_OUTPUT'].update(value=sent_text)
window.refresh()
再生スレッドの開始
def start_playback(text, wpm, frequency, window):
global playback_stopped
playback_stopped = False
play_morse_code(text, wpm, frequency, window)
再生の停止
def stop_playback():
global playback_stopped
playback_stopped = True
GUIレイアウト
sg.theme('NeutralBlue')
layout = [
[sg.Text('文字列を入力してください:')],
[sg.Input(key='INPUT', size=(50, 1))],
[sg.Text('送信済み:'), sg.Input('', size=(45, 1), key='SENT_OUTPUT')],
#[sg.Text('送信済み:', size=(40, 1)), sg.Text('', size=(40, 1), key='SENT_OUTPUT')],
[sg.Text('速度(Word/m):'), sg.Slider(range=(10, 30), default_value=20, orientation='h', key='WPM')],
[sg.Text('周波数(Hz):'), sg.Slider(range=(300, 1000), default_value=800, orientation='h', key='FREQ')],
[sg.Button('発声'), sg.Button('stop'), sg.Button('クリアー'), sg.Button('終了')],
[sg.Text('JA1FML ver 1.0', justification='right', size=(40, 1))]
]
ウィンドウを作成
window = sg.Window('モールス信号発生器', layout, size=(400, 300))
イベントループ
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == '終了':
break
elif event == '発声':
text = values['INPUT']
wpm = values['WPM']
frequency = values['FREQ']
window['SENT_OUTPUT'].update('') # 送信済み表示をクリア
threading.Thread(target=start_playback, args=(text, wpm, frequency, window), daemon=True).start()
elif event == 'stop':
stop_playback() # 再生の停止
elif event == 'クリアー':
window['INPUT'].update('')
window['SENT_OUTPUT'].update('')
ウィンドウを閉じる
window.close()
p.terminate()