Skip to content

Commit

Permalink
Merge pull request #4 from tu-studio/ambi_encoder
Browse files Browse the repository at this point in the history
Ambi encoder
  • Loading branch information
themaxw authored May 8, 2024
2 parents 784998b + 080855c commit 9f378e0
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Building
``` bash
cmake -DCMAKE_BUILD_TYPE=Debug -B ./build
cmake -DCMAKE_BUILD_TYPE=Debug -B ./build -G Ninja
cmake --build ./build --config Debug --target all
```

Expand Down
85 changes: 85 additions & 0 deletions examples/configs/simple_ambi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
port: 8080
n_input_channels: 64
# osc_position_path: /source/pos/ # expect positions in aed format at position_path/aed or at position_path/azim, position_path/elev, position_path/dist

tracks:
- name: hoa
modules:
- gain:
factor: 1
osc_controllable: true
osc_paths: /source/send/hoa
- hoa_encoder:
order: 3
osc_controllable: true
osc_paths: /source/pos/aed
- gain:
factor: 1
# - limiter:
# type: brickwall
# threshold: 0
# - name: wfs # listens to OSC path /source/send/wfs if
# modules:
# - gain:
# factor: 1
# osc_controllable: true
# osc_paths: wfs # implicitely add /gain or other module specific values to that path
# - filter:
# type: HP # HP/LP
# freq: 100 # in Hz
# - gain:
# factor: 1
# - limiter:
# type: brickwall
# threshold: 0
# - delay:
# time: 0 # in ms

# - name: hoa
# modules:
# - gain:
# factor: 1
# osc_controllable: true
# osc_paths: hoa
# - distance_compensator:
# - hoa_encoder:
# order: 3
# - filter:
# type: HP # HP/LP
# freq: 100 # in Hz
# - gain: 5
# - limiter:
# type: brickwall
# threshold: 0
# - delay: 20 # in ms

# - name: lfe
# modules:
# - gain: 1
# osc_controllable: true
# osc_paths: lfe
# - sum:
# - filter:
# type: LP
# freq: 120
# - gain: 1
# - limiter:
# type: brickwall
# threshold: -3
# - delay: 30 # in ms

