Skip to content

Instantly share code, notes, and snippets.

@tomoto
Last active September 17, 2022 23:24
Show Gist options
  • Save tomoto/096555a1468bdbf84335f10b9de4b66d to your computer and use it in GitHub Desktop.
Save tomoto/096555a1468bdbf84335f10b9de4b66d to your computer and use it in GitHub Desktop.
蛇腹MIDIコントローラー / Accordion-like MIDI Controller https://qiita.com/tomoto335/items/92580af26b5c171dc1c0
// ライブラリに The Midibus と G4P を追加すること
import themidibus.*;
import g4p_controls.*;
import processing.serial.*;
String midiOut = "JabaraMIDI"; // 出力先MIDIループバックデバイス名(loopMIDIでこの名前の仮想MIDIデバイスを作っておく)
String serialIn = "COM4"; // センサーデータを読み取る(Arduinoが接続されている)シリアルポート
int midiChannel = 0; // MIDIチャンネル(0-based)
int midiControlNumber = 7; // 送信するCC(コントロールチェンジ)のControl Number
int minValue = 32; // CCの値の最小値(蛇腹静止時の値)
int maxValue = 127; // CCの値の最大値(蛇腹最大動作時の値)
float positiveScale = 500; // 閉じ方向の蛇腹最大動作時の気圧差(気圧差がこの値のときにmaxValueが出る)
float negativeScale = 500; // 開き方向の蛇腹最大動作時の気圧差(同上)
float curveFactor = 0.7; // 気圧差をCCの値に変換する際のカーブ(1が線形、小さくするとcompress、大きくするとexpandになる)
// センサーデータを滞りなく処理できているかを表す値
// (0.5で安定するはずだが、PCが遅いとこの値が大きくなるかもしれない。
// 0.98など1に近い値だったらPC側が遅すぎて正常動作できていない。)
class Stats {
int messageCount = 0;
int nextCheckPoint = 100;
void messageReceived() {
messageCount += 1;
if (frameCount >= nextCheckPoint) {
System.out.println(
String.format("%d/%d (%f)", messageCount, frameCount, float(messageCount) / frameCount));
nextCheckPoint = (frameCount / 100 + 1) * 100;
}
}
}
Stats stats = new Stats();
// 自動キャリブレーション
// (蛇腹を約3秒間静止させるとそのときの平均気圧で基準気圧を更新する)
class Calibrator {
static final int SAMPLES_PER_SEC = 50;
static final int SECONDS = 3;
static final int WINDOW = SAMPLES_PER_SEC * SECONDS;
static final float THRESHOLD = 10;
float samples[] = new float[WINDOW];
int ptr = 0;
void process(float value) {
samples[ptr] = value;
ptr = (ptr + 1) % WINDOW;
if (ptr % 50 == 0) {
float sum = 0;
float min = 9999999;
float max = -9999999;
for (int i = 0; i < WINDOW; i++) {
float s = samples[i];
sum += s;
min = s < min ? s : min;
max = s > max ? s : max;
}
float base = sum / WINDOW;
System.out.println(String.format("%f %f %f", base, max - base, base - min));
if (max - base <= THRESHOLD && base - min <= THRESHOLD) {
baseline = base;
System.out.println(String.format("Baseline updated: %f", baseline));
}
}
}
}
Calibrator calibrator = new Calibrator();
class UI {
GButton muteButton = new GButton(JabaraMIDI.this, 10, 10, 100, 50, "Mute");
// more controls to come
}
UI ui;
Serial serial;
MidiBus mb;
float baseline;
void setup() {
size(640, 160);
frameRate(100); // センサーデータ50サンプル/秒に対して2倍の速さでポートを読む
// MidiBus.list();
ui = new UI();
mb = new MidiBus(this, -1, midiOut);
serial = new Serial(this, serialIn, 115200);
}
void draw() {
String message = serial.readStringUntil('\n');
if (message != null) {
message = message.trim();
try {
float v = Float.parseFloat(message);
process(v);
calibrator.process(v);
stats.messageReceived();
} catch (NumberFormatException e) {
// non-value message
System.out.println(message);
}
}
}
int previousValue = -1;
boolean muted = false;
void process(float value) {
if (baseline == 0 || muted) {
return;
}
float r = pow(abs(value - baseline) / (value > baseline ? positiveScale : negativeScale), curveFactor);
int v = min(minValue + int(r * (maxValue - minValue)), maxValue);
if (previousValue != v) {
mb.sendControllerChange(midiChannel, midiControlNumber, v);
previousValue = v;
}
background(255);
fill(255, 0, 0);
rect(0, 0, width * r, height);
}
void handleButtonEvents(GButton button, GEvent event) {
if (button == ui.muteButton && event == GEvent.CLICKED) {
muted = !muted;
button.setText(muted ? "Unmute" : "Mute");
}
}
#include <M5StickC.h>
#include <Adafruit_BME280.h>
const int SDA_HAT = 0;
const int SCL_HAT = 26;
Adafruit_BME280 bme;
static void checkResetButton() {
if (M5.BtnA.pressedFor(2000)) {
M5.Axp.PowerOff();
}
if (M5.BtnA.wasReleased()) {
ESP.restart();
}
}
static void halt() {
while (true) {
M5.update();
checkResetButton();
delay(100);
}
}
static void beginBME280()
{
Wire.begin(SDA_HAT, SCL_HAT);
bool status = bme.begin(0x76);
if (!status) {
Serial.println("Failed to initialize the BME280 sensor.");
halt();
}
Serial.println("BME280 initialized.");
bme.setSampling(
Adafruit_BME280::MODE_NORMAL,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::SAMPLING_X1,
Adafruit_BME280::SAMPLING_NONE,
Adafruit_BME280::FILTER_X4,
Adafruit_BME280::STANDBY_MS_0_5
);
}
void setup()
{
M5.begin();
M5.Axp.ScreenBreath(8);
M5.Lcd.fillScreen(TFT_RED);
Serial.begin(115200);
beginBME280();
M5.Lcd.fillScreen(TFT_GREEN);
}
const int MEASURE_INTERVAL = 19;
void loop()
{
M5.update();
checkResetButton();
Serial.println(bme.readPressure(), 6);
delay(MEASURE_INTERVAL);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment