• Column

【HAGIWO MOD1】 スネア×ハイハットシーケンサー(P4M2025 ver)/プログラムコード[モジュラーシンセ] 

HAGIWO MOD1用にプログラミングしたスネア&ハイハット・シーケンサーです。

サンプラーのスネア・クローズハイハット&オープンハイハットの組み合わせに適したセッティングです。

何度か類似のプログラミングを投稿していますが、こちらがひとまずの完成版になります。

2025年7月20日に渋谷CIRCUS TOKYOで開催された「Patching for Modular」のライブで使ったシーケンサーです。

1つ目のシーケンサー、2つ目のシーケンサーとも、ライブ演奏で使いやすいスネア・ハットパターンをプログラミングしております。

1つ目のシーケンサーはスネアに特化しており、POT1でリズムパターンを設定します。ノブを回すごとにトリガー回数が増え、最大値で16ステップ全てのトリガーが鳴ります。ステップはシフトレジストしており、1周目は5ステップ目から有効になります。主にスネア音源との相性が良いシーケンサーです。

2つ目のシーケンサーも16ステップで固定されており、POT2でトリガー回数を設定します。また、POT3はランダムコイントスで、POT2で設定したパターンを、さらに確率的に2つの出力に振り分けます。こちらは1ステップ目から有効です。主にクローズハイハット、オープンハイハット音源の組み合わせと相性が良いシーケンサーです。

プログラミング内の  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},の箇所が16ステップシーケンスの書き込み部分にあたります。「0」がトリガーなしで「1」がトリガー発生です。数字を書き換えることで、お好みのリズムパターンに変更できます。

また、新たにランダムハイハットパターンを追加しました。P4ボタンを押すと、ハイハットパターンが、16ステップごとにランダムに再生されます。

注意事項1:chatGPTで作成したプロンプトです。プログラミング知識は一切ないため、動作の安定性は保証はできません。趣味用途でお使いください

注意事項2:リセットインがないため、不規則なクロックには対応していません。BPMが判定しずらいクロックを入れた場合、設定がリセットされる可能性があります。

注意事項3:現在の設定では、安定した動作が保証できるのはBPM30までとなります。さらに遅いテンポで使用したい場合は「triggerTimeout」を任意の数字に修正してください。

[機能紹介]

※入力したクロックをもとに、リズムパターンを生成するモジュールです。

※トリガー長は5msで設定しています。トリガー長は delayMicroseconds(5000); の箇所で変更可能です。(5ms=5000)

POT1:出力1のリズムパターンを設定します。

POT2:出力2のリズムパターンを設定します。

POT3:出力2で設定したリズムパターンを、確率でD10pin、D11pinに振り分けます。中間でおよそ50:50、最小にすると常にD10pinから出力され、最大にすると常にD11pinから出力されます

F1:トリガーIN

F2:出力1のトリガー出力

F3:出力2のトリガー出力1

F4:出力2のトリガー出力2

P4:ランダムスネアパターンとPOT1の切り替えボタン

LEDF1に連動する

[プログラム](ArduinoでMOD1にプログラミングしてください)

const int triggerInputPin = 17;

const int buttonPin = 4;

const int ledPin = 3;

const int out1Pin = 9;

const int out2aPin = 10;

const int out2bPin = 11;

const int knob1Pin = A0;  // 出力1パターン選択

const int knob2Pin = A1;  // 出力2パターン選択

const int probKnobPin = A2;

const int numSteps = 16;

bool pattern1[numSteps];

bool pattern2[numSteps];

int currentStep = 0;

unsigned long lastClockTime = 0;

unsigned long lastTriggerReceived = 0;

bool lastTriggerState = LOW;

bool lastButtonState = HIGH;

bool randomMode = false;

int currentRandomPatternIndex = 0;

const unsigned long triggerTimeout = 500;

const int shiftSize = 8;

bool shiftRegister[shiftSize] = {false};

int shiftIndex = 0;

// 出力1用固定パターン

const bool fixedPatterns1[16][numSteps] = {

  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},

  {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0},

  {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1},

  {0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0},

  {0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0},

  {0,0,0,1,1,0,0,0,0,1,0,0,1,0,0,0},

  {1,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0},

  {1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1},

  {1,0,0,1,0,0,1,0,1,0,0,1,0,1,0,0},

  {1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,0},

  {1,0,1,1,0,1,0,1,1,0,1,0,1,1,0,1},

  {1,0,1,1,0,1,1,0,1,1,0,1,0,1,1,1},

  {1,0,1,0,1,0,1,1,0,1,0,1,1,0,1,1},

  {1,1,0,1,1,0,1,1,1,0,1,1,1,0,1,1},

  {1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1},

  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}

};

