File: PGroups.py

package info (click to toggle)
python-renardo-lib 0.9.12-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,220 kB
  • sloc: python: 10,999; sh: 34; makefile: 7
file content (241 lines) | stat: -rw-r--r-- 8,176 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
from renardo_lib.Patterns.Main import PGroup, PatternMethod, GeneratorPattern, sum_delays
from renardo_lib.Utils import modi, LCM

class PGroupPrime(PGroup):
    WEIGHT  = 1
    """ Base class for PGroups with "behavior" that affect a Player's event dictionary """
    def change_state(self):
        """ To be overridden by any PGroupPrime that changes state after access by a Player """
        return
    def convert_data(self, *args, **kwargs):
        self.change_state()
        return PGroup.convert_data(self, *args, **kwargs)
    def has_behaviour(self):
        return True
    def _get_step(self, dur):
        return float(dur) / len(self)
    def _get_delay(self, delay):
        return delay

class metaPGroupPrime(PGroupPrime):
    """ Base class for PGroups that take any extra arguments to be stored """
    WEIGHT = 3
    def __init__(self, *args, **kwargs):
        PGroupPrime.__init__(self, *args, **kwargs)
        if isinstance(self, PGroup):
            self.meta = self.data[self.ignore:]
            self.data = self.data[:self.ignore]

class PGroupStar(PGroupPrime):
    """ Stutters the values over the length of and event's 'dur' """    
    bracket_style="*()"

class PGroupPlus(PGroupPrime):
    """ Stutters the values over the length of and event's 'sus' """
    bracket_style="+()"
    def get_behaviour(self):
        """ Returns a function that modulates a player event dictionary """
        def action(event, key):
            this_delay = self.calculate_time(float(event['sus']))
            return self._update_event(event, key, this_delay)
        return action

class PGroupPow(PGroupPrime):
    """ Stutters a shuffled version the values over the length of and event's 'dur' """
    bracket_style="**()"
    def calculate_time(self, dur):
        return PGroupPrime.calculate_time(self, dur).shuffle()    

class PGroupDiv(PGroupPrime):
    """ Stutter every other request """
    bracket_style="/()"
    counter = 0
    def __init__(self, *args, **kwargs):
        PGroupPrime.__init__(self, *args, **kwargs)
    def change_state(self):
        self.counter += 1
    def calculate_time(self, dur):
        if self.counter % 2 == 1:
            return PGroupPrime.calculate_time(self, dur)
        else:
            return 0

class PGroupMod(PGroupPlus):
    """ OBSOLETE
        --------
        Useful for when you want many nested groups. This PGroup flattens the original
        but the delay times are calculated in the same way as if the values were neseted
     """
    bracket_style="%()"

    def __len__(self):
        return len([item for item in self])

    def getitem(self, index):
        return list(self)[index]

    def _get_step(self, dur):
        return float(dur) / len(self.data)

    def calculate_time(self, dur):
        """ Returns a PGroup of durations to use as the delay argument
            when this is a sub-class of `PGroupPrime` """
        values = []
        step  = self._get_step(dur)
        for i, item in enumerate(self.data):
            delay = self._get_delay( i * step )
            if hasattr(item, "calculate_time"):
                delay += item.calculate_time( step )
            if isinstance(delay, PGroup):
                values.extend(list(delay))
            else:
                values.append( delay )
        return PGroup(values)

    def __iter__(self):
        return self.get_iter(self.data)

    @staticmethod
    def get_iter(group):
        """ Recursively unpacks nested PGroup into an un-nested group"""
        for item in group:
            if isinstance(item, PGroup):
                for sub in PGroupMod.get_iter(item.data):
                    yield sub
            else:
                yield item

