親子IoTワークショップ

マイコンとクラウドを使ってIoTとプログラミングを学ぼう

[ トップ | 開催予定・概要 | 2024-12開催 | お知らせ | Facebook ]

PyAudioを使って音を作り、スピーカーから流す

ここでは、Pythonのプログラムで音を作り、それをスピーカーから流す方法を説明します。前段階として、「Bluetoothスピーカーを使う」を参考に、スピーカーをラズパイと接続して、スピーカーが使える状態になっていることを確認してください。

PyAudioモジュールのインストール

これからやることは、既存の音声・音楽ファイル(例えばMP3やAACのファイル)や、録音した音(例えばWAVファイル)をスピーカーから流すのではなく、音を一から(プログラムで)作ることです。音のファイルを再生してスピーカーから流す方法については、このページではなく、以下のページを参照してください。

Pythonを使って音を作るには、PyAudioと言うモジュールを使うとよいと思います。モジュールというのは、いくつかの機能をひとまとめにした集合体のことです。PyAudioは、音の作成、再生、録音などの機能をひとまとめにしたモジュールです。

まずは、PyAudioをラズパイにインストールします。Terminalで以下のコマンドを実行してください:

sudo apt install python3-pyaudio -y

ビープ音を出す

PyAudioをインストールし終わったら、sound.pybeep.pyをラズパイ上のThonnyへコピーして保存してください(この2つのファイルは同じフォルダの中に保存してください)。そしてbeep.pyを実行してください。「ピー」と言う音(ビープ音)が何度かスピーカーから鳴れば成功です。「PyAudioのモジュールが見つからない」というようなエラーメッセージが出ると、PyAudioのインストールが成功していない可能性が大です。ThonnyのShellに、エラーメッセージのようなものが何行も表示されると思いますが、音さえ出ていれば心配する必要はありません。単純に無視して構いません。

PyAudioを使って音を作ったり、鳴らしたりするプログラムには、先頭部分に以下の一行を入れてください(beep.pyの先頭行を参照)。

import sound

そして、以下のようにして、音を作る準備をします。具体的には、PyAudioを初期化しています。

stream = sound.init()

ここまでの2行は、PyAudioを使う際には必ずやらねばならないことなので、「おまじない」のようなものだと思ってください。init()という関数(function)が何をしているのかを詳しく知る必要はありません。一つだけ知っておくとよいのは、この関数が「ストリーム」というモノを返す、ということです。ストリームというのは、プログラムで作った音(のデータ)を送る出力先です。ストリームに音(のデータ)を送ると、ストリームはスピーカーと連携してその音を鳴らします。beep.pyでは、sound.init()が返したストリームを、streamという名前の変数(variable)に格納(保存)しています。

ストリームを作ったら、音を作って鳴らす作業に入ります。beep.pyでは、以下のようにsound.beep()という関数(function)を呼んでいます。

sound.beep(stream, 3)

sound.beep()を使う(呼ぶ)には、作成済みのストリームと、ビープ音を何秒鳴らしたいか(秒数の数値)を渡します。上記の例ではビープ音を3秒間鳴らす指示をしています。

beep.pyの残りの部分では、ループを使ってビープ音を繰り返し鳴らしています。「1秒鳴らして1秒黙る(sleepする)」のを10回繰り返しています。

ストリームは、必要なくなったら「閉じる」必要があります。プログラムの最後などで、以下のように閉じるようにしてください。

sound.close(stream)

音、波、周波数

日常生活での「音」というのは、なにか物が震えて(振動して)、その震え(振動)が空気中を伝わって耳に到達するもの、あるいはそのときに耳(聴覚)が認識するもののことです。例えばピアノを弾くと、ピアノの中の弦をハンマーが叩いて弦が震えます。この震えが空気中を伝わって耳に到達します。他の弦楽器も同様に、弦をこすったり、弾いたり、叩くことで弦を震わせて、その震えを空気中に伝えています。人がしゃべったり歌う時には、喉のなかで声帯が震えています。拍手する、机をたたく、物を床に落とすなど、音がする原因は常に「空気の震え(振動)」です。

振動が空気中を伝わる様子を絵やグラフにすると「波」になります。音は、目に見えない波(音波)として空気中を伝わって進む(伝搬する)もののことです。その波を耳(の中の鼓膜)がキャッチすると、人は「あ、音がする」と感じます。

