T16: autosample VST2 via native bridge + SF2/SF3 export hoàn chỉnh, UI chip size/note_count, re-sample on preset change, tests
This commit is contained in:
+70
-16
@@ -64,20 +64,20 @@ def write_sf2(out_path, samples, sample_rate=44100, name="AutoSampled"):
|
||||
phdr += name.encode("ascii", "replace")[:20].ljust(20, b"\x00")
|
||||
phdr += struct.pack("<HHHIII", 0, 0, 0, 0, 0, 0) # preset=0 bank=0 bagNdx=0 libr genre morph
|
||||
phdr += b"EOP".ljust(20, b"\x00") + struct.pack("<HHHIII", 0, 0, 1, 0, 0, 0)
|
||||
pbag = struct.pack("<HH", 0, 0) + struct.pack("<HH", 0, 0)
|
||||
pbag = struct.pack("<HH", 0, 0) + struct.pack("<HH", 1, 0)
|
||||
pmod = b"\x00" * 10
|
||||
pgen = struct.pack("<HH", 0, 0)
|
||||
pgen = struct.pack("<HH", 41, 0) + struct.pack("<HH", 0, 0)
|
||||
inst = bytearray()
|
||||
inst += b"AutoSampled".ljust(20, b"\x00") + struct.pack("<H", 0)
|
||||
inst += b"EOI".ljust(20, b"\x00") + struct.pack("<H", 1)
|
||||
inst += b"EOI".ljust(20, b"\x00") + struct.pack("<H", n) # bagNdx = chi so record terminal zone (ibag co n+1 records)
|
||||
ibag = b"".join(struct.pack("<HH", i * 3, 0) for i in range(n + 1))
|
||||
imod = b"\x00" * 10
|
||||
igen = bytearray()
|
||||
for i, s in enumerate(samples):
|
||||
note = int(s["note"]) & 0xFF
|
||||
igen += struct.pack("<HH", 60, note | (note << 8)) # keyRange lo=hi=note
|
||||
igen += struct.pack("<HH", 69, i) # sampleID
|
||||
igen += struct.pack("<HH", 74, note) # overridingRootKey
|
||||
igen += struct.pack("<HH", 43, note | (note << 8)) # keyRange lo=hi=note
|
||||
igen += struct.pack("<HH", 58, note) # overridingRootKey
|
||||
igen += struct.pack("<HH", 53, i) # sampleID (phai la gen cuoi zone)
|
||||
igen += struct.pack("<HH", 0, 0)
|
||||
shdr = bytearray()
|
||||
for i, s in enumerate(samples):
|
||||
@@ -118,34 +118,88 @@ def _render_note(vst, note, sr, dur, release, velocity):
|
||||
return np.clip(audio * 32767, -32768, 32767).astype(np.int16)
|
||||
|
||||
|
||||
def _postprocess(buf, sr):
|
||||
"""Stereo float32 (2,N) -> int16 mono, peak 0.9 (chung cho 2 engine)."""
|
||||
mono = buf.mean(axis=0)
|
||||
peak = float(np.max(np.abs(mono)))
|
||||
if peak < 1e-5:
|
||||
return None
|
||||
above = np.nonzero(np.abs(mono) > peak * 0.001)[0]
|
||||
start = int(above[0]) if above.size else 0
|
||||
audio = mono[start:] / peak * 0.9
|
||||
return np.clip(audio * 32767, -32768, 32767).astype(np.int16)
|
||||
|
||||
|
||||
def _is_vst2_path(path):
|
||||
"""VST2 = .dll/.so dung rieng (Windows VST3 la FOLDER .vst3, .so VST3
|
||||
co duoi .vst3.so) — native bridge render duoc; pedalboard thi khong."""
|
||||
low = (path or "").lower()
|
||||
if ".vst3" in low:
|
||||
return False
|
||||
return low.endswith(".dll") or low.endswith(".so")
|
||||
|
||||
|
||||
def _render_note_native(plugin_path, note, sr, dur, release, velocity):
|
||||
"""VST2 qua native bridge (Windows) — pedalboard khong ho tro VST2."""
|
||||
from app.core.native_audio_service import get_service
|
||||
bpm = 120.0
|
||||
y = get_service().render_vst2_offline(
|
||||
plugin_path,
|
||||
[{"start_beat": 0.0, "duration_beats": dur * bpm / 60.0,
|
||||
"note": int(note) & 0x7F, "velocity": int(velocity)}],
|
||||
bpm=bpm, sr=sr, tail_sec=release,
|
||||
)
|
||||
return _postprocess(y, sr)
|
||||
|
||||
|
||||
def autosample_sf2(instrument_id, out_path, preset_id=None, preset_path=None,
|
||||
preset_data_b64=None, low=36, high=96, step=2, duration=2.5,
|
||||
release=1.0, velocity=100, sample_rate=44100, name=None, log=None):
|
||||
release=1.0, velocity=100, sample_rate=44100, name=None, log=None,
|
||||
extra_vst_dirs=None):
|
||||
"""Auto-sample VSTi -> SF2 (16-bit mono). Dung cho server endpoint
|
||||
/api/v1/plugins/autosample va CLI main().
|
||||
|
||||
VST3 -> pedalboard (ap preset neu co); VST2 (.dll/.so khong .vst3) ->
|
||||
native bridge (Windows) — pedalboard khong ho tro VST2, preset VST2 chua
|
||||
qua bridge duoc nen bo qua.
|
||||
|
||||
Returns dict {note_count, out_path, size_bytes}.
|
||||
Raises FileNotFoundError neu plugin khong tim thay, RuntimeError neu
|
||||
pedalboard khong kha dung hoac khong render duoc not nao.
|
||||
engine khong kha dung hoac khong render duoc not nao.
|
||||
"""
|
||||
from app.core.vst_engine import PluginManager, apply_preset_to_plugin, HAS_PEDALBOARD
|
||||
if not HAS_PEDALBOARD:
|
||||
raise RuntimeError("pedalboard khong kha dung - khong auto-sample duoc")
|
||||
|
||||
vst = PluginManager().load_vst(instrument_id)
|
||||
if vst is None:
|
||||
plugin_path = PluginManager(extra_vst_dirs=extra_vst_dirs or [])._scan_plugins().get(instrument_id)
|
||||
if not plugin_path:
|
||||
raise FileNotFoundError(f"Khong tim thay VSTi: {instrument_id} - hay Scan trong Plugin Manager truoc")
|
||||
if preset_id or preset_path or preset_data_b64:
|
||||
apply_preset_to_plugin(vst, preset_id=preset_id, preset_path=preset_path,
|
||||
preset_data_b64=preset_data_b64)
|
||||
|
||||
def _log(msg):
|
||||
if log:
|
||||
log(msg)
|
||||
|
||||
if _is_vst2_path(plugin_path):
|
||||
# VST2: native bridge (Windows) — pedalboard khong ho tro. Preset chua
|
||||
# export qua bridge (VST2AudioEngine chua co chunk/preset) -> bo qua.
|
||||
if preset_id or preset_path or preset_data_b64:
|
||||
_log("VST2: native bridge chua ho tro preset - bo qua, sample preset mac dinh")
|
||||
def _render(note):
|
||||
return _render_note_native(plugin_path, note, sample_rate, duration,
|
||||
release, velocity)
|
||||
else:
|
||||
if not HAS_PEDALBOARD:
|
||||
raise RuntimeError("pedalboard khong kha dung - khong auto-sample VST3 duoc")
|
||||
vst = PluginManager(extra_vst_dirs=extra_vst_dirs or []).load_vst(instrument_id)
|
||||
if vst is None:
|
||||
raise FileNotFoundError(f"Khong tim thay VSTi: {instrument_id} - hay Scan trong Plugin Manager truoc")
|
||||
if preset_id or preset_path or preset_data_b64:
|
||||
apply_preset_to_plugin(vst, preset_id=preset_id, preset_path=preset_path,
|
||||
preset_data_b64=preset_data_b64)
|
||||
def _render(note):
|
||||
return _render_note(vst, note, sample_rate, duration, release, velocity)
|
||||
|
||||
samples = []
|
||||
for note in range(low, high + 1, step):
|
||||
frames = _render_note(vst, note, sample_rate, duration, release, velocity)
|
||||
frames = _render(note)
|
||||
if frames is None:
|
||||
_log(f"note {note}: silent, bo qua")
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user