1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2024-11-25 17:54:15 +00:00
oot/tools/audio/extraction/envelope.py

120 lines
3.3 KiB
Python
Raw Normal View History

# SPDX-FileCopyrightText: © 2024 ZeldaRET
# SPDX-License-Identifier: CC0-1.0
#
# Implements envelopes and envelope point structures
#
import collections
from .util import XMLWriter
class EnvDelay(int):
def __str__(self):
return {
0 : "ADSR_DISABLE",
-1 : "ADSR_HANG",
-2 : "ADSR_GOTO",
-3 : "ADSR_RESTART",
}.get(self, super().__str__())
class Envelope:
"""
Array of envelope points
"""
class EnvelopePoint:
"""
typedef struct {
/* 0x0 */ s16 delay;
/* 0x2 */ s16 arg;
} EnvelopePoint; // size = 0x4
"""
def __init__(self, delay, arg):
self.delay = EnvDelay(delay)
self.arg = arg
def __repr__(self):
return str(self)
def __str__(self):
return f"{{ {self.delay}, {self.arg} }}"
def is_disable(self):
return self.delay == 0 and self.arg == 0
def is_hang(self):
return self.delay == -1 and self.arg == 0
def to_xml(self, xml : XMLWriter):
if self.delay == 0: # Disable
assert self.arg == 0
xml.write_element("Disable")
elif self.delay == -1: # Hang
assert self.arg == 0
xml.write_element("Hang")
elif self.delay == -2: # Goto
xml.write_element("Goto",
{ "Arg" : self.arg }
)
elif self.delay == -3: # Restart
assert self.arg == 0
xml.write_element("Restart")
else:
assert self.delay >= 0
xml.write_element("Point",
{
"Delay" : self.delay,
"Arg" : self.arg,
}
)
def __init__(self, points, is_zero=False):
self.name = None # Assigned when bank is finalized
self.is_zero = is_zero
self.release_rates = []
self._release_rate = None # cached
assert len(points) != 0
assert type(points[0]) == Envelope.EnvelopePoint
self.points = points
if not self.is_zero:
while self.points[-1].is_disable():
self.points.pop()
assert self.points[-1].is_hang()
def __str__(self):
out = "{\n"
out += " " + ", ".join([str(point) for point in self.points]) + "\n"
out += "}\n"
return out
def release_rate(self):
if self._release_rate is not None:
return self._release_rate
rates = collections.Counter(self.release_rates).most_common()
assert len(rates) in [0, 1], rates # TODO handle ties?
self._release_rate = 0 if len(rates) == 0 else rates[0][0]
return self._release_rate
def to_xml(self, xml : XMLWriter, name : str):
if self.is_zero:
return xml.write_element("Envelope")
xml.write_start_tag("Envelope",
{
"Name" : name,
"Release" : self.release_rate(),
}
)
for point in self.points[:-1]: # exclude final hang command, will be added by the soundfont compiler on build
point.to_xml(xml)
xml.write_end_tag()