Skip to content

Modules

Modules

Module: deluge_sample

Main classes representing Deluge Sample.

ModOp

Bases: object

Represents a successful modification operation.

Attributes:

Name Type Description
operation str

str

path str

file path

instance Any

modified instance.

Source code in deluge_card/deluge_sample.py
190
191
192
193
194
195
196
197
198
199
200
201
202
@define
class ModOp(object):
    """Represents a successful modification operation.

    Attributes:
        operation: str
        path (str): file path
        instance (Any): modified instance.
    """

    operation: str
    path: str
    instance: object

Sample

Bases: object

represents a sample file.

Attributes:

Name Type Description
path Path

Path object for the sample file.

settings list[SampleSetting]

list of SampleSettings for this sample

Source code in deluge_card/deluge_sample.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@define  # (frozen=True)
class Sample(object):
    """represents a sample file.

    Attributes:
        path (Path): Path object for the sample file.
        settings (list[SampleSetting]): list of SampleSettings for this sample
    """

    path: Path
    settings: List['SampleSetting'] = field(factory=list, eq=False)

    def __eq__(self, other):
        return super(Sample, self).__eq__(other)

    def __hash__(self):
        return super(Sample, self).__hash__()

SampleMoveOperation

Bases: object

Represents a sample file move operation.

Attributes:

Name Type Description
old_path Path

original Path.

new_path Path

new Path.

sample Sample

sample instance.

Source code in deluge_card/deluge_sample.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@define(eq=False)
class SampleMoveOperation(object):
    """Represents a sample file move operation.

    Attributes:
        old_path (Path): original Path.
        new_path (Path): new Path.
        sample (Sample): sample instance.
    """

    old_path: Path
    new_path: Path
    sample: 'Sample'
    uniqid: int = field(init=False)

    def __attrs_post_init__(self):
        self.uniqid = hash(self.old_path)

    def __eq__(self, other):
        return self.uniqid == other.uniqid

    def __hash__(self):
        return self.uniqid

    def do_move(self):
        """Complete the move operation.

        We expect the destination path to exist (much like regular mv) as
        this helps the end user avoid mistakes.
        """
        # if not self.new_path.parent.exists():
        #    self.new_path.parent.mkdir(exist_ok=True, parents=True)
        self.old_path.rename(self.new_path)

do_move()

Complete the move operation.

We expect the destination path to exist (much like regular mv) as this helps the end user avoid mistakes.

Source code in deluge_card/deluge_sample.py
146
147
148
149
150
151
152
153
154
def do_move(self):
    """Complete the move operation.

    We expect the destination path to exist (much like regular mv) as
    this helps the end user avoid mistakes.
    """
    # if not self.new_path.parent.exists():
    #    self.new_path.parent.mkdir(exist_ok=True, parents=True)
    self.old_path.rename(self.new_path)

SampleSetting

Bases: object

represents a sample in the context of a DelugeXML file.

Attributes:

Name Type Description
xml_file deluge_xml.DelugeXML

object for the XML file (song, kit or synth).

xml_path str

Xmlpath string locating the sample setting within the XML.

Source code in deluge_card/deluge_sample.py
176
177
178
179
180
181
182
183
184
185
186
187
@define
class SampleSetting(object):
    """represents a sample in the context of a DelugeXML file.

    Attributes:
        xml_file (deluge_xml.DelugeXML): object for the XML file (song, kit or synth).
        xml_path (str): Xmlpath string locating the sample setting within the XML.
    """

    xml_file: 'deluge_xml.DelugeXML'
    sample: 'Sample'
    xml_path: str

SettingElementUpdater

Bases: object

Setting updater class.

Attributes:

Name Type Description
root_xml_path str

type or root node : /song/, /kit/, or /sound/.

