楽しく運動出来るプロダクトを作ってみた。~新卒3年目がPythonとProcessingでスポーツセンシング映像作り~

# はじめに

前回に引き続きメンバーとIoT機器を使ったプロダクトを作ろうということで
今回は主に映像のコーディング部分担当に。
作成期間の22年5月頃から22年9月までの約4か月間を振り返ります。

ちなみに前回のプロダクト作成の記事はこちら。

https://qiita.com/nanano08/items/d946f31a7ec5aa0164ad

# プロダクトの全体像
スポーツセンシングという事で今回は4種類の運動を
M5stackCore2のジャイロセンサーの値で判別&データをPCに送信。
この部分は前回と同じく担当したメンバーの記事があるのでぜひ見てみて下さい。

https://qiita.com/KechanaGohan/items/c40f35889b6b533f4632


私が作成担当した部分は
ジャイロセンサーの値などデータをBluetoothでPC(=映像を出力する側)で受け取り。
値を反映させた映像、ジェネラティブアートを出力する形とした。
ちなみに運動前中後ごとに音楽も流すようにした。この部分を主に私が作成した感じ。
<br>
成果物はこちら。Python，Processingのスクリプトと音楽ファイルを格納しています。

https://github.com/nanano08/Sports-Sensing-PC-side-Codes


# 使った環境と言語とライブラリ
作成した中でも紹介したい機能を挙げていく。

<br>

<dt>・データをBluetoothで受け取る部分</dt>
<dt>・データをProcessingに送信する部分</dt>
<dt>・音楽を制御する部分</dt>
<dd>環境 : Pycharm</dd>
<dd>言語 : Python</dd>
<dd>使用したライブラリ : pyserial，pygame，pythonosc</dd>

<br>

<dt>・Pythonから送信されたデータを受け取る部分</dt>
<dt>・ジェネラティブアート映像を出力する部分</dt>
<dd>環境:Processingのエディタ</dd>
<dd>言語:Processing</dd>
<dd>使用したライブラリ : oscP5，少しだけJavaのListとか</dd>

<br>
<br>
<br>

…ProcessingでそのままデータをBluetoothで受信、音楽制御すればいいのでは？と思うじゃないですか。
まず映像を作成していて、ものによってはデバッグ中Processingが重くなって映像がスローモーションになる瞬間が起きたり何も表示されなくなる現象が発生することがあった。
Processingにはどうにか映像だけでも問題なく動かして欲しい。

そうなると映像以外の機能、データ受信、音楽制御などは別で動かしたいなと。
※ちなみにProcessingの音楽系ライブラリminimを動かしてみたけど上手く動かず…、Bluetoothについては当時は理解出来ていなかったのと参考記事が少なく…という経緯あり。

ライブラリも比較的新しくて、
参考記事が多い、
Processing間と通信できそうなライブラリがある、
音楽制御ができるライブラリがある、
Bluetoothでデータ受信できるライブラリがある、
これらの条件をなるべく満たす言語と考えたらPythonが候補かなと。

調べてみてPythonにそれぞれ実現できそうなライブラリがあったので採用しました。
実際に試作してみて謎のエラーなく作成できたのも大きかった…！

そんな訳で以降はPython，Processingの中身をピックアップして振り返ります。



# 【Python】データをBluetoothで受信＆Processingに送信
#### 参考にしたサイト

https://tanudon.work/499/

https://www.eqseqs.work/post/python-osc/



#### データをBlutoothで受信して送信データに格納するまで
データをBlutoothで受信するのはポートCOM5になる。
ここはなぜかM5StackCore2とPCによりけり。
COM6だったりするので必要に応じて書き換える。
```
ser = serial.Serial('COM5', 9600)
```
データは読み取ったら、UTF-8でデコードする必要があるらしいので変換。
文字列に変換しているためsplitで値を分割。
```
line = ser.readline()
line_disp = line.strip().decode('UTF-8')
sp_li_di = line_disp.split(',')
```
分割した文字列は数値として送信したいのでfloatに変換して送信の中身に格納している。
```
msg.add_arg(float(sp_li_di[0]))  # 運動種類の番号
```