波には周波数(frequency)という概念があります。これは、波がどのくらい激しく震えているかを表す数値です。1秒間に波が何回震えるか(揺れるか)を表します。したがって、周波数の高い波というのは、細かく、早く、激しく震える波のことで、周波数の低い波というのは、ゆっくり震える波のことです。輪ゴムの長さを変えて指で弾く時、ゴムの長さを短くするとゴムの震える速さは早くなります。これは音の波の周波数が高くなるということです。逆に、ゴムの長さを長くするとゴムの震える速さは遅くなります。これは音の波の周波数が小さくなるということです。また、ゴムで試せばすぐに分かるように、長いゴムを使うと低い音が出て、短いゴムを使うと高い音がでます。つまり耳は、周波数の高い波を「高い音」、周波数の低い波を「低い音」と認識します。なお、周波数の単位はHz(hertz:ヘルツ、英語ではハーツ)といいます。

ビープ音の高さ(周波数)を変える

音楽の世界では、個々の音(musinal note)の高さ(pitch)に固有の周波数が決めらています。例えば、C4という音(ピアノの鍵盤で中央付近にあるドの音)は261.626 Hz、その1オクターブ上のC5は523.251 Hz、という具合です。このように、1オクターブ上がると周波数が倍になり、1オクターブ下がると周波数が半分になる仕掛けになっています。2オクターブ上がれば、周波数は4倍(2倍の2倍)になり、2オクターブ下がると周波数は1/4(半分の半分)になります。

beep.pyでは、sound.beep()を使ってビープ音を鳴らしました。sound.beep()という関数は、周波数が523.251 Hzの音波を作っています。音楽用語でいうところのC5です(C4の1オクターブ上)。もし別の高さの音を鳴らしたい場合には、sound.playTone()という関数を使ってください。この関数を使ったサンプルプログラムがtone.pyです。

stream = sound.init()
sound.playTone(stream, 261.626, 3)

このように、sound.playTone()に渡すデータは以下の3つです。

この例では261.626 Hzの高さの音(音楽でのC4)を3秒間鳴らしています。

ここでは、音楽の音(の周波数)を例として使っていますが、sound.playTone()を使う時には、それ以外の周波数を使っても構いません。ただし、人間の耳で認識できる周波数は、下が20Hz程度、上が20,000 Hz(20 KHz)程度です。20より小さい数値や15,000より大きい数値でsound.playTone()を使うと、音波はスピーカーから出ているが人間には聞こえない、ということが起きます。ここで注意して欲しいのは、あまりに高い周波数の音(あまりに高い音)や、あまりに低い周波数の音(あまりに低い音)を聞きすぎると人体に悪影響があり得るということです。根を詰めて自分の耳の実験をしないようにしてください。ちなみに、CDに収録されている音楽では、22 KHzより高い周波数の音はカットされています。

ところで、超音波(ultrasound)というのは、周波数が高すぎて人間には聞こえない音波のことです(だいたい周波数が20 KHzより高い音波)。妊婦が受ける超音波検査(エコー検査)や、自動車が周辺の障害物を検出するセンサーなどで使われています。なお、人間以外の動物には20 KHzより高い音を聴き取れるものもいます。例えば、犬や猫は50 KHzから60 KHzまで聞こえるそうです(犬笛というのは人間には聞こえないが犬には聞こえる音を出す笛です)。また、イルカやコウモリは150 KHzまで聴き取り、その超音波を使って周囲の物との距離を測れる(真っ暗闇の中でも使えるソナーを備えている)そうです。

音階を鳴らす

C4やC5などの音名が出てきたので、音階(scale)を鳴らしてみましょう。C4のドからC5のドまで、ド・レ・ミ・ファ・・・ドと順番に音を鳴らします。サンプルプログラムはnote.pyです。

このプログラムでは、個々の音名を変数名にして、それぞれの変数に対応する周波数を格納しています。

C4, D4, E4 = (261.626, 293.665, 329.628)

例えば、C4という変数に261.626、D4という変数に293.665、E4という変数に329.628を格納しています。このような書き方をすると、複数の変数に複数の値を格納するのが一行でできます。以下のようにしても同じことはできますが、プログラムが短くなって読みやすくなる利点があります。

C4 = 261.626
D4 = 293.665
E4 = 329.628

ここでは、C4からC5までの音だけを使っています(C4からC5までの音だけに周波数を設定しています)。興味があれば、それより上の音や下の音も使ってみてください。また、シャープやフラットの音も使ってみてください。それぞれの音の周波数はGoogleで検索すればたくさん情報が見つかりますが、例えばここを参照ください。

