โค้ดสร้าง Ai เองภาษาไทย

กระทู้สนทนา
ต้องไปหัดพูดให้มันฟังทุกวันมันจะฝึกพูดเอง


import numpy as np
import time

# =====================================================================
# ส่วนที่ 1: ตัวกรองเสียงและแปลงข้อมูล (Audio Processing -> .npy)
# แก้ไข: LUFS normalization ทำบน amplitude ถูก domain แล้ว
# =====================================================================
class AudioProcessor:
    def __init__(self, chunk_duration_sec=0.5):
        self.LOW_CUT = 20
        self.HIGH_CUT = 20000
        self.MALE_RANGE = (85, 180)
        self.FEMALE_RANGE = (165, 255)
        self.TARGET_LUFS = -20

        # แก้ไข: คำนวณ silence limit จาก chunk_duration แทน hardcode
        self.chunk_duration_sec = chunk_duration_sec
        self.silence_limit_sec = 3.0  # ตัดเสียงหลังเงียบ 3 วินาที
        self.silence_limit_chunks = max(1, int(self.silence_limit_sec / self.chunk_duration_sec))
        print(f"ℹ️ [Config] ตัดเสียงหลังเงียบ {self.silence_limit_sec}s = {self.silence_limit_chunks} chunks")

    def filter_and_convert_to_npy(self, raw_microphone_data):
        """
        กรองขยะแล้วแปลงเป็นตัวเลข FQ, db, Time ทันที
        แก้ไข: LUFS normalization ทำบน amplitude (linear) ไม่ใช่ค่า dB โดยตรง
        """
        # 1. Low-cut / High-cut Filter
        is_valid_freq = (raw_microphone_data['freq'] >= self.LOW_CUT) & \
                        (raw_microphone_data['freq'] <= self.HIGH_CUT)
        if not np.any(is_valid_freq):
            return None

        # 2. แก้ไข LUFS Normalization:
        #    - แปลง dB -> amplitude (linear) ก่อน
        #    - คำนวณ RMS บน amplitude
        #    - หาร scale_factor แล้วแปลงกลับเป็น dB
        db_values = raw_microphone_data['db']
        amplitude_linear = 10 ** (db_values / 20.0)          # dB -> linear amplitude
        current_rms = np.sqrt(np.mean(amplitude_linear ** 2)) # RMS บน linear ถูกต้อง

        target_amplitude = 10 ** (self.TARGET_LUFS / 20.0)   # -20 LUFS -> linear

        if current_rms > 0:
            scale_factor = target_amplitude / current_rms
            normalized_amplitude = amplitude_linear * scale_factor
        else:
            normalized_amplitude = amplitude_linear

        # แปลงกลับเป็น dB เพื่อเก็บใน matrix
        normalized_db = 20 * np.log10(np.maximum(normalized_amplitude, 1e-10))

        # 3. สร้าง Matrix แล้ว Return ออกมาเป็น .npy
        npy_matrix = np.column_stack((
            raw_microphone_data['freq'][is_valid_freq],
            normalized_db[is_valid_freq],
            raw_microphone_data['time'][is_valid_freq]
        ))
        return npy_matrix

    def is_human_speaking(self, npy_matrix):
        """ตรวจสอบว่าเป็นเสียงมนุษย์ไหม (ตรวจจากช่วง FQ เพศชาย/หญิง)"""
        if npy_matrix is None:
            return False
        avg_fq = np.mean(npy_matrix[:, 0])
        if (self.MALE_RANGE[0] <= avg_fq <= self.MALE_RANGE[1]) or \
           (self.FEMALE_RANGE[0] <= avg_fq <= self.FEMALE_RANGE[1]):
            return True
        return False