Source code in deluge_card/deluge_sample.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@define
class SettingElementUpdater(object):
    """Setting updater class.

    Attributes:
        root_xml_path (str): type or root node : /song/, /kit/, or /sound/.
    """

    root_xml_path: str

    def update_settings(self, move_op: 'SampleMoveOperation'):
        """Update settings."""
        # print(f"DEBUG update_song_elements: {sample}")
        for setting in move_op.sample.settings:
            if not setting.xml_path[: len(self.root_xml_path)] == self.root_xml_path:
                continue
            # print(f"DEBUG update_song_elements setting: {setting}")
            setting.xml_file.update_sample_element(setting.xml_path, move_op.new_path)
            yield setting.xml_file

update_settings(move_op)

Update settings.

Source code in deluge_card/deluge_sample.py
51
52
53
54
55
56
57
58
59
def update_settings(self, move_op: 'SampleMoveOperation'):
    """Update settings."""
    # print(f"DEBUG update_song_elements: {sample}")
    for setting in move_op.sample.settings:
        if not setting.xml_path[: len(self.root_xml_path)] == self.root_xml_path:
            continue
        # print(f"DEBUG update_song_elements setting: {setting}")
        setting.xml_file.update_sample_element(setting.xml_path, move_op.new_path)
        yield setting.xml_file

modify_sample_kits(move_ops)

Update kit XML elements.

Source code in deluge_card/deluge_sample.py
68
69
70
71
def modify_sample_kits(move_ops: List['SampleMoveOperation']) -> Iterator['deluge_kit.DelugeKit']:
    """Update kit XML elements."""
    updater = SettingElementUpdater('/kit/')
    return itertools.chain.from_iterable(map(updater.update_settings, move_ops))

modify_sample_paths(root, samples, pattern, dest)

Modify sample paths just as posix mv does.

Source code in deluge_card/deluge_sample.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def modify_sample_paths(
    root: Path, samples: Iterator['Sample'], pattern: str, dest: Path
) -> Iterator['SampleMoveOperation']:
    """Modify sample paths just as posix mv does."""

    def glob_match(sample) -> bool:
        return Path(sample.path).match(pattern)

    def build_move_op(sample) -> SampleMoveOperation:
        # print('DEBUG:', sample.path)
        if dest.suffix == '':
            move_op = SampleMoveOperation(ensure_absolute(root, sample.path), Path(dest, sample.path.name), sample)
        else:
            move_op = SampleMoveOperation(ensure_absolute(root, sample.path), Path(dest), sample)
        # sample.path = move_op.new_path.relative_to(root)
        return move_op

    matching_samples = filter(glob_match, samples)
    return map(build_move_op, matching_samples)

modify_sample_songs(move_ops)

Update song XML elements.

Source code in deluge_card/deluge_sample.py
62
63
64
65
def modify_sample_songs(move_ops: List['SampleMoveOperation']) -> Iterator['deluge_song.DelugeSong']:
    """Update song XML elements."""
    updater = SettingElementUpdater('/song/')
    return itertools.chain.from_iterable(map(updater.update_settings, move_ops))

modify_sample_synths(move_ops)

Update synth XML elements.

Source code in deluge_card/deluge_sample.py
74
75
76
77
def modify_sample_synths(move_ops: List['SampleMoveOperation']) -> Iterator['deluge_synth.DelugeSynth']:
    """Update synth XML elements."""
    updater = SettingElementUpdater('/sound/')
    return itertools.chain.from_iterable(map(updater.update_settings, move_ops))

mv_samples(root, samples, pattern, dest)

Move samples, updating any affected XML files.

Source code in deluge_card/deluge_sample.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def mv_samples(root: Path, samples: Iterator['Sample'], pattern: str, dest: Path):
    """Move samples, updating any affected XML files."""
    dest = ensure_absolute(root, dest)
    validate_mv_dest(root, dest)  # raises exception if args are invalid

    sample_move_ops = list(modify_sample_paths(root, samples, pattern, dest))  # materialise the list

    updated_songs = set(modify_sample_songs(sample_move_ops))
    updated_kits = set(modify_sample_kits(sample_move_ops))
    updated_synths = set(modify_sample_synths(sample_move_ops))

    # write the modified XML, per unique song, kit, synth
    # TODO this is writing files multiple times
    for updated, tag in [(updated_songs, 'song'), (updated_kits, 'kit'), (updated_synths, 'synth')]:
        for xml in updated:
            xml.write_xml()
            yield ModOp(f"update_{tag}_xml", str(xml.path), xml)

    # move the samples
    for move_op in set(sample_move_ops):
        move_op.do_move()
        yield ModOp("move_file", str(move_op.new_path), move_op)

