Skip to content

API

Package

pyawe_ffs

AWE FFS Creator/Parser

The programming interface is quite simple.

This is how to create a new file:

from pyawe_ffs.FfsContainer import FfsContainer
ffs = FfsContainer()
ffs.append("my_wavedata_1.wav")
ffs.append("my_wavedata_2.wav")
ffs.save("my_ffs_buffer.bin")
This also generates my_ffs_buffer.txt and my_ffs_buffer.csv. The CSV file can typically be used in a TableSourceV2 module to map indices to file names.

An FFS buffer file can be read with and data extracted to a directory:

ffs = FfsContainer.from_file("my_ffs_buffer.bin")
for entry in ffs.entries:
    print(f"- {entry}")
    entry.store("/tmp")

If you also want to generate corresponding list and csv files (see above), you can use:

ffs = FfsContainer.from_file("my_ffs_buffer.bin")
ffs.save_filelist_txt("/tmp/mybuffer.txt")
ffs.save_filelist_csv("/tmp/mybuffer.csv")

Modules

pyawe_ffs.FfsContainer

pyawe_ffs.FfsContainer.FfsContainer

A container for several FFS entries

Source code in src/pyawe_ffs/FfsContainer.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class FfsContainer:
    """ A container for several FFS entries
    """
    def __init__(self, max_size: int = 0x400000, old_backend: bool = False):
        self._entries: list[FfsEntry] = []
        self._cfg = {'max_size': max_size, 'coeff_table_size': 14}
        self._old_backend = old_backend

    def append(self, fname: str | Path, data: bytes | None = None):
        """ adds a new file to the container """
        self._entries.append(FfsEntry(str(Path(fname)), data))
        logger.info("Added new entry %s to FfsContainer", fname)

    @property
    def entries(self):
        """ property to return the list of FfsEntry values """
        return self._entries

    @classmethod
    def from_file(cls, fname: Path, old_backend: bool = False):
        """ creates a new FfsContainer based on an existing FFS file """
        if old_backend:
            raise NotImplementedError("Loading from existing file not implemented yet for old backend")
        self = cls(old_backend=old_backend)
        backend = FfsPyBackend.from_file(fname)
        for entry in backend.files:
            self.append(entry.input_file, entry.data)
        return self

    def save(self, fname: str | os.PathLike, dry_run=False):
        """ stores the added file data into the bin buffer
            calls to the backend to convert the data
        """
        fname = Path(fname)
        fname.parent.mkdir(parents=True, exist_ok=True)

        logger.info("Creating FFS data file %s", fname)
        if self._old_backend:
            writer = FfsBackend(self._cfg['max_size'], dry_run)
        else:
            writer = FfsPyBackend(self._cfg['max_size'], dry_run)
        file_paths = [e.path for e in self._entries]
        output = writer.write(fname, file_paths)

        if not dry_run:
            # also store TXT and CSV file with the entry names
            self.save_filelist_txt(fname.with_suffix(".txt"))
            self.save_filelist_csv(fname.with_suffix(".csv"))

        return output

    def save_filelist_txt(self, fname: Path):
        """ saves the list of entries into a TXT file as plain name """
        with open(fname, "w", encoding="utf-8") as f:
            for idx, e in enumerate(self._entries):
                f.write(f"{idx}: {e.path.name}\n")

    def save_filelist_csv(self, fname: Path):
        """ saves the list of entries in a CSV file that can directly be used in Designer

            This file is typically directly used in an Arrayset module which controls
            the file name property of a WaveOneShotPlayerFFS module.
        """

        # TODO: find a better way without numpy dependency,
        #       this is how a UINT32 buffer can be converted to byte buffer/string:
        #       struct.pack("i" * len(cmd.payload['values']), *(cmd.payload['values'])).decode().rstrip('\0')
        #       there should be a similar approach
        #       keeping older (ugly, but known to work) implementation here for now.

        coeff_table_size = self._cfg['coeff_table_size']
        coeff_table = np.zeros((coeff_table_size, len(self._entries)), dtype=np.uint32)

        for idx_file, e in enumerate(self._entries):

            file_bytes = bytearray(e.path.name, 'utf-8')
            for _ in range(4 - len(file_bytes) % 4):
                file_bytes += b'\x00'
            file_bytes_size = min(len(file_bytes), coeff_table_size * 4 - 1)
            for word in range(0, file_bytes_size, 4):
                coeff_table[int(word / 4), idx_file] += file_bytes[word + 3] << 24
                coeff_table[int(word / 4), idx_file] += file_bytes[word + 2] << 16
                coeff_table[int(word / 4), idx_file] += file_bytes[word + 1] << 8
                coeff_table[int(word / 4), idx_file] += file_bytes[word]

        with open(fname, "w", encoding="utf-8") as csv_file:
            my_writer = csv.writer(csv_file, delimiter=',')
            for row_idx in range(np.shape(coeff_table)[0]):
                my_writer.writerow(coeff_table[row_idx, :])
entries property

property to return the list of FfsEntry values

append(fname, data=None)

adds a new file to the container

Source code in src/pyawe_ffs/FfsContainer.py
25
26
27
28
def append(self, fname: str | Path, data: bytes | None = None):
    """ adds a new file to the container """
    self._entries.append(FfsEntry(str(Path(fname)), data))
    logger.info("Added new entry %s to FfsContainer", fname)
