feat: reggaeton production system with intelligent sample selection and FLP generation
This commit is contained in:
225
src/flp_builder/events.py
Normal file
225
src/flp_builder/events.py
Normal file
@@ -0,0 +1,225 @@
|
||||
import struct
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class EventID(IntEnum):
|
||||
WORD = 64
|
||||
DWORD = 128
|
||||
TEXT = 192
|
||||
DATA = 208
|
||||
|
||||
LoopActive = 9
|
||||
ShowInfo = 10
|
||||
Volume = 12
|
||||
PanLaw = 23
|
||||
Licensed = 28
|
||||
TempoCoarse = 66
|
||||
Pitch = 80
|
||||
TempoFine = 93
|
||||
CurGroupId = 146
|
||||
Tempo = 156
|
||||
FLBuild = 159
|
||||
Title = 194
|
||||
Comments = 195
|
||||
Url = 197
|
||||
RTFComments = 198
|
||||
FLVersion = 199
|
||||
Licensee = 200
|
||||
DataPath = 202
|
||||
Genre = 206
|
||||
Artists = 207
|
||||
Timestamp = 237
|
||||
|
||||
ChIsEnabled = 0
|
||||
ChVolByte = 2
|
||||
ChPanByte = 3
|
||||
ChZipped = 15
|
||||
ChType = 21
|
||||
ChRoutedTo = 22
|
||||
ChIsLocked = 32
|
||||
ChNew = 64
|
||||
ChFreqTilt = 69
|
||||
ChFXFlags = 70
|
||||
ChCutoff = 71
|
||||
ChVolWord = 72
|
||||
ChPanWord = 73
|
||||
ChPreamp = 74
|
||||
ChFadeOut = 75
|
||||
ChFadeIn = 76
|
||||
ChResonance = 83
|
||||
ChStereoDelay = 85
|
||||
ChPogo = 86
|
||||
ChTimeShift = 89
|
||||
ChChildren = 94
|
||||
ChSwing = 97
|
||||
ChRingMod = 131
|
||||
ChCutGroup = 132
|
||||
ChRootNote = 135
|
||||
ChDelayModXY = 138
|
||||
ChReverb = 139
|
||||
ChStretchTime = 140
|
||||
ChFineTune = 142
|
||||
ChSamplerFlags = 143
|
||||
ChLayerFlags = 144
|
||||
ChGroupNum = 145
|
||||
ChAUSampleRate = 153
|
||||
ChName = 192
|
||||
ChSamplePath = 196
|
||||
ChDelay = 209
|
||||
ChParameters = 215
|
||||
ChEnvelopeLFO = 218
|
||||
ChLevels = 219
|
||||
ChPolyphony = 221
|
||||
ChTracking = 228
|
||||
ChLevelAdjusts = 229
|
||||
ChAutomation = 234
|
||||
|
||||
PatLooped = 26
|
||||
PatNew = 65
|
||||
PatColor = 150
|
||||
PatName = 193
|
||||
PatChannelIID = 160
|
||||
PatLength = 164
|
||||
PatControllers = 223
|
||||
PatNotes = 224
|
||||
|
||||
PluginColor = 128
|
||||
PluginIcon = 155
|
||||
PluginInternalName = 201
|
||||
PluginName = 203
|
||||
PluginWrapper = 212
|
||||
PluginData = 213
|
||||
|
||||
MixerAPDC = 29
|
||||
MixerParams = 225
|
||||
|
||||
|
||||
def encode_varint(value: int) -> bytes:
|
||||
result = bytearray()
|
||||
while True:
|
||||
byte = value & 0x7F
|
||||
value >>= 7
|
||||
if value:
|
||||
byte |= 0x80
|
||||
result.append(byte)
|
||||
if not value:
|
||||
break
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def encode_text(text: str, utf16: bool = True) -> bytes:
|
||||
if utf16:
|
||||
return text.encode("utf-16-le") + b"\x00\x00"
|
||||
return text.encode("ascii") + b"\x00"
|
||||
|
||||
|
||||
def encode_byte_event(id_: int, value: int) -> bytes:
|
||||
return bytes([id_, value & 0xFF])
|
||||
|
||||
|
||||
def encode_word_event(id_: int, value: int) -> bytes:
|
||||
return bytes([id_]) + struct.pack("<H", value)
|
||||
|
||||
|
||||
def encode_dword_event(id_: int, value: int) -> bytes:
|
||||
return bytes([id_]) + struct.pack("<I", value)
|
||||
|
||||
|
||||
def encode_text_event(id_: int, text: str) -> bytes:
|
||||
data = encode_text(text)
|
||||
return bytes([id_]) + encode_varint(len(data)) + data
|
||||
|
||||
|
||||
def encode_data_event(id_: int, data: bytes) -> bytes:
|
||||
return bytes([id_]) + encode_varint(len(data)) + data
|
||||
|
||||
|
||||
def encode_note_24(
|
||||
position: int,
|
||||
flags: int,
|
||||
rack_channel: int,
|
||||
length: int,
|
||||
key: int,
|
||||
group: int,
|
||||
fine_pitch: int,
|
||||
release: int,
|
||||
midi_channel: int,
|
||||
pan: int,
|
||||
velocity: int,
|
||||
mod_x: int,
|
||||
mod_y: int,
|
||||
) -> bytes:
|
||||
"""Encode a single note in FL Studio's 24-byte format.
|
||||
|
||||
Format (24 bytes, all absolute values):
|
||||
position: uint32 (4) - absolute position in PPQ ticks
|
||||
flags: uint16 (2) - note flags (0x4000 = standard note)
|
||||
rack_channel: uint16 (2) - channel rack index
|
||||
length: uint32 (4) - duration in PPQ ticks
|
||||
key: uint16 (2) - MIDI note number (0-127)
|
||||
group: uint16 (2) - note group
|
||||
fine_pitch: uint8 (1) - fine pitch (0x78 = 120 = no detune)
|
||||
_u1: uint8 (1) - unknown (0x40)
|
||||
release: uint8 (1) - release value
|
||||
midi_channel: uint8 (1) - MIDI channel
|
||||
pan: int8 (1) - stereo pan (64 = center)
|
||||
velocity: uint8 (1) - note velocity
|
||||
mod_x: uint8 (1) - modulation X (128 = center)
|
||||
mod_y: uint8 (1) - modulation Y (128 = center)
|
||||
"""
|
||||
return struct.pack(
|
||||
"<IHHIHHBBBBBBBB",
|
||||
position,
|
||||
flags,
|
||||
rack_channel,
|
||||
length,
|
||||
key,
|
||||
group,
|
||||
fine_pitch,
|
||||
0x40, # unknown byte, always 0x40 in observed data
|
||||
release,
|
||||
midi_channel,
|
||||
pan,
|
||||
velocity,
|
||||
mod_x,
|
||||
mod_y,
|
||||
)
|
||||
|
||||
|
||||
def encode_notes_block(
|
||||
channel_index: int,
|
||||
notes: list[dict],
|
||||
ppq: int = 96,
|
||||
) -> bytes:
|
||||
"""Encode all notes for a pattern as raw note data (no header).
|
||||
|
||||
FL Studio stores notes as a flat array of 24-byte structs.
|
||||
No header or count prefix needed - the event size determines count.
|
||||
"""
|
||||
note_data = bytearray()
|
||||
|
||||
for note in notes:
|
||||
pos = int(note.get("position", 0) * ppq)
|
||||
length = int(note.get("length", 1) * ppq)
|
||||
key = note.get("key", 60)
|
||||
velocity = note.get("velocity", 100)
|
||||
rack_channel = note.get("rack_channel", channel_index)
|
||||
|
||||
note_bytes = encode_note_24(
|
||||
position=pos,
|
||||
flags=0x4000,
|
||||
rack_channel=rack_channel,
|
||||
length=max(length, 1),
|
||||
key=key & 0x7F,
|
||||
group=0,
|
||||
fine_pitch=120,
|
||||
release=64,
|
||||
midi_channel=0,
|
||||
pan=64,
|
||||
velocity=velocity & 0x7F,
|
||||
mod_x=128,
|
||||
mod_y=128,
|
||||
)
|
||||
note_data.extend(note_bytes)
|
||||
|
||||
return bytes(note_data)
|
||||
Reference in New Issue
Block a user