# =====================================================================
# ส่วนที่ 2: สมอง AI ภาษาไทยโดยตรง + สร้างเสียงจากคลื่นหลายคลื่น
# แก้ไข: think_thai_text เทียบด้วยค่าเฉลี่ย ป้องกัน shape mismatch
# =====================================================================
class ThaiBrain:
    def __init__(self):
        # สมองจำความถี่หลายๆ คลื่น (Harmonics) เพื่อจะได้สร้างเสียงพูดได้ ไม่ใช่เสียงติ๊งเดียว
        self.memory = {
            "สวัสดี":   np.array([[150.0, -20.0, 0.1], [800.0, -25.0, 0.15], [160.0, -20.0, 0.2]]),
            "ปูมมะรุม": np.array([[120.0, -20.0, 0.5], [600.0, -28.0, 0.55], [130.0, -20.0, 0.6]]),
        }
        self.thinking_weights = np.ones(3)  # เริ่มต้นด้วย 1 ก่อน ไม่ random เพื่อความเสถียร

    def _get_feature_vector(self, npy_matrix):
        """
        แก้ไข: แปลง matrix ขนาดไม่แน่นอน -> feature vector ขนาดคงที่ (3,)
        โดยเอาค่าเฉลี่ยของ FQ, db, Time ซึ่ง shape ตรงกันเสมอ
        """
        return np.mean(npy_matrix, axis=0)  # shape: (3,) เสมอ

    def think_thai_text(self, list_of_npy_chunks):
        """
        คิดเป็น txt ภาษาไทยจากก้อนเสียงทั้งหมดที่พูด
        แก้ไข: เทียบด้วย feature vector ขนาดคงที่ ป้องกัน shape mismatch
        """
        if not list_of_npy_chunks:
            return "???"

        full_sound_matrix = np.vstack(list_of_npy_chunks)
        input_vector = self._get_feature_vector(full_sound_matrix)  # (3,)

        best_word = "???"
        min_error = float('inf')

        for thai_txt, sound_vectors in self.memory.items():
            memory_vector = self._get_feature_vector(sound_vectors)  # (3,) เสมอ
            # ตอนนี้ทั้งคู่ shape (3,) เปรียบเทียบได้แน่นอน
            error = np.sum(np.abs(memory_vector - (input_vector * self.thinking_weights)))
            if error < min_error:
                min_error = error
                best_word = thai_txt

        return best_word

    def learn_and_remember(self, list_of_npy_chunks, correct_thai_txt):
        """มนุษย์แก้ผิด -> AI เลียนแบบและจดจำเสียงคำนั้น"""
        full_sound_matrix = np.vstack(list_of_npy_chunks)
        self.memory[correct_thai_txt] = full_sound_matrix
        # ปรับน้ำหนักสมองเล็กน้อย (learning rate 0.01 ป้องกันไม่ให้บวมเกินไป)
        self.thinking_weights = np.clip(self.thinking_weights + 0.01, 0.1, 10.0)
        print("🧠 [สมอง]: จดจำการออกเสียงนี้ในภาษาไทยแล้วครับ")

    def speak_sine_wave(self, thai_txt):
        """ดึงเลข FQ, db มาสร้างคลื่นเสียงใหม่หลายคลื่นบวกซ้อนกัน (Superposition)"""
        if thai_txt not in self.memory:
            return None

        sound_data = self.memory[thai_txt]
        sample_rate = 48000
        duration = 0.3

        t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)
        final_wave = np.zeros_like(t)

        # วนลูปสร้างคลื่นจากทุกๆ FQ ที่จดจำไว้แล้วบวกกัน
        for i in range(len(sound_data)):
            target_FQ = sound_data[i, 0]
            target_db  = sound_data[i, 1]

            single_sine = np.sin(2 * np.pi * target_FQ * t)
            amplitude   = 10 ** (target_db / 20.0)
            final_wave += single_sine * amplitude

        # Normalize ไม่ให้ clip เกิน (ป้องกันเสียงแตก)
        max_val = np.max(np.abs(final_wave))
        if max_val > 0:
            final_wave = final_wave / max_val * 0.8

        output_wave_16bit = np.int16(final_wave * 32767)
        return output_wave_16bit


