beat machine をつくる上で、音を鳴らすの同じくらい大事なコントロールパネル。

ミニマムで、これだけしたい。

  • PLAY/STOP 操作
  • BPM 切り換え

また、現在のBPMや拍子を視覚的に見せる員時ゲーターも欲しい。

ということで、8x8 matrix LED(3色)

タクトスイッチ2個。 AliExpressで購入したタッチ式のオン・オフスイッチを利用する。

Adafruit I2C通信の8x8 の制御

さすがAdfruit製。 circuitpython のライブラリがちゃんとある。

ドキュメント

source

配線

  • sda ... GP0
  • scl ...GP1
  • play_btn_pin ... GP2
  • mode_btn_pin ... GP3(今回未使用)
  • sel_btn_pin ...GP4(BPM変更)

プログラム

install

adafruit_ht16k33 ライブラリのinstall が必要。

上記の https://github.com/adafruit/Adafruit_CircuitPython_HT16K33 をdownloadして、mac ならこんな感じにライブラリとしてcopyする。

bash
cp -r adafruit_ht16k33 /Volume/CIRCUITPY/lib

source code

I2C I/F なので、こんな感じサンプルをつくってみた。

control_panel.py · Gistpython
import board
import busio
import displayio
import time
import digitalio
from adafruit_ht16k33.matrix import Matrix8x8x2

