611 lines
25 KiB
Python
611 lines
25 KiB
Python
"""
|
|
Build a PROFESSIONAL reggaeton FLP with REAL SAMPLES from the user's library.
|
|
|
|
Key facts:
|
|
- Only Ch10-19 are sampler channels in the reference FLP (Ch0-9 are VST/plugin)
|
|
- Each sampler channel loads a real WAV from libreria/reggaeton/
|
|
- MIDI notes trigger those real samples
|
|
- 10 channels = kick, snare, hihat, 808, bell, lead, pad, clap, perc, rim
|
|
|
|
Sample selection (professional reggaeton):
|
|
Ch10: kick nes 1 — classic reggaeton kick
|
|
Ch11: snare nes 1 — clean reggaeton snare
|
|
Ch12: hi-hat 1 — tight hihat
|
|
Ch13: Bass Reventado — deep 808 bass (dastin.prod)
|
|
Ch14: bell 4 — bell tone for chords
|
|
Ch15: lead 3 — melodic lead
|
|
Ch16: pad 1 — sustained pad
|
|
Ch17: clap — reggaeton clap (using snap from perc loop)
|
|
Ch18: perc 1 — perc one shot
|
|
Ch19: rim — rim/rimshot
|
|
|
|
Output: output/reggaeton_fuego.flp
|
|
"""
|
|
import struct
|
|
import sys
|
|
import os
|
|
|
|
# ── Paths ──────────────────────────────────────────────────────────────────────
|
|
BASE = r"C:\Users\Administrator\Documents\fl_control"
|
|
CH11_TMPL = os.path.join(BASE, "output", "ch11_kick_template.bin")
|
|
REF_FLP = os.path.join(BASE, r"my space ryt\my space ryt.flp")
|
|
FLP_OUT = os.path.join(BASE, "output", "reggaeton_fuego.flp")
|
|
|
|
# All samples copied here — clean names, no special chars
|
|
SAMPLES_DIR = os.path.join(BASE, "output", "fuego_samples")
|
|
|
|
sys.path.insert(0, BASE)
|
|
|
|
from src.flp_builder.events import (
|
|
EventID,
|
|
encode_text_event,
|
|
encode_word_event,
|
|
encode_data_event,
|
|
encode_notes_block,
|
|
)
|
|
from src.flp_builder.skeleton import ChannelSkeletonLoader
|
|
from src.flp_builder.arrangement import (
|
|
ArrangementItem,
|
|
build_arrangement_section,
|
|
build_track_data_template,
|
|
)
|
|
|
|
# ── Constants ──────────────────────────────────────────────────────────────────
|
|
BPM = 95
|
|
PPQ = 96
|
|
|
|
# Channel indices — ALL sampler channels (10-19)
|
|
CH_KICK = 10
|
|
CH_SNARE = 11
|
|
CH_HH = 12
|
|
CH_808 = 13
|
|
CH_BELL = 14
|
|
CH_LEAD = 15
|
|
CH_PAD = 16
|
|
CH_CLAP = 17
|
|
CH_PERC = 18
|
|
CH_RIM = 19
|
|
|
|
|
|
# Sample assignment: ch_idx → (samples_dir, wav_filename)
|
|
# All samples in fuego_samples/ with clean names
|
|
SAMPLE_ASSIGNMENT = {
|
|
CH_KICK: (SAMPLES_DIR, "kick.wav"),
|
|
CH_SNARE: (SAMPLES_DIR, "snare.wav"),
|
|
CH_HH: (SAMPLES_DIR, "hihat.wav"),
|
|
CH_808: (SAMPLES_DIR, "bass_808.wav"),
|
|
CH_BELL: (SAMPLES_DIR, "bell.wav"),
|
|
CH_LEAD: (SAMPLES_DIR, "lead.wav"),
|
|
CH_PAD: (SAMPLES_DIR, "pad.wav"),
|
|
CH_CLAP: (SAMPLES_DIR, "clap.wav"),
|
|
CH_PERC: (SAMPLES_DIR, "perc.wav"),
|
|
CH_RIM: (SAMPLES_DIR, "rim.wav"),
|
|
}
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# FUEGO CHORD PROGRESSION: Am → Dm → F → E
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
PROGRESSION = [
|
|
{"name": "Am", "bass": 33, "chord": [45,48,52,57], "triad": [57,60,64], "root": 69},
|
|
{"name": "Dm", "bass": 38, "chord": [50,53,57,62], "triad": [62,65,69], "root": 74},
|
|
{"name": "F", "bass": 41, "chord": [53,57,60,65], "triad": [65,69,72], "root": 77},
|
|
{"name": "E", "bass": 40, "chord": [52,56,59,64], "triad": [64,68,71], "root": 76},
|
|
]
|
|
BEATS_PER_CHORD = 8
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# DRUM GENERATORS — using correct channel indices
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def _n(pos, length, ch, vel):
|
|
return {"pos": pos, "len": length, "key": 60, "vel": max(1, min(127, vel))}
|
|
|
|
|
|
def dembow_kick(bars, vel_mult=1.0):
|
|
"""REAL dembow: 0.0, 2.0, 3.25"""
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o, 0.25, CH_KICK, int(120 * vel_mult)))
|
|
notes.append(_n(o + 2.0, 0.25, CH_KICK, int(110 * vel_mult)))
|
|
notes.append(_n(o + 3.25, 0.15, CH_KICK, int(90 * vel_mult)))
|
|
return {CH_KICK: notes}
|
|
|
|
|
|
def perreador_kick(bars, vel_mult=1.0):
|
|
"""Perreador: every beat + offbeat ghosts."""
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
for beat in range(4):
|
|
notes.append(_n(o + beat, 0.25, CH_KICK, int(115 * vel_mult)))
|
|
notes.append(_n(o + beat + 0.5, 0.15, CH_KICK, int(80 * vel_mult)))
|
|
return {CH_KICK: notes}
|
|
|
|
|
|
def sparse_kick(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
notes.append(_n(b * 4.0, 0.25, CH_KICK, int(100 * vel_mult)))
|
|
return {CH_KICK: notes}
|
|
|
|
|
|
def snare_standard(bars, vel_mult=1.0):
|
|
"""Snare: beats 2, 3-and (positions 1.25, 3.0)."""
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o + 1.25, 0.15, CH_SNARE, int(105 * vel_mult)))
|
|
notes.append(_n(o + 3.0, 0.15, CH_SNARE, int(100 * vel_mult)))
|
|
return {CH_SNARE: notes}
|
|
|
|
|
|
def snare_intense(bars, vel_mult=1.0):
|
|
"""Intense snare with ghost hits."""
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o + 1.25, 0.15, CH_SNARE, int(110 * vel_mult)))
|
|
notes.append(_n(o + 1.75, 0.10, CH_SNARE, int(70 * vel_mult)))
|
|
notes.append(_n(o + 3.0, 0.15, CH_SNARE, int(105 * vel_mult)))
|
|
notes.append(_n(o + 3.5, 0.10, CH_SNARE, int(65 * vel_mult)))
|
|
return {CH_SNARE: notes}
|
|
|
|
|
|
def hihat_offbeat(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
for i in range(4):
|
|
notes.append(_n(o + i + 0.5, 0.1, CH_HH, int(55 * vel_mult)))
|
|
return {CH_HH: notes}
|
|
|
|
|
|
def hihat_8th(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
for i in range(8):
|
|
v = 70 if i % 2 == 0 else 50
|
|
notes.append(_n(o + i * 0.5, 0.1, CH_HH, int(v * vel_mult)))
|
|
return {CH_HH: notes}
|
|
|
|
|
|
def hihat_16th(bars, vel_mult=1.0):
|
|
"""Full 16ths with accents and open hats."""
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
for i in range(16):
|
|
p = i * 0.25
|
|
if p % 1.0 == 0.0:
|
|
v, l = 90, 0.1
|
|
elif p % 0.5 == 0.0:
|
|
v, l = 65, 0.1
|
|
else:
|
|
v, l = 40, 0.08
|
|
if i in [5, 10]:
|
|
l = 0.2; v = int(v * 1.2)
|
|
notes.append(_n(o + p, l, CH_HH, int(v * vel_mult)))
|
|
return {CH_HH: notes}
|
|
|
|
|
|
def clap_standard(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o + 1.0, 0.15, CH_CLAP, int(120 * vel_mult)))
|
|
notes.append(_n(o + 3.0, 0.15, CH_CLAP, int(115 * vel_mult)))
|
|
return {CH_CLAP: notes}
|
|
|
|
|
|
def clap_soft(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o + 1.0, 0.15, CH_CLAP, int(80 * vel_mult)))
|
|
notes.append(_n(o + 3.0, 0.15, CH_CLAP, int(75 * vel_mult)))
|
|
return {CH_CLAP: notes}
|
|
|
|
|
|
def perc_offbeat(bars, vel_mult=1.0):
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
notes.append(_n(o + 0.75, 0.1, CH_PERC, int(85 * vel_mult)))
|
|
notes.append(_n(o + 2.75, 0.1, CH_PERC, int(80 * vel_mult)))
|
|
return {CH_PERC: notes}
|
|
|
|
|
|
def rim_build(bars, vel_mult=1.0):
|
|
"""Rim roll building intensity."""
|
|
PATTERNS = [[0,2,8,14], [0,2,4,8,10,14], [0,2,4,6,8,10,12,14], list(range(16))]
|
|
VELS = [50, 65, 80, 100]
|
|
notes = []
|
|
for b in range(bars):
|
|
o = b * 4.0
|
|
v = int(VELS[b % 4] * vel_mult)
|
|
for idx in PATTERNS[b % 4]:
|
|
notes.append(_n(o + idx * 0.25, 0.1, CH_RIM, v))
|
|
return {CH_RIM: notes}
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# MELODIC GENERATORS
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def _mn(pos, length, key, vel):
|
|
"""Melodic note — pitch matters."""
|
|
return {"pos": pos, "len": length, "key": key, "vel": max(1, min(127, vel))}
|
|
|
|
|
|
def bass_808_full(bars, vel_mult=1.0):
|
|
"""808 bass with chord-root movement + fifth variation."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
r = ch["bass"]
|
|
f = r + 7
|
|
v = vel_mult
|
|
notes.append(_mn(base + 0.0, 2.5, r, int(110*v)))
|
|
notes.append(_mn(base + 2.5, 0.5, f, int(80*v)))
|
|
notes.append(_mn(base + 3.0, 2.0, r, int(105*v)))
|
|
notes.append(_mn(base + 5.0, 1.0, r, int(90*v)))
|
|
notes.append(_mn(base + 6.0, 0.5, f, int(75*v)))
|
|
notes.append(_mn(base + 6.5, 1.5, r, int(100*v)))
|
|
return {CH_808: notes}
|
|
|
|
|
|
def bass_808_sparse(bars, vel_mult=1.0):
|
|
"""Sparse 808 for intro — just root, long sustain."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
notes.append(_mn(ci * BEATS_PER_CHORD, 7.5, ch["bass"], int(60 * vel_mult)))
|
|
return {CH_808: notes}
|
|
|
|
|
|
def bell_chords(bars, vel_mult=1.0):
|
|
"""Bell playing offbeat chord stabs — 4-note voicings."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
stabs = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
for sp in stabs:
|
|
v = int((85 + (hash((ci, sp)) % 10)) * vel_mult)
|
|
for pitch in ch["triad"]:
|
|
notes.append(_mn(base + sp, 0.12, pitch, v))
|
|
return {CH_BELL: notes}
|
|
|
|
|
|
def bell_sparse(bars, vel_mult=1.0):
|
|
"""Sparse bell for intro — 4-note voicings, beats 2.5 and 6.5."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
for sp in [2.5, 6.5]:
|
|
v = int(60 * vel_mult)
|
|
for pitch in ch["chord"]:
|
|
notes.append(_mn(base + sp, 0.15, pitch, v))
|
|
return {CH_BELL: notes}
|
|
|
|
|
|
def lead_hook(bars, vel_mult=1.0):
|
|
"""Lead melody — arch contour, chord tones on strong beats."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
lr = ch["root"]
|
|
c = ch["triad"]
|
|
v = vel_mult
|
|
notes.append(_mn(base + 0.0, 1.0, c[0], int(95*v)))
|
|
notes.append(_mn(base + 1.0, 0.5, c[1], int(85*v)))
|
|
notes.append(_mn(base + 1.5, 0.5, c[2], int(100*v)))
|
|
notes.append(_mn(base + 2.0, 1.5, lr, int(105*v)))
|
|
notes.append(_mn(base + 3.5, 0.5, c[2], int(90*v)))
|
|
notes.append(_mn(base + 4.0, 0.5, c[1], int(80*v)))
|
|
notes.append(_mn(base + 4.5, 1.5, c[0], int(95*v)))
|
|
notes.append(_mn(base + 6.0, 0.5, lr-2, int(75*v)))
|
|
notes.append(_mn(base + 6.5, 1.5, c[0], int(90*v)))
|
|
return {CH_LEAD: notes}
|
|
|
|
|
|
def pad_sustained(bars, vel_mult=1.0):
|
|
"""Sustained pad — 4-note voicings."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
for pitch in ch["chord"]:
|
|
notes.append(_mn(base, 7.5, pitch, int(60 * vel_mult)))
|
|
return {CH_PAD: notes}
|
|
|
|
|
|
def pad_swell(bars, vel_mult=1.0):
|
|
"""Pad swell for pre-chorus — crescendo within chord."""
|
|
notes = []
|
|
total = bars * 4
|
|
chords = total // BEATS_PER_CHORD
|
|
for ci in range(chords):
|
|
ch = PROGRESSION[ci % 4]
|
|
base = ci * BEATS_PER_CHORD
|
|
for pitch in ch["chord"]:
|
|
notes.append(_mn(base, 4.0, pitch, int(45 * vel_mult)))
|
|
notes.append(_mn(base + 4, 3.5, pitch, int(70 * vel_mult)))
|
|
return {CH_PAD: notes}
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# PATTERN DEFINITIONS — 20 patterns
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
# All generators return {ch_idx: [notes]}
|
|
ALL_GENERATORS = {
|
|
"dembow_kick": dembow_kick,
|
|
"perreador_kick": perreador_kick,
|
|
"sparse_kick": sparse_kick,
|
|
"snare_std": snare_standard,
|
|
"snare_intense": snare_intense,
|
|
"hh_offbeat": hihat_offbeat,
|
|
"hh_8th": hihat_8th,
|
|
"hh_16th": hihat_16th,
|
|
"clap_std": clap_standard,
|
|
"clap_soft": clap_soft,
|
|
"perc_offbeat": perc_offbeat,
|
|
"rim_build": rim_build,
|
|
"bass_full": bass_808_full,
|
|
"bass_sparse": bass_808_sparse,
|
|
"bell_chords": bell_chords,
|
|
"bell_sparse": bell_sparse,
|
|
"lead_hook": lead_hook,
|
|
"pad_sustained": pad_sustained,
|
|
"pad_swell": pad_swell,
|
|
}
|
|
|
|
PATTERNS = [
|
|
{"id": 1, "name": "Kick Dembow", "gen": "dembow_kick", "bars": 8},
|
|
{"id": 2, "name": "Kick Perreador", "gen": "perreador_kick","bars": 8},
|
|
{"id": 3, "name": "Kick Sparse", "gen": "sparse_kick", "bars": 8},
|
|
{"id": 4, "name": "Snare Standard", "gen": "snare_std", "bars": 8},
|
|
{"id": 5, "name": "Snare Intense", "gen": "snare_intense", "bars": 8},
|
|
{"id": 6, "name": "HH Offbeat", "gen": "hh_offbeat", "bars": 8},
|
|
{"id": 7, "name": "HH 8th", "gen": "hh_8th", "bars": 8},
|
|
{"id": 8, "name": "HH 16th Full", "gen": "hh_16th", "bars": 8},
|
|
{"id": 9, "name": "Clap Standard", "gen": "clap_std", "bars": 8},
|
|
{"id": 10, "name": "Perc Offbeat", "gen": "perc_offbeat", "bars": 8},
|
|
{"id": 11, "name": "Rim Build", "gen": "rim_build", "bars": 4},
|
|
{"id": 12, "name": "808 Bass Full", "gen": "bass_full", "bars": 8},
|
|
{"id": 13, "name": "808 Bass Sparse", "gen": "bass_sparse", "bars": 8},
|
|
{"id": 14, "name": "Bell Chords", "gen": "bell_chords", "bars": 8},
|
|
{"id": 15, "name": "Bell Sparse", "gen": "bell_sparse", "bars": 8},
|
|
{"id": 16, "name": "Lead Hook", "gen": "lead_hook", "bars": 8},
|
|
{"id": 17, "name": "Pad Sustained", "gen": "pad_sustained", "bars": 8},
|
|
{"id": 18, "name": "Pad Swell", "gen": "pad_swell", "bars": 8},
|
|
]
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# ARRANGEMENT — 48 bars, 7 sections
|
|
# 10 tracks (one per sampler channel Ch10-19)
|
|
# Track index in arrangement: 0=kick, 1=snare, 2=hh, 3=808, 4=bell,
|
|
# 5=lead, 6=pad, 7=clap, 8=perc, 9=rim
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
ARRANGEMENT_ITEMS = [
|
|
# INTRO (0-4): ghostly, sparse
|
|
{"pattern": 3, "bar": 0, "bars": 4, "track": 0}, # sparse kick
|
|
{"pattern": 6, "bar": 0, "bars": 4, "track": 2}, # offbeat HH
|
|
{"pattern": 13, "bar": 0, "bars": 4, "track": 3}, # sparse 808
|
|
{"pattern": 15, "bar": 0, "bars": 4, "track": 4}, # sparse bell
|
|
{"pattern": 17, "bar": 0, "bars": 4, "track": 6}, # pad sustained
|
|
|
|
# VERSE 1 (4-12): warming up
|
|
{"pattern": 1, "bar": 4, "bars": 8, "track": 0}, # dembow kick
|
|
{"pattern": 4, "bar": 4, "bars": 8, "track": 1}, # snare std
|
|
{"pattern": 7, "bar": 4, "bars": 8, "track": 2}, # HH 8th
|
|
{"pattern": 12, "bar": 4, "bars": 8, "track": 3}, # 808 full
|
|
{"pattern": 15, "bar": 4, "bars": 8, "track": 4}, # sparse bell
|
|
{"pattern": 17, "bar": 4, "bars": 8, "track": 6}, # pad
|
|
|
|
# PRE-CHORUS (12-16): building tension
|
|
{"pattern": 1, "bar": 12, "bars": 4, "track": 0}, # dembow kick
|
|
{"pattern": 5, "bar": 12, "bars": 4, "track": 1}, # snare intense
|
|
{"pattern": 11, "bar": 12, "bars": 4, "track": 9}, # rim build
|
|
{"pattern": 7, "bar": 12, "bars": 4, "track": 2}, # HH 8th
|
|
{"pattern": 12, "bar": 12, "bars": 4, "track": 3}, # 808 full
|
|
{"pattern": 14, "bar": 12, "bars": 4, "track": 4}, # bell chords
|
|
{"pattern": 18, "bar": 12, "bars": 4, "track": 6}, # pad swell
|
|
|
|
# CHORUS (16-24): FULL ENERGY
|
|
{"pattern": 2, "bar": 16, "bars": 8, "track": 0}, # perreador kick!
|
|
{"pattern": 5, "bar": 16, "bars": 8, "track": 1}, # snare intense
|
|
{"pattern": 8, "bar": 16, "bars": 8, "track": 2}, # HH 16th
|
|
{"pattern": 9, "bar": 16, "bars": 8, "track": 7}, # clap
|
|
{"pattern": 10, "bar": 16, "bars": 8, "track": 8}, # perc offbeat
|
|
{"pattern": 12, "bar": 16, "bars": 8, "track": 3}, # 808 full
|
|
{"pattern": 14, "bar": 16, "bars": 8, "track": 4}, # bell chords
|
|
{"pattern": 16, "bar": 16, "bars": 8, "track": 5}, # lead hook
|
|
{"pattern": 17, "bar": 16, "bars": 8, "track": 6}, # pad
|
|
|
|
# VERSE 2 (24-32): energy maintained, no lead
|
|
{"pattern": 1, "bar": 24, "bars": 8, "track": 0}, # dembow kick
|
|
{"pattern": 4, "bar": 24, "bars": 8, "track": 1}, # snare std
|
|
{"pattern": 7, "bar": 24, "bars": 8, "track": 2}, # HH 8th
|
|
{"pattern": 9, "bar": 24, "bars": 8, "track": 7}, # clap
|
|
{"pattern": 12, "bar": 24, "bars": 8, "track": 3}, # 808 full
|
|
{"pattern": 14, "bar": 24, "bars": 8, "track": 4}, # bell chords
|
|
{"pattern": 17, "bar": 24, "bars": 8, "track": 6}, # pad
|
|
|
|
# BREAKDOWN (32-36): stripped
|
|
{"pattern": 3, "bar": 32, "bars": 4, "track": 0}, # sparse kick
|
|
{"pattern": 6, "bar": 32, "bars": 4, "track": 2}, # offbeat HH
|
|
{"pattern": 13, "bar": 32, "bars": 4, "track": 3}, # sparse 808
|
|
{"pattern": 15, "bar": 32, "bars": 4, "track": 4}, # sparse bell
|
|
{"pattern": 17, "bar": 32, "bars": 4, "track": 6}, # pad
|
|
|
|
# OUTRO (36-48): fading
|
|
{"pattern": 1, "bar": 36, "bars": 12, "track": 0}, # dembow kick
|
|
{"pattern": 4, "bar": 36, "bars": 12, "track": 1}, # snare std
|
|
{"pattern": 7, "bar": 36, "bars": 12, "track": 2}, # HH 8th
|
|
{"pattern": 17, "bar": 36, "bars": 12, "track": 6}, # pad
|
|
]
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# HEADER BUILDER
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def _read_ev(data, pos):
|
|
s = pos
|
|
ib = data[pos]; pos += 1
|
|
if ib < 64: return pos + 1, s, ib, data[s + 1], "byte"
|
|
elif ib < 128: return pos + 2, s, ib, struct.unpack("<H", data[pos:pos + 2])[0], "word"
|
|
elif ib < 192: return pos + 4, s, ib, struct.unpack("<I", data[pos:pos + 4])[0], "dword"
|
|
else:
|
|
sz = 0; sh = 0
|
|
while True:
|
|
b = data[pos]; pos += 1
|
|
sz |= (b & 0x7F) << sh; sh += 7
|
|
if not (b & 0x80): break
|
|
return pos + sz, s, ib, data[pos:pos + sz], "data"
|
|
|
|
|
|
def build_header(ref_bytes):
|
|
pos = 22
|
|
first_pat = None
|
|
while pos < len(ref_bytes):
|
|
np, st, ib, val, vt = _read_ev(ref_bytes, pos)
|
|
if ib == EventID.PatNew:
|
|
first_pat = st
|
|
break
|
|
pos = np
|
|
if first_pat is None:
|
|
raise ValueError("No PatNew found")
|
|
header = bytearray(ref_bytes[22:first_pat])
|
|
p = 0
|
|
while p < len(header):
|
|
np, _, ib, val, vt = _read_ev(bytes(header), p)
|
|
if ib == EventID.Tempo:
|
|
struct.pack_into("<I", header, p + 1, BPM * 1000)
|
|
break
|
|
p = np
|
|
return bytes(header)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# PATTERN BUILDER
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def _conv(notes):
|
|
return [{"position": n["pos"], "length": n["len"], "key": n["key"], "velocity": n["vel"]} for n in notes]
|
|
|
|
|
|
def build_all_patterns():
|
|
buf = bytearray()
|
|
for pat_def in PATTERNS:
|
|
buf += encode_word_event(EventID.PatNew, pat_def["id"] - 1)
|
|
buf += encode_text_event(EventID.PatName, pat_def["name"])
|
|
notes_by_ch = ALL_GENERATORS[pat_def["gen"]](pat_def["bars"])
|
|
for ch_idx, raw_notes in notes_by_ch.items():
|
|
if not raw_notes:
|
|
continue
|
|
buf += encode_data_event(EventID.PatNotes, encode_notes_block(ch_idx, _conv(raw_notes), PPQ))
|
|
return bytes(buf)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# MAIN BUILD
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
def build_fuego():
|
|
print("=" * 60)
|
|
print("FUEGO reggaeton — REAL SAMPLES from library")
|
|
print("=" * 60)
|
|
print(f"Progression: Am -> Dm -> F -> E")
|
|
print(f"Samples from: libreria/reggaeton/")
|
|
print(f"Channels: Ch10-19 (all sampler)")
|
|
print(f"Arrangement: 48 bars, 7 sections")
|
|
print("=" * 60)
|
|
|
|
assert os.path.isfile(REF_FLP), f"MISSING: {REF_FLP}"
|
|
ref_bytes = open(REF_FLP, "rb").read()
|
|
num_channels = struct.unpack("<H", ref_bytes[10:12])[0]
|
|
print(f"\nReference: {len(ref_bytes):,} bytes, {num_channels} channels")
|
|
|
|
# 1. Load channels — ALL 10 samplers get real samples from fuego_samples/
|
|
print("\n[1/4] Loading channels with real samples...")
|
|
loader = ChannelSkeletonLoader(REF_FLP, CH11_TMPL, SAMPLES_DIR)
|
|
|
|
# All channels use samples from fuego_samples/
|
|
channel_bytes = loader.load(melodic_map=SAMPLE_ASSIGNMENT)
|
|
print(f" Channels: {len(channel_bytes):,} bytes")
|
|
for ch_idx in sorted(SAMPLE_ASSIGNMENT.keys()):
|
|
_, w = SAMPLE_ASSIGNMENT[ch_idx]
|
|
print(f" Ch{ch_idx}: {w}")
|
|
|
|
# 2. Build header + patterns
|
|
print("\n[2/4] Building header + patterns...")
|
|
header_bytes = build_header(ref_bytes)
|
|
pattern_bytes = build_all_patterns()
|
|
print(f" Header: {len(header_bytes):,} bytes")
|
|
print(f" Patterns: {len(pattern_bytes):,} bytes ({len(PATTERNS)} patterns)")
|
|
|
|
# 3. Build arrangement
|
|
print("\n[3/4] Building arrangement...")
|
|
track_data_template = build_track_data_template(ref_bytes)
|
|
items = [
|
|
ArrangementItem(
|
|
pattern_id=it["pattern"], bar=it["bar"],
|
|
num_bars=it["bars"], track_index=it["track"],
|
|
)
|
|
for it in ARRANGEMENT_ITEMS
|
|
]
|
|
arrangement_bytes = build_arrangement_section(items, track_data_template, ppq=PPQ)
|
|
print(f" Arrangement: {len(arrangement_bytes):,} bytes ({len(items)} items)")
|
|
|
|
# 4. Assemble
|
|
print("\n[4/4] Assembling FLP...")
|
|
body = header_bytes + pattern_bytes + channel_bytes + arrangement_bytes
|
|
flp = (
|
|
struct.pack("<4sIhHH", b"FLhd", 6, 0, num_channels, PPQ)
|
|
+ b"FLdt" + struct.pack("<I", len(body))
|
|
+ body
|
|
)
|
|
|
|
with open(FLP_OUT, "wb") as f:
|
|
f.write(flp)
|
|
|
|
duration = (48 * 4 / BPM) * 60
|
|
print(f"\n{'=' * 60}")
|
|
print(f" Output: {FLP_OUT}")
|
|
print(f" Size: {len(flp):,} bytes")
|
|
print(f" Duration: ~{duration:.0f}s (48 bars @ {BPM} BPM)")
|
|
print(f" Channels: {num_channels} (Ch0-9 plugin, Ch10-19 sampler)")
|
|
print(f" Patterns: {len(PATTERNS)}")
|
|
print(f" Sections: INTRO -> VERSE1 -> PRE-CHORUS -> CHORUS -> VERSE2 -> BREAKDOWN -> OUTRO")
|
|
print(f"{'=' * 60}")
|
|
|
|
return flp
|
|
|
|
|
|
if __name__ == "__main__":
|
|
build_fuego()
|