Skip to content

Commit

Permalink
Merge pull request #3 from pyBinSim/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
thomas-imt committed Dec 11, 2017
2 parents 3fdd9e2 + 454f637 commit 3858bcf
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 137 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ install:
# Useful for debugging any issues with conda
- conda info -a

- conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy scipy
- conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION numpy
- source activate test-environment
- pip install .

Expand Down
23 changes: 18 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Install

::

$ conda create --name binsim35 python=3.5 numpy scipy
$ conda create --name binsim35 python=3.5 numpy
$ source activate binsim35
$ pip install pybinsim
Expand All @@ -35,13 +35,17 @@ Create ``pyBinSimSettings.txt`` file with content like this
enableCrossfading False
useHeadphoneFilter False
loudnessFactor 1
loopSound False


Start Binaural Simulation

::

import pybinsim
import logging

pybinsim.logger.setLevel(logging.DEBUG) # defaults to INFO

with pybinsim.BinSim('pyBinSimSettings.txt') as binsim:
binsim.stream_start()
Expand All @@ -59,21 +63,24 @@ Config parameter description:
-----------------------------

soundfile:
Defines \*.wav file which is played back at startup. Sound file can contain up to maxChannels audio channels.
Defines \*.wav file which is played back at startup. Sound file can contain up to maxChannels audio channels. Also accepts multiple files separated by '#'; Example: 'soundfile signals/sound1.wav#signals/sound2.wav
blockSize:
Number of samples which are processed per block. Low values reduce delay but increase cpu load.
filterSize:
Defines filter size of the filters loaded with the filter list. Filter size should be a mutltiple of blockSize.
maxChannels:
Maximum number of sound sources/audio channels which can be controlled during runtime.
Maximum number of sound sources/audio channels which can be controlled during runtime. The value for maxChannels must match or exceed the number of channels of soundFile(s).
samplingRate:
Sample rate for filters and soundfiles. Caution: No automatic sample rate conversion.
enableCrossfading:
Enable cross fade between audio blocks. Set 'False' or 'True'.
useHeadphoneFilter:
Enables headhpone equalization. The filterset should contain a filter with the identifier HPFILTER. Set 'False' or 'True'.
loudnessFactor:
Factor for overall output loudness.
Factor for overall output loudness. Attention: Clipping may occur
loopSound:
Enables looping of sound file or sound file list. Set 'False' or 'True'.


OSC Messages and filter lists:
------------------------------
Expand All @@ -91,7 +98,13 @@ When you want to play another sound file you send:

::

/pyBinSimFile file_new.wav
/pyBinSimFile folder/file_new.wav

Or a sound file list

::

/pyBinSimFile folder/file_1.wav#folder/file_2.wav

The audiofile has to be located on the pc where pyBinSim runs. Files are not transmitted over network.

Expand Down
25 changes: 22 additions & 3 deletions pybinsim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
def __version__():
return "1.0.1"
import logging

from pybinsim.application import BinSim

__version__ = "1.1.0"

from pybinsim.application import BinSim


def init_logging(loglevel):
console_handler = logging.StreamHandler()
console_handler.setLevel(loglevel)

formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)

logger = logging.getLogger("pybinsim")
logger.addHandler(console_handler)
logger.setLevel(loglevel)

return logger


logger = init_logging(logging.INFO)
38 changes: 24 additions & 14 deletions pybinsim/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,24 @@

""" Module contains main loop and configuration of pyBinSim """
import time
import logging

import numpy as np
import pyaudio

from pybinsim.convolver import ConvolverFFTW
from pybinsim.filterstorage import FilterStorage
from pybinsim.osc_receiver import OscReceiver
from pybinsim.pose import Pose
from pybinsim.soundhandler import SoundHandler



class BinSimConfig(object):
def __init__(self):

self.log = logging.getLogger("pybinsim.BinSimConfig")

# Default Configuration
self.configurationDict = {'soundfile': '',
'blockSize': 256,
Expand All @@ -44,7 +49,8 @@ def __init__(self):
'useHeadphoneFilter': 'False',
'loudnessFactor': float(1),
'maxChannels': 8,
'samplingRate': 44100}
'samplingRate': 44100,
'loopSound': 'True'}

