import threading
import numpy
import io
+import pedalboard
from pedalboard.io import AudioFile
log = logging.getLogger(__name__)
retval = AudioChunk(b'')
return retval
+ # get the number of samples in the deck back up to DECK_SIZE
def fill_deck(self):
# block if you got nothin
if self.deck_size == 0:
self.on_deck.append(self.get_next_sample())
self.sample_lock.release()
+ # once the deck empties, will fill with zeroes and thus silence
def terminate_all(self):
self.sample_lock.acquire()
self.samples = []
#fifo_path = '/tmp/fifo{}'.format(rand_str)
locked = False
try:
- #os.mkfifo(fifo_path)
- #args = ['ffmpeg', '-i', '{}'.format(filename)]
- #args += ['-f', 's16le', '-acodec', 'pcm_s16le', '-ac', '2', '-ar', '48000', '-']
- #p = await asyncio.create_subprocess_exec(*args, stdin=None, stderr=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)
- #return_fifo = open(fifo_path, 'rb')
- #return_bytes = await asyncio.to_thread(return_fifo.read)
- #return_bytes = return_fifo.read()
- #stdout, stderr = await p.communicate()
- #log.debug(stdout)
- #log.debug(stderr)
- #return_bytes = stdout
- #await p.wait()
- #return_bytes = p.communicate()[0]
- #byte_length = len(return_bytes)
- #sample_index = 0 #len(self.samples)
min_deck_size = self.DECK_SIZE
- byte_start = 0
#return_byte_length = len(return_bytes)
chunks_to_append = []
#byte_reader = io.BytesIO(return_bytes)
# byte processing and chunk generation
- #while byte_length > 0:
- #log.debug(byte_length)
- # keeps us from tailing off the end of the bytestream
-# if MAX_LENGTH > byte_length:
-# sample_bytes = byte_length
-# else:
-# sample_bytes = MAX_LENGTH
-# log.debug('byte start {} sample bytes {}'.format(byte_start, sample_bytes))
- # read in the next chunk, store for now
#next_bytes = byte_reader.read(MAX_LENGTH)
+ buffer_samples = None
with AudioFile(filename).resampled_to(target_sample_rate=SAMPLE_RATE) as f:
- #audio = f.read(f.frames)
- next_samples = f.read(MAX_SAMPLES)
+ # we're reading in the whole file at once, maybe don't want to later for better responsiveness?
+ buffer_samples = f.read(f.frames)
+ #log.debug('samples length: {}'.format(len(buffer_samples)))
+ # process here with pedalboard
+ shift_tones = random.randint(-10, 10)
+ board = pedalboard.Pedalboard([
+ pedalboard.PitchShift(semitones=shift_tones),
+ pedalboard.Gain(gain_db=-3.0)
+ ])
+ buffer_samples = board(buffer_samples, SAMPLE_RATE)
+ index = 0
+
+ next_samples = buffer_samples[0][index:index+MAX_SAMPLES]
+ while len(next_samples) > 0:
channels = len(next_samples.shape)
- #log.debug(next_samples)
- while numpy.size(next_samples) > 0:
- # switch to mono if needed (channel shouldn't change from initial)
+ while len(next_samples) > 0:
+ # this is to format the samples in the style opus wants
#log.debug('channels are {}'.format(channels))
if channels == 2:
- #log.debug('before deleting {}'.format(next_samples.shape))
- #log.debug(next_samples[0][:10])
- #next_samples = numpy.squeeze(next_samples.reshape(1, -1))
- # total length * 2
- rechannel = numpy.empty(next_samples[0].shape[0]*2, dtype='float32')
- # first channel
- rechannel[::2] = next_samples[0]
- # second channel
- rechannel[1::2] = next_samples[1]
- next_samples = rechannel
- #log.debug('after deleting {}'.format(next_samples.shape))
- #log.debug(next_samples[:10])
+ # interleave the samples L/R to form a bytestream eventually
+ next_samples = numpy.squeeze(numpy.dstack((next_samples[0], next_samples[1])).reshape(1, -1))
+ # right now, this is the only path taken (because we discard the other channel)
+ else:
+ next_samples = numpy.repeat(next_samples, 2)
# for the downcast to int16
next_samples *= 32768.0
next_bytes = next_samples.astype('int16')
chunk = AudioChunk(init_samples=next_bytes)
#log.debug(chunk, chunk.samples)
chunks_to_append.append(chunk)
- next_samples = f.read(MAX_SAMPLES)
+ # get next chunk
+ index += MAX_SAMPLES
+ next_samples = buffer_samples[0][index:index+MAX_SAMPLES]
- # chomp
- #byte_start += sample_bytes
- #byte_length = return_byte_length - byte_start
# chunk append/insertion (along with mixing)
# this is the part that actually needs locked access