とても情けないです
今振り上げたこぶしは芝居じみた正義だと感じる、かえるのクーの助手の「井戸中 聖」(いとなか セイ)でございます。
NO BORDERでよろしくおねがいします。

波について理解を深めます
さて、波の理解がまだまだであると痛感しておりますので、波についての基礎的な実験をして理解を深めます。結果は教科書とおりであることは分かっているのですが、なにごとも実際にやってみて、体感することがとても大切だと感じます。リアリティの差は決定的に深層心理での処理能力差に直結します。
2つの基本波形(sin波)をいろいろ操作してみます。(説明上、周波数AとBとします)
和(足し算)
|A-B|がうなりとして発生します。右耳で440Hzの音叉をならし、左耳で442HzにチューニングしたA音を聞けば、頭の中で2Hzのうなりが聞こえます。(両耳で両方聞くより断然おもしろいです)

(リアルタイムに実音をきけるプログラムを作成していますが、デバッグ中。。。完成したらどこかに貼ります。グラフ部のリアルタイム表示動作が安定しない状況です。
追記:バグがとれたので画面を貼りなおします。)
なお、「うなり」はモノラルにして聞けばわかりやすいですが、ステレオ(右左別周波数)の場合かなり個人差があるようです。うなりがほとんど感じられない方や、音がぐるんぐるん回っているように聞こえる方がいるようです。わたくしは、まぁまぁ感じられる方かなと思います。
差(引き算)
片方の波形を反転(=π(180°)位相変化)させたものの和に等しいです。
積(変調)
AM変調
波と波を単純に掛け算したものです。(片方の波はすべての値がマイナスにならないように、計算時にオフセットを加算します)
単純にAM変調すると、A、Bの周波数のほかに|A-B|、A+Bの周波数も発生します。いわゆる「ヘテロダイン」というやつです。600Hzを400Hzで変調すると、200Hzと1000Hzの音が現れるのが聞いていてもわかります。Leftがcarrier相当、Rightがsignal/modulator相当です。

FM変調
FM変調は「FM音源」で倍音をたくさん発生させるしくみとしても利用されています。基本周波数(キャリア)に対して、変調する周波数(シグナル)で周波数の増減を行います。変調する強度を変化させると倍音校正がかなり変化します。

電波といえばラジオについてのおさらい
懐かしのラジオでラジオの基本をおさらい 第1回 | マルツセレクト
懐かしのラジオで、ラジオの基本をおさらい 第2回 | マルツセレクト
FMラジオ完全自作はさすがになかなかないですね~
干渉計についてのおさらい
せっせと日曜プログラム
ひさしぶりにQt + Pythonで画面をつくったら、まったく忘れておりまして苦労しました。
特にマルチスレッドは通常のPythonマルチスレッドではなく、Qt(PyQt)で準備されているマルチスレッドを使用しないと、画面の割り込みがうまくできないことをすっかり忘れていました。
あいかわらずQt+Pythonはあまり人気がないようで、記事は多くないですね。自分用の備忘録が必要だと感じました。
上位天使(熾天使、智天使、座天使)の顕現を申請します

