File: Sequences.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 (366 lines) | stat: -rw-r--r-- 11,377 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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
"""
Sequences.py
------------
All patterns inherit from Base.Pattern. There are two types of pattern:

1. Container types
    * Similar to lists but with different mathematical operators
2. Generator types
    * Similar to generators but can be indexed (returns values based on functions)
"""

import random
import math

from renardo_lib.Patterns.Main import Pattern, asStream, loop_pattern_func
from renardo_lib.Patterns.PGroups import (
    PGroupAnd, GeneratorPattern, PGroup, PGroupStar,
    PGroupPow, PGroupXor, PGroupOr,
    PGroupPlus, PGroupDiv, modi
)
from renardo_lib.Patterns.Operations import LCM
from renardo_lib.Patterns.Generators import PRand
from renardo_lib.Utils import sliceToRange, EuclidsAlgorithm, PulsesToDurations

MAX_SIZE = 2048

#==============================#
#         1. P[] & P()         #
#==============================#

class __pattern__(object):
    ''' Used to define lists as patterns:

        `P[1,2,3]` is equivalent to `Pattern([1,2,3])` and
        `P(1,2,3)` is equivalent to `Pattern((1,2,3))` and
        `P+(1,2,3)` is equivalient to `Pattern((1,2,3))`.

        Ranges can be created using slicing, e.g. `P[1:6:2]` will generate the range
        1 to 6 in steps of 2, thus creating the Pattern `[1, 3, 5]`. Slices can be
        combined with other values in a Pattern such that `P[0,2,1:10]` will return
        the Pattern `P[0, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9]`
    '''
    def __getitem__(self, args):
        if isinstance(args, Pattern):
            return args
        elif isinstance(args, (Pattern.TimeVar, Pattern.PlayerKey)):
            data = [args]
        elif hasattr(args, '__iter__') and not isinstance(args, (str, bytes, GeneratorPattern, PGroup)):
            data = []
            for item in args:
                if type(item) is slice:
                    data.extend(sliceToRange(item))
                else:
                    data.append(item)
        elif type(args) is slice:
            data = sliceToRange(args)
        else:
            data = args
        return Pattern(data)

    def __call__(self, *args):
        return PGroup(args if len(args) > 1 else args[0])

    def __mul__(self, other):
        """ P*[0,1,2] returns PRand([0,1,2])
            P*(0,1,2) returns PGroupStar(0,1,2)
        """
        if isinstance(other, (list, Pattern)):
            return PRand(list(other))
        else:
            return PGroupStar(other)

    def __pow__(self, other):
        """ P**(x1, x2,...,xn) - Returns scrambled version """
        return PGroupPow(other)

    def __xor__(self, other):
        """ P^(x1, x2,..., dur) -  Returns a PGroup that delays each value by dur * n """
        return PGroupXor(other)

    def __or__(self, other):
        """ P|("x", 2) """
        return PGroupOr(other)

    def __add__(self, other):
        return PGroupPlus(other)

    def __radd__(self, other):
        return self + other

    def __truediv__(self, other):
        return PGroupDiv(other)

    def __mod__(self, other):
        if isinstance(other, list):
            return Pattern().fromString(other[0], flat=True)
        return PGroupMod(other)

    def __and__(self, other):
        return PGroupAnd(other)

    def __invert__(self):
        return __reverse_pattern__()

class __reverse_pattern__(__pattern__):
    def __getattr__(self, name):
        return ~object.__getattr__(self, name)


# This is a pattern creator
P = __pattern__()

#================================#
#      2. Pattern Functions      #
#================================#

#: Pattern functions that take patterns as arguments

def PShuf(seq):
    ''' PShuf(seq) -> Returns a shuffled version of seq'''
    return Pattern(seq).shuffle()

def PAlt(pat1, pat2, *patN):
    ''' Returns a Pattern generated by alternating the values in the given sequences '''
    data = []
    item = [asStream(p) for p in [pat1, pat2] + list(patN)]
    size = LCM(*[len(i) for i in item])
    for n in range(size):
        for i in item:
            data.append(modi(i,n))
    return Pattern(data)

