PySimpleGUI

モールス信号受信練習アプリ ver7.0

欧文用と和文用アプリを一つのアプリに統合しました。

このcodeをPython にコピペすれば、働くはずです。

これがmain windowでこれにsub windowの欧文アプリと和文アプリを合体させました。 

## モールス受信アプリ ver 7.0.py  10/17/2024  chatGPT_6 

import PySimpleGUI as sg
import subprocess
import os
import sys

sg.theme('NeutralBlue')

# Windows の場合、CREATE_NO_WINDOW フラグを設定
if sys.platform == 'win32':
    import subprocess
    CREATE_NO_WINDOW = 0x08000000
else:
    CREATE_NO_WINDOW = 0

# アプリ実行中のプロセスを管理する変数
proc_obun = None
proc_wabun = None

# アプリ起動時に欧文アプリを自動的に開く関数
def open_obun_app():
    global proc_obun
    app_path = os.path.expanduser('~/Desktop/モールス発生器 ver 7.0/欧文モールス受信アプリ e7.0.py')
    if proc_obun and proc_obun.poll() is None:
        proc_obun.terminate()  # 既存のプロセスを終了
    proc_obun = subprocess.Popen(['python', app_path], creationflags=CREATE_NO_WINDOW)

# アプリ起動時に和文アプリを自動的に開く関数
def open_wabun_app():
    global proc_wabun
    app_path = os.path.expanduser('~/Desktop/モールス発生器 ver 7.0/和文モールス受信アプリ j7.0.py')
    if proc_wabun and proc_wabun.poll() is None:
        proc_wabun.terminate()  # 既存のプロセスを終了
    proc_wabun = subprocess.Popen(['python', app_path], creationflags=CREATE_NO_WINDOW)

# メインウィンドウのレイアウト
layout = [
    [sg.Text('', size=(10,6))],  # ボタン類を一列下げるダミー
    [sg.Button('欧文', size=(10, 2), key='欧文')],
    [sg.Button('和文', size=(10, 2),  key='和文')],
    [sg.Text('', size=(10,3))],
    [sg.Button('End', size=(10, 2), button_color=('blue','pink'))],
    [sg.Text('', key='status')]  # ここでstatusを追加
]

# ウインドウ位置, サイズ
win_location = (1000, 300)
win_size = (480, 600)

# メインウィンドウの作成
window = sg.Window("ver 7.0", layout, size=win_size, location=win_location, no_titlebar=True, grab_anywhere=True )

# イベントループ
while True:
    event, values = window.read(timeout=100)  # 0.1秒ごとにポーリング

    # 初回起動時に欧文アプリを起動
    if proc_obun is None:
        open_obun_app()

    # ウィンドウが閉じられた場合
    if event == sg.WIN_CLOSED:
        # 終了時に実行中のサブプロセスがあれば終了
        if proc_obun and proc_obun.poll() is None:
            proc_obun.terminate()
        if proc_wabun and proc_wabun.poll() is None:
            proc_wabun.terminate()
        break

    # End ボタンの処理
    if event == 'End':
        if sg.popup_yes_no('終了しますか?') == 'Yes':  # 確認ポップアップ
            if proc_obun and proc_obun.poll() is None:
                proc_obun.terminate()
            if proc_wabun and proc_wabun.poll() is None:
                proc_wabun.terminate()
            break

    # 欧文アプリを表示
    if event == "欧文":
        if proc_wabun and proc_wabun.poll() is None:
            proc_wabun.terminate()  # 和文アプリが動作していたら終了

        open_obun_app()

    # 和文アプリを表示
    if event == "和文":
        if proc_obun and proc_obun.poll() is None:
            proc_obun.terminate()  # 欧文アプリが動作していたら終了

        open_wabun_app()

    # サブプロセスが終了している場合、再度 欧文アプリを表示
    if proc_obun and proc_obun.poll() is not None:
        open_obun_app()

# ウィンドウを閉じる
window.close()

---------------------------------
こちらが欧文用アプリ

# 欧文モールス受信アプリ ver e7.0.py 10/16/2024

# windowサイズ、ロケーションをmasterに合わせた
# 'あいうえお'を全て大文字で表示させるために変換必要 10/15/2024
# 何かの拍子にsubがmainの裏側に来ることがある。

import PySimpleGUI as sg
import numpy as np
import pyaudio
import time
import threading
import random
import pygame

# 初期設定
sg.theme('NeutralBlue')
p = pyaudio.PyAudio()
VOLUME = 1
SAMPLE_RATE = 44100

# pygameの初期化
pygame.mixer.init(frequency=SAMPLE_RATE)

is_playing = False
playback_thread = None  # スレッドを格納する変数を追加

