Rasberry Piに繋げた音センサーが反応したら音楽を鳴らしてみる


はじめに

今回の記事は、前回の記事(Rasberry Piで音センサーと連携する)の続きです。
「赤ちゃんが泣いたら、ランダムな音楽を鳴らす」というアプリを、Raspiで構築しようとしています。
※音センサーを使用したいだけの方は、前回の記事だけ参照してもらえば問題ありません。

この仕組みを作るためには、以下のことができなければなりません。
1.Rasberry Piで音楽を鳴らす
2.音センサーで検知させる
3.センサー検知後に、ランダムな曲を選択する
などなど

前回までで「1.Rasberry Piで音楽を鳴らす」と「2.音センサーで検知させる」はできるようになったので、
今回は、「3.センサー検知後に、ランダムな曲を選択する」ということを主眼に記事を書いていきます。

環境(準備するもの)

・Python
・Rasberry Pi
・USBミニスピーカ(DAISO製)
・音センサー(Grove - Loudness Sensor)
・ジャンパーケーブル(メス-メス)

※音センサーは、ここからポチりました。
※ジャンパーケーブル(メス-メス)は、ここからポチりました。

今回やりたいこと

初回の記事では、「特定の音楽を鳴らす」ということができていました。
毎回同じ音楽では飽きてしまうので、以下のような仕様にしたいと思います。
「特定のフォルダに格納された音楽ファイルから、任意の音楽を鳴らす」

さて、前回までの記事を読み返すと、現状は以下のことはできそうです。
・音センサーが反応した時に
・特定の音楽を鳴らす

どうやら、実現したいことと、できることの差異は「任意の音楽をかける」という部分だけクリアすれば良さそうです。

任意の楽曲ファイル名の取得

「特定のフォルダに格納された音楽ファイルから、任意の音楽を鳴らす」を要素分解してみましょう
これはどうやら以下のことを満たせば実現できそうです。

・特定のフォルダに格納された音楽ファイルから1曲を抽出する
・抽出した曲を、プレーヤーに渡す

たったこれだけですね。

このうち「・特定のフォルダに格納された音楽ファイルから1曲を抽出する」は、以下の感じでできました。
音楽ファイルは「/home/pi/work/mp3」フォルダに、mp3拡張子として配置しているものとします。

def get_music():
        musicFiles = [r.split('/')[-1] for r in glob.glob('/home/pi/work/mp3/*.mp3')]
        musicName = random.choice(musicFiles)
        return musicName

簡単に説明すると、

musicFiles = [r.split('/')[-1] for r in glob.glob('/home/pi/work/mp3/*.mp3')]

上記の処理では「/home/pi/work/mp3」に格納したmp3のファイル名を全て取得する、という処理になります。
要素を分解すると、
「glob.glob('/home/pi/work/mp3/*.mp3')」の部分でファイル名は取得できます。
しかしこれでは数得したファイル名が以下のようになってしまいます。
・/home/pi/work/mp3/XXX.mp3
・/home/pi/work/mp3/YYY.mp3
・/home/pi/work/mp3/ZZZ.mp3
でも欲しいのは、「XXX.mp3」とか「YYY.mp3」というファイル名であり、フォルダ名は不要だったりします。

そのため、これらのファイルを一度変数rに格納し、(for r in )繰り返し処理をします。
なんの処理を繰り返すかというと、余分な「/home/pi/work/mp3/」を削除し、純粋なファイル名のみを取得します。

r.split('/')[-1]

上記の処理をすることで、"/"ごとに文字列が区切れます。
「home」、「pi」、「work」、「mp3」、「XXX.mp3」という5つの文字列に分割できます。(split関数の処理)
このうち、一番右の「XXX.mp3」という文字列が欲しいので、[-1]で一番右の文字列を取得します。
もしも

r.split('/')[1]

とすると、一番左の文字列が取得できるので、「home」が取得できます。
(正の数は左から。負の数は右から値を取る、みたいな感じですね)

ここで変数「musicFiles」には、ファイル名の一覧(XXX.mp3、YYY.mp3、ZZZ.mp3)が格納されたので、任意の1つを抽出します。

musicName = random.choice(musicFiles)

という処理がそれです。

任意の1つのファイル名を選択したので、呼び出し元にreturnします。

音楽再生の処理

音楽をかけるメソッドは、初回記事を参考にして、以下のような感じで良さそうです。

def play_music(musicName):
        musicFile = '/home/pi/work/mp3/' + musicName

        #音楽のメタ情報を取得する
     #MP3関数を使うには「from mutagen.mp3 import MP3」を指定しておくこと
        audio = MP3(musicFile)
        #音楽の長さを表示する
        print (audio.info.length)

        #音楽を再生する
        command = 'omxplayer ' +  musicFile
        proc = subprocess.Popen(command,shell=True,stdin=subprocess.PIPE)

        #-- 音楽が終わるまで(音楽の長さ分)センサー反応を止める処理()  --
        time.sleep(audio.info.length)
        #-------------------------------------

        #終了処理
        proc.stdin.write("q")