# - name: lf_crossover
# modules:
# - gain: 1
# osc_controllable: true
# osc_paths: [wfs, hoa]
# - distance_compensator
# - sum
# - filter:
# type: LP
# freq: 120
# - gain: 1
# - limiter:
# type: brickwall
# threshold: -3
# - delay: 30 # in ms
24 changes: 24 additions & 0 deletions source/ConfigParser/ConfigParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ std::shared_ptr<ModuleConfig> ConfigParser::parse_module(YAML::Node module){
return parse_module_gain(module);
} else if (name == "filter") {
return parse_module_filter(module);
} else if (name == "hoa_encoder") {
return parse_module_ambi_encoder(module);
} else {
std::cout << "[error] Unknown module: " << name << std::endl;
return nullptr;
Expand Down Expand Up @@ -165,6 +167,28 @@ ModuleConfigPtr ConfigParser::parse_module_filter(YAML::Node module){
return config;
}

ModuleConfigPtr ConfigParser::parse_module_ambi_encoder(YAML::Node module){
AmbiEncoderConfigPtr config = std::make_shared<AmbiEncoderConfig>();
parse_module_osc_params(module, config);
if(!module.IsMap()){
std::cout << "Ambisonics encoder missing parameters" << std::endl;
return nullptr;
}

YAML::Node moduleConfigNode = module.begin()->second;
if (!moduleConfigNode.IsMap()){
std::cout << "Ambisonics encoder missing parameters" << std::endl;
return nullptr;
}

if (moduleConfigNode["order"].IsDefined()){
config->order = moduleConfigNode["order"].as<int>();
} else {
std::cout << "Ambisonics encoder order unspecified" << std::endl;
}
return config;
}

ConfigParser::~ConfigParser(){
}

Expand Down
2 changes: 2 additions & 0 deletions source/ConfigParser/ConfigParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <ModuleConfig.h>
#include <GainConfig.h>
#include <FilterConfig.h>
#include <AmbiEncoderConfig.h>

class ConfigParser
{
Expand All @@ -26,6 +27,7 @@ class ConfigParser
ModuleConfigPtr parse_module(YAML::Node module);
ModuleConfigPtr parse_module_gain(YAML::Node module);
ModuleConfigPtr parse_module_filter(YAML::Node module);
ModuleConfigPtr parse_module_ambi_encoder(YAML::Node module);

YAML::Node config;
std::shared_ptr<AudioMatrixConfig> parsed_config;
Expand Down
14 changes: 11 additions & 3 deletions source/JackClient/JackClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,18 @@ JackClient::JackClient([[ maybe_unused ]] int argc, char *argv[]) {
// tell the JACK server to call `jack_shutdown()' if it ever shuts down, either entirely, or if it just decides to stop calling us.
jack_on_shutdown (m_client, shutdown, this);

// registers a function to be called when the maximum buffer size changes
jack_set_buffer_size_callback(m_client, buffer_size_callback, this);

// registers a function to be called when the sample rate changes
std::cout << "[debug] registering sample rate callback" << std::endl;
jack_set_sample_rate_callback(m_client, sample_rate_callback, this);

// TODO reenable changing of buffer size. when this is called before jack_set_sample_rate_callback the callback is executed multiple times, and when exiting the program jack crashes...
// // registers a function to be called when the maximum buffer size changes
// std::cout << "[debug] registering buffer size callback" << std::endl;
// jack_set_buffer_size_callback(m_client, buffer_size_callback, this);



}

JackClient::~JackClient() {
Expand Down Expand Up @@ -161,8 +167,10 @@ void JackClient::prepare() {

void JackClient::prepare(HostAudioConfig config) {
// TODO: shall the client be deactivated before preparing?

std::cout << "[debug] entering audio matrix prepare with HostAudioConfig buffer_size=" << config.m_host_buffer_size << ", sr=" << config.m_host_sample_rate << std::endl;
audio_matrix->prepare(config);
std::cout << "[debug] exiting audio matrix prepare" << std::endl;


size_t n_input_channels = audio_matrix->get_n_input_channels();
size_t n_output_channels = audio_matrix->get_n_output_channels();
Expand Down
108 changes: 108 additions & 0 deletions source/Module/AmbiEncoder/AmbiEncoder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include <AmbiEncoder.h>

AmbiEncoder::AmbiEncoder(AmbiEncoderConfigPtr config, std::shared_ptr<lo::ServerThread> osc_server): m_config(config){
if (m_config->osc_controllable && osc_server != nullptr){
std::cout << "[info] Registering function on path " << m_config->osc_path << std::endl;
osc_server->add_method(m_config->osc_path, "ifff", osc_pos_callback, this);
} else if(m_config->osc_controllable && osc_server == nullptr){
std::cout << "[error] osc_server is null, cannot add ambi encoder callback method" << std::endl;
}

m_sh.initalize(m_config->order);
}

size_t AmbiEncoder::initialize(size_t input_channels) {
m_n_input_channels = input_channels;
int power_order = m_config->order + 1;
m_n_output_channels = power_order * power_order;

// initialize position arrays and containers for spherical harmonics
// for (size_t i = 0; i < input_channels; i++) {
// m_position.push_back({0,0,1});
// m_sh_containers.push_back(std::vector<float>(m_n_output_channels));
// m_sh_containers_prev.push_back(std::vector<float>(m_n_output_channels));
// m_position_changed.push_back(true);
// }
m_position = std::vector<PositionAED>(m_n_input_channels, {0,0,1});
m_sh_containers = std::vector<std::vector<float>>(m_n_input_channels, std::vector<float>(m_n_output_channels, 0));
m_sh_containers_prev = std::vector<std::vector<float>>(m_n_input_channels, std::vector<float>(m_n_output_channels, 0));
m_position_changed = std::vector<bool>(m_n_input_channels, true);

return m_n_output_channels;
}

void AmbiEncoder::prepare(HostAudioConfig host_audio_config){
m_buffer.initialize(m_n_output_channels, host_audio_config.m_host_buffer_size);
}

void AmbiEncoder::process(AudioBufferF &buffer, size_t nframes){
m_buffer.clear();
// TODO implement smoothing, saving of previous SH buffer
for (size_t in_channel = 0; in_channel < m_n_input_channels; in_channel++) {
if (m_position_changed[in_channel]){
m_position_changed[in_channel] = false;
update_spherical_harmonics(in_channel);
// std::cout << "source " << in_channel <<" changed: " << m_sh_containers_prev[in_channel][0] << "->" << m_sh_containers[in_channel][0] << std::endl;

for (size_t out_channel = 0; out_channel < m_n_output_channels; out_channel++){
float sh_prev = m_sh_containers_prev[in_channel][out_channel];
float sh_new = m_sh_containers[in_channel][out_channel];
float stepsize = (sh_new-sh_prev) / nframes;
for (size_t j = 0; j < nframes; j++){
float sample = m_buffer.getSample(out_channel, j) + buffer.getSample(in_channel, j) * (sh_prev + j *stepsize);
m_buffer.setSample(out_channel, j, sample);
}

// update sh in prev container
m_sh_containers_prev[in_channel][out_channel] = sh_new;
}
} else {
for (size_t out_channel = 0; out_channel < m_n_output_channels; out_channel++){
for (size_t j = 0; j < nframes; j++){
float sample = m_buffer.getSample(out_channel, j) + buffer.getSample(in_channel, j) * m_sh_containers_prev[in_channel][out_channel];
m_buffer.setSample(out_channel, j, sample);
}
}
}
}

for (size_t out_channel = 0; out_channel < m_n_output_channels; out_channel++){
for (size_t j = 0; j < nframes; j++){
buffer.setSample(out_channel, j, m_buffer.getSample(out_channel, j));
}
}

}

void AmbiEncoder::set_aed(size_t channel, float azimuth, float elevation, float distance){
if (!(channel >= 0 && channel < m_n_input_channels)){
return;
}
std::cout << "updated position for source " << channel << std::endl;
m_position[channel].azim = azimuth;
m_position[channel].elev = elevation;
m_position[channel].dist = distance;
m_position_changed[channel] = true;
// TODO set m_is_changed = True
}



void AmbiEncoder::update_spherical_harmonics(size_t channel){
m_sh.update_spherical_harmonics(m_position[channel].azim, m_position[channel].elev, m_position[channel].dist, m_sh_containers[channel]);

// TODO
}


int AmbiEncoder::osc_pos_callback([[ maybe_unused ]] const char *path,[[ maybe_unused ]] const char *types, lo_arg **argv, int argc, [[ maybe_unused ]] lo_message data, void *user_data){
// std::ignore = path;
// std::ignore = types;
// std::ignore = data;
if(argc==4){
AmbiEncoder* ambi_encoder = (AmbiEncoder*) user_data;
ambi_encoder->set_aed(argv[0]->i, argv[1]->f, argv[2]->f, argv[3]->f);
return 0;
}
return -1;
}
51 changes: 51 additions & 0 deletions source/Module/AmbiEncoder/AmbiEncoder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef AMBIENCODER_H
#define AMBIENCODER_H

#include <vector>
#include <Module.h>
#include <AmbiEncoderConfig.h>
#include <HostAudioConfig.h>
#include <SphericalHarmonics.h>
#include <atomic>

struct PositionAED {
float azim;
float elev;
float dist;
};

class AmbiEncoder : public Module{
public:
AmbiEncoder() = delete;
AmbiEncoder(std::shared_ptr<AmbiEncoderConfig> config, std::shared_ptr<lo::ServerThread> osc_server);
~AmbiEncoder() = default;

size_t initialize(size_t input_channels) override;
void prepare(HostAudioConfig host_audio_config);
void process(AudioBufferF &buffer, size_t nframes) override;

// void set_azimuth(size_t channel, float azimuth);
// void set_elevation(size_t channel, float elevation);
// void set_distance(size_t channel, float distance);
void set_aed(size_t channel, float azimuth, float elevation, float distance);


private:

void update_spherical_harmonics(size_t channel);
static int osc_pos_callback(const char *path, const char *types, lo_arg **argv, int argc, lo_message data, void *user_data);

AmbiEncoderConfigPtr m_config;
std::vector<PositionAED> m_position;

AudioBufferF m_buffer;

SphericalHarmonics m_sh;
std::vector<std::vector<float>> m_sh_containers;
std::vector<std::vector<float>> m_sh_containers_prev;
std::vector<bool> m_position_changed;

};


#endif //AMBIENCODER_H
16 changes: 16 additions & 0 deletions source/Module/AmbiEncoder/AmbiEncoderConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef AMBIENCODERCONFIG_H
#define AMBIENCODERCONFIG_H

#include <ModuleConfig.h>

struct AmbiEncoderConfig : ModuleConfig{
int order;

const Modules module_type() const override{
return Modules::AMBI_ENCODER;
}
};

typedef std::shared_ptr<AmbiEncoderConfig> AmbiEncoderConfigPtr;

#endif //AMBIENCODERCONFIG_H
2 changes: 1 addition & 1 deletion source/Module/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Module {

public:
Module();
~Module();
virtual ~Module();

virtual size_t initialize(size_t input_channels) = 0;
virtual void prepare(HostAudioConfig host_audio_config);
Expand Down
3 changes: 2 additions & 1 deletion source/Module/ModuleConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ enum Modules{
GAIN,
FILTER,
DISTANCE_COMPENSATOR,
SUM
SUM,
AMBI_ENCODER,
};

struct ModuleConfig{
Expand Down
6 changes: 6 additions & 0 deletions source/Track/Track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Track::Track(const TrackConfig& config, std::shared_ptr<lo::ServerThread> osc_se
m_modules.push_back(std::static_pointer_cast<Module>(gain));
}
break;
case Modules::AMBI_ENCODER:
{
auto ambi_config = std::dynamic_pointer_cast<AmbiEncoderConfig>(module_config);
std::shared_ptr<AmbiEncoder> ambi_encoder = std::make_shared<AmbiEncoder>(ambi_config, osc_server);
m_modules.push_back(std::static_pointer_cast<Module> (ambi_encoder));
}
default:
break;
}
Expand Down
Loading

0 comments on commit 9f378e0

Please sign in to comment.