def PStretch(seq, size):
    ''' Returns 'seq' as a Pattern and looped until its length is 'size'
        e.g. `PStretch([0,1,2], 5)` returns `P[0, 1, 2, 0, 1]` '''
    return Pattern(seq).stretch(size)

def PPairs(seq, func=lambda n: 8-n):
    """ Laces a sequence with a second sequence obtained
        by performing a function on the original. By default this is
        `lambda n: 8 - n`. """
    i = 0
    data = []
    for item in seq:
        data.append(item)
        data.append(func(item))
        i += 1
        if i >= MAX_SIZE:
            break
    return Pattern(data)

def PZip(pat1, pat2, *patN):
    ''' Creates a Pattern that 'zips' together multiple patterns. `PZip([0,1,2], [3,4])`
        will create the Pattern `P[(0, 3), (1, 4), (2, 3), (0, 4), (1, 3), (2, 4)]` '''
    l, p = [], []
    for pat in [pat1, pat2] + list(patN):
        p.append(P[pat])
        l.append(len(p[-1]))
    length = LCM(*l)
    return Pattern([tuple(pat[i] for pat in p) for i in range(length)])


def PZip2(pat1, pat2, rule=lambda a, b: True):
    ''' Like `PZip` but only uses two Patterns. Zips together values if they satisfy the rule. '''
    length = LCM(len(pat1), len(pat2))
    data = []
    i = 0
    while i < length:
        a, b = modi(pat1,i), modi(pat2,i)
        if rule(a, b):
            data.append((a,b))
        i += 1
    return Pattern(data)

@loop_pattern_func
def PStutter(x, n=2):
    """ PStutter(seq, n) -> Creates a pattern such that each item in the array is repeated n times (n can be a pattern) """
    return Pattern([x for i in range(n)])

@loop_pattern_func
def PSq(a=1, b=2, c=3):
    ''' Returns a Pattern of square numbers in the range a to a+c '''
    return Pattern([x**b for x in range(a,a+c)])

@loop_pattern_func
def P10(n):
    ''' Returns an n-length Pattern of a randomly generated series of 1's and 0's '''
    return Pattern([random.choice((0,1)) for i in range(int(n))])

@loop_pattern_func
def PStep(n, value, default=0):
    ''' Returns a Pattern that every n-term is 'value' otherwise 'default' '''
    return Pattern([default] * (n-1) + [value])

@loop_pattern_func
def PSum(n, total, **kwargs):
    """
    Returns a Pattern of length 'n' that sums to equal 'total'

    e.g. PSum(3,8) -> P[3, 3, 2]
         PSum(5,4) -> P[1, 0.75, 0.75, 0.75, 0.75]
    """
    lim = kwargs.get("lim", 0.125)

    data = [total + 1]

    step = 1
    while sum(data) > total:
        data = [step for x in range(n)]
        step *= 0.5

    i = 0
    while sum(data) < total and step >= lim:
        if sum(data) + step > total:
            step *= 0.5
        else:
            data[i % n] += step
            i += 1

    return Pattern(data)

@loop_pattern_func
def PRange(start, stop=None, step=1):
    """ Returns a Pattern equivalent to ``Pattern(range(start, stop, step))`` """
    if stop is None:
        
        start, stop = 0, start

    if start == stop:

        return Pattern(start)
    
    if (start > stop and step > 0) or (start < stop and step < 0):
    
        step = step*-1

    return Pattern(list(range(start, stop, step)))

@loop_pattern_func
def PTri(start, stop=None, step=1):
    """ Returns a Pattern equivalent to ``Pattern(range(start, stop, step))`` with its reversed form
    appended."""
    if stop is None: 
        start, stop = 0, start
    pat = PRange(start, stop, step)
    return pat | pat.reverse()[1:-1]

@loop_pattern_func
def PSine(n=16):
    """ Returns values of one cycle of sine wave split into 'n' parts """
    i = (2 * math.pi) / n
    return Pattern([math.sin(i * j) for j in range(int(n))])

