開発ブログ

BLOG

連符・ポリリズム・スウィングを、もっと細かく選べるようにした

パターンに 5 連符・6 連符・7 連符・32 分音符を足し、ポリリズムを 4 対 3 や 7 対 4 のような比で、8 分と 16 分のスウィングを跳ね方の強さで選べるようにした。拍の中で鳴らすガイド音の位置を拍ごとに求める方法と、16 分を跳ねさせる専用のパターンをやめ、スライダーで跳ね方の強さを選ぶ形にした理由を書く。

公開 2026年10月11日

Metronome.fm のパターン(拍のクリックとは別に、1 拍の中で鳴らすガイド音)の種類を増やしました。5 連符・6 連符・7 連符・32 分音符を選べるようになり、ポリリズムは 3 対 2 だけでなく 4 対 3 や 7 対 4 のような比を選べます。8 分音符(8th)と 16 分音符(16th)のパターンでは、スウィングの量(跳ね方の強さ)を変えられます。

メトロノームの画面のパターンの欄。番号 1 は右上の「編集」、番号 2 は Quintuplet・Sextuplet・Septuplet・32nd の 4 つのボタンの行
パターンの欄
  1. パターンの「編集」。パターンの欄に出すものを選ぶ
  2. 新しく選べるようになった 5 連符・6 連符・7 連符・32 分音符

この記事では、ポリリズムの比とスウィングの量に合わせて、ガイド音を鳴らす位置をどう求めるようにしたかを書きます。前に保存した設定の扱いと、増えたパターンから欄に出すものを選べるようにしたことにも触れます。

ガイド音は「拍の中の位置」で決まる

ガイド音の位置は、パターンごとの関数が「拍の頭から 1 拍のどれだけ先で鳴らすか」を 0 以上 1 未満の数で返し、シーケンサー(音を鳴らす時刻を決める部分)がそれに 1 拍の長さを掛けて時刻にします。拍の頭では拍のクリックが鳴るので、0 は入れません。8 分音符なら [1/2]、3 連符なら [1/3, 2/3] です。

5 連符・6 連符・7 連符・32 分音符も、1 拍を等しく分けた位置を返すだけです(32 分音符は 1 拍を 8 つに分けます)。

/** 1 拍を count 個に等しく分けたときの、拍の頭を除く位置 */
const evenlySpacedOffsets = (count: number) =>
  Array.from({ length: count - 1 }, (_, index) => (index + 1) / count);

case "quintuplet":
  return evenlySpacedOffsets(5);
case "32nd":
  return evenlySpacedOffsets(8);

この形のままでは済まなかったのは、1 拍の中に収まらないポリリズムと、位置そのものを動かすスウィングです。

ポリリズムは拍ごとに求める

ポリリズムの欄で、音の数と拍の数をそれぞれ 2〜9 から選べます。4 と 3 なら、3 拍のあいだに 4 回、等しい間隔でガイド音が鳴ります。

パターンで Poly を選んだ画面。番号 1 は「Poly 4:3」のボタン、番号 2 は「音の数 4」「拍の数 3」のステッパー
ポリリズムを 4 対 3 にしたところ
  1. ボタンの下に、選んでいる比が出る
  2. 音の数と拍の数

ポリリズムには、比が 1 周する拍のまとまりがあります(3 対 2 なら 2 拍。以下、周期と呼びます)。これまでの 3 対 2 は、周期の頭の拍で、2 拍ぶんの音の時刻をまとめて前もって決めていました(予約)。最後の音は、予約した拍の頭から 4/3 拍先です。同じやり方で比を選べるようにすると、7 対 4 では 3 拍以上先の音までまとめて予約することになります。

先の拍の音をまとめて予約すると、そのあいだにテンポが変わったときに位置がずれます。シーケンサーは、予約した時点の 1 拍の長さで時刻を決めるためです。テンポを少しずつ上げるプログレッシブモードでは、周期の途中でテンポが変わります。

そこで、拍ごとに「その拍に入る音」だけを求めるようにしました。音の数を notes、拍の数を beats とし、周期の頭の音を 0 番目と数えると、k 番目の音は周期の頭から k × beats / notes 拍の位置です。これを、周期の中で何拍目か(beatInCycle)と拍の中の位置に分けます。

const getPolyrhythmOffsets = (
  [notes, beats]: Readonly<PolyrhythmRatio>,
  totalBeatIndex: number,
) => {
  const beatInCycle = ((totalBeatIndex % beats) + beats) % beats;
  // 位置は k × beats / notes 拍。小数の誤差で拍の境目を取り違えないよう、整数のまま拍と余りに分ける
  return Array.from({ length: notes - 1 }, (_, index) => (index + 1) * beats)
    .filter((position) => Math.floor(position / notes) === beatInCycle)
    .map((position) => (position % notes) / notes);
};

位置を k × (beats / notes) のように小数で求めると、拍の境目にちょうど来る音(4 対 2 なら、周期の頭から 2 拍目の頭に来る音)が、計算の誤差で前の拍の終わりと取り違えられるおそれがあります。そのため、k × beats を整数のまま notes で割り、商を拍、余りを拍の中の位置にしています。

7 対 4 なら、4 拍の周期の中で次の位置に鳴ります。周期の頭の音は拍のクリックと重なるので、ガイド音としては鳴らしません。

周期の中の拍
鳴る位置(拍の中)
1 拍目
4/7
2 拍目
1/7・5/7
3 拍目
2/7・6/7
4 拍目
3/7

周期は、小節の中の拍番号ではなく、再生を始めてからの通しの拍番号で数えます。3/4 で 3 対 2 を鳴らすと、2 小節・6 拍で 1 周し、小節をまたいでも等間隔に鳴ります。