class PGroupOr(metaPGroupPrime):
    """ Used to specify `sample` values, usually from within a play string using values 
        between "bar" signs e.g. "|x2|" """
    bracket_style="|()"
    ignore = -1
    def __init__(self, seq=[]):
        metaPGroupPrime.__init__(self, seq)

        # May be changed to a Pattern
        
        if self.__class__ is not PGroupOr:
        
            return
        
        self.data = self.data[:1] # Make sure we only have 1 element for data

    def calculate_sample(self):
        sample = self.meta[0]
        if isinstance(sample, PGroupPrime):
            sample = PGroup(sample)
        elif isinstance(sample, GeneratorPattern):
            sample = sample.getitem()
        return sample

    def calculate_time(self, *args, **kwargs):
        """ Return a single value, as its always "length" 1 """
        char_delay = PGroupPrime.calculate_time(self, *args, **kwargs)[0]
        samp_delay = self.meta[0].calculate_time(*args, **kwargs) if isinstance(self.meta[0], PGroup) else 0
        return sum_delays(char_delay, samp_delay)

    def _get_delay(self, *args, **kwargs):
        return 0
    def _get_step(self, dur):
        return dur

#class PGroupFloorDiv(PGroupPrime):
#    """ Unused """
#    bracket_style="//()"

#class PGroupSub(PGroupPrime):
#    """ Unused """
#    bracket_style="-()"

class PGroupXor(metaPGroupPrime):
    """ The delay of this PGroup is specified by the last value (not included in the data) """
    bracket_style="^()"
    ignore = -1
    def __init__(self, seq=[]):
        if isinstance(seq, self.__class__):
            self.data = seq.data
            self.meta = seq.meta
            return
        metaPGroupPrime.__init__(self, seq)
        # May be changed to a Pattern
        if self.__class__ is not PGroupXor:
            return
        # Make sure we have at least 1 item of data
        if len(self.data) == 0 and len(self.meta) == 1:
            self.data = self.meta
            self.meta = [0]
    def _get_step(self, dur):
        return self.meta[0]
    def _get_delay(self, delay):
        return delay


class PGroupAnd(PGroupPrime):
    """ Unused """
    bracket_style="&()"
    delay = 0
    def __init__(self, args):
        PGroupPrime.__init__(self, args[0])
        if len(args) > 0:
            self.delay = args[1]
    def calculate_step(self, i, dur):
        return i * self.delay

# Define any pattern methods that use PGroupPrimes

@PatternMethod
def offadd(self, value, dur=0.5):
    return self + PGroupXor((0, value, dur))    

@PatternMethod
def offmul(self, value, dur=0.5):
    #return self * PGroupXor(1, value).set_delay(dur)
    return self * PGroupXor((1, value, dur))

@PatternMethod
def offlayer(self, method, dur=0.5, *args, **kwargs):
    """ Zips a pattern with a modified version of itself. Method argument
        can be a function that takes this pattern as its first argument,
        or the name of a Pattern method as a string. """
    
    if callable(method):
        func = method
        args = [self] + list(args)
    else:
        func = getattr(self, method)
        assert callable(func)

    return self.zip(func(*args, **kwargs), dtype=lambda a, b: PGroupXor([a, b, dur]))
    
@PatternMethod
def amen(self, size=2):
    """ Merges and laces the first and last two items such that a
        drum pattern "x-o-" would become "(x[xo])-o([-o]-)" """
    new = []
    for n in range( LCM(len(self), 4) ):
        if  n % 4 == 0:
            new.append([self[n], PGroupPlus(self[n], modi(self, n + size))])
        elif n % 4 == size:
            new.append( [self[n]]*3+[self[n-1]] )
        elif n % 4 == size + 1:
            new.append( [PGroupPlus(self[n], self[n-1]), [self[n], self[n-1]] ] )
        else:
            new.append(self[n])
    return self.__class__(new)

@PatternMethod
def bubble(self, size=2):
    """ Merges and laces the first and last two items such that a
        drum pattern "x-o-" would become "(x[xo])-o([-o]-)" """
    new = []
    for n in range(len(self.data)):
        if  n % 4 == 0:
            new.append([self.data[n], PGroupPlus(self.data[n], modi(self.data, n + size))])
        elif n % 4 == 2:
            new.append( [self.data[n]]*3+[self.data[n-1]] )
        elif n % 4 == 3:
            new.append( [PGroupPlus(self.data[n], self.data[n-1]), [self.data[n], self.data[n-1]] ] )
        else:
            new.append(self.data[n])
    return self.__class__(new)