~hecanjog/pippi

b94ecd2e332bd770de223102e89de1e64665048d — Erik Schoster 4 months ago 4cff9c3
Remove old graincloud module
7 files changed, 4 insertions(+), 288 deletions(-)

M banner.png
D pippi/grains.pxd
D pippi/grains.pyx
M pippi/soundbuffer.pyx
M setup.py
M tests/test_graincloud.py
M tests/test_shapes.py
M banner.png => banner.png +0 -0
D pippi/grains.pxd => pippi/grains.pxd +0 -25
@@ 1,25 0,0 @@
#cython: language_level=3

#from pippi.ugens.core cimport ugen_t

cdef class Cloud:
    cdef double[:,:] snd
    cdef unsigned int framelength
    cdef double length
    cdef unsigned int channels
    cdef unsigned int samplerate

    cdef unsigned int wtsize

    cdef double[:] window
    cdef double[:] position
    cdef double[:] amp
    cdef double[:] speed
    cdef double[:] spread
    cdef double[:] jitter
    cdef double[:] grainlength
    cdef double[:] grid

    cdef int[:] mask
    cdef bint has_mask


D pippi/grains.pyx => pippi/grains.pyx +0 -224
@@ 1,224 0,0 @@
#cython: language_level=3

from pippi.wavetables cimport HANN, PHASOR, to_window, to_wavetable, Wavetable
from pippi cimport interpolation
from pippi.soundbuffer cimport SoundBuffer
from libc.stdlib cimport rand, RAND_MAX, malloc, free
import numpy as np
cimport cython
from cpython cimport array
import array

@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline double _linear_pos(double[:] data, double phase) nogil:
    cdef int dlength = <int>len(data)
    phase *= <double>(dlength-1)

    if dlength == 1:
        return data[0]

    elif dlength < 1:
        return 0

    cdef double frac = phase - <long>phase
    cdef long i = <long>phase
    cdef double a, b

    if i >= dlength-1:
        return 0

    a = data[i]
    b = data[i+1]

    return (1.0 - frac) * a + (frac * b)

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef double[:,:] _speed(double speed, double[:,:] original, double[:,:] out, int channels) nogil:
    cdef unsigned int inlength = len(original)
    cdef unsigned int outlength = len(out)
    cdef double phase_inc = (1.0/inlength) * (inlength-1) * speed
    cdef double phase = 0, pos = 0
    cdef int c = 0, i = 0

    for i in range(outlength):
        for c in range(channels):
            out[i,c] = interpolation._linear_point(original[:,c], phase)

        phase += phase_inc * speed
        if phase >= inlength:
            break

    return out