メトロノームの音を音声ファイルに書き出す機能も、同じ関数で位置を決めています。そのため、3 対 2 の書き出しの波形はわずかに変わりました。2 拍目の音を「前の拍の頭から 4/3 拍」ではなく「その拍の頭から 1/3 拍」で求めるようになり、サンプル(音声のデータの 1 点)の位置の切り捨て方が変わったためです。ずれは最大 1 サンプルで、書き出しの 44.1kHz では約 0.02 ミリ秒です。

スウィングは 1 つの量にまとめた

これまでは、16 分音符を跳ねさせる「16th(s)」というパターンと、カスタムビート(パターンの Custom で、自分でリズムを組む機能)の「シャッフル」のスイッチがありました。どちらも、16 分音符の 2 つ目と 4 つ目を 3 連符のリズムの位置(8 分音符の長さの 2/3)へずらすだけで、跳ね方の強さは変えられませんでした。

16th(s) を別のパターンとして残したまま強さも選べるようにすると、「跳ねない 16 分」と「跳ねる 16 分」の違いが、ボタンとスライダーの 2 か所に分かれます。跳ねないのはスウィング 50% のときとすれば、選ぶ場所は 1 つで済みます。

そこで今回、16th(s) をなくして 16th に一本化し、8th と 16th を選んだときにスウィングのスライダーを出すようにしました。カスタムビートのシャッフルのスイッチも、同じスライダーに置き換えています。

パターンで 16th を選んだ画面。番号 1 は「16th」のボタン、番号 2 はスウィングのスライダー(67%)
16th でスウィングを 67% にしたところ
  1. 16th を選ぶと、下にスライダーが出る
  2. スウィングの量(8th・16th とも 50〜75%)

スウィングの量は、跳ねさせる 2 つの音のうち、前の音が占める長さの割合です。8th なら 1 拍を、16th なら 8 分音符 1 つぶんを前後 2 つに分け、前の音の長さがこの割合になるところまで、後ろの音を遅らせます。

case "8th":
  // 裏の 8 分をスウィングの量だけ遅らせる(0.5 なら拍の真ん中)
  return [swingRatio];
case "16th":
  // 8 分音符 2 つそれぞれの中で、後ろの 16 分音符をスウィングの量だけ遅らせる(0.5 なら等間隔)
  return [swingRatio / 2, 1 / 2, 1 / 2 + swingRatio / 2];

16th なら、50% で跳ねない 16 分音符、67%(2/3)でこれまでの 16th(s) と同じ 3 連符のリズムの位置、75% で付点 16 分音符の跳ね方になります。8th で跳ねさせると、ジャズの 8 分のスウィングも練習できます。

16th(s) を使った前の設定は、スウィング 67% で読み込む

保存した設定・ブラウザに残っている前回の状態・書き出した設定ファイルには、16thShuffle(画面の 16th(s))や、カスタムビートのシャッフルをオンにした状態が残っています。読み込むときは、どれも同じ関数で「16th(またはカスタムビート)で、スウィング 2/3」として扱うので、これまでと同じ鳴り方のまま使えます。

export const migrateLegacySwing = ({
  guideNoteType,
  swingRatio,
  isCustomBeatShuffleEnabled = false,
}: {
  guideNoteType: unknown;
  swingRatio?: unknown;
  isCustomBeatShuffleEnabled?: boolean;
}): { guideNoteType: GuideNoteType; swingRatio: number } => {
  const swing = normalizeSwingRatio(swingRatio);
  const legacySwing =
    swing > DEFAULT_SWING_RATIO ? swing : LEGACY_SHUFFLE_SWING_RATIO;
  if (guideNoteType === "16thShuffle") {
    return { guideNoteType: "16th", swingRatio: legacySwing };
  }
  if (guideNoteType === "custom" && isCustomBeatShuffleEnabled) {
    return { guideNoteType: "custom", swingRatio: legacySwing };
  }
  return {
    guideNoteType: isGuideNoteType(guideNoteType) ? guideNoteType : "off",
    swingRatio: swing,
  };
};

鳴り方が変わっていないかは、音声の書き出しの波形で確かめました。書き出しのテストでは、パターンごとに、書き出した波形から求めたハッシュ(値が完全に一致するかを見るため、波形のバイト列から求めた短い値)を記録しています。16th(s) とカスタムビートのシャッフルのテストを「16th でスウィング 2/3」「カスタムビートでスウィング 2/3」に書き換えても、記録したハッシュは変わりませんでした。前の節の 3 対 2 とは違い、こちらは波形が変わらなかったということです。

出すパターンは自分で選ぶ

パターンが 10 個になり、そのまま並べると 3 段になります。5 連符や 32 分音符を使わない人には、段が増えるだけです。

そこで、テンポや拍子の欄にあるプリセットの「編集」と同じように、パターンの欄にも「編集」を付けました。開いたモーダルで、パターンの欄に出すものを選べます。

パターンプリセットの設定のモーダル。番号 1 は、オフになっている Quintuplet・Sextuplet・Septuplet・32nd の行
パターンの編集
  1. はじめはオフにしている 5 連符・6 連符・7 連符・32 分音符

はじめに出すのは、これまでもあったパターン(Off・8th・Triplet・16th・Poly・Custom)だけです。5 連符・6 連符・7 連符・32 分音符は、使う人が「編集」でオンにすると欄に出ます。どれを出すかは、設定を保存すると一緒に残ります。

マニュアルのパターンの節にも、使い方を書いています。

この記事を共有

X でポストはてなブックマーク