パーカッション生成ツール

パーカッションのリズムパターンを自動生成するスクリプト。

スクリプト


+ スクリプト
# ==========================================================
# @brief パーカッション生成ツール.
# @note 機能概要
#
# @author FL Studio 非公式 wiki (https://w.atwiki.jp/flstudio2/)
# @license MIT
#   再配布は自由です。
#   ただし Image-Line社 (https://www.image-line.com) 以外が
#   有料の販売物に含めることは禁止します
# ==========================================================
from flpianoroll import *
from enum import IntEnum
import random
from typing import List
 
MAX_VELOCITY = 10
# リズムの定義 (Velocityの倍率。最大は10)
PATTERN_TBL = {
# ----------------------1  2   3  4  5  6  7   8  9 10  11 12 13 14  15 16
  "4x4":               [10, 0, 0, 0] * 4,
  "OFF BEAT":          [0, 0, 10, 0] * 4,
  "HI-HAT 0011":       [0, 0, 10, 7] * 4,
  "HI-HAT 0111":       [0, 7, 10, 7] * 4,
  "HI-HAT 1011":       [7, 0, 10, 7] * 4,
  "HI-HAT 1111":       [3, 7, 10, 7] * 4,
  "332 x2":            [10, 0, 0, 10, 0, 0, 10, 0] * 2,
  "333322":            [10, 0, 0, 10, 0, 0, 10, 0, 0, 10, 0, 0, 10, 0, 10, 0],
  "334 24":            [10, 0, 0, 10, 0, 0, 10, 0, 0, 0, 10, 0, 10, 0, 0, 0],
  "JERSEY CLUB":       [10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 10, 0, 0, 10, 0],
  "FILL 8":            [10, 0] * 8,
  "FILL 16":           [10] * 16,
  "FILL 3":            [10, 10, 0, 10, 10, 0, 10, 10, 0, 10, 10, 0, 10, 10, 0, 10],
}
 
# ピッチ.
PITCH_C2 = 12 * 2
PITCH_C3 = 12 * 3
PITCH_C4 = 12 * 4
PITCH_C5 = 12 * 5
PITCH_C6 = 12 * 6
PITCH_C7 = 12 * 7
PITCH_C8 = 12 * 8
 
# 音の長さ (FLは1小節あたり384).
TICK_PPQ = 96
TICK_4   = TICK_PPQ // 1
TICK_8   = TICK_PPQ // 2
TICK_12  = TICK_PPQ // 3 # Triplets
TICK_16  = TICK_PPQ // 4
TICK_32  = TICK_PPQ // 8
TICK_64  = TICK_PPQ // 16
TICK_128 = TICK_PPQ // 32
 
# ピッチオフセット
MAX_PITCH_OFS = 120  # 最大.
MIN_PITCH_OFS = -120 # 最小.
 
# ラベル.
LABEL_PITCH         = 'Pitch'
LABEL_BEATS         = '4 beats x'
LABEL_PATTERN       = 'Pattern'
LABEL_PATTERN_ROT   = 'Rotation'
LABEL_VELOCITY      = 'Velocity'
LABEL_DURATION      = 'Duration'
LABEL_MOD_X         = 'Mod X'
LABEL_MOD_Y         = 'Mod Y'
LABEL_RND_VEL       = 'Random Velocity'
LABEL_RND_PAN       = 'Random Pan'
LABEL_RND_NOTE      = 'Random Note'
LABEL_RND_NOTE_VEL  = 'Rnd Note Velocity'
LABEL_TRIPLETS      = 'Triplets'
 
# 抽選する.
def rand_choice(v:float) -> bool:
  """ vの確率でTrueを返す """
  if v <= 0.0:
    return False # 確率0%はFalse.
  if v >= 1.0:
    return True # 確率100%はTrue.
 
  # 確率計算.
  return random.random() < v
 
def rand_int(a:int, b:int) -> int:
  """ a <= rnd <= b のランダムな整数値を返す """
  return random.randint(a, b)
