できたもの
[記事用動画]
— zakuzaku-3 (@zakuzaku_3) July 2, 2023
明和電機のSUSHI BEATっぽい電子楽器をpythonで作ってみる。
キーボードを押すと、予め入力したリズムが再生されます。
MuseScoreでmidiファイルを生成しました。 pic.twitter.com/YvMgbGZWhI
はじめに
みなさんはSUSHI BEATをご存知でしょうか?
オタマトーンで有名な明和電機が2020年に発売した寿司型電子楽器です。
1つ1つの寿司に異なる音楽が入っていて、押すとリピート再生されます。
4つの寿司は音程とテンポが合っているので、複数同時に押すと、アンサンブルできます。
なお、動画後ろのロボットが欲しいのですが、どうやら非売品のようです。
今回はMIDIとpythonを使って、キーボードでできるSUSHI BEATっぽい電子楽器を作っていきます。
実行環境
OS:Ubuntu 22.04 LTS
Python: 3.10.6
下準備(作曲)
そのままSUSHI BEATの音を鳴らすわけにはいかないので、自分で音楽を作るところから初めます。
pythonで楽器を鳴らす方法はいくらでもありますが、今回は自分で楽譜を打ち込む必要があったので、MIDIを使用することにしました。
まずは、MuseScoreをインストールします。
それっぽいメロディを打ち込んで行きます。