cdef class Cloud:
    def __cinit__(self, 
            SoundBuffer snd, 
            object window=None, 
            object position=None,
            object amp=1.0,
            object speed=1.0, 
            object spread=0.0, 
            object jitter=0.0, 
            object grainlength=0.2, 
            object grid=None,
            object mask=None,
            unsigned int wtsize=4096,
        ):

        self.snd = snd.frames
        self.framelength = <unsigned int>len(snd)
        self.length = <double>(self.framelength / <double>snd.samplerate)
        self.channels = <unsigned int>snd.channels
        self.samplerate = <unsigned int>snd.samplerate

        self.wtsize = wtsize

        if window is None:
            window = 'sine'
        self.window = to_window(window)

        if position is None:
            self.position = Wavetable('phasor', 0, 1, window=True).data
        else:
            self.position = to_window(position)

        self.amp = to_window(amp)
        self.speed = to_window(speed)
        self.spread = to_window(spread)
        self.jitter = to_wavetable(jitter)
        self.grainlength = to_window(grainlength)

        if grid is None:
            self.grid = np.multiply(self.grainlength, 0.3)
        else:
            self.grid = to_window(grid)

        if mask is None:
            self.has_mask = False
        else:
            self.has_mask = True
            self.mask = array.array('i', mask)

    def play(self, double length):
        cdef double[:] grid = np.divide(self.grid, length)
        cdef unsigned int outframelength = <unsigned int>(self.samplerate * length)
        cdef double[:,:] out = np.zeros((outframelength, self.channels), dtype='d')
        cdef unsigned int write_pos=0
        cdef unsigned int read_pos=0
        cdef unsigned int grainlength
        cdef double pos = 0
        cdef double grainpos = 0
        cdef double panpos = 0
        cdef double sample = 0
        cdef double spread = 0
        cdef double speed, amp
        cdef unsigned int write_boundry = outframelength-1
        cdef unsigned int read_boundry = len(self.snd)-1
        cdef unsigned int masklength = 0
        cdef unsigned int count = 0

        cdef int i, c

        cdef double minspeed = min(self.speed)
        cdef unsigned long maxgrainlength = <unsigned long>(max(self.grainlength) * self.samplerate)
        cdef double[:,:] grain = np.zeros((maxgrainlength, self.channels), dtype='d')
        cdef double[:,:] grainresamp
        cdef unsigned int resamplength

        if self.has_mask:
            masklength = <unsigned int>len(self.mask)

        cdef int grain_window_index
        cdef double grain_window_pos
        cdef double grain_window_frac

        while pos <= 1:
            write_pos = <unsigned int>(pos * write_boundry)
            write_jitter = <int>(_linear_pos(self.jitter, pos) * (rand()/<double>RAND_MAX) * self.samplerate)
            write_pos += max(-write_jitter, write_jitter)

            if self.has_mask and self.mask[<int>(count % masklength)] == 0:
                pos += _linear_pos(grid, pos)
                count += 1
                continue

            grainlength = <unsigned int>(_linear_pos(self.grainlength, pos) * self.samplerate)
            if write_pos + grainlength > write_boundry:
                break

            speed = _linear_pos(self.speed, pos)
            read_pos = <unsigned int>(_linear_pos(self.position, pos) * (read_boundry-grainlength))
            spread = _linear_pos(self.spread, pos)
            panpos = (rand()/<double>RAND_MAX) * spread + (0.5 - (spread * 0.5))
            pans = [panpos, 1-panpos]

            for i in range(grainlength):
                grainpos = <double>i / (grainlength-1)
                amp = <double>_linear_pos(self.amp, pos)

                """
                cdef int dlength = <int>len(data)
                phase *= <double>(dlength-1)

                if dlength == 1:
                    return data[0]

                elif dlength < 1:
                    return 0

                cdef double frac = phase - <long>phase
                cdef long i = <long>phase
                cdef double a, b

                if i >= dlength-1:
                    return 0

                a = data[i]
                b = data[i+1]

                return (1.0 - frac) * a + (frac * b)
                """

                grain_window_pos = grainpos * (len(self.window)-2)
                grain_window_index = <int>grain_window_pos
                grain_window_frac = grain_window_pos - grain_window_index

                amp *= (1.0 - grain_window_frac) * self.window[grain_window_index] + (grain_window_frac * self.window[grain_window_index+1])
        
                #amp *= _linear_pos(self.window, grainpos) 

                if read_pos + i > read_boundry:
                    break

                for c in range(self.channels):
                    panpos = pans[c % 2]
                    grain[i,c] = self.snd[read_pos+i, c] * amp

            if speed != 1:
                resamplength = <unsigned int>(grainlength * (1.0/speed))
                grainresamp = np.zeros((resamplength, self.channels), dtype='d')

                grainresamp = _speed(speed, grain, grainresamp, <int>self.channels)
                grainlength = <unsigned int>(grainlength * (1.0/speed))

                for i in range(resamplength):
                    if write_pos+i > write_boundry:
                        break

                    for c in range(self.channels):
                        out[write_pos+i, c] += grainresamp[i,c]

            else:
                for i in range(grainlength):
                    for c in range(self.channels):
                        out[write_pos+i, c] += grain[i,c]

            pos += _linear_pos(grid, pos)
            count += 1

        return SoundBuffer(out, channels=self.channels, samplerate=self.samplerate)



M pippi/soundbuffer.pyx => pippi/soundbuffer.pyx +0 -2
@@ 20,7 20,6 @@ from pippi cimport fft
from pippi import graph
from pippi.defaults cimport DEFAULT_SAMPLERATE, DEFAULT_CHANNELS, DEFAULT_SOUNDFILE, PI
from pippi cimport grains2
from pippi cimport grains
from pippi cimport soundpipe

np.import_array()