def rand_range(a:float, b:float) -> float:
  """ a <= rnd <= b のランダムな小数を返す """
  return random.uniform(a, b)
def clamp(v, a, b):
  """ a <= v <= b の範囲になるように clampする """
  return a if v < a else b if v > b else v
 
# 3連符の種別.
class Triplets(IntEnum):
  NONE   = 0
  SKIP_1 = 1
  SKIP_2 = 2
  SKIP_3 = 3
  SKIP_4 = 4
TRIPLETS_LIST = ["NONE", "SKIP 1", "★SKIP 2", "SKIP 3", "SKIP 4"]
 
class Pattern:
  def __init__(self, vel:float):
    self.vel = 1.0 * vel / MAX_VELOCITY # 0.0〜1.0に正規化.
  def is_valid(self) -> bool:
    # velocityが0より大きければ有効.
    return self.vel > 0
 
def createDialog():
  """ ダイアログ生成"""
  form = ScriptDialog('Percussion tool', '')
 
  # ボタン追加.
  form.AddInputKnobInt(LABEL_PITCH, PITCH_C5, PITCH_C2, PITCH_C8)
  form.AddInputKnobInt(LABEL_BEATS, 1, 1, 5)
  form.AddInputCombo(LABEL_PATTERN, PATTERN_TBL.keys(), 3)
  form.AddInputKnobInt(LABEL_PATTERN_ROT, 0, -15, 15)
  form.AddInputKnob(LABEL_VELOCITY, 0.8, 0.0, 1.0)
  form.AddInputKnob(LABEL_DURATION, 0.0, 0.0, 1.0)
  form.AddInputKnob(LABEL_MOD_X, 0.5, 0.0, 1.0)
  form.AddInputKnob(LABEL_MOD_Y, 0.5, 0.0, 1.0)
  form.AddInputKnob(LABEL_RND_VEL, 0.0, 0.0, 1.0)
  form.AddInputKnob(LABEL_RND_PAN, 0.0, 0.0, 1.0)
  form.AddInputKnob(LABEL_RND_NOTE, 0.0, 0.0, 1.0)
  form.AddInputKnob(LABEL_RND_NOTE_VEL, 0.4, 0.0, 1.0)
  form.AddInputCombo(LABEL_TRIPLETS, TRIPLETS_LIST, 0)
 
  return form
 
def rotate_list(l, v):
  """ リストをvの値で回転する """
  v = v%len(l) * -1
  return l[v:] + l[:v]
 
def get_table_value_from_idx(table, idx:int) -> List[int]:
  """ 辞書型からインデックス指定で値を取得する (Python3.6以降はこれでOK) """
  return list(table.values())[idx]
 
def normalize_to_range(v, a, b):
  """
  a <= v <= b となるように正規化する
  Note:
  - a > b の場合、範囲を入れ替えて正規化します
  - a == b の場合、値は固定値 a になります
  """
  d = b - a
  if d < 0:
    a, b = b, a # a > b の場合は値を入れ替えます
  elif d == 0:
    return a # a = b の場合は固定値.
 
  return ((v - a) % d) + a
 
def normalize_to_symmetric_range(v, a):
  """ -a <= v <= a となるように正規化する """
  return normalize_to_range(v, -a, a)
 
# ツール用にNoteクラスを拡張
class PercNote:
  def __init__(self, max_bars):
    self.note = Note()
    self.max_bars = max_bars
 
  def duplicate(self):
    """ 複製を返す """
    ret = PercNote(self.max_bars)
    ret.note = self.note.clone()
    return ret
 