// 出力2用固定パターン(ランダム対象)

const bool fixedPatterns2[16][numSteps] = {

  {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},

  {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0},

  {0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0},

  {0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0},

  {0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0},

  {0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,1},

  {0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0},

  {0,1,0,0,1,0,0,1,0,0,1,0,0,1,1,0},

  {0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1},

  {1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,0},

  {1,0,1,1,0,1,0,1,1,0,1,0,1,1,0,1},

  {1,0,1,1,0,1,1,0,1,1,0,1,0,1,1,1},

  {1,0,1,0,1,0,1,1,0,1,0,1,1,0,1,1},

  {1,1,0,1,1,0,1,1,1,0,1,1,1,0,1,1},

  {1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1},

  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}

};

void generateFixedPattern1(bool* pattern, int index) {

  for (int i = 0; i < numSteps; i++) {

    int shifted = (i – 4 + numSteps) % numSteps;

    pattern[i] = fixedPatterns1[index][shifted];

  }

}

void generateFixedPattern2(bool* pattern, int index) {

  for (int i = 0; i < numSteps; i++) {

    pattern[i] = fixedPatterns2[index][i];

  }

}

int readKnobIndex(int analogPin) {

  int raw = analogRead(analogPin);

  int step = map(raw, 0, 1023, 0, 16);

  if (step > 15) step = 15;

  return step;

}

float readProbability() {

  return analogRead(probKnobPin) / 1023.0;

}

void sendTrigger(int pin) {

  digitalWrite(pin, HIGH);

  delayMicroseconds(5000);

  digitalWrite(pin, LOW);

}

void setup() {

  pinMode(triggerInputPin, INPUT);

  pinMode(buttonPin, INPUT_PULLUP);

  pinMode(ledPin, OUTPUT);

  pinMode(out1Pin, OUTPUT);

  pinMode(out2aPin, OUTPUT);

  pinMode(out2bPin, OUTPUT);

  randomSeed(analogRead(A3));

  generateFixedPattern1(pattern1, 0);

  generateFixedPattern2(pattern2, 0);

  lastTriggerState = digitalRead(triggerInputPin);

  lastTriggerReceived = millis();

}

bool checkClock() {

  bool triggered = false;

  bool currentTrigger = digitalRead(triggerInputPin);

  unsigned long now = millis();

  if (currentTrigger && !lastTriggerState) {

    if (now – lastTriggerReceived > triggerTimeout) {

      currentStep = 0;

      shiftIndex = 0;

    }

    lastTriggerReceived = now;

    triggered = true;

  }

  lastTriggerState = currentTrigger;

  return triggered;

}

void updateModeButton() {

  bool currentButton = digitalRead(buttonPin);

  if (!currentButton && lastButtonState) {

    randomMode = !randomMode;

    if (randomMode) {

      currentRandomPatternIndex = random(0, 16);

    }

  }

  lastButtonState = currentButton;

}

void loop() {

  updateModeButton();

  if (checkClock()) {

    digitalWrite(ledPin, HIGH);

    delayMicroseconds(1000);

    digitalWrite(ledPin, LOW);

    int index1 = readKnobIndex(knob1Pin);

    int index2 = readKnobIndex(knob2Pin);

    generateFixedPattern1(pattern1, index1);

    if (randomMode) {

      if (currentStep == 0) {

        currentRandomPatternIndex = random(0, 16);

      }

      generateFixedPattern2(pattern2, currentRandomPatternIndex);

    } else {

      generateFixedPattern2(pattern2, index2);

    }

    shiftRegister[shiftIndex] = pattern1[currentStep];

    int delayedIndex = (shiftIndex + shiftSize – 4) % shiftSize;

    if (shiftRegister[delayedIndex]) {

      sendTrigger(out1Pin);

    }

    if (pattern2[currentStep]) {

      float prob = readProbability();

      if (random(0, 1000) < prob * 1000.0) {

        sendTrigger(out2bPin);

      } else {

        sendTrigger(out2aPin);

      }

    }

    currentStep = (currentStep + 1) % numSteps;

    shiftIndex = (shiftIndex + 1) % shiftSize;

  }

}