# 欧文モールス信号辞書
E_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': '----.',
    '?': '..--..', ',': '--..--', '.': '.-.-.-', '/': '-..-.', '@': '.--.-.',
    '-': '-....-', '(': '-.--.', ')': '-.--.-' }

# 欧文単語リスト
E_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"
]

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)
    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)
    sound_array_stereo = np.column_stack((sound_array, sound_array))
    return pygame.sndarray.make_sound(sound_array_stereo)

def play_dot(frequency):
    dot_sound = generate_tone(frequency, DOT_DURATION)
    dot_sound.play()
    time.sleep(DOT_DURATION)

def play_dash(frequency):
    dash_sound = generate_tone(frequency, DASH_DURATION)
    dash_sound.play()
    time.sleep(DASH_DURATION)

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

def play_morse_code_for_letter(letter, frequency):
    morse_code = E_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)

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)

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)
    is_playing = False
    window.write_event_value('-FINISHED-', None)  # 発声完了時にイベント送信

def start_playback(word_sample, frequency):
    time.sleep(1)  # 表示の1秒後に再生を開始
    play_morse_code(word_sample, frequency)

layout = [
    [sg.Text('')],
    [sg.Text('欧文モールス信号', text_color='red', background_color='White', font=('Arial',18))],
    [sg.Radio('欧文Auto', group_id=1, key='E_AUTO', text_color='red', default=True),
     sg.Radio('欧文KB', group_id=1, key='E_KB', text_color='red')],
    [sg.Text('発声単語:')],
    [sg.Multiline(size=(40, 10), text_color='Blue', font=('Arial', 16, 'bold'), key='INPUT')],
    [sg.Text('単語数:'), sg.Slider(range=(10, 100), default_value=40, resolution=10, orientation='h', key='NBR')],
    [sg.Text('速度(Word/m):'), sg.Slider(range=(4, 20), default_value=10, resolution=1, orientation='h', key='WPM')],
    [sg.Text('周波数(Hz):'), sg.Slider(range=(300, 1000), default_value=700, resolution=100, orientation='h', key='FREQ')],
    [sg.Text('')],
    [sg.Button('Start', size=(10, 2), key='Start', disabled=False),
    sg.Button('End', size=(10, 2), key='End', disabled=False), sg.Push(),
    sg.Push(), sg.Text('JA1FML ver e7.0', text_color='LightBlue')]
]


win_location = (1100, 300)
win_size = (380, 600)

window = sg.Window('欧文モールス受信 ver e7.0', layout, size = win_size, location = win_location, no_titlebar=True,  grab_anywhere=True, finalize=True)

while True:
    event, values = window.read()

    if event == sg.WINDOW_CLOSED or event == 'End':
        if is_playing:
            sg.popup("発声中は「End」を押すことができません。")
        else:
            confirm_exit = sg.popup_yes_no('アプリを終了しますか?')
            if confirm_exit == 'Yes':
                break

    elif event == 'Start':
        if not is_playing:  # 発声中でないことを確認
            window['End'].update(disabled=True)  # 発声開始時にEndボタンを無効にする
            window['Start'].update(disabled=True)  # Startボタンを無効にする

            wpm = int(values['WPM'])
            frequency = int(values['FREQ'])

            DOT_DURATION, DASH_DURATION, SYMBOL_SPACE_DURATION, LETTER_SPACE_DURATION, WORD_SPACE_DURATION = calculate_timings(wpm)

            if values['E_AUTO']:
                nbr = int(values['NBR'])
                word_sample = random.sample(E_word_list, nbr)
                input_words = ' '.join(word_sample)

                window['INPUT'].update(input_words)  # 単語を入力欄に表示

                playback_thread = threading.Thread(target=start_playback, args=(word_sample, frequency), daemon=True)
                playback_thread.start()

            elif values['E_KB']:
                input_text = window['INPUT'].get().strip()
                if input_text:
                    word_sample = input_text.split()

                    playback_thread = threading.Thread(target=start_playback, args=(word_sample, frequency), daemon=True)
                    playback_thread.start()
                else:
                    sg.popup('発声単語を入力してください。')

    elif event == '-FINISHED-':  # 発声が完了したときの処理
        window['End'].update(disabled=False)
        window['Start'].update(disabled=False)

window.close()

------------------------------------
こちらが和文用アプリ


# 和文モールス受信window j6.0.py 10/15/2024

# windowサイズ、ロケーションをmasterに合わせた
# 'あいうえお'を全て大文字で表示させるために変換必要 10/15/2024
# 何かの拍子にsubがmainの裏側に来ることがある。 


import PySimpleGUI as sg
import numpy as np
import pyaudio
import time
import threading
import random
import pygame

# 初期設定
sg.theme('NeutralBlue')
p = pyaudio.PyAudio()
VOLUME = 1
SAMPLE_RATE = 44100