def read_from_file(self, filepath):
config = open(filepath, 'r')
Expand All @@ -55,7 +61,7 @@ def read_from_file(self, filepath):
if key in self.configurationDict:
self.configurationDict[key] = type(self.configurationDict[key])(line_content[1])
else:
print('Entry ' + key + ' is unknown')
self.log.warning('Entry ' + key + ' is unknown')

def get(self, setting):
return self.configurationDict[setting]
Expand All @@ -67,7 +73,9 @@ class BinSim(object):
"""

def __init__(self, config_file):
print("BinSim: init")

self.log = logging.getLogger("pybinsim.BinSim")
self.log.info("BinSim: init")

# Read Configuration File
self.config = BinSimConfig()
Expand All @@ -93,7 +101,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.__cleanup()

def stream_start(self):
print("BinSim: stream_start")
self.log.info("BinSim: stream_start")
self.stream = self.p.open(format=pyaudio.paFloat32, channels=2,
rate=self.sampleRate, output=True,
frames_per_buffer=self.blockSize,
Expand All @@ -105,11 +113,11 @@ def stream_start(self):

def initialize_pybinsim(self):


self.result = np.empty([self.config.get('blockSize'), 2], np.dtype(np.float32))
self.block = np.empty([self.config.get('maxChannels'), self.config.get('blockSize')], np.dtype(np.float32))

# Create FilterStorage
print(type(self.config.get('blockSize')))
filterStorage = FilterStorage(self.config.get('filterSize'), self.config.get('blockSize'),
self.config.get('filterList'))

Expand All @@ -120,11 +128,13 @@ def initialize_pybinsim(self):

# Create SoundHandler
soundHandler = SoundHandler(self.config.get('blockSize'), self.config.get('maxChannels'),
self.config.get('samplingRate'))
soundHandler.request_new_sound_file([self.config.get('soundfile')])
self.config.get('samplingRate'), self.config.get('loopSound'))

soundfile_list = self.config.get('soundfile')
soundHandler.request_new_sound_file(soundfile_list)

# Create N convolvers depending on the number of wav channels
print('Number of Channels: ' + str(self.config.get('maxChannels')))
self.log.info('Number of Channels: ' + str(self.config.get('maxChannels')))
convolvers = [None] * self.config.get('maxChannels')
for n in range(self.config.get('maxChannels')):
convolvers[n] = ConvolverFFTW(self.config.get('filterSize'), self.config.get('blockSize'), False)
Expand All @@ -133,18 +143,18 @@ def initialize_pybinsim(self):
convolverHP = None
if self.config.get('useHeadphoneFilter') == 'True':
convolverHP = ConvolverFFTW(self.config.get('filterSize'), self.config.get('blockSize'), True)
left, right = filterStorage.get_filter(['HPFILTER'])
convolverHP.setIR(left, right, False)
hpfilter= filterStorage.get_headphone_filter()
convolverHP.setIR(hpfilter, False)

return convolverHP, convolvers, filterStorage, oscReceiver, soundHandler

def close(self):
print("BinSim: close")
self.log.info("BinSim: close")
self.stream_close()
self.p.terminate()

def stream_close(self):
print("BinSim: stream_close")
self.log.info("BinSim: stream_close")
self.stream.stop_stream()
self.stream.close()

Expand Down Expand Up @@ -185,8 +195,8 @@ def callback(in_data, frame_count, time_info, status):
if binsim.oscReceiver.is_filter_update_necessary(n):
# print('Updating Filter')
filterValueList = binsim.oscReceiver.get_current_values(n)
leftFilter, rightFilter = binsim.filterStorage.get_filter(filterValueList)
binsim.convolvers[n].setIR(leftFilter, rightFilter, callback.config.get('enableCrossfading'))
filter = binsim.filterStorage.get_filter(Pose.from_filterValueList(filterValueList))
binsim.convolvers[n].setIR(filter, callback.config.get('enableCrossfading'))

left, right = binsim.convolvers[n].process(binsim.block[n, :])

Expand Down
60 changes: 50 additions & 10 deletions pybinsim/convolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import multiprocessing

import logging
import numpy as np
import pyfftw
from past.builtins import xrange
Expand All @@ -36,7 +37,10 @@ class ConvolverFFTW(object):
"""

def __init__(self, ir_size, block_size, process_stereo):
print("Convolver: init")

self.log = logging.getLogger("pybinsim.ConvolverFFTW")
self.log.info("Convolver: init")