validate_mv_dest(root, dest)

Source code in deluge_card/deluge_sample.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def validate_mv_dest(root: Path, dest: Path):
    """Check: dest path must be a child of root and must exist."""
    absolute_dest = ensure_absolute(root, dest)

    # file as target
    if absolute_dest.suffix:  # looks like a file target
        if not absolute_dest.parent.exists():
            raise ValueError(f"target folder does not exist: {dest}")
    # folder as target
    elif not (absolute_dest.is_dir()):
        raise ValueError(f"target folder does not exist: {dest}")

    try:
        absolute_dest.parent.relative_to(root)
    except ValueError:
        raise ValueError("Destination must be a sub-folder of card.")

Module: deluge_song

Main classes representing a Deluge Song.

Credit & thanks to Jamie Faye ref https://github.com/jamiefaye/downrush/blob/master/xmlView/src/SongUtils.js

DelugeSong

Bases: DelugeXml

Class representing song data on a DelugeCard (in SONGS/*.xml).

Attributes:

Name Type Description
cardfs DelugeCardFS

Card folder system containing this file.

path Path

Path object for the sample file. file.

Source code in deluge_card/deluge_song.py
 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@define(repr=False, hash=False, eq=False)
class DelugeSong(DelugeXml):
    """Class representing song data on a DelugeCard (in SONGS/*.xml).

    Attributes:
        cardfs (DelugeCardFS): Card folder system containing this file.
        path (Path): Path object for the sample file. file.
    """

    cardfs: 'DelugeCardFS'
    path: Path

    def __attrs_post_init__(self):
        # self.samples_xpath = ".//*[@fileName]"
        self.root_elem = 'song'
        super(DelugeSong, self).__attrs_post_init__()

    def __repr__(self) -> str:
        return f"DelugeSong({self.path})"

    def minimum_firmware(self) -> str:
        """Get the songs earliest Compatible Firmware version.

        Returns:
            str: earliestCompatibleFirmware version.
        """
        return self.xmlroot.get('earliestCompatibleFirmware')

    def root_note(self) -> int:
        """Get the root note.

        Returns:
            int: root note (e.g 36 for C3).
        """
        return int(self.xmlroot.get('rootNote'))

    def mode_notes(self) -> List[int]:
        """Get the notes in the song scale (mode).

        Returns:
            [int]: list of mode intervals, relative to root.
        """
        notes = self.xmlroot.findall('.//modeNotes/modeNote')
        return [int(e.text) for e in notes]

    def scale_mode(self) -> str:
        """Get the descriptive name of the song scale (mode).

        Returns:
            str: scale_mode name.
        """
        mn = self.mode_notes()
        try:
            return Mode(mn).name
        except ValueError:
            return 'other'

    def scale(self) -> str:
        """Get the song scale and key.

        Returns:
            str: scale name.
        """
        mode = self.scale_mode()
        root_note = self.root_note() % 12
        return f'{SCALE[root_note]} {mode}'

    def tempo(self) -> float:
        """Get the song tempo in beats per minute.

        Returns:
            float: tempo BPM.

        Javascript:
            [downrush convertTempo()](https://github.com/jamiefaye/downrush/blob
            /a4fa2794002cdcebb093848af501ca17a32abe9a/xmlView/src/SongViewLib.js#L508)
        """
        # // Return song tempo calculated from timePerTimerTick and timerTickFraction
        # function convertTempo(jsong)
        # {
        #     let fractPart = (jsong.timerTickFraction>>>0) / 0x100000000;
        #     let realTPT = Number(jsong.timePerTimerTick) + fractPart;
        #     // Timer tick math: 44100 = standard Fs; 48 = PPQN;
        #     // tempo = (44100 * 60) / 48 * realTPT;
        #     // tempo = 55125 / realTPT
        #     // rounded to 1 place after decimal point:
        #     let tempo = Math.round(551250 / realTPT) / 10;
        #
        #     // console.log("timePerTimerTick=" + jsong.timePerTimerTick + " realTPT= " +  realTPT +
        #     //      " tempo= " + tempo);
        #     // console.log("timerTickFraction=" + jsong.timerTickFraction + " fractPart= " +  fractPart);
        #     return tempo;
        # }
        fractPart = (int(self.xmlroot.get('timerTickFraction'))) / int('0x100000000', 16)
        # print(int('0x100000000', 16))
        # print(fractPart)
        realTPT = float(self.xmlroot.get('timePerTimerTick')) + fractPart
        # print(realTPT)
        tempo = round((44100 * 60) / (96 * realTPT), 1)
        # tempo = round(55125/realTPT/2, 1)
        return tempo

    @property
    def synths(self) -> Generator[DelugeSongSound, Any, Any]:
        """The synths defined in this song."""
        sounds = self.xmlroot.findall('.//instruments/sound')
        for s in sounds:
            yield DelugeSongSound(s)

    @property
    def kits(self) -> Generator[Kit, Any, Any]:
        """The kits defined in this song."""
        kits = self.xmlroot.findall('.//instruments/kit')
        for k in kits:
            yield (Kit(k))

kits() property

The kits defined in this song.

Source code in deluge_card/deluge_song.py
168
169
170
171
172
173
@property
def kits(self) -> Generator[Kit, Any, Any]:
    """The kits defined in this song."""
    kits = self.xmlroot.findall('.//instruments/kit')
    for k in kits:
        yield (Kit(k))

minimum_firmware()

Get the songs earliest Compatible Firmware version.

Returns:

Name Type Description
str str

earliestCompatibleFirmware version.

Source code in deluge_card/deluge_song.py
79
80
81
82
83
84
85
def minimum_firmware(self) -> str:
    """Get the songs earliest Compatible Firmware version.

    Returns:
        str: earliestCompatibleFirmware version.
    """
    return self.xmlroot.get('earliestCompatibleFirmware')

mode_notes()

Get the notes in the song scale (mode).

Returns:

Type Description
List[int]

[int]: list of mode intervals, relative to root.

Source code in deluge_card/deluge_song.py
 95
 96
 97
 98
 99
100
101
102
def mode_notes(self) -> List[int]:
    """Get the notes in the song scale (mode).

    Returns:
        [int]: list of mode intervals, relative to root.
    """
    notes = self.xmlroot.findall('.//modeNotes/modeNote')
    return [int(e.text) for e in notes]

root_note()

Get the root note.

Returns:

Name Type Description
int int

root note (e.g 36 for C3).

Source code in deluge_card/deluge_song.py
87
88
89
90
91
92
93
def root_note(self) -> int:
    """Get the root note.

    Returns:
        int: root note (e.g 36 for C3).
    """
    return int(self.xmlroot.get('rootNote'))

scale()

Get the song scale and key.

Returns:

Name Type Description
str str

scale name.

Source code in deluge_card/deluge_song.py
116
117
118
119
120
121
122
123
124
def scale(self) -> str:
    """Get the song scale and key.

    Returns:
        str: scale name.
    """
    mode = self.scale_mode()
    root_note = self.root_note() % 12
    return f'{SCALE[root_note]} {mode}'

scale_mode()

Get the descriptive name of the song scale (mode).

Returns:

Name Type Description
str str

scale_mode name.

Source code in deluge_card/deluge_song.py
104
105
106
107
108
109
110
111
112
113
114
def scale_mode(self) -> str:
    """Get the descriptive name of the song scale (mode).

    Returns:
        str: scale_mode name.
    """
    mn = self.mode_notes()
    try:
        return Mode(mn).name
    except ValueError:
        return 'other'

synths() property

The synths defined in this song.

Source code in deluge_card/deluge_song.py
161
162
163
164
165
166
@property
def synths(self) -> Generator[DelugeSongSound, Any, Any]:
    """The synths defined in this song."""
    sounds = self.xmlroot.findall('.//instruments/sound')
    for s in sounds:
        yield DelugeSongSound(s)

tempo()

Get the song tempo in beats per minute.

Returns:

Name Type Description
float float

tempo BPM.

Javascript

downrush convertTempo()

Source code in deluge_card/deluge_song.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def tempo(self) -> float:
    """Get the song tempo in beats per minute.

    Returns:
        float: tempo BPM.

    Javascript:
        [downrush convertTempo()](https://github.com/jamiefaye/downrush/blob
        /a4fa2794002cdcebb093848af501ca17a32abe9a/xmlView/src/SongViewLib.js#L508)
    """
    # // Return song tempo calculated from timePerTimerTick and timerTickFraction
    # function convertTempo(jsong)
    # {
    #     let fractPart = (jsong.timerTickFraction>>>0) / 0x100000000;
    #     let realTPT = Number(jsong.timePerTimerTick) + fractPart;
    #     // Timer tick math: 44100 = standard Fs; 48 = PPQN;
    #     // tempo = (44100 * 60) / 48 * realTPT;
    #     // tempo = 55125 / realTPT
    #     // rounded to 1 place after decimal point:
    #     let tempo = Math.round(551250 / realTPT) / 10;
    #
    #     // console.log("timePerTimerTick=" + jsong.timePerTimerTick + " realTPT= " +  realTPT +
    #     //      " tempo= " + tempo);
    #     // console.log("timerTickFraction=" + jsong.timerTickFraction + " fractPart= " +  fractPart);
    #     return tempo;
    # }
    fractPart = (int(self.xmlroot.get('timerTickFraction'))) / int('0x100000000', 16)
    # print(int('0x100000000', 16))
    # print(fractPart)
    realTPT = float(self.xmlroot.get('timePerTimerTick')) + fractPart
    # print(realTPT)
    tempo = round((44100 * 60) / (96 * realTPT), 1)
    # tempo = round(55125/realTPT/2, 1)
    return tempo

Kit dataclass

Describes a kit object.

Source code in deluge_card/deluge_song.py
44
45
46
47
48
49
50
51
52
53
54
55
56
@dataclass
class Kit:
    """Describes a kit object."""

    kit: lxml.etree._Element
    sounds: Iterator[DelugeSongSound] = field(init=False)

    def __post_init__(self):
        def gen_sounds():
            for s in self.kit.find('soundSources').iter('sound'):
                yield DelugeSongKitSound(s)

        self.sounds = gen_sounds()

Mode

Bases: enum.Enum

Enum for the scale modes.

Source code in deluge_card/deluge_song.py
32
33
34
35
36
37
38
39
40
41
class Mode(enum.Enum):
    """Enum for the scale modes."""

    major = [0, 2, 4, 5, 7, 9, 11]
    minor = [0, 2, 3, 5, 7, 9, 10]
    dorian = [0, 2, 3, 5, 7, 9, 10]
    phrygian = [0, 1, 3, 5, 7, 8, 10]
    lydian = [0, 2, 4, 6, 7, 9, 11]
    mixolydian = [0, 2, 4, 5, 7, 9, 10]
    locrian = [0, 1, 3, 5, 6, 8, 10]

Module: deluge_kit

Main class representing a Deluge Kit.

DelugeKit

Bases: DelugeXml

Class representing kit data on a DelugeCard (in KITS/*.xml).

Attributes:

Name Type Description
cardfs DelugeCardFS

Card folder system containing this file.

path Path

Path object for the sample file. file.

Source code in deluge_card/deluge_kit.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@define(repr=False, hash=False, eq=False)
class DelugeKit(DelugeXml):
    """Class representing kit data on a DelugeCard (in KITS/*.xml).

    Attributes:
        cardfs (DelugeCardFS): Card folder system containing this file.
        path (Path): Path object for the sample file. file.
    """

    cardfs: 'DelugeCardFS'
    path: Path

    def __attrs_post_init__(self):
        # Sself.samples_xpath = ".//fileName"
        self.root_elem = 'kit'
        super(DelugeKit, self).__attrs_post_init__()

    def __repr__(self) -> str:
        return f"DelugeKit({self.path})"

Module: deluge_synth

Main class representing a Deluge Synth.

DelugeSynth

Bases: DelugeXml

Class representing a synth template on a DelugeCard (in SYNTHS/*.xml).

Attributes:

Name Type Description
cardfs DelugeCardFS

Card folder system containing this file.

path Path

Path object for the sample file. file.

Source code in deluge_card/deluge_synth.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@define(repr=False, hash=False, eq=False)
class DelugeSynth(DelugeXml):
    """Class representing a synth template on a DelugeCard (in SYNTHS/*.xml).

    Attributes:
        cardfs (DelugeCardFS): Card folder system containing this file.
        path (Path): Path object for the sample file. file.
    """

    cardfs: 'DelugeCardFS'
    path: Path

    def __attrs_post_init__(self):
        self.root_elem = 'sound'
        super().__attrs_post_init__()

    def __repr__(self) -> str:
        return f"DelugeSynth({self.path})"

Module: deluge_xml

Base class for a Deluge XML file.

DelugeXml

Class representing XML n a DelugeCard (in SONG|KIT|SYNTH xml).

Attributes:

Name Type Description
cardfs DelugeCardFS

Card folder system containing this file.

path Path

Path object for the sample file. file.

Source code in deluge_card/deluge_xml.py
 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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@define
class DelugeXml:
    """Class representing XML n a DelugeCard (in SONG|KIT|SYNTH xml).

    Attributes:
        cardfs (DelugeCardFS): Card folder system containing this file.
        path (Path): Path object for the sample file. file.
    """

    cardfs: 'DelugeCardFS'
    path: Path
    xmlroot: etree.ElementTree = field(init=False)
    uniqid: int = field(init=False)
    # samples_xpath: str = field(init=False)
    root_elem: str = field(init=False)

    def __attrs_post_init__(self):
        self.uniqid = hash(f'{str(self.cardfs.card_root)}{str(self.path)}')
        # ultimately we might want to lazy load here ....
        # see https://stackoverflow.com/questions/55548536/python-attrs-class-attribute-cached-lazy-load
        try:
            parser = etree.XMLParser(recover=True)
            self.xmlroot = etree.parse(read_and_clean_xml(self.path), parser).getroot()
        except Exception as err:
            print(f'parsing {self.path} raises.')
            raise err

    def __eq__(self, other):
        return self.uniqid == other.uniqid

    def __hash__(self):
        return self.uniqid

    def update_sample_element(self, xml_path, sample_path):
        """Update XML element from sample_setting."""
        tree = etree.ElementTree(self.xmlroot)
        elem = tree.find(xml_path.replace(f'/{self.root_elem}/', '//'))
        # print('DEBUG old path', elem.get('fileName'), elem)
        sample_path = sample_path.relative_to(self.cardfs.card_root)
        if elem.tag == 'fileName':
            elem.text = str(sample_path)
        else:
            elem.set('fileName', str(sample_path))
        return elem

    def write_xml(self, new_path=None) -> str:
        """Write the song XML."""
        filename = new_path or self.path
        with open(filename, 'wb') as doc:
            doc.write(etree.tostring(self.xmlroot, pretty_print=True))
        return str(filename)

    def samples(self, pattern: str = "", allow_missing=False) -> Iterator[Sample]:
        """Generator for samples referenced in the DelugeXML file.

        Args:
            pattern (str): glob-style filename pattern.

        Yields:
            object (Sample): the next sample object.
        """
        tree = etree.ElementTree(self.xmlroot)
        sample_map: Dict[Path, Sample] = dict()

        def update_sample_map(sample_file, tree) -> None:
            sample = Sample(ensure_absolute(self.cardfs.card_root, Path(sample_file)))
            if sample.path in sample_map.keys():
                sample = sample_map[sample.path]
            else:
                sample_map[sample.path] = sample
            sample.settings.append(SampleSetting(self, sample, tree.getpath(e)))

        def match_pattern(sample_file: str, pattern: str) -> None:
            if sample_file:
                if (not allow_missing) and (not ensure_absolute(self.cardfs.card_root, Path(sample_file)).exists()):
                    return
                if not pattern:
                    update_sample_map(sample_file, tree)
                elif PurePath(sample_file).match(pattern):
                    update_sample_map(sample_file, tree)

        for e in self.xmlroot.findall(".//*[@fileName]"):
            match_pattern(e.get('fileName'), pattern)

        for e in self.xmlroot.findall(".//fileName"):
            match_pattern(e.text, pattern)

        return (m for m in sample_map.values())

samples(pattern='', allow_missing=False)

Generator for samples referenced in the DelugeXML file.

Parameters:

Name Type Description Default
pattern str

glob-style filename pattern.

''

Yields:

Name Type Description
object Sample

the next sample object.

Source code in deluge_card/deluge_xml.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
def samples(self, pattern: str = "", allow_missing=False) -> Iterator[Sample]:
    """Generator for samples referenced in the DelugeXML file.

    Args:
        pattern (str): glob-style filename pattern.

    Yields:
        object (Sample): the next sample object.
    """
    tree = etree.ElementTree(self.xmlroot)
    sample_map: Dict[Path, Sample] = dict()

    def update_sample_map(sample_file, tree) -> None:
        sample = Sample(ensure_absolute(self.cardfs.card_root, Path(sample_file)))
        if sample.path in sample_map.keys():
            sample = sample_map[sample.path]
        else:
            sample_map[sample.path] = sample
        sample.settings.append(SampleSetting(self, sample, tree.getpath(e)))

    def match_pattern(sample_file: str, pattern: str) -> None:
        if sample_file:
            if (not allow_missing) and (not ensure_absolute(self.cardfs.card_root, Path(sample_file)).exists()):
                return
            if not pattern:
                update_sample_map(sample_file, tree)
            elif PurePath(sample_file).match(pattern):
                update_sample_map(sample_file, tree)

    for e in self.xmlroot.findall(".//*[@fileName]"):
        match_pattern(e.get('fileName'), pattern)

    for e in self.xmlroot.findall(".//fileName"):
        match_pattern(e.text, pattern)

    return (m for m in sample_map.values())

update_sample_element(xml_path, sample_path)

Update XML element from sample_setting.

Source code in deluge_card/deluge_xml.py
68
69
70
71
72
73
74
75
76
77
78
def update_sample_element(self, xml_path, sample_path):
    """Update XML element from sample_setting."""
    tree = etree.ElementTree(self.xmlroot)
    elem = tree.find(xml_path.replace(f'/{self.root_elem}/', '//'))
    # print('DEBUG old path', elem.get('fileName'), elem)
    sample_path = sample_path.relative_to(self.cardfs.card_root)
    if elem.tag == 'fileName':
        elem.text = str(sample_path)
    else:
        elem.set('fileName', str(sample_path))
    return elem

write_xml(new_path=None)

Write the song XML.

Source code in deluge_card/deluge_xml.py
80
81
82
83
84
85
def write_xml(self, new_path=None) -> str:
    """Write the song XML."""
    filename = new_path or self.path
    with open(filename, 'wb') as doc:
        doc.write(etree.tostring(self.xmlroot, pretty_print=True))
    return str(filename)

read_and_clean_xml(xml_path)

Strip illegal elements.

Source code in deluge_card/deluge_xml.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def read_and_clean_xml(xml_path):
    """Strip illegal elements."""
    newxml = io.BytesIO()
    with open(xml_path, 'rb') as f:
        lcount = 0
        for line in f.readlines():
            lcount += 1
            if b'<firmwareVersion>' == line[:17] and lcount < 3:
                continue
            if b'<earliestCompatibleFirmware>' == line[:28] and lcount < 4:
                continue
            newxml.write(line)
    newxml.seek(0)
    return newxml