#### Processingに送信（実際にはOSC通信）
PythonとProcessing間でデータを送受信する方法を探していたところ
どちらの言語にもライブラリがあるOSCを採用した。
Pythonは送信側なのでクライアントを作成する。
```
IP = '127.0.0.1'
PORT = 6700
# UDPのクライアントを作る
client = udp_client.UDPClient(IP, PORT)
```
送信先と送信部分はこの通り。
```
# Processingで受け取り(補足：Processingは/colorでmsgデータを受け取る）
msg = OscMessageBuilder(address='/color')
m = msg.build()
client.send(m)
```

<br>

####   送受信まとめ
繋げてみるとこのように書ける。

```python:python
import serial  # 実際はpyserialをDLする
from pythonosc import udp_client
from pythonosc.osc_message_builder import OscMessageBuilder

IP = '127.0.0.1'
PORT = 6700

# UDPのクライアントを作る
client = udp_client.UDPClient(IP, PORT)
# portセット(必要に応じて変更する。Core2はCOM6と115200かも，StickCPlusはCOM4と9600)
ser = serial.Serial('COM5', 9600)

while 1:
    # portから値受け取り
    line = ser.readline()
    line_disp = line.strip().decode('UTF-8')
    sp_li_di = line_disp.split(',')
    # Processingで受け取り(補足：Processingは/colorでmsgデータを受け取る）
    msg = OscMessageBuilder(address='/color')

    msg.add_arg(float(sp_li_di[0]))  # 運動種類の番号
    msg.add_arg(float(sp_li_di[1]))  # 運動開始前の10秒カウントダウン（開始後はずっと0）
    msg.add_arg(float(sp_li_di[2]))  # ウォーキング回数
    msg.add_arg(float(sp_li_di[3]))  # スクワット回数
    msg.add_arg(float(sp_li_di[4]))  # サイドプランクの分.秒数
    msg.add_arg(float(sp_li_di[5]))  # ランジ回数
    # Core2から受け取る値の数の分、箱を用意する
    msg.add_arg(float(sp_li_di[6]))  # accX ウォーキング
    msg.add_arg(float(sp_li_di[7]))  # accY ウォーキング
    msg.add_arg(float(sp_li_di[8]))  # accZ ウォーキング
    msg.add_arg(float(sp_li_di[9]))  # gyroX ウォーキング
    msg.add_arg(float(sp_li_di[10]))  # gyroY
    msg.add_arg(float(sp_li_di[11]))  # gyroZ
    msg.add_arg(float(sp_li_di[12]))  # pitch(姿勢角データ)
    msg.add_arg(float(sp_li_di[13]))  # roll(姿勢角データ)スクワット
    msg.add_arg(float(sp_li_di[14]))  # yaw(姿勢角データ)
    msg.add_arg(float(sp_li_di[15]))  # x_angle(加速度をもとに角度を算出)サイドプランクとランジ
    msg.add_arg(float(sp_li_di[16]))  # y_angle(加速度をもとに角度を算出)サイドプランクとランジ

    m = msg.build()

    # print(m.address, m.params) debug用

    client.send(m)


```



# 【Python】音楽を制御
#### 参考にしたサイト

https://shizenkarasuzon.hatenablog.com/entry/2019/02/24/090652

音楽の設定自体はスタンダードに初期設定、音楽ファイルの読み込み、
再生、音量設定、再生回数（-1でループ再生の箇所）で構成。

個人的なポイントとしては運動の種類ごと、運動前後に音楽を切り替えたいので
while文(運動種類・前後で値が切り替わる条件)の中に
elif文(運動種類・前後を指すデータに従い条件分岐する)を入れている箇所。

ちなみに音楽ファイルはSpringin’さんから用意させていただきました。

https://www.springin.org/sound-stock/

<br>

#### 音楽の切り替え部分まとめ

