Skip to content

Commit

Permalink
- typing
Browse files Browse the repository at this point in the history
- fix: chain cropping correctly checks if cropped chain is empty
- fix: resolve missing abstract methods for rendering
- fix: correct qualified names
  • Loading branch information
gesellkammer committed Jul 10, 2024
1 parent 8ba1518 commit 88a6fc8
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 103 deletions.
60 changes: 30 additions & 30 deletions maelzel/core/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def __init__(self,
if not isinstance(items, list):
items = list(items)
for item in items:
assert isinstance(item, MEvent)
if item.parent is not None:
# We need to make a copy in this case
item = item.copy()
Expand Down Expand Up @@ -1804,6 +1805,33 @@ def absorbInitialOffset(self, removeRedundantOffsets=True):
self.removeRedundantOffsets()
self._changed()

def _cropped(self, startbeat: F, endbeat: F, absorbOffset=False
) -> Self:
items = []
# frame = chain.absOffset()
for item, offset in self.itemsWithOffset():
if offset > endbeat or (offset == endbeat and item.dur > 0):
break

if item.dur == 0 and startbeat <= offset:
items.append(item.clone(offset=offset - startbeat))
elif offset + item.dur > startbeat:
# Add a cropped part or the entire item?
if startbeat <= offset and offset + item.dur <= endbeat:
items.append(item.clone(offset=offset - startbeat))
else:
if isinstance(item, MEvent):
item2 = item.cropped(startbeat, endbeat)
items.append(item2.clone(offset=item2.offset - startbeat))
else:
# TODO: combine these two operations, if needed
self = item._cropped(startbeat, endbeat, absorbOffset=True)
items.append(self.clone(offset=self.offset - startbeat))
out = self.clone(items=items, offset=startbeat)
if absorbOffset:
out.absorbInitialOffset()
return out

def cropped(self, start: beat_t, end: beat_t) -> Self | None:
"""
Returns a copy of this chain, cropped to the given beat range
Expand All @@ -1821,41 +1849,13 @@ def cropped(self, start: beat_t, end: beat_t) -> Self | None:
sco = self.activeScorestruct()
startbeat = sco.asBeat(start)
endbeat = sco.asBeat(end)
cropped = _cropped(self, startbeat=startbeat, endbeat=endbeat)
if not cropped:
cropped = self._cropped(startbeat=startbeat, endbeat=endbeat)
if not cropped.items:
return None
cropped.removeRedundantOffsets()
return cropped


def _cropped(chain: Chain, startbeat: F, endbeat: F, absorbOffset=False
) -> Chain:
items = []
# frame = chain.absOffset()
for item, offset in chain.itemsWithOffset():
if offset > endbeat or (offset == endbeat and item.dur > 0):
break

if item.dur == 0 and startbeat <= offset:
items.append(item.clone(offset=offset - startbeat))
elif offset + item.dur > startbeat:
# Add a cropped part or the entire item?
if startbeat <= offset and offset + item.dur <= endbeat:
items.append(item.clone(offset=offset - startbeat))
else:
if isinstance(item, MEvent):
item2 = item.cropped(startbeat, endbeat)
items.append(item2.clone(offset=item2.offset - startbeat))
else:
# TODO: combine these two operations, if needed
chain = _cropped(item, startbeat, endbeat, absorbOffset=True)
items.append(chain.clone(offset=chain.offset - startbeat))
out = chain.clone(items=items, offset=startbeat)
if absorbOffset:
out.absorbInitialOffset()
return out


class PartGroup:
"""
This class represents a group of parts
Expand Down
51 changes: 33 additions & 18 deletions maelzel/core/offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import numpy as np

import csoundengine
import csoundengine.instr
from csoundengine import Event
from csoundengine.sessionhandler import SessionHandler

from maelzel.core import renderer
Expand Down Expand Up @@ -44,6 +46,25 @@ def __init__(self, renderer: OfflineRenderer):
def sched(self, event: csoundengine.event.Event):
return self.renderer._schedSessionEvent(event)

def schedEvent(self, event: Event) -> csoundengine.schedevent.SchedEvent:
return self.renderer.schedEvent(event)

def makeTable(self,
data: np.ndarray | list[float] | None = None,
size: int | tuple[int, int] = 0,
sr: int = 0,
) -> TableProxy:
return self.renderer.makeTable(data=data, size=size, sr=sr)

def readSoundfile(self,
path: str,
chan=0,
skiptime=0.,
delay=0.,
force=False,
) -> TableProxy:
return self.renderer.readSoundfile(soundfile=path, chan=chan, skiptime=skiptime)


class OfflineRenderer(renderer.Renderer):
"""
Expand Down Expand Up @@ -159,8 +180,8 @@ def __init__(self,
self.showAtExit = False
"""Display the results at exit if running in jupyter"""

self.csoundRenderer: csoundengine.offline.Renderer = self._makeCsoundRenderer()
"""The actual csoundengine.Renderer"""
self.csoundRenderer: csoundengine.offline.OfflineSession = self._makeCsoundRenderer()
"""The actual csoundengine.OfflineSession"""

def isRealtime(self) -> bool:
"""Is this a realtime renderer?"""
Expand All @@ -177,12 +198,12 @@ def getSession(self) -> csoundengine.session.Session | None:
return self._session
return None

def _makeCsoundRenderer(self) -> csoundengine.offline.Renderer:
def _makeCsoundRenderer(self) -> csoundengine.offline.OfflineSession:
"""
Construct a :class:`csoundengine.Renderer` from this OfflineRenderer
Construct a :class:`csoundengine.OfflineSession` from this OfflineRenderer
Returns:
the corresponding :class:`csoundengine.Renderer`
the corresponding :class:`csoundengine.offline.OfflineSession`
"""
from maelzel.core import playback
renderer = self.presetManager.makeRenderer(self.sr, ksmps=self.ksmps,
Expand Down Expand Up @@ -237,7 +258,7 @@ def getInstr(self, instrname: str) -> csoundengine.instr.Instr | None:
return instr

@property
def scheduledEvents(self) -> dict[int, csoundengine.offline.SchedEvent]:
def scheduledEvents(self) -> dict[int, csoundengine.schedevent.SchedEvent]:
"""The scheduled events"""
return self.csoundRenderer.scheduledEvents

Expand Down Expand Up @@ -362,7 +383,7 @@ def registerInstr(self, name: str, instrdef: csoundengine.instr.Instr
self.instrs[name] = instrdef
self.csoundRenderer.registerInstr(instrdef)

def play(self, obj: mobj.MObj, **kws) -> csoundengine.offline.SchedEventGroup:
def play(self, obj: mobj.MObj, **kws) -> csoundengine.schedevent.SchedEventGroup:
"""
Schedule the events generated by this obj to be renderer offline
Expand Down Expand Up @@ -448,7 +469,7 @@ def schedEvents(self,
coreevents: list[SynthEvent],
sessionevents: list[csoundengine.event.Event] = None,
whenfinished: Callable = None
) -> csoundengine.offline.SchedEventGroup:
) -> csoundengine.schedevent.SchedEventGroup:
"""
Schedule multiple events as returned by :meth:`MObj.events() <maelzel.core.MObj.events>`
Expand All @@ -474,7 +495,7 @@ def schedEvents(self,
scoreEvents = [self.schedEvent(ev) for ev in coreevents]
if sessionevents:
scoreEvents.extend(self._schedSessionEvent(ev) for ev in sessionevents)
return csoundengine.offline.SchedEventGroup(scoreEvents)
return csoundengine.schedevent.SchedEventGroup(scoreEvents)

def definedInstrs(self) -> dict[str, csoundengine.instr.Instr]:
"""
Expand Down Expand Up @@ -505,7 +526,7 @@ def playSample(self,
skip=0.,
fade: float | tuple[float, float] | None = None,
crossfade=0.02,
) -> csoundengine.offline.SchedEvent:
) -> csoundengine.schedevent.SchedEvent:
"""
Play a sample through this renderer
Expand Down Expand Up @@ -540,7 +561,7 @@ def sched(self,
args: list[float] | dict[str, float] = None,
whenfinished=None,
relative=True,
**kws) -> csoundengine.offline.SchedEvent:
**kws) -> csoundengine.schedevent.SchedEvent:
"""
Schedule a csound event
Expand Down Expand Up @@ -778,11 +799,6 @@ def __enter__(self):
if session:
session.setHandler(_OfflineSessionHandler(self))

# if playback.isSessionActive():
# session = self.getSession()
# if not session:
# self._session = session = playback.playSession()
# self._oldSessionSchedCallback = session.setSchedCallback(self._schedSessionEvent)
return self

def __exit__(self, exc_type, exc_value, traceback):
Expand Down Expand Up @@ -883,8 +899,6 @@ def render(outfile='',
run=True,
endtime=0.,
show=False,
tempfolder='',
fmt='wav',
**kws
) -> OfflineRenderer:
"""
Expand Down Expand Up @@ -919,6 +933,7 @@ def render(outfile='',
endtime: if given, sets the end time of the rendered segment. A value
of 0. indicates to render everything. A value is needed if there
are endless events
show: display the resulting OfflineRenderer when running inside jupyter
workspace: if given, this workspace overrides the active workspace
Returns:
Expand Down
Loading

0 comments on commit 88a6fc8

Please sign in to comment.