Skip to content

Commit

Permalink
vfp: update generator
Browse files Browse the repository at this point in the history
  • Loading branch information
SoulMelody committed Jan 8, 2025
1 parent e26d633 commit 5f53137
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 54 deletions.
90 changes: 49 additions & 41 deletions libresvip/plugins/vfp/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ class VOXFactoryNote(BaseModel):
name: str
syllable: str
ticks: float
duration: float
duration_ticks: float = Field(alias="durationTicks")
velocity: int
duration: float = 0.25
velocity: int = 1
note_type: Optional[str] = Field(None, alias="noteType")
vibrato_depth: Optional[float] = Field(None, alias="vibratoDepth")
pre_bend: Optional[float] = Field(None, alias="preBend")
post_bend: Optional[float] = Field(None, alias="postBend")
harmonic_ratio: Optional[float] = Field(None, alias="harmonicRatio")
pitch_bends: list[float] = Field(alias="pitchBends")
pitch_bends: list[float] = Field(default_factory=list, alias="pitchBends")


class VOXFactoryMetadata(BaseModel):
Expand All @@ -34,18 +34,18 @@ class VOXFactoryMetadata(BaseModel):


class VOXFactoryClipBase(BaseModel):
name: str
start_quarter: float = Field(alias="startQuarter")
offset_quarter: float = Field(alias="offsetQuarter")
name: str = ""
start_quarter: float = Field(0, alias="startQuarter")
offset_quarter: float = Field(0, alias="offsetQuarter")
length: float
use_source: bool = Field(alias="useSource")
use_source: bool = Field(True, alias="useSource")
audio_data_key: Optional[str] = Field(None, alias="audioDataKey")
audio_data_order: list[str] = Field(alias="audioDataOrder")
audio_data_quarter: float = Field(alias="audioDataQuarter")
audio_data_order: list[str] = Field(default_factory=list, alias="audioDataOrder")
audio_data_quarter: float = Field(0, alias="audioDataQuarter")
note_bank: dict[str, VOXFactoryNote] = Field(alias="noteBank")
note_order: list[str] = Field(alias="noteOrder")
next_note_index: int = Field(alias="nextNoteIndex")
pinned_audio_data_order: list[str] = Field(alias="pinnedAudioDataOrder")
next_note_index: int = Field(0, alias="nextNoteIndex")
pinned_audio_data_order: list[str] = Field(default_factory=list, alias="pinnedAudioDataOrder")
metadata: Optional[VOXFactoryMetadata] = None


Expand Down Expand Up @@ -77,18 +77,18 @@ def extract_audio(self, info: ValidationInfo) -> Self:


class VOXFactoryAudioViewProperty(BaseModel):
view: str
colormap: str
window: str
window_size: int = Field(alias="windowSize")
hop_size: int = Field(alias="hopSize")
f_min: float = Field(alias="fMin")
f_max: None = Field(alias="fMax")
level_min: None = Field(alias="levelMin")
level_max: None = Field(alias="levelMax")
level_scale: str = Field(alias="levelScale")
num_bins: int = Field(alias="numBins")
bins_per_octave: int = Field(alias="binsPerOctave")
view: str = "waveform"
colormap: str = "Heated Metal"
window: str = "Hann"
window_size: int = Field(1024, alias="windowSize")
hop_size: int = Field(256, alias="hopSize")
f_min: float = Field(27.5, alias="fMin")
f_max: Optional[float] = Field(None, alias="fMax")
level_min: Optional[float] = Field(None, alias="levelMin")
level_max: Optional[float] = Field(None, alias="levelMax")
level_scale: str = Field("dB", alias="levelScale")
num_bins: int = Field(230, alias="numBins")
bins_per_octave: int = Field(24, alias="binsPerOctave")


class VOXFactoryDevice(BaseModel):
Expand All @@ -100,19 +100,21 @@ class VOXFactoryDevice(BaseModel):


class VOXFactoryTrackBase(BaseModel):
name: str
name: str = ""
instrument: Optional[str] = None
h: int
color: str
volume: float
pan: float
solo: bool
mute: bool
arm: bool
h: int = 3
color: str = "#7878f1"
volume: float = 1.0
pan: float = 0.0
solo: bool = False
mute: bool = False
arm: bool = False
clip_order: list[str] = Field(alias="clipOrder")
device_bank: dict[str, VOXFactoryDevice] = Field(alias="deviceBank")
device_order: list[str] = Field(alias="deviceOrder")
audio_view_property: VOXFactoryAudioViewProperty = Field(alias="audioViewProperty")
device_bank: dict[str, VOXFactoryDevice] = Field(default_factory=dict, alias="deviceBank")
device_order: list[str] = Field(default_factory=list, alias="deviceOrder")
audio_view_property: VOXFactoryAudioViewProperty = Field(
default_factory=VOXFactoryAudioViewProperty, alias="audioViewProperty"
)


class VOXFactoryVocalTrack(VOXFactoryTrackBase):
Expand Down Expand Up @@ -150,13 +152,19 @@ class VOXFactoryAudioData(BaseModel):