```python:python
import pygame

# Music
pygame.mixer.init()

    # 音楽切り替え処理
    for ls_Num in range(1):
        ch_list0[ls_Num - 1] = ch_list0[ls_Num]
    
    # sp_li_di[0]は運動種類の番号が入っている
    in_list = int(float(sp_li_di[0]))
    ch_list0[0] = int(float(sp_li_di[0]))

    # 連続でデータ受信するので運動種類が切り替わる条件
    while ch_list0[0] != ch_list0[1]: 
        if in_list == 0: # 運動前
            pygame.mixer.music.load("Short_SF_02.mp3")
            pygame.mixer.music.set_volume(0.5)
            pygame.mixer.music.play(-1)  # -1でループ再生
            break
        elif in_list == 1: # ウォーキング
            pygame.mixer.music.load("自然_03.mp3")
            pygame.mixer.music.set_volume(0.8)
            pygame.mixer.music.play(-1)  # -1でループ再生
            break
        elif in_list == 2: # スクワット
            pygame.mixer.music.load("テクノ_03.mp3")
            pygame.mixer.music.set_volume(0.8)
            pygame.mixer.music.play(-1)  # -1でループ再生
            break
        elif in_list == 3: # サイドプランク
            pygame.mixer.music.load("MusicBox_04.mp3")
            pygame.mixer.music.set_volume(0.8)
            pygame.mixer.music.play(-1)  # -1でループ再生
            break
        elif in_list == 4: # ランジ
            pygame.mixer.music.load("Short60_MaemukiPoP_01.mp3")
            pygame.mixer.music.set_volume(0.8)
            pygame.mixer.music.play(-1)  # -1でループ再生
            break
        elif in_list == 100:  # 終了時の処理
            pygame.mixer.music.pause()
            break
        else:
            print("debug now")
            break
```

# 【Processing】データを受信
#### 参考にしたサイト

https://www.eqseqs.work/post/python-osc/

わりと参考記事のまま作ったら動いたのでこの通りコーディングした。
while文で配列XYZに受信データを格納しているくらい。
OSCライブラリは初めて触ったのでなるほどこう作るのねとなった。

```java:sketch_220927.pde
import netP5.*;
import oscP5.*;

// OSC関係
// OSCの初期化 (受信ポートは6700に設定する)
OscP5 osc = new OscP5(this, 6700);
float[] XYZ = new float[17];
int c_i = 0;

// OSC関係
void oscEvent(OscMessage msg) {
  if (msg.checkAddrPattern("/color")) {
    //下の代入を元に箱用意する
    //float core206 = msg.get(6).floatValue();
    //下の代入を元に箱用意する
    //XYZ[0] = core206;

    //繰り返し文で受信したデータを１つずつ配列に格納
    c_i = 0;
    while(c_i < 17) {
      XYZ[c_i] = msg.get(c_i).floatValue();
      c_i++;
    }
  }
}
```



# 【Processing】ジェネラティブアート（映像切り替え機能）
#### 参考にしたサイト

https://takawo.hatenablog.com/entry/2017/10/02/195321

映像自体も紹介したいところだがまあまあボリュームがあるので映像の切り替え部分を紹介する。
※映像自体は時間があれば投稿します。
参考記事のように切り替えできるようにvoidをオーバーライドしている。
まず最初にオーバーライド元のsetup()，draw()はこの通り。
```java:sketch_220927.pde
void setup() {
  size(700,700);
  background(0);
  colorMode(HSB,360,100,100);
  textSize(50);
  ch_sec =second();
  
  // オーバーライド先を配列に格納している
  apps = new ArrayList<AppBase>();
  apps.add(new BlueSquare(this));
  apps.add(new CurveShapeS(this));
  apps.add(new RandomDots(this));
  apps.add(new CircleCorner(this));
  apps.add(new BallSlactRect(this));
  apps.add(new ResultScreen(this));
  
  for(AppBase app:apps){
    app.setup();
  }
  selected = 0;
  
}

void draw() {
  apps.get(selected).update();
  apps.get(selected).draw();
  
}
```
<br>

次にオーバーライドするために必要な抽象クラスはこの通り。
ここは参考記事のまま作ったのとオーバーライドってこう準備が要るのか～と知った。

```java:AppBase.pde
// 抽象クラスAppBase
// 個別のスケッチを実行するクラスの設計図となる
abstract class AppBase {
  protected PApplet parent;
  public AppBase(PApplet _parent) {
    parent = _parent;
  }
  public void setup() {
    //抽象クラスなので何も書かない
  }
  public void update() {
    //抽象クラスなので何も書かない
  }
  public void draw() {
    //抽象クラスなので何も書かない
  }
}
```
<br>

