見出し画像

音源再生ライブラリ書き換え(6)

前回、Copilot先生に「一時停止機能」を追加してもらったMyPlayerクラスです。これで、まあ、動いていたようなので(一時停止と、同じ位置からの再開ができていた)、コードの中身を自分なりに「解読」してみます。

import time
from threading import Thread

import numpy as np
import sounddevice as sd
from pydub import AudioSegment


class MyPlayer:
    def __init__(self):
        super().__init__()
        self.filename = None
        self.audio = None
        self.audio_data = None  # オーディオデータ
        self.sr = None  # サンプルレート
        self.is_playing = False
        self.is_paused = False
        self.playstarttime = None
        self.pause_time = 0
        self.pause_sample = 0

    # ファイル読み込み
    def set_mp3file(self, filename):
        self.filename = filename
        self.audio = AudioSegment.from_mp3(filename)
        self.audio_data = np.array(self.audio.get_array_of_samples())
        if self.audio.channels == 2:
            self.audio_data = self.audio_data.reshape((-1, 2))
        self.sr = self.audio.frame_rate

    def play_audio(self):
        if self.is_paused:
            start_sample = self.pause_sample
            self.is_paused = False
        else:
            start_sample = 0
        self.is_playing = True
        sd.play(self.audio_data[start_sample:], samplerate=self.sr)
        sd.wait()
        self.is_playing = False

    def play(self):
        if not self.is_playing:
            self.playstarttime = time.time() - self.pause_time
            Thread(target=self.play_audio).start()

    def pause(self):
        if self.is_playing:
            self.is_paused = True
            self.pause_time = time.time() - self.playstarttime
            self.pause_sample = int(self.pause_time * self.sr)
            sd.stop()

    def stop(self):
        if self.is_playing or self.is_paused:
            sd.stop()
            self.is_playing = False
            self.is_paused = False
            self.pause_time = 0
            self.playstarttime = None

    @property
    def duration(self):
        return self.audio.duration_seconds

    @property
    def pos(self):
        if self.is_playing:
            return time.time() - self.playstarttime
        elif self.is_paused:
            return self.pause_time
        else:
            return None

class MyPlayer

init

インスタンス変数の宣言箇所
filename ・・・読み込んだmp3のファイル名
audio・・・pydubのAudioSegmentとして音声データを保持
audio_data・・・numpy配列として音声データを保持
sr・・・サンプルレート
is_playing ・・・再生中かどうか(bool) 初期値はfalse
is_paused ・・・一時停止中かどうか(bool) 初期値はfalse
playstarttime・・・再生開始時刻
pause_time・・・一時停止した位置(先頭からの秒数)
pause_sample・・・一時停止した位置(先頭からのフレーム数)

set_mp3file

mp3ファイルを読み込んで、filename、audio、audio_data、srに各情報をセットする。(特に今回、目新しい内容ではない。)

pause

新規のメソッド

    def pause(self):
        if self.is_playing:
            self.is_paused = True
            self.pause_time = time.time() - self.playstarttime
            self.pause_sample = int(self.pause_time * self.sr)
            sd.stop()

・if self.is_playing により、再生中だけ、機能する。
・・is_pausedをTrueにして、pause_timeを現在時刻と、開始時刻の差から現在の再生位置を求めてpause_timeに格納。そこまでのサンプリングデータ数を整数値でpause_sampleに格納して停止。
 再生中 is_playing :True ⇒is_paused はTrueになって再生は停止

※ぱっと見、pause中にpauseが再実行されると変なことになる?いや、play_audio抜けるときにself.is_playing = False が実行されているので一時停止中はis_playingはFalseになっており、影響なしのようです。

play_audioとplay

pause導入前

    def play_audio(self):
        if self.filename is not None:
            if not self.is_playing:
                self.is_playing = True
                # 音声の再生
                sd.play(self.audio_data, samplerate=self.sr)
                sd.wait()
                self.is_playing = False

    def play(self):
        if not self.is_playing:
            Thread(target=self.play_audio).start()

pause導入後

    def play_audio(self):
        if self.is_paused:
            start_sample = self.pause_sample
            self.is_paused = False
        else:
            start_sample = 0
        self.is_playing = True
        sd.play(self.audio_data[start_sample:], samplerate=self.sr)
        sd.wait()
        self.is_playing = False

    def play(self):
        if not self.is_playing:
            self.playstarttime = time.time() - self.pause_time
            Thread(target=self.play_audio).start()

解読
is_pausedではstart_sampleには停止位置のフレーム数が入る。そうでなければ0、つまり先頭から再生。
 is_paused状態から再生開始する場合は、開始時点を現在時刻から今の位置までの再生時間を引いた時点に過去にさかのぼって設定して、あたかも、その時点から再生が続いているかのように処理。

pos

    @property
    def pos(self):
        if self.is_playing:
            return time.time() - self.playstarttime
        elif self.is_paused:
            return self.pause_time
        else:
            return None

再生中なら、開始からの時間、pause中ならpause_timeを返す。

stop

    def stop(self):
        if self.is_playing or self.is_paused:
            sd.stop()
            self.is_playing = False
            self.is_paused = False
            self.pause_time = 0
            self.playstarttime = None

is_playing もしくは is_pausedの状態を解除。

ようやく理解できた(かな?)

 昨晩は、全く頭に入ってこなかった部分ですが、一日経ってどうにか今は、動作の流れを把握できたように自分では思っています。
 ここからいじるとしたら、時刻を求めるところでtimeモジュールの利用をやめて、前回やったsd.Stream().time を使う、ということくらいかな?


いいなと思ったら応援しよう!