from_file(fname, old_backend=False) classmethod

creates a new FfsContainer based on an existing FFS file

Source code in src/pyawe_ffs/FfsContainer.py
35
36
37
38
39
40
41
42
43
44
@classmethod
def from_file(cls, fname: Path, old_backend: bool = False):
    """ creates a new FfsContainer based on an existing FFS file """
    if old_backend:
        raise NotImplementedError("Loading from existing file not implemented yet for old backend")
    self = cls(old_backend=old_backend)
    backend = FfsPyBackend.from_file(fname)
    for entry in backend.files:
        self.append(entry.input_file, entry.data)
    return self
save(fname, dry_run=False)

stores the added file data into the bin buffer calls to the backend to convert the data

Source code in src/pyawe_ffs/FfsContainer.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def save(self, fname: str | os.PathLike, dry_run=False):
    """ stores the added file data into the bin buffer
        calls to the backend to convert the data
    """
    fname = Path(fname)
    fname.parent.mkdir(parents=True, exist_ok=True)

    logger.info("Creating FFS data file %s", fname)
    if self._old_backend:
        writer = FfsBackend(self._cfg['max_size'], dry_run)
    else:
        writer = FfsPyBackend(self._cfg['max_size'], dry_run)
    file_paths = [e.path for e in self._entries]
    output = writer.write(fname, file_paths)

    if not dry_run:
        # also store TXT and CSV file with the entry names
        self.save_filelist_txt(fname.with_suffix(".txt"))
        self.save_filelist_csv(fname.with_suffix(".csv"))

    return output
save_filelist_csv(fname)

saves the list of entries in a CSV file that can directly be used in Designer

This file is typically directly used in an Arrayset module which controls the file name property of a WaveOneShotPlayerFFS module.

Source code in src/pyawe_ffs/FfsContainer.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def save_filelist_csv(self, fname: Path):
    """ saves the list of entries in a CSV file that can directly be used in Designer

        This file is typically directly used in an Arrayset module which controls
        the file name property of a WaveOneShotPlayerFFS module.
    """

    # TODO: find a better way without numpy dependency,
    #       this is how a UINT32 buffer can be converted to byte buffer/string:
    #       struct.pack("i" * len(cmd.payload['values']), *(cmd.payload['values'])).decode().rstrip('\0')
    #       there should be a similar approach
    #       keeping older (ugly, but known to work) implementation here for now.

    coeff_table_size = self._cfg['coeff_table_size']
    coeff_table = np.zeros((coeff_table_size, len(self._entries)), dtype=np.uint32)

    for idx_file, e in enumerate(self._entries):

        file_bytes = bytearray(e.path.name, 'utf-8')
        for _ in range(4 - len(file_bytes) % 4):
            file_bytes += b'\x00'
        file_bytes_size = min(len(file_bytes), coeff_table_size * 4 - 1)
        for word in range(0, file_bytes_size, 4):
            coeff_table[int(word / 4), idx_file] += file_bytes[word + 3] << 24
            coeff_table[int(word / 4), idx_file] += file_bytes[word + 2] << 16
            coeff_table[int(word / 4), idx_file] += file_bytes[word + 1] << 8
            coeff_table[int(word / 4), idx_file] += file_bytes[word]

    with open(fname, "w", encoding="utf-8") as csv_file:
        my_writer = csv.writer(csv_file, delimiter=',')
        for row_idx in range(np.shape(coeff_table)[0]):
            my_writer.writerow(coeff_table[row_idx, :])
save_filelist_txt(fname)

saves the list of entries into a TXT file as plain name

Source code in src/pyawe_ffs/FfsContainer.py
68
69
70
71
72
def save_filelist_txt(self, fname: Path):
    """ saves the list of entries into a TXT file as plain name """
    with open(fname, "w", encoding="utf-8") as f:
        for idx, e in enumerate(self._entries):
            f.write(f"{idx}: {e.path.name}\n")

pyawe_ffs.FfsEntry

pyawe_ffs.FfsEntry.FfsEntry

a data entry in an FFS container

Source code in src/pyawe_ffs/FfsEntry.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class FfsEntry:
    """ a data entry in an FFS container

    """
    def __init__(self, fname: str, data: bytes | None = None):
        self._fname = fname
        self._content = data

    def __str__(self):
        return f"FfsEntry: {str(self._fname)} - {len(self._content)} bytes"

    def store(self, outdir: str | Path):
        """ store the data of this FFS entry in an output directory """
        outdir = Path(outdir)
        outdir.mkdir(parents=True, exist_ok=True)
        logger.info("Writing to %s", outdir / self._fname)
        with (outdir / self._fname).open("wb") as f:
            f.write(self._content)

    @property
    def path(self):
        """ returns the path to the mentioned file """
        return Path(self._fname)
path property

returns the path to the mentioned file

store(outdir)

store the data of this FFS entry in an output directory

Source code in src/pyawe_ffs/FfsEntry.py
22
23
24
25
26
27
28
def store(self, outdir: str | Path):
    """ store the data of this FFS entry in an output directory """
    outdir = Path(outdir)
    outdir.mkdir(parents=True, exist_ok=True)
    logger.info("Writing to %s", outdir / self._fname)
    with (outdir / self._fname).open("wb") as f:
        f.write(self._content)