# Percussion Tool.
class Perc:
  def __init__(self, form):
    self.form = form
    self.max_steps = self.getInputValue(LABEL_BEATS) * 16
 
  def next_step(self):
    # 次のステップに進む.
    pass
 
  def getInputValue(self, s:str):
    """ form から値を取得 """
    return self.form.GetInputValue(s)
 
  def create_note(self, step:int, ptn:Pattern) -> PercNote:
    """ ノート生成 """
    if self.is_skip_triplets(step):
      return None # スキップするステップ.
 
    tick      = self.getInputValue(LABEL_DURATION)
    max_bars  = self.getInputValue(LABEL_BEATS)
    # ノート生成.
    n = PercNote(max_bars)
 
    # 基本情報を設定.
    n.note.time = step * TICK_16
    if self.is_triplets():
      n.note.time = (step * 3 // 4) * TICK_12 # 3連符.
 
    n.note.length   = int(TICK_4 * tick) # 4分音符に対する割合.
    n.note.velocity = self.getInputValue(LABEL_VELOCITY) * ptn.vel
    n.note.fcut     = self.getInputValue(LABEL_MOD_X)
    n.note.fres     = self.getInputValue(LABEL_MOD_Y)
 
    # ランダム処理.
    rnd_vel = self.getInputValue(LABEL_RND_VEL)
    if rnd_vel > 0:
      # ランダムVelocity.
      v = n.note.velocity + 0.5 * rand_range(-1.0, 1.0) * rnd_vel
      n.note.velocity = clamp(v, 0.0, 1.0) 
 
    rnd_pan = self.getInputValue(LABEL_RND_PAN)
    if rnd_pan > 0:
      # ランダムPAN.
      n.note.pan = 0.5 + 0.5 * rand_range(-1.0, 1.0) * rnd_pan
 
    return n
 
  def interval_to_pitch(self, idx:int) -> int:
    """ ピッチの取得 """
    pitch = self.getInputValue(LABEL_PITCH) 
    return pitch
 
  def get_pattern(self) -> List[Pattern]:
    """ パターンの取得 """
    idx = self.form.GetInputValue(LABEL_PATTERN)
    rotation = self.form.GetInputValue(LABEL_PATTERN_ROT)
    pattern = get_table_value_from_idx(PATTERN_TBL, idx)
    if rotation != 0:
      # パターンを回転
      pattern = rotate_list(pattern, rotation)
 
    # barの値だけ繰り返す.
    bar_mul = self.form.GetInputValue(LABEL_BEATS)
 
    # ランダムノートの出現割合.
    rnd_note = self.getInputValue(LABEL_RND_NOTE)
    rnd_note_vel = self.getInputValue(LABEL_RND_NOTE_VEL)
 
    # Patternデータに変換
    ret:List[Pattern] = []
    for d in pattern * bar_mul:
      if d == 0:
        # 休符.
        if rand_choice(rnd_note):
          # ランダムノートの確率で休符をノートにする.
          d = int(MAX_VELOCITY * rnd_note_vel)
 
      ptn = Pattern(d)
      ret.append(ptn)
    return ret
 
  def is_triplets(self) -> bool:
    """ 3連符モードかどうか """
    triplets = self.form.GetInputValue(LABEL_TRIPLETS)
    if triplets == Triplets.NONE:
      return False # 3連符モードでない.
 
    return True # 3連符モード.    
 
  def is_skip_triplets(self, step:int) -> bool:
    """ トリプレットのステップ数かどうか """
    triplets = self.form.GetInputValue(LABEL_TRIPLETS)
    if triplets == Triplets.NONE:
      return False # 3連符無効.
 
    if triplets != (step % 4) + 1: # 1拍4ステップで1始まり.
      return False # 該当しない.
 
    # 3連符のスキップステップ.
    return True
 
def apply(form):
  """ ノートの設定 """
  # ノートをすべて消去.
  score.clear()
 
  perc = Perc(form)
 
  for step, ptn in enumerate(perc.get_pattern()):
    # ノート生成.
    n = perc.create_note(step, ptn)
    if n is None:
      # スキップノート.
      perc.next_step()
      continue
 
    # ピッチを設定.    
    n.note.number = perc.getInputValue(LABEL_PITCH)
 
    if ptn.is_valid():
      # 休符でない
      n.note.number = perc.interval_to_pitch(step)
      score.addNote(n.note) # スコアに追加.
 
    else:
      # 休符.
      pass
 
    perc.next_step()
 
最終更新:2025年04月02日 07:13