« ^ »

今日やった事 - 20240830

所要時間: 約 2分

今日は台風が近付いているようで雨がよく降る。近くの川は、この雨の影響で氾濫するかもしれないらしい。この部屋は大雨が降ると雨漏りするため、天井から水が落ちてきている。いろんな枝葉の事を無視して、目標に対して取り組んできた。だから雨漏りぐらいはする。いつか雨漏りしない部屋に住んでみたい。確か、遠方にいる仲間達の住む土地にも近くに川がある。皆が元気でやっているのか、少し心配だ。ただ向こうには敵がいるから、そいつらは全員洪水で流されればいいなと思う。

speech2speeh

wavファイルから音声データを取得する

音声データの取得はsounddeviceで取得するが、検証時にはファイルから読み出した方が楽な事がある。そのためファイルから入力と同じ形式のデータを取得できるようにした。

import librosa
import numpy as np

original_wav_data, original_sampling_rate = librosa.load("hello.wav", sr=None)
sampling_rate = 16000
wav_data = librosa.resample(
    original_wav_data,
    orig_sr=original_sampling_rate,
    target_sr=sampling_rate)
事前準備

もしかすると、型は少し異なるかもしれない。

音声データを取得する

音声データはsounddevice経由で取得する。

import sounddevice
import numpy as np

SAMPLING_RATE = 16000
WINDOW_SIZE_SAMPLES = 512

def callback_sound(indata: np.ndarray[np.ndarray[np.int16]],
                   outdata, frames, time, status):
    pass

with sounddevice.Stream(samplerate=SAMPLING_RATE, dtype="int16",
                        channels=1, blocksize=WINDOW_SIZE_SAMPLES,
                        callback=callback_sound):
    while True:
        time.sleep(0.01)

元々はFFmpegを使って標準入出力で受け渡しをしていたが、データを処理しずらいため、この実装に変更した。

ノイズリダクション

発話検出

import numpy as np
import torch

vad_model, utils = torch.hub.load("snakers4/silero-vad", "silero_vad")

(get_speech_timestamps,
 save_audio,
 read_audio,
 VADIterator,
 collect_chunks) = utils

vad_model.reset_states()

sound_data = torch.Tensor(wav_data)

window_size = 512  # Sampling rateが16000の場合512
chunk = sound_data[0:window_size]  # 検出したい箇所を切り出す

ret = vad_model(chunk, sampling_rate)
speech_prob = ret.item()  # 閾値よりも大きな数字であれば発話と見做す

if speech_prob > 0.5:  # 発話検出の閾値。ここは自分で調整 (例: 0.5)
    print("発話中")
else:
    print("発話なし")

文字起こし

from reazonspeech.nemo.asr.audio import audio_from_tensor, audio_from_numpy
from nemo.collections.asr.models import EncDecRNNTBPEModel
from reazonspeech.nemo.asr.interface import AudioData
from reazonspeech.nemo.asr.transcribe import transcribe

rez_model = EncDecRNNTBPEModel.from_pretrained(
    'reazon-research/reazonspeech-nemo-v2',
    map_location="cpu")

audio: AudioData = audio_from_numpy(wav_data, sampling_rate)
ret = transcribe(rez_model, audio)
text = "".join(sg.text for sg in ret.segments)
print(text)

回答生成

from mlx_lm import load, generate

model, tokenizer = load("mlx-community/Llama-3-Swallow-8B-Instruct-v0.1-8bit")
response = generate(model, tokenizer, prompt="hello", verbose=True)
print(response)

テキストを音声データに変換する

import sounddevice
import numpy as np
import melo.api

sentence = "こんにちわ"

model = melo.api.TTS(language="JP", device="mps")
print(model.hps.data.spk2id)

audio_chunk = model.tts_to_file(sentence, model.hps.data.spk2id["JP"], quiet=True)
audio_chunk2 = librosa.resample(audio_chunk, orig_sr=44100, target_sr=16000)
audio_chunk3 = (audio_chunk2 * 32768).astype(np.int16)

sounddevice.play(audio_chunk3, 12100)

音を出す

import sounddevice as sd
import numpy as np


samplerate = 44100  # サンプリングレートの設定
duration = 2.0  # 秒数
frequency = 440.0  # 再生する音の周波数

# サイン波を生成
t = np.linspace(0, duration, int(samplerate * duration), endpoint=False)
waveform = 0.5 * np.sin(2 * np.pi * frequency * t)

def callback(outdata, frames, time, status):
    # デフォルトでは np.int16 形式でデータを渡す
    outdata[:] = (waveform[:frames] * 32767).astype(np.int16).tostring()

# RawOutputStreamを使用してデータをストリーミング
with sd.RawOutputStream(samplerate=samplerate, channels=1, dtype='int16', callback=callback):
    # duration秒間のデータをストリーミング
    sd.sleep(int(duration * 1000))