class ControlPanel:
    def __init__(self, play_btn_pin, mode_btn_pin, sel_btn_pin, i2c):

        # for Touch button
        self.play_btn_ = digitalio.DigitalInOut(play_btn_pin)
        self.play_btn_status_ = False
        self.play_btn_.switch_to_input(pull=digitalio.Pull.DOWN)

        # for BPM change and mode button(not use)
        self.mode_btn_ = digitalio.DigitalInOut(mode_btn_pin)
        self.mode_btn_status_ = True
        self.mode_btn_.switch_to_input(pull=digitalio.Pull.UP)
        self.sel_btn_ = digitalio.DigitalInOut(sel_btn_pin)
        self.sel_btn_status_ = True
        self.sel_btn_.switch_to_input(pull=digitalio.Pull.UP)

        # 8x8 matrix LED(RED/GREEN/YELLOW)
        self.matrix_ = Matrix8x8x2(i2c)
        # BPM setting
        self.bpm_index_ = 2  # default 60
        self.numbers_ = [50, 55, 60, 100, 110, 120]
        self.number_matrixes_ = [
            [
                # 50
                # 1  2  3  4  5  6  7  8
                [1, 1, 1, 0, 1, 1, 1, 1],
                [1, 0, 0, 0, 1, 0, 0, 1],
                [1, 1, 1, 0, 1, 0, 0, 1],
                [0, 0, 1, 0, 1, 0, 0, 1],
                [0, 0, 1, 0, 1, 0, 0, 1],
                [1, 1, 1, 0, 1, 1, 1, 1],
            ],
            [
                # 55
                # 1  2  3  4  5  6  7  8
                [1, 1, 1, 0, 1, 1, 1, 0],
                [1, 0, 0, 0, 1, 0, 0, 0],
                [1, 1, 1, 0, 1, 1, 1, 0],
                [0, 0, 1, 0, 0, 0, 1, 0],
                [0, 0, 1, 0, 0, 0, 1, 0],
                [1, 1, 1, 0, 1, 1, 1, 0],
            ],
            [
                # 60
                # 1  2  3  4  5  6  7  8
                [1, 1, 1, 0, 1, 1, 1, 1],
                [1, 0, 0, 0, 1, 0, 0, 1],
                [1, 1, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 1, 1, 0, 1, 1, 1, 1],
            ],
            [
                # 100
                # 1  2  3  4  5  6  7  8
                [1, 0, 1, 1, 1, 1, 1, 1],
                [1, 0, 1, 0, 1, 1, 0, 1],
                [1, 0, 1, 0, 1, 1, 0, 1],
                [1, 0, 1, 0, 1, 1, 0, 1],
                [1, 0, 1, 0, 1, 1, 0, 1],
                [1, 0, 1, 1, 1, 1, 1, 1],
            ],
            [
                # 110
                # 1  2  3  4  5  6  7  8
                [1, 0, 1, 0, 1, 1, 1, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 0, 0, 1],
                [1, 0, 1, 0, 1, 1, 1, 1],
            ],
            [
                # 120
                # 1  2  3  4  5  6  7  8
                [1, 0, 1, 1, 1, 1, 1, 1],
                [1, 0, 0, 0, 1, 1, 0, 1],
                [1, 0, 0, 0, 1, 1, 0, 1],
                [1, 0, 1, 1, 1, 1, 0, 1],
                [1, 0, 1, 0, 0, 1, 0, 1],
                [1, 0, 1, 1, 1, 1, 1, 1],
            ],
        ]

    def get_event(self):

        event = ""
        if self.play_btn_status_ != self.play_btn_.value:
            if self.play_btn_status_ is False:
                # off -> on
                event = "play"
            if self.play_btn_status_ is True:
                event = "stop"
            self.play_btn_status_ = self.play_btn_.value

        mode_btn_status = self.mode_btn_.value
        if self.mode_btn_status_ != mode_btn_status:
            if mode_btn_status is False:
                event = "mode"
            self.mode_btn_status_ = mode_btn_status

        sel_btn_status = self.sel_btn_.value
        if self.sel_btn_status_ != sel_btn_status:
            if sel_btn_status is False:
                event = "sel"
            self.sel_btn_status_ = sel_btn_status

        return event

    def bpm_change(self):
        self.bpm_index_ += 1
        if self.bpm_index_ >= len(self.numbers_):
            self.bpm_index_ = 0

    def get_bpm_interval(self):
        bpm = self.numbers_[self.bpm_index_]

        tune_time = 8  # 1/8
        nano_interval_of_beat = 60 * 1000_000_000 / bpm
        interval = nano_interval_of_beat / (tune_time)
        print(int(interval))

        return interval

    def display_bpm(self):
        number_matrix = self.number_matrixes_[self.bpm_index_]
        for y in range(6):
            for x in range(8):
                if number_matrix[y][x]:
                    self.matrix_[y, x] = self.matrix_.LED_YELLOW
                else:
                    self.matrix_[y, x] = self.matrix_.LED_OFF

    def display_tune(self, tune_no):
        if tune_no == 0:
            for x in range(1, 8):
                self.matrix_[7, x] = self.matrix_.LED_OFF
            self.matrix_[7, 0] = self.matrix_.LED_GREEN
        else:
            if tune_no < 4:
                self.matrix_[7, tune_no] = self.matrix_.LED_GREEN
            else:
                self.matrix_[7, tune_no] = self.matrix_.LED_RED

if __name__ == "__main__":

    sda = board.GP0
    scl = board.GP1

    play_btn_pin = board.GP2
    mode_btn_pin = board.GP3
    sel_btn_pin = board.GP4

    i2c = busio.I2C(scl, sda)
    ctl = ControlPanel(play_btn_pin, mode_btn_pin, sel_btn_pin, i2c)

    # BPM default
    tune_no = 0
    ctl.display_bpm()
    ctl.display_tune(tune_no)

    interval = ctl.get_bpm_interval()

    # nano second from nano second
    now = time.monotonic_ns() + interval
    next_time = now + interval
    while True:
        now = time.monotonic_ns()

        event = ctl.get_event()
        if event == "sel":
            ctl.bpm_change()
            ctl.display_bpm()
            interval = ctl.get_bpm_interval()
        elif event == "play":
            next_time = now
        elif event == "stop":
            next_time = -1

        if next_time == -1 or next_time > now:
            # skip to next tune time
            continue

        next_time += interval

        tune_no += 1
        if tune_no >= 8:
            tune_no = 0
        ctl.display_tune(tune_no)

        time.sleep(0.00001)

demo

以下へ続く