# pygameの初期化
pygame.mixer.init(frequency=SAMPLE_RATE)

is_playing = False
playback_thread = None  # スレッドを格納する変数を追加

# 和文モールス信号辞書作成必要
J_MORSE_CODE_DICT = {
    'ア': '--.--', 'イ': '.-', 'ウ': '..-', 'エ': '-.---', 'オ': '.-...',
    'あ': '--.--', 'い': '.-', 'う': '..-', 'え': '-.---', 'お': '.-...',    
    'カ': '.-..', 'キ': '-.-..', 'ク': '...-', 'ケ': '-.--', 'コ': '----',    
    'か': '.-..', 'き': '-.-..', 'く': '...-', 'け': '-.--', 'こ': '----',
    'ガ': '.-.. ..', 'ギ': '-.-.. ..', 'グ': '...- ..', 'ゲ': '-.-- ..', 'ゴ': '---- ..',
    'が': '.-.. ..', 'ぎ': '-.-.. ..', 'ぐ': '...- ..', 'げ': '-.-- ..', 'ご': '---- ..',
    'サ': '-.-.-', 'シ': '--.-.', 'ス': '---.-', 'セ': '.---.', 'ソ': '---.',
    'ザ': '-.-.- ..', 'ジ': '--.-. ..', 'ズ': '---.- ..', 'デ': '.-.-- ..', 'ゾ': '---. ..',
    'さ': '-.-.-', 'し': '--.-.', 'す': '---.-', 'せ': '.---.', 'そ': '---.',
    'ざ': '-.-.- ..', 'じ': '--.-. ..', 'ず': '---.- ..', 'で': '.-.-- ..', 'ぞ': '---. ..',
    'タ': '-.', 'チ': '..-.', 'ツ': '.--.', 'テ': '.-.--', 'ト': '..-..',
    'ダ': '-. ..', 'ヂ': '..-. ..', 'ヅ': '.--. ..', 'デ': '.-.-- ..', 'ド': '..-.. ..', 
    'た': '-.', 'ち': '..-.', 'つ': '.--.', 'て': '.-.--', 'と': '..-..',
    'ナ': '.-.', 'ニ': '-.-.', 'ヌ': '....','ネ': '--.-', 'ノ': '..--', 
    'な': '.-.', 'に': '-.-.', 'ぬ': '....','ね': '--.-', 'の': '..--',    
    'ハ': '-...', 'ヒ': '--..-', 'フ': '--..', 'へ': '.', 'ホ': '-..',
    'バ': '-... ..', 'ビ': '--..- ..', 'ブ': '--.. ..', 'べ': '. ..', 'ボ': '-.. ..',
    'パ': '-... ..--.', 'ピ': '--..- ..--.', 'プ': '--.. ..--.', 'ペ': '. ..--.', 'ポ': '-.. ..--.',
    'は': '-...', 'ひ': '--..-', 'ふ': '--..', 'へ': '.', 'ほ': '-..',
    'ば': '-... ..', 'び': '--..- ..', 'ぶ': '--.. ..', 'べ': '. ..', 'ぼ': '-.. ..',
    'ぱ': '-... ..--.', 'ぴ': '--..- ..--.', 'ぷ': '--.. ..--.', 'ぺ': '. ..--.', 'ぽ': '-.. ..--.',
    'マ': '-..-', 'ミ': '..-.-', 'ム': '-', 'メ': '-...-', 'モ': '-..-.',    
    'ま': '-..-', 'み': '..-.-', 'む': '-', 'め': '-...-', 'も': '-..-.',
    'ヤ': '.--',  'ユ': '-..--', 'ヨ': '--',
    'ャ': '.--',  'ュ': '-..--', 'ょ': '--',
    'や': '.--',  'ゆ': '-..--', 'よ': '--',
    'ゃ': '.--',  'ゅ': '-..--', 'ョ': '--',
    'ラ': '...', 'リ': '--.', 'ル': '-.--.', 'レ': '---', 'ロ': '.-.-',    
    'ら': '...', 'り': '--.', 'る': '-.--.', 'れ': '---', 'ろ': '.-.-',
    'ワ': '-.-', 'ヲ': '.---', 'ン': '.-.-.',
    'わ': '-.-', 'を': '.---', 'ん': '.-.-.',
    '0': '-----', '1': '.----', '2': '..---', '3': '...--', '4': '....-',
    '5': '.....', '6': '-....', '7': '--...', '8': '---..', '9': '----.',
    '?': '..--..', '(': '.-..-.', ')': '.-..-.', 'ー': '.--.-', '-': '.--.-'}    