@@ 716,7 715,6 @@ cdef class SoundBuffer:
        """ Create a new Cloud from this SoundBuffer
        """
        return grains2.Cloud2(self, *args, **kwargs).play(length)
        #return grains.Cloud(self, *args, **kwargs).play(length)

    def copy(self):
        """ Return a new copy of this SoundBuffer.

M setup.py => setup.py +0 -7
@@ 68,12 68,6 @@ ext_modules = cythonize([
            include_dirs=INCLUDES + ['libpippi/vendor/fft'],
            define_macros=MACROS
        ),
        Extension('pippi.grains', ['pippi/grains.pyx'],
            libraries=['soundpipe'], 
            library_dirs=['/usr/local/lib'],
            include_dirs=INCLUDES,
            define_macros=MACROS
        ),
        Extension('pippi.grains2', [
                'libpippi/src/pippicore.c',
                'libpippi/src/oscs.tape.c',


@@ 83,7 77,6 @@ ext_modules = cythonize([
            include_dirs=INCLUDES,
            define_macros=MACROS
        ),

        Extension('pippi.lists', ['pippi/lists.pyx'],
            include_dirs=INCLUDES, 
            define_macros=MACROS

M tests/test_graincloud.py => tests/test_graincloud.py +3 -29
@@ 5,7 5,7 @@ import tempfile
from unittest import TestCase

from pippi.soundbuffer import SoundBuffer
from pippi import dsp, grains, grains2, fx, shapes
from pippi import dsp, grains2, fx, shapes

class TestCloud(TestCase):
    def setUp(self):


@@ 36,40 36,24 @@ class TestCloud(TestCase):
        out.write('tests/renders/graincloud_libpippi_grainlength_modulated.wav')

    def test_user_window(self):
        # Alix Dobkin
        snd = dsp.read('tests/sounds/living.wav')

        length = 12
        length = 2

        # right half of a sharply tapered hann window, basically
        win = dsp.win('pluckout').taper(dsp.randint(10, 100))

        # empty buffer 2x the length of the sample
        out = dsp.buffer(length=length, channels=snd.channels, samplerate=snd.samplerate)

        # four layers of grains
        for _ in range(4):
            # grain playback speed
            speed = shapes.win('sine', dsp.rand(0.125, 0.5), dsp.rand(1, 2))

            amp = shapes.win('sine', 0.3, 1) 

            # stereo spread
            spread = shapes.win('sine', 0, 1) 

            # start increment in seconds
            grid = shapes.win('sine', -1, 1, length=dsp.rand(1, 10))

            # length of the grain in seconds
            grainlength = shapes.win('sine', dsp.MS*1, 0.4, length=dsp.rand(1, 10))

            gridjitter = shapes.win('sine', 0, 1)
            grainjitter = shapes.win('sine', 0, 1)

            # pulsewidth of the grain window between 0 & 1
            pulsewidth = shapes.win('sine', 0.5, 2)

            # render the layer
            layer = snd.cloud(length, 
                amp=amp,
                window=win, 


@@ 84,22 68,15 @@ class TestCloud(TestCase):
                gridmaxjitter=dsp.rand(0.01,1),
                gridjitter=gridjitter,
            )

            # and dub it into the output buffer
            out.dub(layer)

        # squish
        out = fx.compressor(out*8, -15, 15)
        out = fx.norm(out, 1)

        # done
        out.write('tests/renders/graincloud_libpippi_user_window.wav')

    def test_phase_modulation(self):
        snd = dsp.read('tests/sounds/living.wav')
        phase = [1,0.5,1,0,0.5]
        phase = 3
        out = snd.cloud(snd.dur*2, grainlength=0.1, phase=phase)
        out = snd.cloud(snd.dur*2, grainlength=0.1, phase=0.3)
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_phase_modulated.wav')



@@ 116,8 93,6 @@ class TestCloud(TestCase):
        out = fx.norm(out, 1)
        out.write('tests/renders/graincloud_libpippi_speed_modulated.wav')


    """
    def test_libpippi_pulsed_graincloud(self):
        sound = SoundBuffer(filename='tests/sounds/living.wav')
        out = sound.cloud(10, grainlength=0.06, grid=0.12)


@@ 185,4 160,3 @@ class TestCloud(TestCase):
            )

        out.write('tests/renders/graincloud_libpippi_grainsize.wav')
    """

M tests/test_shapes.py => tests/test_shapes.py +1 -1
@@ 47,7 47,7 @@ class TestShapes(TestCase):
                grid=grid, 
                speed=dsp.win(shapes.win('sine', length=1), 0.03, 2),
                spread='rnd', 
                jitter='rnd'
                grainjitter='rnd'
            )

        out.write('tests/renders/shape_pulsar.wav')