打ち込み終わったら、楽器毎別々のMIDIファイルに分けて保存します。
以下の4つのファイルを生成しました。
* key_beat-Bass_Synthesizer.mid
* key_beat-Drumset.mid
* key_beat-Effect_Synthesizer.mid
* key_beat-Brightness_Synthesizer.mid
これをpythonで読み込んで、再生させます。
以下のリンクにMIDIファイルを格納したので、お試ししたい方はダウンロードしてください。
Google Drive
コード
外部ライブラリとして、
画面描画・キー入力:pygame
MIDI再生:mido
を使用しています。
from pygame.locals import *
import pygame
import mido
import threading
#pygameの処理
pygame.init()
screen = pygame.display.set_mode((420, 200))
pygame.display.set_caption("key beat")
font = pygame.font.Font(None, 72)
key_pos = {#各キーのテキストの表示位置
"a": (50,100),
"s": (150,100),
"d": (250,100),
"f": (350,100),
}
#midoの処理
ports = mido.get_output_names()
port = mido.open_output(ports[2])#数字は環境にあわせる
key_dict = ["a", "s", "d", "f"]# 使用するキー
midi_dict = {# 読み込むMIDIファイルを表記
"a": mido.MidiFile('key_beat-Drumset.mid'),
"s": mido.MidiFile('key_beat-Brightness_Synthesizer.mid'),
"d": mido.MidiFile('key_beat-Effect_Synthesizer.mid'),
"f": mido.MidiFile('key_beat-Bass_Synthesizer.mid')
}
channel_dict = {# MIDIが使用するチャンネル
"a": 9,
"s": 11,
"d": 2,
"f": 1
}
key_state = {#各キーのステータス。True:押されている、False:離れている
"a": False,
"s": False,
"d": False,
"f": False,
}
#MIDIファイルの再生。キーを押したときに呼ぶ関数。非同期にするため、threadingを使用
def play(key):
mid = midi_dict[key]
while True:#押されている間は繰り返し
for msg in mid.play():
if not key_state[key]:
return
print(msg)
port.send(msg)
thread_dict = {
"a": threading.Thread(target=play, args="a"),
"s": threading.Thread(target=play, args="s"),
"d": threading.Thread(target=play, args="d"),
"f": threading.Thread(target=play, args="f"),
}
#MIDIファイルの停止時の処理
def stop(channel):
stop_msg = mido.Message('control_change', control=7, value=0, channel=channel)# チャンネルボリュームを0にする。
port.send(stop_msg)
#画面描画
def screet_text():
while True:
screen.fill((0, 0, 0)) #背景色は黒
for key in key_dict:
if key_state[key]:
if screet_text.light:# 赤LEDを表現するために、チカチカさせている。
color = (255, 0, 0)
else:
color = (255, 255, 255)
else:
color = (100, 100, 100)
screen.blit(font.render(key.upper(), True, color), key_pos[key])
screet_text.light = not screet_text.light
pygame.display.update()
screet_text.light = True
screen_thread = threading.Thread(target=screet_text)
screen_thread.start()
if __name__ == "__main__":
while True:
for event in pygame.event.get():
if not(event.type == KEYDOWN or event.type == KEYUP):#キー入力以外は無視
continue
if event.key == K_ESCAPE:# ESCキーでプログラム終了
pygame.quit()
exit()
key = pygame.key.name(event.key)
if not pygame.key.name(event.key) in key_dict:# 使用していないキーは無視
continue
if event.type == KEYDOWN:# キーを押すと、再生させるスレッドを起動する。
key_state[key] = True
thread_dict[key].start()
if event.type == KEYUP:# キーを離すと、再生させるスレッドを停止する。
key_state[key] = False
stop(channel_dict[key])
thread_dict[key].join()
thread_dict[key] = threading.Thread(target=play, args=key)# 新しいスレッドを作らないと、RuntimeError: threads can only be started onceが発生する。
できたもの
(再掲)
[記事用動画]
— zakuzaku-3 (@zakuzaku_3) July 2, 2023
明和電機のSUSHI BEATっぽい電子楽器をpythonで作ってみる。
キーボードを押すと、予め入力したリズムが再生されます。
MuseScoreでmidiファイルを生成しました。 pic.twitter.com/YvMgbGZWhI
まとめ
明和電機のSUSHI BEATっぽい電子楽器をpythonで作ってみました。
コードを書くより、MIDIを打ち込む方が時間かかりました。
より、楽曲を磨いていけば、SUSHI BEATみたいにノリノリの音楽が奏でられると思います。
参考サイト
大変感謝しております。
追記
MIDIだと音源の入れ替えが難しいかと思い、mp3バージョンも作成しました。
こちらの方が再生遅延が少ないです。
from pygame.locals import *
import pygame
import threading
import vlc
import time
#pygameの処理
pygame.init()
screen = pygame.display.set_mode((420, 200))
pygame.display.set_caption("key beat")
font = pygame.font.Font(None, 72)
key_pos = {#各キーのテキストの表示位置
"a": (50,100),
"s": (150,100),
"d": (250,100),
"f": (350,100),
}
key_dict = ["a", "s", "d", "f"]# 使用するキー
mp3_dict = {# 読み込むMIDIファイルを表記
"a": vlc.MediaPlayer(),
"s": vlc.MediaPlayer(),
"d": vlc.MediaPlayer(),
"f": vlc.MediaPlayer()
}
mp3_dict["a"].set_mrl("key_beat-Drumset.mp3")
mp3_dict["s"].set_mrl("key_beat-Brightness_Synthesizer.mp3")
mp3_dict["d"].set_mrl("key_beat-Effect_Synthesizer.mp3")
mp3_dict["f"].set_mrl("key_beat-Bass_Synthesizer.mp3")
channel_dict = {# MIDIが使用するチャンネル
"a": 9,
"s": 11,
"d": 2,
"f": 1
}
key_state = {#各キーのステータス。True:押されている、False:離れている
"a": False,
"s": False,
"d": False,
"f": False,
}
#MIDIファイルの再生。キーを押したときに呼ぶ関数。非同期にするため、threadingを使用
def play(key):
mp3 = mp3_dict[key]
mp3.play()
while True:
if not key_state[key]:
mp3.stop()
return
time.sleep(0.1)
thread_dict = {
"a": threading.Thread(target=play, args="a"),
"s": threading.Thread(target=play, args="s"),
"d": threading.Thread(target=play, args="d"),
"f": threading.Thread(target=play, args="f"),
}
#画面描画
def screet_text():
while True:
screen.fill((0, 0, 0)) #背景色は黒
for key in key_dict:
if key_state[key]:
if screet_text.light:# 赤LEDを表現するために、チカチカさせている。
color = (255, 0, 0)
else:
color = (255, 255, 255)
else:
color = (100, 100, 100)
screen.blit(font.render(key.upper(), True, color), key_pos[key])
screet_text.light = not screet_text.light
pygame.display.update()
screet_text.light = True
screen_thread = threading.Thread(target=screet_text)
screen_thread.start()
if __name__ == "__main__":
while True:
for event in pygame.event.get():
if not(event.type == KEYDOWN or event.type == KEYUP):#キー入力以外は無視
continue
if event.key == K_ESCAPE:# ESCキーでプログラム終了
pygame.quit()
exit()
key = pygame.key.name(event.key)
if not pygame.key.name(event.key) in key_dict:# 使用していないキーは無視
continue
if event.type == KEYDOWN:# キーを押すと、再生させるスレッドを起動する。
key_state[key] = True
thread_dict[key].start()
if event.type == KEYUP:# キーを離すと、再生させるスレッドを停止する。
key_state[key] = False
thread_dict[key].join()
thread_dict[key] = threading.Thread(target=play, args=key)# 新しいスレッドを作らないと、RuntimeError: threads can only be started onceが発生する。