project serialization
This commit is contained in:
@@ -2,4 +2,5 @@ pymp3
|
|||||||
textual
|
textual
|
||||||
textual-slider
|
textual-slider
|
||||||
textual-plot
|
textual-plot
|
||||||
numpy
|
numpy
|
||||||
|
msgpack-numpy
|
||||||
14
src/main.py
14
src/main.py
@@ -1,7 +1,19 @@
|
|||||||
from ui.app import AppUI
|
from ui.app import AppUI
|
||||||
|
from project import Project, ProjectChannel, ChannelChunk, AudioChannelChunk
|
||||||
|
from ui.widgets.chunk_types.audio import AudioChunk
|
||||||
|
import librosa
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
test_project = Project(
|
||||||
|
[
|
||||||
|
ProjectChannel("my channel", chunks=[
|
||||||
|
ChannelChunk(name="hi"),
|
||||||
|
AudioChannelChunk(librosa.load("cool sample.mp3", sr=None, mono=False))
|
||||||
|
])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# start the ui
|
# start the ui
|
||||||
app = AppUI()
|
app = AppUI(test_project)
|
||||||
app.run()
|
app.run()
|
||||||
124
src/project.py
124
src/project.py
@@ -1,3 +1,123 @@
|
|||||||
|
import msgpack
|
||||||
|
import enum
|
||||||
|
import numpy as np
|
||||||
|
import msgpack_numpy
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
|
|
||||||
|
|
||||||
|
msgpack_numpy.patch()
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TimeSignature:
|
||||||
|
beats_per_measure: float
|
||||||
|
note_value: float
|
||||||
|
|
||||||
|
class ChunkType(enum.Enum):
|
||||||
|
CHUNK = 1
|
||||||
|
AUDIO = enum.auto()
|
||||||
|
MIDI = enum.auto()
|
||||||
|
|
||||||
|
class ChannelChunk:
|
||||||
|
def __init__(self, position: float = 0.0, name: str = "Chunk", chunk_type: ChunkType = ChunkType.CHUNK):
|
||||||
|
self.position = position # position is how many bars into the song the chunk is
|
||||||
|
self.name = name
|
||||||
|
self.chunk_type = chunk_type
|
||||||
|
|
||||||
|
def from_json(json: dict) -> ChannelChunk:
|
||||||
|
return ChannelChunk(
|
||||||
|
chunk_type = ChunkType(json["type"]),
|
||||||
|
name = json["name"],
|
||||||
|
position = json["position"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"type": self.chunk_type.value,
|
||||||
|
"name": self.name,
|
||||||
|
"position": self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioChannelChunk(ChannelChunk):
|
||||||
|
def __init__(self, audio_data: np.ndarray, position: float = 0.0, name: str = "Sample"):
|
||||||
|
super().__init__(position, name, chunk_type=ChunkType.AUDIO)
|
||||||
|
self.audio_data = audio_data
|
||||||
|
|
||||||
|
def from_json(json: dict) -> ChannelChunk:
|
||||||
|
return AudioChannelChunk(
|
||||||
|
name = json["name"],
|
||||||
|
position = json["position"],
|
||||||
|
audio_data = json["audio_data"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"type": self.chunk_type.value,
|
||||||
|
"name": self.name,
|
||||||
|
"position": self.position,
|
||||||
|
"audio_data": self.audio_data
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk_type_associations = {
|
||||||
|
ChunkType.CHUNK: ChannelChunk,
|
||||||
|
ChunkType.AUDIO: AudioChannelChunk
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectChannel:
|
||||||
|
def __init__(self, name: str = "", volume: int = 0, pan: int = 0, mute: bool = False, solo: bool = False, chunks: list[ChannelChunk] = []):
|
||||||
|
self.name = name
|
||||||
|
self.volume = volume
|
||||||
|
self.pan = pan
|
||||||
|
self.mute = mute
|
||||||
|
self.solo = solo
|
||||||
|
self.chunks = chunks
|
||||||
|
|
||||||
|
def from_json(json: dict) -> ProjectChannel:
|
||||||
|
return ProjectChannel(
|
||||||
|
name = json["name"],
|
||||||
|
volume = json["volume"],
|
||||||
|
pan = json["pan"],
|
||||||
|
mute = json["mute"],
|
||||||
|
solo = json["solo"],
|
||||||
|
chunks = [chunk_type_associations[ChunkType(chunk["type"])].from_json(chunk) for chunk in json["chunks"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"volume": self.volume,
|
||||||
|
"pan": self.pan,
|
||||||
|
"mute": self.mute,
|
||||||
|
"solo": self.solo,
|
||||||
|
"chunks": [chunk.to_json() for chunk in self.chunks]
|
||||||
|
}
|
||||||
|
|
||||||
class Project:
|
class Project:
|
||||||
def __init__(self):
|
def __init__(self, channels: list[ProjectChannel], version: float = 1.0, bpm: float = 120, time_signature: TimeSignature = TimeSignature(4, 4)):
|
||||||
pass
|
self.version = version
|
||||||
|
self.bpm = bpm
|
||||||
|
self.time_signature = time_signature
|
||||||
|
self.channels = channels
|
||||||
|
|
||||||
|
def from_file(file_path: str) -> Project:
|
||||||
|
with open(file_path, "rb") as f:
|
||||||
|
return Project.from_json(msgpack.unpackb(f.read()))
|
||||||
|
|
||||||
|
def from_json(json: dict) -> Project:
|
||||||
|
return Project(
|
||||||
|
version = json["version"],
|
||||||
|
time_signature = TimeSignature(json["time_signature"]["beats_per_measure"], json["time_signature"]["note_value"]),
|
||||||
|
bpm = json["bpm"],
|
||||||
|
channels = [ProjectChannel.from_json(channel) for channel in json["channels"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"version": self.version,
|
||||||
|
"time_signature": asdict(self.time_signature),
|
||||||
|
"bpm": self.bpm,
|
||||||
|
"channels": [channel.to_json() for channel in self.channels]
|
||||||
|
}
|
||||||
|
|
||||||
|
def write_to_file(self, file_path: str):
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(msgpack.packb(self.to_json()))
|
||||||
@@ -7,9 +7,11 @@ from ui.widgets.project_settings import ProjectSettings
|
|||||||
|
|
||||||
|
|
||||||
class AppUI(App):
|
class AppUI(App):
|
||||||
def __init__(self):
|
def __init__(self, project):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.zoom_level = 0.05
|
self.zoom_level = 0.05
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Sidebar()
|
yield Sidebar()
|
||||||
|
|||||||
Reference in New Issue
Block a user