102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
|
|
from io import BytesIO
|
||
|
|
from typing import Optional, Union, BinaryIO, List
|
||
|
|
|
||
|
|
from chipchune._util import read_short, read_int, read_str
|
||
|
|
from .data_types import WavetableMeta
|
||
|
|
from .enums import _FurWavetableImportType
|
||
|
|
|
||
|
|
FILE_MAGIC_STR = b'-Furnace waveta-'
|
||
|
|
EMBED_MAGIC_STR = b'WAVE'
|
||
|
|
|
||
|
|
|
||
|
|
class FurnaceWavetable:
|
||
|
|
def __init__(self, file_name: Optional[str] = None) -> None:
|
||
|
|
"""
|
||
|
|
Creates or opens a new Furnace wavetable as a Python object.
|
||
|
|
|
||
|
|
:param file_name: (Optional)
|
||
|
|
If specified, then it will parse a file as a FurnaceWavetable. If file name (str) is
|
||
|
|
given, it will load that file.
|
||
|
|
|
||
|
|
Defaults to None.
|
||
|
|
"""
|
||
|
|
self.file_name: Optional[str] = None
|
||
|
|
"""
|
||
|
|
Original file name, if the object was initialized with one.
|
||
|
|
"""
|
||
|
|
self.meta: WavetableMeta = WavetableMeta()
|
||
|
|
"""
|
||
|
|
Wavetable metadata.
|
||
|
|
"""
|
||
|
|
self.data: List[int] = []
|
||
|
|
"""
|
||
|
|
Wavetable data.
|
||
|
|
"""
|
||
|
|
|
||
|
|
if isinstance(file_name, str):
|
||
|
|
self.load_from_file(file_name)
|
||
|
|
|
||
|
|
def load_from_file(self, file_name: Optional[str] = None) -> None:
|
||
|
|
if isinstance(file_name, str):
|
||
|
|
self.file_name = file_name
|
||
|
|
if self.file_name is None:
|
||
|
|
raise RuntimeError('No file name set, either set self.file_name or pass file_name to the function')
|
||
|
|
|
||
|
|
# since we're loading from an uncompressed file, we can just check the file magic number
|
||
|
|
with open(self.file_name, 'rb') as f:
|
||
|
|
detect_magic = f.peek(len(FILE_MAGIC_STR))[:len(FILE_MAGIC_STR)]
|
||
|
|
if detect_magic == FILE_MAGIC_STR:
|
||
|
|
return self.load_from_stream(f, _FurWavetableImportType.FILE)
|
||
|
|
else: # uncompressed for sure
|
||
|
|
raise ValueError('No recognized file type magic')
|
||
|
|
|
||
|
|
def load_from_bytes(self, data: bytes, import_as: Union[int, _FurWavetableImportType]) -> None:
|
||
|
|
"""
|
||
|
|
Load a wavetable from a series of bytes.
|
||
|
|
|
||
|
|
:param data: Bytes
|
||
|
|
"""
|
||
|
|
return self.load_from_stream(
|
||
|
|
BytesIO(data),
|
||
|
|
import_as
|
||
|
|
)
|
||
|
|
|
||
|
|
def load_from_stream(self, stream: BinaryIO, import_as: Union[int, _FurWavetableImportType]) -> None:
|
||
|
|
"""
|
||
|
|
Load a wavetable from an **uncompressed** stream.
|
||
|
|
|
||
|
|
:param stream: File-like object containing the uncompressed wavetable.
|
||
|
|
:param import_as: int
|
||
|
|
- 0 = wavetable file
|
||
|
|
- 1 = wavetable embedded in module
|
||
|
|
"""
|
||
|
|
if import_as == _FurWavetableImportType.FILE:
|
||
|
|
if stream.read(len(FILE_MAGIC_STR)) != FILE_MAGIC_STR:
|
||
|
|
raise ValueError('Bad magic value for a wavetable file')
|
||
|
|
version = read_short(stream)
|
||
|
|
read_short(stream) # reserved
|
||
|
|
self.__load_embed(stream)
|
||
|
|
|
||
|
|
elif import_as == _FurWavetableImportType.EMBED:
|
||
|
|
return self.__load_embed(stream)
|
||
|
|
|
||
|
|
else:
|
||
|
|
raise ValueError('Invalid import type')
|
||
|
|
|
||
|
|
def __load_embed(self, stream: BinaryIO) -> None:
|
||
|
|
if stream.read(len(EMBED_MAGIC_STR)) != EMBED_MAGIC_STR:
|
||
|
|
raise RuntimeError('Bad magic value for a wavetable embed')
|
||
|
|
|
||
|
|
blk_size = read_int(stream)
|
||
|
|
if blk_size > 0:
|
||
|
|
wt_data = BytesIO(stream.read(blk_size))
|
||
|
|
else:
|
||
|
|
wt_data = stream
|
||
|
|
|
||
|
|
self.meta.name = read_str(wt_data)
|
||
|
|
self.meta.width = read_int(wt_data)
|
||
|
|
read_int(wt_data) # reserved
|
||
|
|
self.meta.height = read_int(wt_data) + 1 # serialized height is 1 lower than actual value
|
||
|
|
|
||
|
|
self.data = [read_int(wt_data) for _ in range(self.meta.width)]
|