聖なる顕現の恐怖を以て愚かな人類をお導きください。よろしくお願いします
これは天使それとも使徒(ファンタジーファン&EVAファン必修科目)
(追記:バグがとれたので貼ります。実験プログラムなので、相変わらず醜いです)
ソースプログラム
Gitに載せるつもりですが、とりあえずのソースを貼っておきます。(Gitに載せたら、ここを消します)
SoundTest01.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.uic import loadUi
import pyaudio
import math
from enum import Enum
import numpy as np
import pyqtgraph
CHUNK = 1024
CHANNELS = 2
RATE = 48000
MAX_SIG = 32000
MAX_AREA = 32736
FORMAT = pyaudio.paInt16
MODE_STEREO = 1
MODE_MONO = 2
QT_FORM = 'SoundTest.ui'
class Mode(Enum):
STEREO = 1
MONO = 2
AM = 3
FM = 4
class Test(QMainWindow):
UPDATE_SECOND = 10
def __init__(self):
super(Test, self).__init__()
loadUi(QT_FORM, self)
self.setWindowTitle('Fromant Test')
self.r_freq = self.doubleSpinBox_right.value()
self.l_freq = self.doubleSpinBox_left.value()
self.p = SoundExperiment(self.r_freq, self.l_freq, Mode.STEREO)
# Update timer setting
self.timer = QTimer()
self.timer.timeout.connect(self.plot_update)
self.timer.start(self.UPDATE_SECOND)
self.data = np.zeros(1)
self.l_data = np.zeros(1)
self.r_data = np.zeros(1)
self.plotSpectrum.setYRange(-80, 0) # set axis y range
self.plotSpectrum.setXRange(0, 2000) # set axis x range
self.plotSignal.setYRange(-35000, 35000) # set axis y range
self.plotSignal.setXRange(0, 1023) # set axis x range
self.mode = None
@pyqtSlot()
def on_pushButton_start_clicked(self):
self.label_debug.setText('pushButton_start clicked')
self.p.start()
self.label_debug.setText('start')
@pyqtSlot()
def on_pushButton_stop_clicked(self):
self.label_debug.setText('pushButton_stop clicked')
self.p.sound_stop()
@pyqtSlot()
def on_pushButton_close_clicked(self):
self.label_debug.setText('pushButton_close clicked')
self.p = None
self.close()
@pyqtSlot(float)
def on_doubleSpinBox_left_valueChanged(self, w_float):
self.label_debug.setText('doubleSpinBox_left:{0}'.format(w_float))
self.l_freq = w_float
self.p.set_l_freq(w_float)
@pyqtSlot(float)
def on_doubleSpinBox_right_valueChanged(self, w_float):
self.label_debug.setText('doubleSpinBox_right:{0}'.format(w_float))
self.r_freq = w_float
self.p.set_r_freq(w_float)
@pyqtSlot(float)
def on_doubleSpinBox_modulation_valueChanged(self, w_float):
self.label_debug.setText('doubleSpinBox_modulationt:{0}'.format(w_float))
self.p.set_modulation(w_float)
@pyqtSlot()
def on_radioButton_stereo_clicked(self):
self.label_debug.setText('on_radioButton_stereo clicked')
self.p.set_mode(Mode.STEREO)
self.mode = Mode.STEREO
@pyqtSlot()
def on_radioButton_mono_clicked(self):
self.label_debug.setText('on_radioButton_mono clicked')
self.p.set_mode(Mode.MONO)
self.mode = Mode.MONO
@pyqtSlot()
def on_radioButton_am_clicked(self):
self.label_debug.setText('on_radioButton_am clicked')
self.p.set_mode(Mode.AM)
self.mode = Mode.AM
@pyqtSlot()
def on_radioButton_fm_clicked(self):
self.label_debug.setText('on_radioButton_fm clicked')
self.p.set_mode(Mode.FM)
self.mode = Mode.FM
def get_mode(self):
if self.radioButton_stereo.isChecked():
self.p.set_mode(Mode.STEREO)
elif self.radioButton_mono.isChecked():
self.p.set_mode(Mode.MONO)
def plot_update(self):
# Get audio input
if self.p is None:
return
data, l_data, r_data = self.p.get_data()
if data is None:
return
self.data = np.append(self.data, data) # Dummy Extend
if len(self.data) / 1024 > 10:
self.data = self.data[1024:]
self.fft_data = self.FFT_AMP(self.data)
self.fft_axis = np.fft.fftfreq(len(self.data), d=1.0 / RATE)
self.fft_power = 20.0 * np.log10(self.fft_data * self.fft_data) - 20 * np.log10(MAX_AREA * MAX_AREA) - 120
if self.mode == Mode.STEREO:
self.l_data = np.append(self.l_data, l_data) # Dummy Extend
self.r_data = np.append(self.r_data, r_data) # Dummy Extend
if len(self.l_data) / 1024 > 10:
self.l_data = self.l_data[1024:]
self.fft_l_data = self.FFT_AMP(self.l_data)
self.fft_l_axis = np.fft.fftfreq(len(self.l_data), d=1.0 / RATE)
self.fft_l_power = 20.0 * np.log10(self.fft_l_data * self.fft_l_data) - 20 * np.log10(MAX_AREA * MAX_AREA) - 120
if len(self.r_data) / 1024 > 10:
self.r_data = self.r_data[1024:]
self.fft_r_data = self.FFT_AMP(self.r_data)
self.fft_r_axis = np.fft.fftfreq(len(self.r_data), d=1.0 / RATE)
self.fft_r_power = 20.0 * np.log10(self.fft_r_data * self.fft_r_data) - 20 * np.log10(MAX_AREA * MAX_AREA) - 120
if self.mode == Mode.STEREO:
self.plotSignal.plot(x=range(1024), y=self.data[0:1024], clear=True, pen="y")
self.plotSignal.plot(x=range(1024), y=self.l_data[0:1024], clear=False, pen="g")
self.plotSignal.plot(x=range(1024), y=self.r_data[0:1024], clear=False, pen="r")
self.plotSpectrum.plot(x=self.fft_l_axis, y=self.fft_l_power, clear=True, pen="g")
self.plotSpectrum.plot(x=self.fft_r_axis, y=self.fft_r_power, clear=False, pen="r")
else:
self.plotSpectrum.plot(x=self.fft_axis, y=self.fft_power, clear=True, pen="y")
self.plotSignal.plot(x=range(1024), y=self.data[0:1024], clear=True, pen="y")
def FFT_AMP(self, data):
data = np.hamming(len(data)) * data
data = np.fft.fft(data)
data = np.abs(data)
return data
class SoundExperiment(QThread):
def __init__(self, r_freq, l_freq, mode):
super(SoundExperiment, self).__init__()
self.mutex = QMutex()
self.startFlg = False
self.index = 0
self.r_freq = r_freq
self.l_freq = l_freq
self.mode = mode
self.modulation = 1.0
self.pa = pyaudio.PyAudio()
self.wave_data = None
self.left_data = None
self.right_data = None
self.wave_bin_data = None
self.stream = None
def __del__(self):
self.sound_stop()
self.wait()
pass
def run(self):
if not self.startFlg:
self.startFlg = True
self.stream = self.pa.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
output=True)
while self.startFlg:
self.wave_bin_data, self.wave_data, self.left_data, self.right_data = self.create_data(CHUNK, self.index)
self.index += 1
self.stream.write(self.wave_bin_data)
self.stream.stop_stream()
self.stream.close()
else:
return
def sound_stop(self):
with QMutexLocker(self.mutex):
self.startFlg = False
def set_r_freq(self, r_freq):
self.r_freq = r_freq
def set_l_freq(self, l_freq):
self.l_freq = l_freq
def set_modulation(self, modulation):
self.modulation = modulation
def set_mode(self, mode):
self.mode = mode
def create_one_sample(self, a_sample_no, a_index):
if self.mode == Mode.STEREO:
float_data_l = math.sin(self.l_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG
float_data_r = math.sin(self.r_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG
elif self.mode == Mode.MONO:
float_data_l = (math.sin(self.l_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG \
+ math.sin(
self.r_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG) / 2
float_data_r = float_data_l
elif self.mode == Mode.AM:
float_data_l = (math.sin(self.l_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * \
(1.0 + math.sin(
self.r_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE)) / 2) * MAX_SIG
float_data_r = float_data_l
elif self.mode == Mode.FM:
signal_r = math.sin(self.r_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE)
float_data_l = math.sin(self.l_freq * 2 * math.pi * (
a_index * CHUNK + a_sample_no) / RATE + self.modulation * signal_r) * MAX_SIG
float_data_r = float_data_l
else:
float_data_l = math.sin(self.l_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG
float_data_r = math.sin(self.r_freq * 2 * math.pi * (a_index * CHUNK + a_sample_no) / RATE) * MAX_SIG
mixed = (float_data_l + float_data_r) / 2
byte_date_l = int(float_data_l).to_bytes(2, 'little', signed=True)
byte_date_r = int(float_data_r).to_bytes(2, 'little', signed=True)
return byte_date_l + byte_date_r, mixed, float_data_l, float_data_r
def create_data(self, a_chunk, a_index):
rtn_sample = b''
mixed = []
left = []
right = []
for sample_no in range(a_chunk):
one_sample, one_mixed, one_left, one_right = self.create_one_sample(sample_no, a_index)
rtn_sample += one_sample
mixed.append(one_mixed)
left.append(one_left)
right.append(one_right)
return rtn_sample, mixed, left, right
def get_data(self):
return self.wave_data, self.left_data, self.right_data
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Test()
window.show()
sys.exit(app.exec_())
SoundTest.ui
(作成操作は designer.exeでGUI操作で行います)

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>586</width>
<height>791</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="pushButton_start">
<property name="geometry">
<rect>
<x>300</x>
<y>170</y>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>40</x>
<y>70</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Left</string>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>400</x>
<y>120</y>
<width>31</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Hz</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>40</x>
<y>20</y>
<width>351</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="text">
<string>Sine Wave Experiment</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>240</x>
<y>70</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Right</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_close">
<property name="geometry">
<rect>
<x>350</x>
<y>270</y>
<width>81</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
<widget class="QDoubleSpinBox" name="doubleSpinBox_left">
<property name="geometry">
<rect>
<x>40</x>
<y>110</y>
<width>151</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="minimum">
<double>20.000000000000000</double>
</property>
<property name="maximum">
<double>20000.000000000000000</double>
</property>
<property name="value">
<double>442.000000000000000</double>
</property>
</widget>
<widget class="QDoubleSpinBox" name="doubleSpinBox_right">
<property name="geometry">
<rect>
<x>240</x>
<y>110</y>
<width>151</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>20000.000000000000000</double>
</property>
<property name="value">
<double>440.000000000000000</double>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>200</x>
<y>120</y>
<width>31</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>16</pointsize>
</font>
</property>
<property name="text">
<string>Hz</string>
</property>
</widget>
<widget class="QPushButton" name="pushButton_stop">
<property name="geometry">
<rect>
<x>300</x>
<y>210</y>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
<widget class="QLabel" name="label_debug">
<property name="geometry">
<rect>
<x>40</x>
<y>310</y>
<width>281</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Debug Display</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton_stereo">
<property name="geometry">
<rect>
<x>40</x>
<y>170</y>
<width>221</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Normal Stereo (L R Separate)</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton_mono">
<property name="geometry">
<rect>
<x>40</x>
<y>190</y>
<width>221</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Normal Mono (L+R ADD)</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton_am">
<property name="geometry">
<rect>
<x>40</x>
<y>210</y>
<width>251</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>AM (Amplitude Modulation)</string>
</property>
</widget>
<widget class="QRadioButton" name="radioButton_fm">
<property name="geometry">
<rect>
<x>40</x>
<y>230</y>
<width>251</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>FM (Frequency Modulation)</string>
</property>
</widget>
<widget class="QDoubleSpinBox" name="doubleSpinBox_modulation">
<property name="geometry">
<rect>
<x>170</x>
<y>250</y>
<width>62</width>
<height>22</height>
</rect>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>80</x>
<y>250</y>
<width>81</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>FM Modulation</string>
</property>
</widget>
<widget class="PlotWidget" name="plotSpectrum">
<property name="geometry">
<rect>
<x>20</x>
<y>540</y>
<width>551</width>
<height>191</height>
</rect>
</property>
</widget>
<widget class="PlotWidget" name="plotSignal">
<property name="geometry">
<rect>
<x>20</x>
<y>330</y>
<width>551</width>
<height>192</height>
</rect>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>586</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QGraphicsView</extends>
<header location="global">pyqtgraph</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
※Windowsで pip pyaudioはエラーになるので、以下の情報を参照ください。