最後にオーバーライド先はこの通り。
オーバーライド先BlueSquareが選択されている時に
BlueSquareのsetup()，draw()の映像が出力されるようになる。
ちなみにBlueSquare.pdeは運動前の映像。
こんな感じで運動前中後の画面をそれぞれ作成した。

```java:BlueSquare.pde
class BlueSquare extends AppBase {
  PVector pos;
  PVector vel;

  BlueSquare(PApplet _parent) {
    super(_parent);
  }
  @Override void setup() {
    pos = new PVector(parent.width/2, parent.height/2, 0);
    vel = PVector.random2D().mult(4);
    
    parent.colorMode(HSB, 360, 100, 100);
    parent.rectMode(CENTER);
    
  }
  @Override void update() {
    pos.add(vel);
    if(pos.x < 0 || pos.x > width){
      vel.x *= -1;
    }
    if(pos.y < 0 || pos.y > height){
      vel.y *= -1;
    }
    coH = int(random(0, 300));
    coS = int(random(0, 100));
    coB = int(random(0, 100));
  }
  @Override void draw() {
    blendMode(BLEND);
    parent.colorMode(HSB, coH, coS, coB);
    background(0);
    parent.noStroke();
    
    fill(10, 80, 100);//0927ADD
    //テキスト関係
    //ST_01.writeST(pos.x);//運動時間（後で入れる値修正する）
    ST_02.writeST(XYZ[2]);//ウォーキング回数
    ST_03.writeST(XYZ[3]);//スクワット回数
    ST_04.writeST(XYZ[4]);//サイドプランク回数？
    ST_05.writeST(XYZ[5]);//ランジ回数
    
    BSquare.writeST(XYZ[1]);
    
    rect(pos.x, pos.y, 50, 50);
    
  }
}
```



# 当初のスケジュールを振り返って
予定されていた全体のスケジュールとしては

6月 計画(要件など決める)
7月 検証
8月 設計
8月末 物として形になっている状態にする
9月 修正・調整・成果物発表

のはずが
計画がしっかり固まっていなかったので8月末時点には遅延していました。
私自身も計画が固まっていないことをあまり認識できていないまま作成し始めたので
主に映像作成に時間を割いてしまっていました。
なので実際には

8月末 まだ設計
9月初 とりあえずメンバーとM5stackCore2を連携
　　　※実は手元に同機種がなかったのでこの時点で初めて連携した。
9月中 連携後、計画が固まりきっていない箇所などで齟齬が生じていることを認識する
　　　※改めてメンバーと計画内容を確認。担当部分のコーディングは問題なかったので修正・調整は結構早かったかも。
9月末 手元にM5StackCore2はないのでリモートで連携して最終調整
　　　※修正版のスクリプトをメンバーに送付して、動作確認してもらい、デバック中のエラーはチャットで様子を聞いて解決したり。
9月末 成果物発表
　　　実際にM5StackCore2を身体に装着・手に持って運動すると、
　　　運動データが映像に反映されて映像を楽しみつつ運動できるプロダクトを作成できた
　　　※直前まで最終調整してもらっていた。毎度ぎりぎりまで連携ありがとうございました…！！

というスケジュールで9月は時間の余裕がなく大変でした。（ほぼ自分のおかげで）
もう少し後半~終盤に余裕を持てるようになるには
思った以上に事前に計画内容を固めることは大事だと学びました。



# 最後に

久しぶりにProcessingでまあまあの量コーディングしたので
オブジェクト指向を思い出すいい機会になったり
実はずっと寝かせていたオーバーライドというのはこう書くのかだとか
OSC通信、Bluetooth通信の仕方を知ったり技術的に色々触ることができたと思います。

技術以外では自分の中で進め方がわからない、終わりが見えないと思うときは
今回のプロダクトに限らずおそらく根本的な部分、
自分の中の計画内容が曖昧な状態、
あまり認識できていない状態であることが多そうだと思いました。
（元々、長期の計画を立てることが苦手なので関係あるかも？と思った）

9月末の最終調整は時間を割いたけど連携していく中で
最終的な成果物が出来上がる過程はとても楽しかったです。

ジェネラティブアートの映像部分は紹介したい箇所が
いくつかあるのでもしかしたら別の記事で投稿するかもしれません。

ここまで見ていただきありがとうございました。