@loop_pattern_func
def PEuclid(n, k):
    ''' Returns the Euclidean rhythm which spreads 'n' pulses over 'k' steps as evenly as possible.
        e.g. `PEuclid(3, 8)` will return `P[1, 0, 0, 1, 0, 0, 1, 0]` '''
    return Pattern( EuclidsAlgorithm(n, k) )

@loop_pattern_func
def PEuclid2(n, k, lo, hi):
    ''' Same as PEuclid except it returns an array filled with 'lo' value instead of 0
        and 'hi' value instead of 1. Can be used to generate characters patterns used to
        play sample like play(PEuclid2(3,8,'-','X')) will be equivalent to
        play(P['X', '-', '-', 'X', '-', '-', 'X', '-'])
        that's like saying play("X--X--X-")
        '''
    return Pattern( EuclidsAlgorithm(n, k, lo, hi) )

@loop_pattern_func
def PBern(size=16, ratio=0.5):
    """ Returns a pattern of 1s and 0s based on the ratio value (between 0 and 1).
        This is called a Bernoulli sequence. """
    return Pattern([int(random.random() < ratio) for n in range(size)])


def PBeat(string, start=0, dur=0.5):
    """ Returns a Pattern of durations based on an input string where
        non-whitespace denote a pulse e.g.
        ::

            >>> PBeat("x xxx x")
            P[1, 0.5, 0.5, 1, 0.5]
    """
    data = [int(char != " ") for char in list(string)]
    pattern = Pattern(PulsesToDurations( data ))
    if start != 0:
        pattern = pattern.rotate(int(start))
    return pattern * dur

@loop_pattern_func
def PDur(n, k, start=0, dur=0.25):
    """ Returns the *actual* durations based on Euclidean rhythms (see PEuclid) where dur
        is the length of each step.
        ::
            >>> PDur(3, 8)
            P[0.75, 0.75, 0.5]
            >>> PDur(5, 16)
            P[0.75, 0.75, 0.75, 0.75, 1]
    """
    # If we have more pulses then steps, double the steps and decrease the duration

    while n > k:
        k   = k * 2
        dur = dur / 2

    pattern = Pattern(PulsesToDurations( EuclidsAlgorithm(n, k) ))
    if start != 0:
        pattern = pattern.rotate(int(start))
    return pattern * dur

@loop_pattern_func # forces it into a stream instead of Group
def PDelay(*args):
    return PDur(*args).accum().group()

@loop_pattern_func
def PStrum(n=4):
    """ Returns a pattern of durations similar to how you might strum a guitar """
    return (Pattern([1,1/2]).stutter([1,n + 1])|Pattern([1.5,1/2]).stutter([1,n])|1)

def PQuicken(dur=1/2, stepsize=3, steps=6):
    """ Returns a PGroup of delay amounts that gradually decrease """
    delay = []
    count = 0
    for i in range(steps):
        for j in range(stepsize-1):
            delay.append( count )
            count += dur / stepsize
        dur /= stepsize
    return PGroup(delay)


def PRhythm(durations):
    """ Converts all tuples/PGroups into delays calculated using the PDur algorithm.
    e.g.
        PRhythm([1,(3,8)]) -> P[1,(2,0.75,1.5)]
    *work in progress*
    """
    if len(durations) == 1:
        item = durations[0]
        if isinstance(item, (tuple, PGroup)):
            return PDur(*item)
        else:
            return durations
    else:
        durations = asStream(durations).data
        output = Pattern()
        for i in range(len( asStream(durations).data ) ):
            item = durations[i]
            if isinstance(item, (list, Pattern)):
                value = PRhythm(item)
            elif isinstance(item, (tuple, PGroup)):
                dur   = PDur(*item)
                value = PGroup(sum(dur)|dur.accum()[1:])
            else:
                value = item
            output.append(value)
        return output


def PJoin(patterns):
    """ Joins a list of patterns together """
    data = []
    for pattern in patterns:
        data.extend(pattern)
    return Pattern(data)