Raspberry Piでアナログシンセを制御する (2)GUI編

## 概要
* メイカーイベントの出展物として、音楽をテーマにしたものをつくりたいと思った。
* アナログシンセサイザー（ドラムマシン）の制御にRaspbery Piが使用できないかと考えた。 
* 回路図を探し、トランジスタ技術のバックナンバーに掲載の回路を使うこととした。
* 前回記事の続きとして、シーケンサーをGUIで開発してみる。  
https://qiita.com/cami_oshimo/items/479dc29866d872d52674

※アナログシンセサイザーとは？
　電子部品の特性を利用し、音声信号を合成することで音色を出力する電子楽器。
　1970年代の音楽で良く使われていたが、現在も当時の機材からサンプリングされた音が使われており、独特の魅力、音色から愛好家も多い。

Raspberry PiのGPIOからスイッチング制御できるようにしている様子
![36525237_1888423527880473_4922785832709914624_o.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/5b016d0b-e702-a6f2-a0b9-18ce34347fd1.jpeg)

演奏できるパートを増やし、何とか４枚のブレッドボードへ収まるよう試行錯誤している様子
![19756596_1501897453199751_3165531917612433512_n.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/1ea12fe5-4188-927f-1bb1-f695ac73d5d0.jpeg)
![29351715_1777331245656369_8712233315311881601_o.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/17f51cf8-44e1-f3b0-e3f8-f4acc9b07e46.jpeg)
![36521836_1888423511213808_8370938311270727680_o.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/3bd024cb-b30b-6fa6-a9a5-b19c04b2073a.jpeg)

イベント展示用に杉足場板の廃材に固定、アクリル板で保護した様子
![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/b331283c-666c-b1b4-38e4-fff19ddbf99f.png)

シーケンサーを開発している様子
![スクリーンショット 2021-03-26 14.20.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/efaf45f6-cd2a-c730-54d3-ef0d70016b5a.png)

出来上がったシーケンサーの画面
![35306036_1862539893802170_8311927436308643840_o.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701445/6091186b-2899-c93a-c680-a89131921f6e.jpeg)

## 用意するもの
* Raspberry Pi3 Model B (4でもおそらく大丈夫)
* Raspberry Pi3 Model B B+ 対応 電源セット(5V 3.0A)
* トランジスタ技術 2016年2月号
* 必要な電子部品類
* ブレッドボード、ジャンパーワイヤ 
* DC9V 1.3A ACアダプター
* アンプ内蔵PCスピーカー（できればウーファー付き）
* アクリル板と固定ネジ（回路の保護用）
* ホットボンド（ジャンパーワイヤの固定、絶縁用）

## 環境構築
* Raspberry PiのOSをセットアップし、I2Cを有効化する  
https://www.raspberrypi.org/downloads/  
https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-i2c/  

## 組み立て
* トランジスタ技術 2016年2月号 P194〜P203の回路を参考にブレッドボード上に回路を作成する  
※著作物のため掲載は控えます

## プログラミング
* GUIの開発にはTKinter(Python)を使うことにした。
* 32パターンの発音状態を定義し、読み込んたCSVファイルの0/1と数式で対応させ、GPIOを制御している。
* 再生、BPM変更、繰り返し数指定、新規作成、編集、保存機能を実装した。

メインプログラム

