Last active
September 17, 2022 23:24
-
-
Save tomoto/096555a1468bdbf84335f10b9de4b66d to your computer and use it in GitHub Desktop.
蛇腹MIDIコントローラー / Accordion-like MIDI Controller https://qiita.com/tomoto335/items/92580af26b5c171dc1c0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ライブラリに 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"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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