# Get Basic infos
self.IR_size = ir_size
self.block_size = block_size
Expand All @@ -52,6 +56,17 @@ def __init__(self, ir_size, block_size, process_stereo):
self.crossFadeIn *= 1 / float((self.block_size - 1))
self.crossFadeOut[:] = np.flipud(self.crossFadeIn)

# Create default filter and fftw plan
# Filter format: [nBlocks,blockSize*4]
# 0 to blockSize*2: left filter
# blockSize*2 to blockSize*4: right filter
self.default_filter = pyfftw.zeros_aligned([self.IR_blocks, 2 * (self.block_size + 1)], np.dtype(np.float32))

self.filter_fftw_plan = pyfftw.builders.rfft(np.zeros(self.block_size * 2), overwrite_input=True,
planner_effort='FFTW_MEASURE',
threads=nThreads)


# Create Input Buffers and create fftw plans
self.buffer = pyfftw.zeros_aligned(self.block_size * 2, dtype='float32')
self.bufferFftPlan = pyfftw.builders.rfft(self.buffer, overwrite_input=True,
Expand All @@ -71,7 +86,7 @@ def __init__(self, ir_size, block_size, process_stereo):
self.FDL_left = pyfftw.zeros_aligned(self.IR_blocks * (self.block_size + 1), dtype='complex64')
self.FDL_right = pyfftw.zeros_aligned(self.IR_blocks * (self.block_size + 1), dtype='complex64')

# Arrays for the result of the complex multipla and add
# Arrays for the result of the complex multiply and add
# These should be memory aligned because ifft is performed with these data
self.resultLeftFreq = pyfftw.zeros_aligned(self.block_size + 1, dtype='complex64')
self.resultRightFreq = pyfftw.zeros_aligned(self.block_size + 1, dtype='complex64')
Expand Down Expand Up @@ -110,25 +125,50 @@ def get_counter(self):
"""
return self.processCounter

def setIR(self, left_filter, right_filter, do_interpolation):
def transform_filter(self, filter):
"""
Transform filter to freq domain
:param filter:
:return: transformed filter
"""
IR_left = filter[:, 0]
IR_right = filter[:, 1]

# Split IRs in blocks
IR_left_blocked = np.reshape(IR_left, (self.IR_blocks, self.block_size))
IR_right_blocked = np.reshape(IR_right, (self.IR_blocks, self.block_size))

# Add zeroes to each block
IR_left_blocked = np.concatenate((IR_left_blocked, np.zeros([self.IR_blocks, self.block_size])), axis=1)
IR_right_blocked = np.concatenate((IR_right_blocked, np.zeros([self.IR_blocks, self.block_size])), axis=1)

TF_left_blocked = np.zeros([self.IR_blocks, self.block_size + 1], np.dtype(np.complex64))
TF_right_blocked = np.zeros([self.IR_blocks, self.block_size + 1], np.dtype(np.complex64))

for ir_block_count in range(0, self.IR_blocks):
TF_left_blocked[ir_block_count] = self.filter_fftw_plan(IR_left_blocked[ir_block_count])
TF_right_blocked[ir_block_count] = self.filter_fftw_plan(IR_right_blocked[ir_block_count])

return TF_left_blocked, TF_right_blocked

def setIR(self, filter, do_interpolation):
"""
Hand over a new set of filters to the convolver
and define if you want to perform an interpolation/crossfade
:param left_filter:
:param right_filter:
:param filter:
:param do_interpolation:
:return: None
"""
# Save old filters in case imnterpolation is needed
# Save old filters in case interpolation is needed
self.TF_left_blocked_previous = self.TF_left_blocked
self.TF_right_blocked_previous = self.TF_right_blocked

# apply new filters
self.TF_left_blocked = left_filter
self.TF_right_blocked = right_filter
self.TF_left_blocked, self.TF_right_blocked = self.transform_filter(filter)

# Interpolation means cross fading the output blocks (linera interpolation)
# Interpolation means cross fading the output blocks (linear interpolation)
self.interpolate = do_interpolation

def process_nothing(self):
Expand Down Expand Up @@ -178,7 +218,7 @@ def fill_buffer_stereo(self, block):

if block.size < self.block_size:
# print('Fill up last block')
print(np.shape(block))
# print(np.shape(block))
block = np.concatenate((block, np.zeros(((self.block_size - block.size), 2))), 0)

if self.processCounter == 0:
Expand Down
Loading

0 comments on commit 3858bcf

Please sign in to comment.