Skip to content

Instantly share code, notes, and snippets.

@tam17aki
Last active January 10, 2022 01:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tam17aki/d5a580ff5b6be11dc4997fbd93c733ad to your computer and use it in GitHub Desktop.
Save tam17aki/d5a580ff5b6be11dc4997fbd93c733ad to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""PyAudioとPySimpleGUIを使ったリアルタイムFFTスペクトル表示."""
# Copyright (C) 2022 by Akira TAMAMORI
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# 参考: https://gist.github.com/KenoLeon/c4c32ee7d0b133b24211a2250d56fdce
import numpy as np
import pyaudio
import PySimpleGUI as sg
CHUNK = 1024
RATE = 16000
CHANNEL = 1
pAud = pyaudio.PyAudio()
SHORT_MAX = 32767
SHORT_MIN = -32768
EPSIRON = 1.0e-6
FONT = "Any 16"
WINSIZE = (512, 300)
LAYOUT = [
[
sg.Graph(
canvas_size=WINSIZE,
graph_bottom_left=(0, -10),
graph_top_right=(int(CHUNK / 2), 300),
background_color="white",
key="-GRAPH-",
)
],
[
sg.Button("収録開始", key="-LISTEN-", font=FONT),
sg.Button("収録停止", key="-STOP-", font=FONT, disabled=True),
sg.Button("終了", key="-EXIT-", font=FONT),
],
]
WINDOW = sg.Window("Waveform plot", LAYOUT, finalize=True)
GRAPH = WINDOW["-GRAPH-"]
STREAM = None
AUDIODATA = np.array([])
TIMEOUT = 10 # 10 msec for redraw
def stop():
"""音声収録を中止する."""
if STREAM is not None:
STREAM.stop_stream()
STREAM.close()
WINDOW["-STOP-"].update(disabled=True)
WINDOW["-LISTEN-"].update(disabled=False)
def callback(in_data, frame_count, time_info, status):
"""PyAudioのstream経由でデータを取得し、プロット用変数に格納する."""
global AUDIODATA
AUDIODATA = np.frombuffer(in_data, dtype=np.int16)
return (in_data, pyaudio.paContinue)
def listen():
"""音声収録を開始する."""
WINDOW["-STOP-"].update(disabled=False)
WINDOW["-LISTEN-"].update(disabled=True)
stream = pAud.open(
format=pyaudio.paInt16,
channels=CHANNEL,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback=callback,
)
stream.start_stream()
return stream
def plotWaveform():
"""FFTスペクトルをグラフに描画する."""
GRAPH.erase() # 再描画
# 軸を描く
GRAPH.draw_line((0, 0), (1024, 0))
GRAPH.draw_line((0, SHORT_MIN), (0, SHORT_MIN))
# 軸のラベルを描く
GRAPH.draw_text("kHz", location=(500, 10), color="black")
# FFTスペクトルプロット
fftspec = np.fft.fft(AUDIODATA)
fftspec = np.abs(fftspec) ** 2
fftspec = fftspec[0 : int(CHUNK / 2)]
fftspec = 20 * np.log10(fftspec + EPSIRON)
prev_x = prev_y = None
for x, y in enumerate(fftspec):
if prev_x is not None:
# 前の点と現在の点を線で結ぶ
GRAPH.draw_line((prev_x, prev_y), (x, y), color="red")
prev_x, prev_y = x, y
while True:
event, values = WINDOW.read(timeout=TIMEOUT)
if event in (sg.WIN_CLOSED, "-EXIT-"):
stop()
pAud.terminate()
break
if event == "-LISTEN-":
STREAM = listen()
elif event == "-STOP-":
stop()
elif AUDIODATA.size != 0:
plotWaveform()
WINDOW.close()
@PySimpleGUI
Copy link

E3lsDTen1v

This is really nice!!

Can you add screenshots of your awesome programs?

@PySimpleGUI
Copy link

I changed some of your calls to use the PEP8 compliant versions (update instead of Update, draw_line rather than DrawLine). Also replaced the window.FindElement(key), an older way elements were looked up, with the preferred window[key] way.

It's identical to your code in every way except these minor name changes.

I'm really impressed with this program! Thank you for making it.

#!/usr/bin/env python3

"""PyAudioとPySimpleGUIを使ったリアルタイムFFTスペクトル表示."""

# Copyright (C) 2022 by Akira TAMAMORI

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# 参考: https://gist.github.com/KenoLeon/c4c32ee7d0b133b24211a2250d56fdce

import numpy as np
import pyaudio
import PySimpleGUI as sg

CHUNK = 1024
RATE = 16000
CHANNEL = 1

pAud = pyaudio.PyAudio()
SHORT_MAX = 32767
SHORT_MIN = -32768
EPSIRON = 1.0e-6

FONT = "Any 16"
WINSIZE = (512, 300)
LAYOUT = [
    [
        sg.Graph(
            canvas_size=WINSIZE,
            graph_bottom_left=(0, -10),
            graph_top_right=(int(CHUNK / 2), 300),
            background_color="white",
            key="-GRAPH-",
        )
    ],
    [
        sg.Button("収録開始", key="-LISTEN-", font=FONT),
        sg.Button("収録停止", key="-STOP-", font=FONT, disabled=True),
        sg.Button("終了", key="-EXIT-", font=FONT),
    ],
]
WINDOW = sg.Window("Waveform plot", LAYOUT, finalize=True)
GRAPH = WINDOW["-GRAPH-"]
STREAM = None
AUDIODATA = np.array([])
TIMEOUT = 10  # 10 msec for redraw


def stop():
    """音声収録を中止する."""
    if STREAM is not None:
        STREAM.stop_stream()
        STREAM.close()
        WINDOW["-STOP-"].update(disabled=True)
        WINDOW["-LISTEN-"].update(disabled=False)


def callback(in_data, frame_count, time_info, status):
    """PyAudioのstream経由でデータを取得し、プロット用変数に格納する."""
    global AUDIODATA
    AUDIODATA = np.frombuffer(in_data, dtype=np.int16)
    return (in_data, pyaudio.paContinue)


def listen():
    """音声収録を開始する."""

    WINDOW["-STOP-"].update(disabled=False)
    WINDOW["-LISTEN-"].update(disabled=True)

    stream = pAud.open(
        format=pyaudio.paInt16,
        channels=CHANNEL,
        rate=RATE,
        input=True,
        frames_per_buffer=CHUNK,
        stream_callback=callback,
    )
    stream.start_stream()
    return stream


def plotWaveform():
    """FFTスペクトルをグラフに描画する."""
    GRAPH.erase()  # 再描画

    # 軸を描く
    GRAPH.draw_line((0, 0), (1024, 0))
    GRAPH.draw_line((0, SHORT_MIN), (0, SHORT_MIN))

    # 軸のラベルを描く
    GRAPH.draw_text("kHz", location=(500, 10), color="black")

    # FFTスペクトルプロット
    fftspec = np.fft.fft(AUDIODATA)
    fftspec = np.abs(fftspec) ** 2
    fftspec = fftspec[0 : int(CHUNK / 2)]
    fftspec = 20 * np.log10(fftspec + EPSIRON)

    prev_x = prev_y = None
    for x, y in enumerate(fftspec):
        if prev_x is not None:
            # 前の点と現在の点を線で結ぶ
            GRAPH.draw_line((prev_x, prev_y), (x, y), color="red")
        prev_x, prev_y = x, y


while True:
    event, values = WINDOW.read(timeout=TIMEOUT)

    if event in (sg.WIN_CLOSED, "-EXIT-"):
        stop()
        pAud.terminate()
        break
    if event == "-LISTEN-":
        STREAM = listen()
    elif event == "-STOP-":
        stop()
    elif AUDIODATA.size != 0:
        plotWaveform()

WINDOW.close()

@tam17aki
Copy link
Author

Hi,

Thank you very much for your comment.
I have updated the code!

Best regards,
Akira

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment