Trellisの制御 とControlPanelができた。

midi 制御も過去にやった。

全部組み合わせて高機能なメトロノーム、beat machine もどきをつくる。

配線

  • sda … GP0
  • scl …GP1
  • play_btn_pin … GP2
  • sel_btn_pin …GP4(BPM変更)

I2Cのアドレスは、以下の通り。

  • 0x70 ... 8x8 3色LED Matrix
  • 0x77, 0x72, 0x73, 0x71 ... Trellis

プログラム

ControlPanel のinstall

前回つくった ControlPanelを利用する。 実際には、 control_panel.py をRPi Pico の CIRCUITPY Volumeのlibフォルダに copyしておく。

downloadして、Macならこんな感じでcopy。

bash
cp control_panel.py /Volume/CIRCUITPY/lib

制御

  1. 初期化
    • MIDI 初期化
    • I2C 初期化
    • Trellis 初期化
    • ControlPanel 初期化
    • 8x8 3色Matrix に初期BPMの数値を表示
  2. 主制御 ... BPMの1拍を1/8づつ刻んで、そのタイミングで操作や表示処理を実行
    • 現在時刻を取得
    • ControlPanel操作を検知
      • play event なら演奏開始
      • stop event なら演奏停止
      • sel event ならBPMの数値を変更(50,55,60,100,110,120 をloop)
    • 1拍の1/8毎に、Trellis ON/OFF を検知して、MIDI データを送信

souce code

beat_trellis.py · Gistpython
import time
import busio
import board
import digitalio
from adafruit_trellis import Trellis

import usb_midi
import adafruit_midi
from adafruit_midi.note_off import NoteOff
from adafruit_midi.note_on import NoteOn

from control_panel import ControlPanel

# Garageband notes for drum
BD=36   # バスドラム
SN=38   # スネア1
RM=40   # リムタム=スネア2
CH=42   # クローズドハイハット
OH=46   # オープンハイハット
HT=50   # ハイタム
LT=45   # ロータム
FT=43   # フロアタム
HC=49   # クラッシュド・シンバル(ハイ)
LC=49   # クラッシュド・シンバル(ロウ)
RC=51   # ライドシンバル

class BeatTrellis:
    """
    board
    +---+---+
    | A | B |
    +---+---+
    | C | D |
    +---+---+

    keymap
     0  1  2  3 16 17 18 19
     4  5  6  7 21 22 23 24
     8  9 10 11 25 26 27 28
    12 13 14 15 29 30 31 32
    32 33 34 35 48 49 50 51
    36 37 38 39 52 53 54 55
    40 41 42 43 56 57 58 59
    44 45 46 47 60 61 62 63
    """
    def __init__(self, midi, i2c):
        self.pressed_buttons_ = set()
        self.on_notes_ = []

        # Trellis size
        self.ncol = 8
        self.nrow = 8

        # Trellis address
        # [113, 114, 115, 119] [ 0x71, 0x72, 0x73, 0x77 ]

        self.trellis_ = Trellis(i2c, [0x77, 0x72, 0x73, 0x71])
        self.trellis_.auto_show = False

        #
        # midi
        #
        self.midi_ = midi

        #
        # (x, y) to keyno
        #
        self.rowcols_ = [
            [  0,  1,  2,  3, 16, 17, 18, 19 ],
            [  4,  5,  6,  7, 20, 21, 22, 23 ],
            [  8,  9, 10, 11, 24, 25, 26, 27 ],
            [ 12, 13, 14, 15, 28, 29, 30, 31 ],
            [ 32, 33, 34, 35, 48, 49, 50, 51 ],
            [ 36, 37, 38, 39, 52, 53, 54, 55 ],
            [ 40, 41, 42, 43, 56, 57, 58, 59 ],
            [ 44, 45, 46, 47, 60, 61, 62, 63 ]
        ]

        """
            Finger Drum
            [ LC, LT, FT, HC ],
            [ CH, HT, OH, CH ],
            [ RC, SN, RM, RC ],
            [ XX, BD, BD, XX ],
        """

        self.notes_ = [
                HC, # 0
                LC, # 0
                CH, # 2
                HT, # 3
                LT, # 4
                RM, # 5
                SN, # 6
                BD, # 7
        ]

        #
        # keyno to (x, y)
        #
        self.keymap_ = []
        # board A
        for y in range(0, 4):
            for x in range(0, 4):
                self.keymap_.append([x, y])
        # board B
        for y in range(0, 4):
            for x in range(0, 4):
                self.keymap_.append([x+4, y])
        # board C
        for y in range(0, 4):
            for x in range(0, 4):
                self.keymap_.append([x, y+4])
        # board D
        for y in range(0, 4):
            for x in range(0, 4):
                self.keymap_.append([x+4, y+4])

        # all off keypad led
        for keyno in range(self.nrow* self.ncol):
            self.light2(keyno, False)
        self.show()
    #
    # midi
    #
    def midi_send(self, note, vol):
        if vol == 0:
            self.midi_.send(NoteOff(note, 0))
        else:
            self.midi_.send(NoteOn(note, vol))

    def stroke(self, tune_no):

        # note off prev on
        for note in self.on_notes_:
            self.midi_send(note, 0)
        self.on_notes_ = []

        # note on, light on key
        for y in range(0, 8):   # skip top line
            keyno = self.point2keyno(tune_no, y)
            if self.trellis_.led[keyno]:
                # on
                note = self.notes_[y]
                self.midi_send(note, 100)
                # save on notes
                self.on_notes_.append(note)

    def show(self):
        self.trellis_.show()

    def keyno2point(self, keyno):
        return self.keymap_[keyno]

    def point2keyno(self, x, y):
        return self.rowcols_[y][x]

    def light(self, x, y, val):
        keyno = self.point2keyno(x, y)
        self.trellis_.led[keyno] = val

    def light2(self, keyno, val):
        point = self.keyno2point(keyno)
        self.trellis_.led[keyno] = val

    def get_presses(self):

        just_pressed, released = self.trellis_.read_buttons()
        for keyno in just_pressed:
            # print('pressed:', b)
            if self.trellis_.led[keyno]:
                # on -> off
                bt.light2(keyno, False)
            else:
                # off -> on
                bt.light2(keyno, True)
        if len(just_pressed):
            return True

        return False

if __name__ == "__main__":

    #
    # MIDI
    #
    midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)

    #
    # I2C init
    #
    sda = board.GP0
    scl = board.GP1
    i2c = busio.I2C(scl, sda)

    #
    # Trellis
    #
    bt = BeatTrellis(midi, i2c)

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

    ctl = ControlPanel(play_btn_pin, mode_btn_pin, sel_btn_pin, i2c)

    # display init
    tune_no = 0
    ctl.display_bpm()
    ctl.display_tune(tune_no)
    interval = ctl.get_bpm_interval()

    now = time.monotonic_ns() + interval
    next_time = now + interval
    while True:
        is_change = False
        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

        is_change = bt.get_presses()
        if is_change:
            bt.show()

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

        next_time += interval

        # stroke midi note
        bt.stroke(tune_no)
        tune_no += 1
        if tune_no >= bt.ncol:
            tune_no = 0

        ctl.display_tune(tune_no)

demo

GarageBandのPianoRoll もこの通り。そこそこ正確なリズムになってる。

エンクロージャー

とりあえず、つくったけど、ControlPanel を収容するところまでそのうちに。