音の長さを変える

これまで、sound.playTone()を使う際には、何秒音を鳴らしたいか(秒数)を指定してきました。秒指定が便利なときは多々ありますが、さほど便利ではないときもあります。例えば音楽の演奏で、どの音をどのくらいの長さで弾くかは直接秒指定されてはいません。楽譜を見ても「この音を1秒間弾け」というようなことは書いてありません。

楽譜の中では、全音符、半音符、四分音符、八分音符といった音符の種類にによって「長さ指定」がされています。それぞれの音符が何秒になるのかは、曲のテンポによって決まります。テンポの単位は「beats per minute (BPM)」といい、1分間に何回ビート(拍)を刻むかを表します。例えばBPMが60なら、1秒に1回ビート(拍)を刻みます。このとき、4/4拍子なら4分音符(quater note)が一拍の長さ、つまり1秒になります。つまり、全音符、半音符、八分音符は、四分音符の長さの4倍(4秒)、2倍(2秒)、半分(0.5秒)になります。

このようなBPMの数値と音符の種類から音の長さを計算するサンプルプログラムがnote2.pyです。以下のようにBPMをセットし(ここでは60)、そこから音符の種類ごとの長さを計算しています。

BPM = 60
wNote = (60/BPM) * 4
hNote, qNote, eNote = (wNote/2, wNote/4, wNote/8)

そして、sound.playTone()関数を使って音を鳴らす時には、音符の種類を指定します(秒数を明示的に指定するのではない)。以下の例では四分音符(qNote)を指定しています。つまりC4(ド)を四分音符で鳴らせと命令しています。

sound.playTone(stream, C4, qNote)

各音の長さを調節するには、冒頭部分の「BPM = 60」を変更します。例えば「BPM = 120」にすれば、曲のテンポが倍になるので、四分音符(qNote)の長さは半分になります。BPMの数値を変えてこのプログラムを何度か動かしてみてください。

次のサンプルプログラム、note3.pyは、実際の楽譜の情報をプログラム化したものです。「ドレミの歌」の最初の部分を使っています。楽譜の中の各音の高さと長さを忠実に使ってplayTone()を呼ぶプログラムです。このように音をひとつひとつプログラムで記述する作業を「打ち込み」などと呼びます。興味があれば、「ドレミの歌」の残りの部分を打ち込んでみてください(楽譜はGoogleで検索するとすぐに見つかります)。また、他の曲を打ち込みむのも楽しそうです。参考までに、バッハの「アリア」という曲(BWV988、ゴールドバーグ変奏曲)の最初の部分を打ち込んだのがbwv988-aria.pyです。

音の大きさ(ボリューム)を変える

音の大きさ(ボリューム)を変えることも可能です。以下のように、sound.playTone()を使う時にボリュームに関する指定を追加してください。

sound.playTone(stream, C4, qNote, 0.2)

このように、sound.playTone()に4つのデータを渡します。

音の大きさは0から1の間の数(小数)で表します。1.0が最大(100%)のボリューム、0.0が最小(0%)のボリュームです。

sound.playTone()を使うときにボリューム指定をしないと1.0がデフォルトの値として使われます。

volume.pyは、ド・レ・ミ・ファ(C4、D4、E4、F4)の4音を20%のボリュームで鳴らし、その後ソ・ラ・シ・ド(G4、A4、B4、C5)の4音を100%のボリュームで鳴らします。

複数の音を同時に鳴らす

これまでは、ひとつの(高さの)音だけを鳴らしてきましたが、ここでは複数の(高さの)音を同時に鳴らしてみます。そのためにsound.playTones()という関数を使います。「Tones」の部分が複数形になっている点に注意してください。サンプルプログラムはovertones-chords.pyです。

sound.playTones(stream, [261.626,523.251], 1)

このように、sound.playTones()を使うには3つのデータを渡します。

この例では、261.626と523.251という2つの周波数をリストにまとめて、それをsound.playTones()に渡しています。リストというのは、データが順番に並んでいるもののことで、[ と ]の間にデータを並べて表現します。リストについてはこのページを参照のこと。

ここでsound.playTones()は、261.626と523.251の周波数の音を両方同時に鳴らします。261.626はC4、523.251はC5の周波数なので、1オクターブ離れたドの音が同時に鳴ります。このように、倍の周波数の音のことを倍音といいます。