```Python:/home/pi/sequencer.py
#!/usr/bin/python3

import RPi.GPIO as GPIO

import time

import csv

import tkinter

import tkinter.filedialog as tkfd

import threading

PIN0 = 11

PIN1 = 12

PIN2 = 13

PIN3 = 15

PIN4 = 16

PTN0 = []

PTN1 = [PIN0]

PTN2 = [PIN1]

PTN3 = [PIN0,PIN1]

PTN4 = [PIN2]

PTN5 = [PIN0,PIN2]

PTN6 = [PIN1,PIN2]

PTN7 = [PIN0,PIN1,PIN2]

PTN8 = [PIN3]

PTN9 = [PIN0,PIN3]

PTN10 = [PIN1,PIN3]

PTN11 = [PIN0,PIN1,PIN3]

PTN12 = [PIN2,PIN3]

PTN13 = [PIN0,PIN2,PIN3]

PTN14 = [PIN1,PIN2,PIN3]

PTN15 = [PIN0,PIN1,PIN2,PIN3]

PTN16 = [PIN4]

PTN17 = [PIN0,PIN4]

PTN18 = [PIN1,PIN4]

PTN19 = [PIN0,PIN1,PIN4]

PTN20 = [PIN2,PIN4]

PTN21 = [PIN0,PIN2,PIN4]

PTN22 = [PIN1,PIN2,PIN4]

PTN23 = [PIN0,PIN1,PIN2,PIN4]

PTN24 = [PIN3,PIN4]

PTN25 = [PIN0,PIN3,PIN4]

PTN26 = [PIN1,PIN3,PIN4]

PTN27 = [PIN0,PIN1,PIN3,PIN4]

PTN28 = [PIN2,PIN3,PIN4]

PTN29 = [PIN0,PIN2,PIN3,PIN4]

PTN30 = [PIN1,PIN2,PIN3,PIN4]

PTN31 = [PIN0,PIN1,PIN2,PIN3,PIN4]

PTNNM = [PTN0,PTN1,PTN2,PTN3,PTN4,PTN5,PTN6,PTN7,PTN8,PTN9,PTN10,PTN11,PTN12,PTN13,PTN14,PTN15,PTN16,PTN17,PTN18,PTN19,PTN20,P
TN21,PTN22,PTN23,PTN24,PTN25,PTN26,PTN27,PTN28,PTN29,PTN30,PTN31]

GPIO.setmode(GPIO.BOARD)


GPIO.setup(PTN0,GPIO.OUT)

GPIO.setup(PTN1,GPIO.OUT)

GPIO.setup(PTN2,GPIO.OUT)

GPIO.setup(PTN3,GPIO.OUT)

GPIO.setup(PTN4,GPIO.OUT)

GPIO.setup(PTN5,GPIO.OUT)

GPIO.setup(PTN6,GPIO.OUT)

GPIO.setup(PTN7,GPIO.OUT)

GPIO.setup(PTN8,GPIO.OUT)

GPIO.setup(PTN9,GPIO.OUT)

GPIO.setup(PTN10,GPIO.OUT)

GPIO.setup(PTN11,GPIO.OUT)

GPIO.setup(PTN12,GPIO.OUT)

GPIO.setup(PTN13,GPIO.OUT)

GPIO.setup(PTN14,GPIO.OUT)

GPIO.setup(PTN15,GPIO.OUT)

GPIO.setup(PTN16,GPIO.OUT)

GPIO.setup(PTN17,GPIO.OUT)

GPIO.setup(PTN18,GPIO.OUT)

GPIO.setup(PTN19,GPIO.OUT)

GPIO.setup(PTN20,GPIO.OUT)

GPIO.setup(PTN21,GPIO.OUT)

GPIO.setup(PTN22,GPIO.OUT)

GPIO.setup(PTN23,GPIO.OUT)

GPIO.setup(PTN24,GPIO.OUT)

GPIO.setup(PTN25,GPIO.OUT)

GPIO.setup(PTN26,GPIO.OUT)

GPIO.setup(PTN27,GPIO.OUT)

GPIO.setup(PTN28,GPIO.OUT)

GPIO.setup(PTN29,GPIO.OUT)

GPIO.setup(PTN30,GPIO.OUT)

GPIO.setup(PTN31,GPIO.OUT)

root = tkinter.Tk()

root.title("sequencer [new]")

root.geometry("1230x275+100+200")

root.config(bg = '#333333')

root.resizable(0,0)

LabelC = tkinter.Label(root, text="Cymbal", width=10, fg='white', bg='#333333')
LabelH1 = tkinter.Label(root, text="Hihat1", width=10, fg='white', bg='#333333')
LabelH2 = tkinter.Label(root, text="Hihat2", width=10, fg='white', bg='#333333')
LabelS = tkinter.Label(root, text="Snare" , width=10, fg='white', bg='#333333')
LabelB = tkinter.Label(root, text="Bass"  , width=10, fg='white', bg='#333333')
LabelT = tkinter.Label(root, text=""  , width=10, bg='#333333')
LabelT2 = tkinter.Label(root, text=""  , width=10, bg='#333333')

ButtonP = tkinter.Button(root, text="Play"  , width=10, height=2, fg='black', bg='#FF5192', relief='ridge')
ButtonL = tkinter.Button(root, text="Load"  , width=10, height=2, fg='black', bg='#4689FF', relief='ridge')
ButtonS = tkinter.Button(root, text="Save"  , width=10, height=2, fg='black', bg='#4DF9B9', relief='ridge')
ButtonN = tkinter.Button(root, text="New"  , width=10, height=2, fg='black', bg='#D0FF43', relief='ridge')
ButtonE = tkinter.Button(root, text="Exit"  , width=10, height=2, fg='black', bg='#9057FF', relief='ridge')
ScaleBPM = tkinter.Scale(root, label='BPM', orient='h', from_=60, to=255,length=170, fg='white', bg='#333333')
ScaleBPM.set(120)
ScaleREP = tkinter.Scale(root, label='Repeat', orient='h', from_=1, to=16,length=170, fg='white', bg='#333333')
ScaleREP.set(1)

LabelC.grid(row=1, column=0)
LabelH1.grid(row=2, column=0)
LabelH2.grid(row=3, column=0)
LabelS.grid(row=4, column=0)
LabelB.grid(row=5, column=0)
LabelT.grid(row=0, column=0)
LabelT2.grid(row=6, column=0)
ScaleBPM.grid(row=7, column=1,columnspan=5)
ScaleREP.grid(row=7, column=7,columnspan=5)

ButtonP.grid(row=7, column=17,columnspan=3)
ButtonL.grid(row=7, column=13,columnspan=3)
ButtonN.grid(row=7, column=25, columnspan=3)
ButtonS.grid(row=7, column=21,columnspan=3,pady=10 )
ButtonE.grid(row=7, column=29,columnspan=3,pady=10 )

def clchange(event):


    if event.widget["bg"] != "#FF6928":

        event.widget["bg"] = "#FF6928"

    elif event.widget["fg"] == "#555555":

         event.widget["bg"] = "#555555"

    else:

        event.widget["bg"] = "#777777"

ButtonNm = [[0 for j in range(6)] for i in range(32)]

for bn in range(5):

    for bc in range(32):

        if 0 <= bc <= 3 or 8 <= bc <= 11 or 16 <= bc <= 19 or 24 <= bc <= 27:

            ButtonNm[bc][bn] = tkinter.Button(root, width=1, fg = "#555555", bg = "#555555")

        else:

            ButtonNm[bc][bn] = tkinter.Button(root, width=1, fg = "#777777", bg = "#777777")


        ButtonNm[bc][bn].grid(row=bn+1, column=bc+1)

        ButtonNm[bc][bn].bind("<1>",clchange)

def fileopen(event):

    fileNM = tkfd.askopenfilename(initialdir = "/home/pi", filetypes = (("csv files","*.csv"),))

    CsvFile = csv.reader(open(fileNM),delimiter=',')

    root.title("sequencer ["+fileNM+"]")

    line2 = []

    for i in CsvFile:

        line2.append(i)

    for bn in range(5):

        for bc in range(32):

            if 0 <= bc <= 3 or 8 <= bc <= 11 or 16 <= bc <= 19 or 24 <= bc <= 27:

                ButtonNm[bc][bn].config(bg = "#555555")

            else:

                ButtonNm[bc][bn].config(bg = "#777777")


    for bn in range(5):

        for bc in range(32):

            if line2[bc][bn] == "1":

                ButtonNm[bc][-(bn-4)].config(bg = "#FF6928")

def filesave(event):

    fileNM = tkfd.asksaveasfilename(initialdir = "/home/pi", filetypes = (("csv files","*.csv"),))

    f = open(fileNM, 'w')

    writer = csv.writer(f)

    line3 = [[0 for j in range(5)] for i in range(32)]

    for bn in range(5):

        for bc in range(32):

            if ButtonNm[bc][-(bn-4)].cget("bg") == "#FF6928":

                line3[bc][bn] = 1

            else:

                line3[bc][bn] = 0

    writer.writerows(line3)

    f.close()

    root.title("sequencer ["+fileNM+"]")


def new(event):

    for bn in range(5):

        for bc in range(32):

            if 0 <= bc <= 3 or 8 <= bc <= 11 or 16 <= bc <= 19 or 24 <= bc <= 27:

                ButtonNm[bc][bn].config(bg = "#555555")

            else:

                ButtonNm[bc][-(bn-4)].config(bg = "#777777")


    root.title("sequencer [new]")

def play(event):

    GPIO.setmode(GPIO.BOARD)

    GPIO.setup(PTN0,GPIO.OUT)

    GPIO.setup(PTN1,GPIO.OUT)

    GPIO.setup(PTN2,GPIO.OUT)

    GPIO.setup(PTN3,GPIO.OUT)

    GPIO.setup(PTN4,GPIO.OUT)

    GPIO.setup(PTN5,GPIO.OUT)

    GPIO.setup(PTN6,GPIO.OUT)

    GPIO.setup(PTN7,GPIO.OUT)

    GPIO.setup(PTN8,GPIO.OUT)

    GPIO.setup(PTN9,GPIO.OUT)

    GPIO.setup(PTN10,GPIO.OUT)

    GPIO.setup(PTN11,GPIO.OUT)

    GPIO.setup(PTN12,GPIO.OUT)

    GPIO.setup(PTN13,GPIO.OUT)

    GPIO.setup(PTN14,GPIO.OUT)

    GPIO.setup(PTN15,GPIO.OUT)

    GPIO.setup(PTN16,GPIO.OUT)

    GPIO.setup(PTN17,GPIO.OUT)

    GPIO.setup(PTN18,GPIO.OUT)

    GPIO.setup(PTN19,GPIO.OUT)

    GPIO.setup(PTN20,GPIO.OUT)

    GPIO.setup(PTN21,GPIO.OUT)

    GPIO.setup(PTN22,GPIO.OUT)

    GPIO.setup(PTN23,GPIO.OUT)

    GPIO.setup(PTN24,GPIO.OUT)

    GPIO.setup(PTN25,GPIO.OUT)

    GPIO.setup(PTN26,GPIO.OUT)

    GPIO.setup(PTN27,GPIO.OUT)

    GPIO.setup(PTN28,GPIO.OUT)

    GPIO.setup(PTN29,GPIO.OUT)

    GPIO.setup(PTN30,GPIO.OUT)

    GPIO.setup(PTN31,GPIO.OUT)

    line = [[0 for j in range(5)] for i in range(32)]

    for bn in range(5):

        for bc in range(32):

            if ButtonNm[bc][-(bn-4)].cget("bg") == "#FF6928":

                line[bc][bn] = 1

            else:

                line[bc][bn] = 0

    bpm = int(ScaleBPM.get())

    repeat = int(ScaleREP.get())

    wait = 60 / float(bpm) * 0.25 -0.01

    print('BPM' + str(bpm))

    for rep in range(repeat):

        for cnt in range(len(line)):

            PTNNO = 0

            instct = len(line[0])

            for instno in range(instct):

                PTNNO += int(line[cnt][instno]) * int(pow(2,instno))

            print(line[cnt])

            GPIO.output(PTNNM[PTNNO],True)

            time.sleep(0.01)

            GPIO.output(PTNNM[PTNNO],False)

            time.sleep(wait)


    GPIO.cleanup()

def exit(event):

    quit()


ButtonP.bind("<1>",play)

ButtonL.bind("<1>",fileopen)

ButtonS.bind("<1>",filesave)

ButtonN.bind("<1>",new)

ButtonE.bind("<1>",exit)

GPIO.cleanup()


root.mainloop()
```

CSVファイルの例(8ビート)

```sh:/home/pi/8beat.csv 
1,0,0,0,1
0,0,0,0,0
0,0,0,0,0
0,0,0,0,0
0,1,1,0,0
0,0,0,0,0
0,0,1,0,0
0,0,0,0,0
1,0,1,0,0
0,0,0,0,0
1,0,1,0,0
0,0,0,0,0
0,1,1,0,0
0,0,0,0,0
0,0,1,0,0
0,0,0,0,0
1,0,1,0,0
0,0,0,0,0
0,0,1,0,0
0,0,0,0,0
0,1,1,0,0
0,0,0,0,0
0,0,1,0,0
0,0,0,0,0
1,0,1,0,0
0,0,0,0,0
1,0,1,0,0
0,0,0,0,0
0,1,1,0,0
0,0,0,0,0
0,0,1,0,0
0,0,0,0,0
```

## 製作してみて
* TKinterのスキルをある程度獲得することが出来た。
* マルチスレッドに対応してないため、演奏中は操作が出来ない。次バージョンでは改善予定。
* 何らかの機器のシーケンサーとしてRaspberry Pi + TKinterの組み合わせは安価で実現できる。  
タッチパネル機能付きのディスプレイと合わせれば使い勝手も良くなると思われる。