# 和文単語リスト
J_word_list = [
    "ハジメマシテ", "コンニチハ","ナマエ","シバラクデス","サヨーナラ", "ロールコール", 
    "アマチュア", "ハム", "タダイマカラ","ジューショ", "アシタ", "リグ", "タナカ" , 
    "カナガワ", "アツギ", "イセハラ", "オーブン" ,"デンシン", "デンワ", "サンポ", "サンカ", 
    "ハム", "タダイマカラ","トーキヨウ", "マチダ", "サガミハラ", "599" ,"コーシン", 
    "アリガトウゴザイマシタ", "マタオネガイシマス", "ワブン" ,"キハラ", "ナカムラ", "デンパ",
    "シンゴー","73","コンバンハ","オヤスミナサイ","ナントカ","パワー","テレビ","ヒトリ","デワマタ"
    ]

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)
    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)
    sound_array_stereo = np.column_stack((sound_array, sound_array))
    return pygame.sndarray.make_sound(sound_array_stereo)

def play_dot(frequency):
    dot_sound = generate_tone(frequency, DOT_DURATION)
    dot_sound.play()
    time.sleep(DOT_DURATION)

def play_dash(frequency):
    dash_sound = generate_tone(frequency, DASH_DURATION)
    dash_sound.play()
    time.sleep(DASH_DURATION)

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

def play_morse_code_for_letter(letter, frequency):
    morse_code = J_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)

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)

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)
    is_playing = False
    window.write_event_value('-FINISHED-', None)  # 発声完了時にイベント送信

def start_playback(word_sample, frequency):
    time.sleep(1)  # 表示の1秒後に再生を開始
    play_morse_code(word_sample, frequency)

layout = [
    [sg.Text('')],
    [sg.Text('和文モールス信号', text_color='blue', background_color='White', font=('Arial',18))],
    [sg.Radio('和文Auto', group_id=1, key='J_AUTO', text_color='blue', default=True),
     sg.Radio('和文KB', group_id=1, key='J_KB', text_color='blue')],
    [sg.Text('発声単語:')],
    [sg.Multiline(size=(40, 10), text_color='Blue', font=('Arial', 16, 'bold'), key='INPUT')],
    [sg.Text('単語数:'), sg.Slider(range=(10, 100), default_value=40, resolution=10, orientation='h', key='NBR')],
    [sg.Text('速度(Word/m):'), sg.Slider(range=(4, 20), default_value=14, resolution=1, orientation='h', key='WPM')],
    [sg.Text('周波数(Hz):'), sg.Slider(range=(300, 1000), default_value=700, resolution=100, orientation='h', key='FREQ')],
    [sg.Text('')],
    [sg.Button('Start', size=(10, 2), key='Start', disabled=False),
    sg.Button('End', size=(10, 2), key='End', disabled=False), sg.Push(),
    sg.Push(), sg.Text('JA1FML ver j7.0', text_color='LightBlue')]
]


win_location = (1100, 300)
win_size = (380, 600)

window = sg.Window('和文モールス受信 ver j7.0', layout, size = win_size, location = win_location, no_titlebar=True, grab_anywhere=True, finalize=True)

while True:
    event, values = window.read()

    if event == sg.WINDOW_CLOSED or event == 'End':
        if is_playing:
            sg.popup("発声中は「End」を押すことができません。")
        else:
            confirm_exit = sg.popup_yes_no('アプリを終了しますか?')
            if confirm_exit == 'Yes':
                break


    elif event == 'Start':
        if not is_playing:  # 発声中でないことを確認
            window['End'].update(disabled=True)  # 発声開始時にEndボタンを無効にする
            window['Start'].update(disabled=True)  # Startボタンを無効にする

            wpm = int(values['WPM'])
            frequency = int(values['FREQ'])

            DOT_DURATION, DASH_DURATION, SYMBOL_SPACE_DURATION, LETTER_SPACE_DURATION, WORD_SPACE_DURATION = calculate_timings(wpm)

            if values['J_AUTO']:
                nbr = int(values['NBR'])
                word_sample = random.sample(J_word_list, nbr)
                input_words = ' '.join(word_sample)

                window['INPUT'].update(input_words)  # 単語を入力欄に表示

                playback_thread = threading.Thread(target=start_playback, args=(word_sample, frequency), daemon=True)
                playback_thread.start()

            elif values['J_KB']:
                input_text = window['INPUT'].get().strip()
                if input_text:
                    word_sample = input_text.split()

                    playback_thread = threading.Thread(target=start_playback, args=(word_sample, frequency), daemon=True)
                    playback_thread.start()
                else:
                    sg.popup('発声単語を入力してください。')

    elif event == '-FINISHED-':  # 発声が完了したときの処理
        window['End'].update(disabled=False)
        window['Start'].update(disabled=False)

window.close()

 

 

PAGE TOP