コメントに記載したので、各関数の解説はここではしないです。
さっきの関数と違って、1行でいろんな処理を詰め込んでいないので、比較的やってることは読みやすいかと思います。

ただ。。。
冒頭で「musicFile = '/home/pi/work/mp3/' + musicName」とやっているように、
曲名選択のメソッドで、わざわざ「'/home/pi/work/mp3/'」の部分を分割しなくてもよかったですね。
テヘペロ

出来上がりのファイルはこちら

ということで、あとは出来上がりのファイルを載せておきます。
私のraspiに配置しているファイルをそのまま載せます。
コメントが汚いとか、同じもモジュールを何回もインポートしているとかありますが、寛大な目でみてやってください。

otosensor_music_new.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SEN02281P ----- RaspberryPi GPIO
# =SIG ---------- 13
# =NC
# =VCC ---------- 2
# =GND ---------- 20

#import paho.mqtt.client as mqtt
import time
import RPi.GPIO as GPIO

import subprocess
import time

from mutagen.mp3 import MP3

import glob
import random


#def on_connect(client,userdata,flags,rc):
#   print( "Connection with result code " + str(rc) )
#   client.subscribe( "sen02281p_1" )
#
#def on_message(client,userdata,msg):
#   print( msg.topic + " " + str(msg.payload) )

def get_music():
        musicFiles = [r.split('/')[-1] for r in glob.glob('/home/pi/work/mp3/*.mp3')]
        musicName = random.choice(musicFiles)
        return musicName


def play_music(musicName):
        musicFile = '/home/pi/work/mp3/' + musicName

        audio = MP3(musicFile)
        print (audio.info.length)

        #動画を再生する
        command = 'omxplayer ' +  musicFile
        proc = subprocess.Popen(command,shell=True,stdin=subprocess.PIPE)

        #-- なんらかの処理()  --
        time.sleep(audio.info.length)
        #time.sleep(10)
        #-------------------------------------

        #終了処理
        proc.stdin.write("q")

def reading(sensor):
    sum = -1 
    if sensor == 0:
#                print "test function."
        sum = 0
        for i in range(0,20):
            time.sleep(0.1)
            a = GPIO.input(SIG)
            sum += a
#                        print sum
    else:
        print "Incorrect function."

    return sum

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
SIG = 13
GPIO.setup(SIG,GPIO.IN)

#client = mqtt.Client()
#client.on_connect = on_connect
#client.on_message = on_message

#client.connect( "iot.eclipse.org", 1883 )
music_flg = 0
fix_music_flg_ZERO = 0
music_threshold = 5
#while client.loop() == 0:
while True:
    msg = reading(0);
        print(msg)
    #pass

        if msg > 5:
            music_flg+=1
        else:
            music_flg = fix_music_flg_ZERO

        if music_flg > music_threshold:
            musicName = get_music();
            play_music(musicName);
            music_flg = fix_music_flg_ZERO

            time.sleep(10)


GPIO.cleanup()
print('Finiish')

最後のwhile文のところだけコメントを記して、簡単に説明しておきます。

fix_music_flg_ZERO = 0
music_threshold = 5
while True:
    msg = reading(0); ##以下の説明であるように、センサーが出力した数値を取得
        print(msg)
    #pass

        if msg > 5:       ##センサーの出力が「5」以上だったら、
            music_flg+=1   ##連続カウント(music_flg)を増やす
        else:              ##センサーの出力が「5」未満だったら、
            music_flg = fix_music_flg_ZERO    ##連続カウント(music_flg)を初期値(0)に戻す

        if music_flg > music_threshold:   ##連続カウント(music_flg)がmusic_threshold(5)より大きい場合
            musicName = get_music();   ##楽曲名を取得
            play_music(musicName);       ##楽曲をかける
            music_flg = fix_music_flg_ZERO   ##連続カウント(music_flg)をfix_music_flg_ZERO(0)に戻す

            time.sleep(10)       ##すぐに音楽がかかるのも嫌なので、10秒待たせる

前回の記事でも触れたように、音センサーが反応すると、以下のようにセンサーは音の反応を数値で出力します。

この数値が変数「msg」です。
使用しているセンサーでは、最大値が20ですが、
「5」が5回より大きい(6以上)連続して発生した場合に、音楽を鳴らすというようにしています。

ファイルの実行

こんな感じです。
「Audio Codec~~~」のところで音楽がかかっています。

最後に

冒頭でも書いたように、「赤ちゃんが泣いたら、ランダムな音楽を鳴らす」というアプリを、Raspiで構築したのですが、
音楽が鳴った程度では赤子は泣きやみません
愛情を持って抱っこしてあげましょう

子育て前線からは以上でーす。