# =====================================================================
# ส่วนที่ 3: ระบบ UI + ตัดเสียงอัตโนมัติ (ไม่ตัดตอนกำลังพูด)
# แก้ไข: silence_limit_chunks ดึงมาจาก AudioProcessor โดยตรง
# =====================================================================
class SmartAudioUI:
    def __init__(self, chunk_duration_sec=0.5):
        self.processor = AudioProcessor(chunk_duration_sec=chunk_duration_sec)
        self.brain = ThaiBrain()

        # ดึงค่า limit จาก processor (ไม่ hardcode ซ้ำ)
        self.silence_limit_chunks = self.processor.silence_limit_chunks

        self.silence_counter  = 0
        self.is_recording     = False
        self.temp_npy_buffer  = []

    def run(self):
        print("=" * 60)
        print("🎤 ระบบ AI ฟัง-พูด ภาษาไทย (Auto-Cut Active)")
        print("ℹ️ ระบบจะบันทึกเมื่อมีคนพูด และตัดอัตโนมัติเมื่อเงียบ")
        print("=" * 60)

        # =====================================================
        # จำลอง Stream จากไมค์ (chunk ละ 0.5 วินาที)
        # ในความเป็นจริง: แทนด้วย loop อ่าน PyAudio stream
        # silence_limit_chunks = 3 chunks * 0.5s = 1.5s (demo)
        # หรือจะตั้ง chunk_duration_sec=0.5, silence_limit_sec=3.0 = 6 chunks
        # =====================================================
        fake_mic_stream = [
            # chunk 1: เงียบ (ขยะใต้ 20Hz)
            {'freq': np.array([15.0, 10.0]),   'db': np.array([-5.0,  -8.0]),  'time': np.array([0.0, 0.1])},
            # chunk 2: เริ่มพูด (150Hz + Harmonic 800Hz)
            {'freq': np.array([150.0, 800.0]), 'db': np.array([-10.0, -15.0]), 'time': np.array([0.2, 0.3])},
            # chunk 3: พูดต่อ
            {'freq': np.array([155.0, 810.0]), 'db': np.array([-12.0, -16.0]), 'time': np.array([0.4, 0.5])},
            # chunk 4: หยุดพูด (เงียบ #1)
            {'freq': np.array([20.0, 25.0]),   'db': np.array([-40.0, -42.0]), 'time': np.array([0.6, 0.7])},
            # chunk 5: เงียบ #2
            {'freq': np.array([18.0, 22.0]),   'db': np.array([-41.0, -43.0]), 'time': np.array([0.8, 0.9])},
            # chunk 6: เงียบ #3 -> ตัดเสียง (3 chunks * 0.5s = 1.5s)
            {'freq': np.array([19.0, 21.0]),   'db': np.array([-42.0, -44.0]), 'time': np.array([1.0, 1.1])},
            # chunk 7: เงียบ #4 (หลังตัดแล้ว ระบบรอคนพูดใหม่)
            {'freq': np.array([10.0, 12.0]),   'db': np.array([-50.0, -50.0]), 'time': np.array([1.2, 1.3])},
        ]

        for chunk_idx, raw_chunk in enumerate(fake_mic_stream):
            print(f"\n[Stream chunk {chunk_idx + 1}/{len(fake_mic_stream)}]")

            # 1. แปลงเป็น .npy
            npy_data = self.processor.filter_and_convert_to_npy(raw_chunk)

            # 2. ตรวจว่าเป็นเสียงมนุษย์ไหม
            has_human_voice = self.processor.is_human_speaking(npy_data)

            # --------------------------------------------------
            # กรณีที่ 1: ไม่มีเสียงคนพูด
            # --------------------------------------------------
            if not has_human_voice:
                if not self.is_recording:
                    # ยังไม่เคยเริ่มบันทึก -> รอเฉยๆ
                    print("🚫 ไม่มีเสียงคนพูด รอ...")
                    continue
                else:
                    # กำลังบันทึกอยู่แล้วพึ่งเงียบ -> นับ
                    self.silence_counter += 1
                    print(f"⏸️ หยุดพูดชั่วคราว... นับเงียบ {self.silence_counter}/{self.silence_limit_chunks}")

                    if self.silence_counter >= self.silence_limit_chunks:
                        print("\n" + "=" * 40)
                        print("✂️ [ตัดเสียงอัตโนมัติ!] เงียบเกินเกณฑ์")
                        self.proces
แก้ไขข้อความเมื่อ
แสดงความคิดเห็น
โปรดศึกษาและยอมรับนโยบายข้อมูลส่วนบุคคลก่อนเริ่มใช้งาน อ่านเพิ่มเติมได้ที่นี่