Skip to content

Commit

Permalink
DAW scene - init
Browse files Browse the repository at this point in the history
  • Loading branch information
example.sk committed Nov 4, 2024
1 parent 5e845c3 commit fbfec70
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 0 deletions.
2 changes: 2 additions & 0 deletions ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
midi,
spread_drivers,
bge,
dawscene
)
from .buttonspanel import AudVisButtonsPanel_Npanel

Expand Down Expand Up @@ -123,4 +124,5 @@ def on_blendfile_save():
+ install_ui.classes \
+ preferences.classes \
+ bge.classes \
+ dawscene.classes \
+ []
1 change: 1 addition & 0 deletions ui/dawscene/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/*.blend1
115 changes: 115 additions & 0 deletions ui/dawscene/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from xml.etree.ElementTree import Element

import bpy
import zipfile
import pathlib
import bpy_extras
from xml.etree import ElementTree

from .parser import dawproject
from .scene import Scene
from ..buttonspanel import AudVisButtonsPanel_Npanel


class AUDVIS_OT_DawSceneTo3D(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
bl_idname = "audvis.dawsceneto3d"
bl_label = "Import DAW Scene"

filepath: bpy.props.StringProperty(name=".dawscene file path", subtype='FILE_PATH', options={'SKIP_SAVE'})
# filter_search: bpy.props.StringProperty(default="*.dawscene", options={'SKIP_SAVE'})
filter_glob: bpy.props.StringProperty(
default="*.dawproject",
options={'HIDDEN'},
)

@classmethod
def poll(cls, context):
return True

def invoke(self, context, event):
context.window_manager.fileselect_add(self)
self.filter_search = '*.dawproject'
return {'RUNNING_MODAL'}

def _render(self, context, dawscene: Scene):
scene = context.scene
if 'dawscene' not in bpy.data.node_groups:
libpath = pathlib.Path(__file__).parent.resolve().__str__() + "/dawscene-blender3_6.blend"
with bpy.data.libraries.load(libpath) as (data_from, data_to):
data_to.materials = data_from.materials
data_to.node_groups = data_from.node_groups
mesh = self._init_mesh(dawscene)
y = 0.0
for track in dawscene.tracks:
y = y + .1
for clip in track.clips:
mesh.vertices.add(1)
mesh.update()
vertex = mesh.vertices[-1]
vertex.co[0] = float(clip.time)
vertex.co[1] = -y
mesh.attributes['width'].data[-1].value = float(clip.duration)
mesh.attributes['height'].data[-1].value = float(.1)
mesh.attributes['layer'].data[-1].value = 1
mesh.attributes['clr'].data[-1].color = clip.color

obj = bpy.data.objects.new('dawscene', mesh)
scene.collection.objects.link(obj)
mod = obj.modifiers.new(name="dawscene", type="NODES")

mod.node_group = bpy.data.node_groups['dawscene']
keyframe_path = mod.path_from_id('["Input_5"]')
mod['Input_5'] = 0.0
obj.keyframe_insert(data_path=keyframe_path, frame=1)
mod['Input_5'] = 1.0
obj.keyframe_insert(data_path=keyframe_path, frame=100)

def execute(self, context):
if not self.filepath:
return {'CANCELLED'}
parsed_scene = None
if self.filepath.endswith(".dawproject"):
parsed_scene = dawproject.parse(self.filepath)
# TODO: add other DAWs
else:
return {'CANCELLED'}
self._render(context, parsed_scene)
return {'FINISHED'}

def _init_mesh(self, dawscene: Scene):
mesh = bpy.data.meshes.new(dawscene.name if dawscene.name is not None else 'dawscene')
mesh.attributes.new('width', 'FLOAT', 'POINT')
mesh.attributes.new('height', 'FLOAT', 'POINT')
mesh.attributes.new('layer', 'INT', 'POINT')
mesh.attributes.new('clr', 'FLOAT_COLOR', 'POINT')
return mesh


def _parse_color(input: str):
tmp = input.lstrip('#')
return (
int(tmp[0:2], 16) / 255,
int(tmp[2:4], 16) / 255,
int(tmp[4:6], 16) / 255,
1,
)


class AUDVIS_PT_DawprojectTo3d(AudVisButtonsPanel_Npanel):
bl_label = "Import DAWscene"

@classmethod
def poll(cls, context):
return True

def draw(self, context):
layout = self.layout
scene = context.scene
col = layout.column(align=True)
col.operator('audvis.dawsceneto3d')


classes = [
AUDVIS_OT_DawSceneTo3D,
AUDVIS_PT_DawprojectTo3d,
]
Binary file added ui/dawscene/dawscene-blender3_6.blend
Binary file not shown.
Empty file added ui/dawscene/parser/als.py
Empty file.
75 changes: 75 additions & 0 deletions ui/dawscene/parser/dawproject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import zipfile
import pathlib
from xml.etree import ElementTree
from pprint import pp

from ..scene import (Scene, Track, Clip, Note, Group)


def parse(filepath) -> Scene:
zip = zipfile.ZipFile(file=filepath, mode='r')
xml = ElementTree.parse(zip.open('project.xml'))
DawProjectParser(xml)
return DawProjectParser(xml).scene


class DawProjectParser:
xml: ElementTree
scene: Scene
track_by_id: dict
line_height = .3

def __init__(self, xml):
self.track_by_id = {}
self.xml = xml
self.scene = Scene()
self.all_tracks = {}
self.read_tracks()
self.read_lines()
self.scene.print()

def read_tracks(self):
for track_el in self.xml.findall('./Structure//Track'):
color = _parse_color(track_el.attrib['color']) if 'color' in track_el.attrib else None
track = Track(name=track_el.attrib['name'], color=color)
self.track_by_id[track_el.attrib['id']] = track
self.scene.tracks.append(track)

def read_lines(self):
lanes = self.xml.findall('./Arrangement/Lanes/Lanes')
for lane in lanes:
track = self.track_by_id[lane.attrib['track']]
for clip_el in lane.findall('./Clips/Clip'):
self.parse_clip(track, clip_el)

def parse_clip(self, track: Track, clip_el):
color = (.1, .5, .1, 1)
if "color" in clip_el.attrib:
color = _parse_color(clip_el.attrib['color'])
elif track and track.color:
color = track.color
clip = Clip(
name=clip_el.attrib['name'] if 'name' in clip_el.attrib else None,
time=clip_el.attrib['time'],
duration=clip_el.attrib['duration'],
color=color)
for note_el in clip_el.findall('./Notes/Note'):
clip.notes.append(Note(
key=int(note_el.attrib['key']),
duration=float(note_el.attrib['duration']),
velocity=float(note_el.attrib['vel']),
rel=float(note_el.attrib['rel']),
ch=int(note_el.attrib['channel']),
time=float(note_el.attrib['time'])
))
track.clips.append(clip)


def _parse_color(input: str):
tmp = input.lstrip('#')
return (
int(tmp[0:2], 16) / 255,
int(tmp[2:4], 16) / 255,
int(tmp[4:6], 16) / 255,
1,
)
Empty file added ui/dawscene/parser/midi.py
Empty file.
67 changes: 67 additions & 0 deletions ui/dawscene/scene.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from typing import List, Self


class Note:
note: int
time: float
duration: float
velocity: float
rel: float # relative velocity

def __init__(self, key: int, ch: int, time: float, duration: float, velocity: float, rel: float):
self.key = key
self.ch = ch
self.time = time
self.duration = duration
self.velocity = velocity
self.rel = rel


class Clip:
# sound: ? TODO
time: float
duration: float
name: str | None
color: tuple | None
notes: List[Note]

def __init__(self, time: float, duration: float, name: str, color: tuple):
self.name = name
self.color = color
self.time = time
self.duration = duration
self.notes = []


class Track:
name: str
color: tuple
clips: List[Clip]

def __init__(self, name: str, color: tuple):
self.name = name
self.color = color
self.clips = []


class Group:
children: List[Self]


class Scene:
name: str | None
tracks: List[Track]
groups: List[Group]

def __init__(self, name: str | None = None):
self.name = name
self.groups = []
self.tracks = []

def print(self):
for t in self.tracks:
print('TRACK: {}'.format(t.name))
for clip in t.clips:
print(' CLIP: {}, time: {}, duration: {}'.format(clip.name, clip.time, clip.duration))
for note in clip.notes:
print(' NOTE: {}, time: {}, duration: {}'.format(note.key, note.time, note.duration))

0 comments on commit fbfec70

Please sign in to comment.