class VOXFactoryProject(BaseModel):
version: str
version: str = "0.12.0"
tempo: float
time_signature: list[int] = Field(alias="timeSignature")
project_name: str = Field(alias="projectName")
project_name: str = Field("Untitled Project", alias="projectName")
track_bank: dict[str, VOXFactoryTrack] = Field(alias="trackBank")
track_order: list[str] = Field(alias="trackOrder")
selected_track_bank: list[str] = Field(alias="selectedTrackBank")
selected_clip_bank: list[VOXFactorySelectedClipBankItem] = Field(alias="selectedClipBank")
selected_note_bank: list[VOXFactorySelectedNoteBankItem] = Field(alias="selectedNoteBank")
audio_data_bank: dict[str, VOXFactoryAudioData] = Field(alias="audioDataBank")
track_order: list[str] = Field(default_factory=list, alias="trackOrder")
selected_track_bank: list[str] = Field(default_factory=list, alias="selectedTrackBank")
selected_clip_bank: list[VOXFactorySelectedClipBankItem] = Field(
default_factory=list, alias="selectedClipBank"
)
selected_note_bank: list[VOXFactorySelectedNoteBankItem] = Field(
default_factory=list, alias="selectedNoteBank"
)
audio_data_bank: dict[str, VOXFactoryAudioData] = Field(
default_factory=dict, alias="audioDataBank"
)
3 changes: 2 additions & 1 deletion libresvip/plugins/vfp/vox_factory_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ def dump(self, path: pathlib.Path, project: Project, options: OutputOptions) ->
archive_file.writestr(
"project.json",
json.dumps(
vox_factory_project.model_dump(mode="json", exclude_none=True, by_alias=True),
vox_factory_project.model_dump(mode="json", by_alias=True),
ensure_ascii=False,
),
)
archive_file.mkdir("resources")
for audio_name, audio_path in generator.audio_paths.items():
archive_file.writestr(f"resources/{audio_name}", audio_path.read_bytes())
path.write_bytes(buffer.getvalue())
35 changes: 25 additions & 10 deletions libresvip/plugins/vfp/vox_factory_generator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import dataclasses
import math
import pathlib
import secrets

from libresvip.core.constants import DEFAULT_BPM
from libresvip.core.constants import DEFAULT_BPM, TICKS_IN_BEAT
from libresvip.core.time_sync import TimeSynchronizer
from libresvip.model.base import Note, Project, SingingTrack, SongTempo, TimeSignature, Track

from .model import (
Expand All @@ -17,9 +20,13 @@
@dataclasses.dataclass
class VOXFactoryGenerator:
options: OutputOptions
prefix: str = dataclasses.field(init=False)
audio_paths: dict[str, pathlib.Path] = dataclasses.field(default_factory=dict)
synchronizer: TimeSynchronizer = dataclasses.field(init=False)

def generate_project(self, project: Project) -> VOXFactoryProject:
self.prefix = secrets.token_hex(5)
self.synchronizer = TimeSynchronizer(project.song_tempo_list)
vox_project = VOXFactoryProject(
tempo=self.generate_tempo(project.song_tempo_list),
time_signature=self.generate_time_signature(project.time_signature_list),
Expand All @@ -41,10 +48,9 @@ def generate_tracks(self, tracks: list[Track]) -> dict[str, VOXFactoryTrack]:
track_bank = {}
for i, track in enumerate(tracks):
if isinstance(track, SingingTrack):
note_list = self.generate_notes(track.note_list)
clip_bank = {f"clip_{i}": clip for i, clip in enumerate(note_list)}
clip_bank = self.generate_notes(track.note_list)
clip_order = sorted(clip_bank.keys())
track_bank[str(i)] = VOXFactoryVocalTrack(
track_bank[f"{self.prefix}-tr{i}"] = VOXFactoryVocalTrack(
clip_bank=clip_bank,
clip_order=clip_order,
)
Expand All @@ -53,18 +59,27 @@ def generate_tracks(self, tracks: list[Track]) -> dict[str, VOXFactoryTrack]:
def generate_notes(self, notes: list[Note]) -> dict[str, VOXFactoryVocalClip]:
note_bank = {}
note_order = []
max_ticks = notes[-1].end_pos if notes else 0
max_quarter = max_ticks / TICKS_IN_BEAT
for i, note in enumerate(notes):
note_bank[f"note_{i}"] = self.generate_note(note)
note_order.append(f"note_{i}")
return {
"clip": VOXFactoryVocalClip(
note_bank[f"{self.prefix}-no{i}"] = self.generate_note(note)
note_order.append(f"{self.prefix}-no{i}")
clip_count = math.ceil(max_quarter / 32)
clip_bank = {}
for i in range(clip_count):
clip_bank[f"{self.prefix}-cl{i}"] = VOXFactoryVocalClip(
start_quarter=32 * i,
offset_quarter=32 * i,
length=32,
note_bank=note_bank,
note_order=note_order,
),
}
)
return clip_bank

def generate_note(self, note: Note) -> VOXFactoryNote:
note_start_time = self.synchronizer.get_actual_secs_from_ticks(note.start_pos)
return VOXFactoryNote(
time=note_start_time,
ticks=note.start_pos,
duration_ticks=note.length,
midi=note.key_number,
Expand Down
3 changes: 1 addition & 2 deletions libresvip/plugins/vfp/vox_factory_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ class VOXFactoryParser:
first_bar_length: int = dataclasses.field(init=False)

def parse_project(self, vox_project: VOXFactoryProject) -> Project:
project = Project(
return Project(
song_tempo_list=self.parse_tempos(vox_project.tempo),
time_signature_list=self.parse_time_signatures(vox_project.time_signature),
track_list=self.parse_tracks(vox_project),
)
return project

def parse_tempos(self, tempo: float) -> list[SongTempo]:
tempos = [
Expand Down

0 comments on commit 5f53137

Please sign in to comment.