sound.playTones()に渡す周波数の数は2個だけでなく、3個でも4個でも、何個でも構いません。サンプルプログラム(overtones-chords.py)では、以下のように3つの周波数を渡して和音(コード)を鳴らしています。

sound.playTones(stream, [C4,E4,G4], 2)
sound.playTones(stream, [D4,F4,A4], 2)
sound.playTones(stream, [C4,F4,A4], 2)

最初にC4、E4、G4(ド、ミ、ソ)のコード、続いてD4、F4、A4(レ、ファ、ラ)、そしてC4、F4、A4(ド、ファ、ラ)を鳴らしています。

sound.playTone()と同じように、sound.playTones()もボリュームの調節ができます。以下のように、0と1の間の小数でボリューム指定を追加してください。

sound.playTones(stream, [C4,E4,G4], 2, 0.5)

この例ではボリュームを50%に絞っています。

鳴らした音をWAVファイルに保存する

ここでは、sound.playTone()やsound.playTones()で鳴らした音をデータとして保存する方法を説明します。サンプル・プログラムはsave-note-as-wav.pyです。

ここで新しいのは、関数sound.playTone()が返すデータを使っていることです。これまでは、以下のようにsound.playTone()を呼んで音を出しただけでした。

sound.playTone(stream, C4, hNote)

実はsound.playTone()には、音を出す以外にもうひとつ機能があり、それが音の元になっているデータを返すことです。以下のように、sound.playTone()が返すデータを変数(variable)に入れて、後で使うことができます。ここではsamplesという名前の変数を使っています。

samples = sound.playTone(stream, C4, hNote)
sound.saveSamplesAsWav(samples, "output1.wav")

そして、音の元になっているデータをsound.saveSamplesAsWav()に渡すと、そのデータをWAV(ウェイヴ、またはワヴ)形式のファイルに保存することができます。上の例のように、ファイル名も指定します(ここではoutput1.wav)。

ここでは「音の元になっているデータ」が具体的に何なのかは説明しません。それについては、「ビープ音(純音)をグラフにして「見える化」する」を参照してください。

保存したWAVファイルを再生するには、ファイル・マネージャーでダブルクリックするか、以下のコマンドをTerminalで実行してください。

vlc -I dummy output1.wav --play-and-exit

WAV形式は、音の「生データ」のようなもので、データの圧縮はなされていません。したがって、通常.wavファイルのサイズは巨大です。ここで圧縮とは、音の品質をできるだけ落とさずに、データのサイズを小さくすることです。音声を圧縮したデータ形式として有名なのがMP3やAACです。.wavファイルを.mp3ファイルへ変換するには、以下のコマンドを実行します。

ffmpeg -i output1.wav output1.mp3

.wavファイルを.aacファイルへ変換するには、以下のコマンドを実行します。

ffmpeg -i output1.wav output1.aac

sound.playTone()を使ってひとつの音を鳴らして保存するだけでなく、sound.playTones()を使って複数の音を鳴らして保存することもできます。save-note-as-wav.pyでは、以下のようにsound.playTones()から音の(元になっている)データを受け取り、sound.saveSamplesAsWav()に渡しています。

samples = sound.playTones(stream, [C4,E4,G4], hNote)
sound.saveSamplesAsWav(samples, "output2.wav")

ここでは、C4、E4、G4からなるコードを鳴らし、その音をoutput2.wavというWAVファイルに保存しています。

次のサンプル・プログラムsave-piece-as-wav.pyは、sound.playTone()やsound.playTones()を複数回使って曲(のような音)を鳴らして、それをWAVファイルに保存しています。以下のように、sound.playTone()やsound.playTones()を呼ぶたびに音の(元になっている)データを受け取り、それを連結していきます。音データを連結するにはsound.concatnateSamples()を使います。

allSamples = sound.playTone(stream, C4, 1)

samples = sound.playTone(stream, D4, 1)
allSamples = sound.concatnateSamples(allSamples, samples)

上の例では、まずC4の音1秒分のデータを変数allSamplesに入れ、次にD4の音1秒分のデータを変数samplesに入れています。そしてsound.concatnateSamples()を使って2つのデータを連結し、2秒分の音データを作っています。最初の1秒がC4の音で、残り1秒がD4の音になっています。

sound.concatnateSamples()は連結した音データを返します。上の例ではそれを変数allSamplesに入れています。そして、以下のようにsound.saveSamplesAsWav()を使って音データをWAVファイルに保存しています。

sound.saveSamplesAsWav(allSamples, "output3.wav")

自習プロジェクトの目次に戻る