Implementing libPD (Pure Data wrapper) in Python
Asked Answered
C

4

7

I've created a simple text-based escape the room game in Python, with the intention of embedding a Pure Data patch (via libPd) in order to playback a different soundfile (this will later be replaced with an algorithm for generative music) for each of my different rooms.

The python code I'm currently working with was taken from one of the examples in the libPD github. It is as follows -

import pyaudio
import wave
import sys
from pylibpd import *

p = pyaudio.PyAudio()

ch = 2
sr = 48000
tpb = 16
bs = 64

stream = p.open(format = pyaudio.paInt16,
                channels = ch,
                rate = sr,
                input = True,
                output = True,
                frames_per_buffer = bs * tpb)

m = PdManager(ch, ch, sr, 1)
libpd_open_patch('wavfile.pd')

while 1:
    data = stream.read(bs)
    outp = m.process(data)
    stream.write(outp)

stream.close()
p.terminate()
libpd_release()

The pure data patch simply plays back a pre-rendered wav file, however the resulting output sounds almost as if it has been bitcrushed. I'm guessing the problem is to do with the block size but am not sure.

If anyone has experience in embedding lidPD within Python I'd be greatly appreciated as I'm sure what I'm trying to achieve is embarrassingly simple.

Thanks in advance, Cap

Cnut answered 3/7, 2013 at 16:56 Comment(0)
C
3

I ended up using a workaround and imported pygame (as opposed to pyaudio) to handle the audio and initialise the patch. It works without a hitch.

Thanks for your help.

*For anyone that encounters a similar problem, check out "pygame_test.py" in the libPd github for python.

Cnut answered 12/7, 2013 at 15:7 Comment(0)
M
2

I had similar problems. Using a callback fixed it for me.

Here is the python to play a sine wave.


    import pyaudio
    from pylibpd import *
    import time

    def callback(in_data,frame_count,time_info,status):
        outp = m.process(data)
        return (outp,pyaudio.paContinue)

    p  = pyaudio.PyAudio()
    bs = libpd_blocksize()

    stream = p.open(format = pyaudio.paInt16,
                    channels = 1,
                    rate = 44100,
                    input = False,
                    output = True,
                    frames_per_buffer = bs,
                    stream_callback=callback)

    m = PdManager(1, 1 , 44100, 1)

    libpd_open_patch('sine.pd')

    data=array.array('B',[0]*bs)

    while stream.is_active():
        time.sleep(.1)

    stream.close()
    p.terminate()
    libpd_release()

and the patch "sine.pd"


    #N canvas 647 301 450 300 10;
    #X obj 67 211 dac~;
    #X obj 24 126 osc~ 1000;
    #X obj 16 181 *~ 0.2;
    #X connect 1 0 2 0;
    #X connect 2 0 0 0;

Murine answered 12/4, 2015 at 12:11 Comment(1)
nice. This should be the default example delivered with pylibpdBrittenybrittingham
L
0

There are a few parts to this.

  1. The block size of the audio file is wrong because you set tpb = 16 instead of 1. By setting it to 16 you are making the block size 16 * 64 instead of 64.

  2. There could be an issue with sample rates. Are you sure that your sound file is 48000hz and not 44100hz?

Lyly answered 12/7, 2013 at 4:53 Comment(3)
Thanks for the reply Adam. I was under the impression that I'd set the block size to 64 and by setting the ticks per buffer to 16 it would give me 1024 frames per buffer. Originally I had a tpb setting of 1 but the "bit crushing" was still present.Cnut
I've also tried both 44.1k and 48k files whilst altering the samplerate within the code to no avail. I think it has something to do with paInt16 as changing it to paInt32 or paInt8 varies the crushed effect to some extent but as I said, I simply pulled this from example code and not sure what it should be doing exactly.Cnut
I also tried a different route in the form of a puredata patch that simply omitted a sine tone when called in python. The same issue occurs.Cnut
A
0

I've refactored the sample above a bit:

import pyaudio
from pylibpd import *

class PdAudio:
    def __init__(self):
        self.sample_rate = 44100
        self.num_channel = 2
        self.pd = self.__InitPd(self.num_channel, self.sample_rate)
        self.py_audio = pyaudio.PyAudio()
        self.block_size = libpd_blocksize()
        self.stream = self.__InitAudio(self.num_channel, self.sample_rate,self.block_size)
        self.inbuf = array.array('h', range(self.block_size))
        print("Blocksize: %d" % self.block_size)

    def StartPatchInBackground(self, filename):
        self.patch = libpd_open_patch(filename, '.')

    def IsPlaying(self):
        return self.stream.is_active()

    def __InitAudio(self, num_channels, sample_rate, block_size):
        return self.py_audio.open(format = pyaudio.paInt16,
                                  channels = num_channels,
                                  rate = sample_rate,
                                  input = False,
                                  output = True,
                                  frames_per_buffer = block_size,
                                  stream_callback=self.__AudioCallback)

    def __InitPd(self, num_channels, sample_rate):
        return PdManager(1, num_channels, sample_rate, 1)

    def __AudioCallback(self, in_data,frame_count,time_info,status):
        outp = self.pd.process(self.inbuf)
        return (outp.tobytes(),pyaudio.paContinue)

    def __del__(self):
        self.stream.close()
        self.pd.terminate()
        libpd_release()

pd_audio = PdAudio()
pd_audio.StartPatchInBackground('bloopy.pd')

https://github.com/jkammerl/pylibpd_pyaudio/blob/main/pd_callback_example.py

Amnesty answered 12/8, 2021 at 21:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.