import PySimpleGUI as sg
import pygame
import numpy as np
import time
import random
import threading
import sys
sg.theme('LightBrown2')
# Initialize pygame mixer for sound generation (モノラル用にチャンネル数を1に設定)
pygame.mixer.init(frequency=44100, size=-16, channels=1)
# Morse code dictionary
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': '--..',
'1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....',
'6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----',
}
# 単語リスト
word_list = [
"kind", "surround", "university", "break", "it", "speech", "parachute",
"change", "command", "refugee", "hurt", "power", "minister", "riot",
"recover", "male", "funeral", "stop", "define", "transport", "realistic",
"mass", "universe", "street", "he", "square", "deaf", "stretch", "organize",
"neutral", "concern", "emotion", "corruption", "particle", "area", "station",
"through", "get", "inform", "tell", "transportation", "fit", "limit", "tissue",
"genocide", "old", "repeat", "dismiss", "today", "baby", "near", "television",
"fetus", "memorial", "tube", "self", "minor", "secret", "anniversary", "spring",
"earth", "class", "exercise", "desert", "modern", "ask", "where", "wave", "once",
"search", "provide", "half", "grass", "organ", "fat", "but", "supervise", "morning",
"sympathy", "with", "incite", "flower", "execute", "remove", "poverty", "year",
"industry", "close", "rebel", "drink", "liquid", "probably", "balloon", "genes",
"glass", "plot", "trick", "fly", "fine", "general", "wild", "adult", "sugar",
"victim", "so", "turn", "ice", "false", "crush", "expert", "available", "ceasefire",
"most", "when", "exchange", "overthrow", "Congress", "holy", "stone", "describe",
"torture", "tradition", "destroy", "react", "affect", "develop", "seize", "few",
"mother", "degree", "family", "holiday", "atmosphere", "by", "pipe", "aggression",
"hurry", "missing", "line", "evaporate", "resolution", "local", "nerve", "temperature",
"substance", "include", "knife", "truce", "raise", "vote", "yet", "research", "chromosome",
"help", "surprise", "ever", "discuss", "revolt", "intervene", "on", "soon", "fact",
"sheep", "skill", "shape", "play", "invite", "soil", "attention", "new", "happy",
"ecology", "announce", "voice", "possible", "awake", "money", "burn", "place",
"examine", "come", "resist", "purchase", "huge", "apologize", "insane", "struggle",
"long", "parliament", "list", "here", "south", "summer", "tie", "partner", "brief",
"store", "mental", "find", "thick"
]
is_playing = False # 発声中の状態を管理するフラグ
# Function to generate sound samples for a dot or dash
# クリックノイズ対策したcode
def generate_tone(frequency, duration):
sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False)
# 音波の生成
tone = 32767 * np.sin(2 * np.pi * frequency * t)
# フェードイン・フェードアウトの適用
fade_in_out_duration = int(sample_rate * 0.01) # 0.01秒のフェード
fade_in = np.linspace(0, 1, fade_in_out_duration)
fade_out = np.linspace(1, 0, fade_in_out_duration)
# 音の全体にフェードイン・フェードアウトを適用
tone[:fade_in_out_duration] *= fade_in
tone[-fade_in_out_duration:] *= fade_out
sound_array = tone.astype(np.int16)
# 1次元配列を2次元に変換 (pygameのサウンド生成用に必要)
sound_array_stereo = np.column_stack((sound_array, sound_array))
return pygame.sndarray.make_sound(sound_array_stereo)
""" クリックノイズ出ているcode
def generate_tone(frequency, duration):
sample_rate = 44100
t = np.linspace(0, duration, int(sample_rate * duration), False)
tone = 32767 * np.sin(2 * np.pi * frequency * t)
sound_array = tone.astype(np.int16)
# 1次元配列を2次元に変換 (pygameのサウンド生成用に必要)
sound_array_stereo = np.column_stack((sound_array, sound_array))
return pygame.sndarray.make_sound(sound_array_stereo)
"""
# Function to play a dot sound
def play_dot(frequency):
dot_sound = generate_tone(frequency, DOT_DURATION)
dot_sound.play()
time.sleep(DOT_DURATION)
# Function to play a dash sound
def play_dash(frequency):
dash_sound = generate_tone(frequency, DASH_DURATION)
dash_sound.play()
time.sleep(DASH_DURATION)
# Function to calculate Morse code timings based on WPM
def calculate_timings(wpm):
unit_time = 1.2 / wpm
dot_duration = unit_time
dash_duration = 3 * unit_time
symbol_space_duration = unit_time
letter_space_duration = 3 * unit_time
word_space_duration = 7 * unit_time
return dot_duration, dash_duration, symbol_space_duration, letter_space_duration, word_space_duration
# Function to play Morse code for a given letter
def play_morse_code_for_letter(letter, frequency):
morse_code = MORSE_CODE_DICT.get(letter.upper(), '')
for symbol in morse_code:
if not is_playing:
break
if symbol == '.':
play_dot(frequency)
elif symbol == '-':
play_dash(frequency)
time.sleep(SYMBOL_SPACE_DURATION)
time.sleep(LETTER_SPACE_DURATION)
# Function to play Morse code for a word
def play_morse_code_for_word(word, frequency):
global is_playing
for letter in word:
if not is_playing:
break
play_morse_code_for_letter(letter, frequency)
time.sleep(WORD_SPACE_DURATION)
# Function to handle playing of Morse code for all words
def play_morse_code(word_sample, frequency):
global is_playing
is_playing = True
for word in word_sample:
if not is_playing:
break
play_morse_code_for_word(word, frequency)
morse_code = ' '.join([MORSE_CODE_DICT.get(letter.upper(), '') for letter in word])
window['SENT_OUTPUT'].update(morse_code + '\n', append=True)
# GUIレイアウト
layout = [
[
sg.Frame('英単語', [
[sg.Multiline(size=(30, 30), text_color='Blue', font=('Arial', 14, 'bold'), key='INPUT')]
], size=(200, 450)),
sg.Frame('発声済モールス符号', [
[sg.Multiline(size=(80, 30), text_color='Red', font=('Arial', 14, 'bold'), key='SENT_OUTPUT')]
], size=(300, 450))
],
[sg.Text('単語数:'), sg.Slider(range=(10, 200), default_value=10, resolution=10, orientation='h', key='NBR')],
[sg.Text('速度(Word/m):'), sg.Slider(range=(10, 30), default_value=20, resolution=2, orientation='h', key='WPM')],
[sg.Text('周波数(Hz):'), sg.Slider(range=(300, 1000), default_value=600, resolution=10, orientation='h', key='FREQ')],
[sg.Text()],
[sg.Text(' '), sg.Button('Start'), sg.Button('Stop', disabled=True), sg.Button('All Clear'), sg.Button('End')],
[sg.Text('JA1FML ver 4.2', text_color='LightBlue', justification='right', expand_x=True)]
]
window = sg.Window('モールス信号練習機 ver 4.2', layout, size=(500, 700), finalize=True)
# Main event loop
while True:
event, values = window.read()
"""
if event == sg.WIN_CLOSED or event == 'End':
if sg.popup_yes_no("終了しますか?") == 'Yes':
is_playing = False
break
"""
if event == sg.WIN_CLOSED or event == 'End':
if sg.popup_yes_no("終了しますか?") == 'Yes':
window.close()
sys.exit() # プログラム全体を終了 break
if event == 'Start':
nbr = int(values['NBR'])
wpm = int(values['WPM'])
frequency = int(values['FREQ'])
# 再生する単語の選択
word_sample = random.sample(word_list, nbr)
# Morseコードのタイミングを計算
DOT_DURATION, DASH_DURATION, SYMBOL_SPACE_DURATION, LETTER_SPACE_DURATION, WORD_SPACE_DURATION = calculate_timings(wpm)
# GUIに単語を表示
word_display = '\n'.join(word_sample)
window['INPUT'].update(word_display)
window['SENT_OUTPUT'].update('')
window['Start'].update(disabled=True)
window['Stop'].update(disabled=False)
window['All Clear'].update(disabled=True)
# Start a new thread to play Morse code without blocking the GUI
threading.Thread(target=play_morse_code, args=(word_sample, frequency), daemon=True).start()
if event == 'Stop':
is_playing = False
window['Start'].update(disabled=False)
window['Stop'].update(disabled=True)
window['All Clear'].update(disabled=False)
if event == 'All Clear':
if sg.popup_yes_no("クリアしますか?") == 'Yes':
window['INPUT'].update('')
window['SENT_OUTPUT'].update('')
is_playing = False
# Clean up pygame mixer resources
pygame.mixer.quit()
window.close()