From e33d7651b3894c8723af24aca83e21d61fb2cdf3 Mon Sep 17 00:00:00 2001 From: Mikael Karlsson Date: Tue, 16 Jun 2020 17:03:26 +0200 Subject: [PATCH] Updates for v0.3.0 --- CHANGELOG.md | 14 ++++++++++++++ README.md | 25 +++++++++++++++++++++---- config/config.exs | 14 +++++++++++++- mix.exs | 6 +++--- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a10294f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +## 0.3.0 - 2020-06-16 + +### Added +- AVX for X86_64 machines +- Configurable ratio between buffer and period size +- Configurable period size +- Direct conversion (hw vs. plughw) +- Nerves build for RPi +- Set SCHED_RR realtime policy if possible + +### Changed +- Use enif_select instead of callback +- Use mmap instead of copy write +- Number of channels configured as key-value now (see config/config.exs) diff --git a/README.md b/README.md index 44ab650..8647428 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The idea is to create some base for experimenting with sound synthesis in Elixir ## What it is -Sound card drivers are configured to do asynchronous calls around every 5 ms to the xalsa_server process which delivers the number of frames (PCM samples) to the device driver to keep the card busy for yet another round of 5 ms. The frames are sent to a ring buffer made of two frame buffers. While one is used for generating audio the other is filled from the xalsa server. +Sound card drivers are configured to use poll descriptors that are used by enif_select_write function. The buffer sizes are set so that a ready4write message will be sent to the xalsa_server around every 6 ms which in turn delivers the number of frames (PCM samples) to the device driver to keep the card busy for yet another round of 6 ms. The frames are sent to a ring buffer made of two frame buffers. While one is used for generating audio the other is filled from the xalsa server. The xalsa_server queues samples on a per channel and process id basis, meaning that sample frames sent from one process to a specific channel will be queued behind existing frames from the same process if they exist (not yet consumed by the audio driver). Frames from other processes will be put in their own queue. Frames from the different queues will be mixed before delivered to the driver. This means every process may synthesize and sequence their own tone(s) independently. @@ -19,7 +19,7 @@ The frames are to be in a binary array of 32 bit floats for the C api. The Xalsa ## Installation -- If using from own application, append line: `{:xalsa, "~> 0.2.0"}` to +- If using from own application, append line: `{:xalsa, "~> 0.3.0"}` to your dependency list. - else checkout from github. - mix deps.get @@ -33,6 +33,23 @@ Default setting of application environment is in mix.exs. **NOTE**: Other applications on Linux like Pulseaudio may compete with your sound driver resources. In this case consult the documentation for your Linux distribution. For some reason it just worked on my Kubuntu distro. +### Realtime scheduling policy +The xalsa_manager process will set up realtime scheduling policy for all the +beam scheduler threads using the os command `chrt` if this is allowed for +the user. +To allow realtime scheduling policy SCHED_RR with chrt one needs a setup +of group audio (or any other unique name) and user added to this: +``` + groupadd audio + usermod -a -G audio yourUserID +``` + and in /etc/security/limits.d/audio.conf: +``` + @audio - rtprio 95 + @audio - memlock unlimited +``` +See also [JACK configuration](https://jackaudio.org/faq/linux_rt_config.html#1-editing-the-configuration-file) + ## Running - mix test @@ -48,8 +65,8 @@ So considering that the BEAM (Erlang VM) has good support for multicore and that Some things I wanted to find out: - How soft realtime Erlang can meet some hard realtime driver conditions. -- How to decouple periodical sound card driver callbacks and distribute to many Erlang (Elixir) processes using processes and message passing. +- How to decouple periodical sound card driver callbacks and distribute to many cores using processes and message passing. - If NIFs are good when optimizing sound generation. - Check if an old Erlang programmer can learn and appreciate Elixir. Since Elixir is "the new kid in the block" I thought that any sound synthesis software would get more community attention if using Elixir instead of Erlang. -Results sofar is that Xalsa can mixin frames from around 50 erlang processes before missing the buffer timeframe (5 ms) on an ordinary laptop at 44100 Hz sample rate. The SC server manages timeframes in the ms range and with far more (at least 10x) so called UGens. So Xalsa is quite a bit behind but if synthesis can be pushed up some layer and spread on more cores it may still be feasible. The intention is to test this in other applications using Xalsa. This will probably also answer how well Elixir plays in an Erlang wired brain. +Results sofar is that Xalsa can mixin frames from around 50 erlang processes before missing the buffer timeframe (6 ms) on an ordinary laptop at 44100 Hz sample rate. The SC server manages timeframes in the ms range and with far more (at least 10x) so called UGens. So Xalsa is quite a bit behind but if synthesis can be pushed up some layer and spread on more cores it may still be feasible. The intention is to test this in other applications using Xalsa. This will probably also answer how well Elixir plays in an Erlang wired brain. diff --git a/config/config.exs b/config/config.exs index 5f9ffa7..8747eaf 100644 --- a/config/config.exs +++ b/config/config.exs @@ -16,7 +16,7 @@ use Mix.Config # # Application.get_env(:xalsa, :key) # -# rate 44100 | 48000 | 96000 | 192000 +# rate 44100 | 48000 | 96000 | 192000 - default 44100 # Configuring PCM devices can be tricky. # Use alsa commands aplay -l and aplay -L to find your sound cards and PCM # devices. Look for PCMs supporting @@ -32,6 +32,18 @@ use Mix.Config # [channels: 2, period_size: 256, period_buffer_size_ratio: 2]}, # {:"plughw:HDMI,3", [channels: 2]}] +# Optional; channels, period_size, period_buffer_size_ratio + +# channels default: 2 + +# period_size default as function of rate +# 44100: 256 +# 48000: 256 +# 96000: 512 +# 192000: 1024 + +# period_buffer_size_ratio default: 2 + # You can also configure a third-party app: # # config :logger, level: :info diff --git a/mix.exs b/mix.exs index d246dca..0f6c5c2 100644 --- a/mix.exs +++ b/mix.exs @@ -9,8 +9,8 @@ defmodule Xalsa.MixProject do make_cwd: "c_src", make_clean: ["clean"], compilers: [:elixir_make] ++ Mix.compilers(), - version: "0.2.1", - elixir: "~> 1.8", + version: "0.3.0", + elixir: "~> 1.9", start_permanent: Mix.env() == :prod, deps: deps(), description: description(), @@ -41,7 +41,7 @@ defmodule Xalsa.MixProject do defp deps do [ {:elixir_make, "~> 0.6", runtime: false}, - {:ex_doc, "~> 0.20.2", only: :dev, runtime: false} + {:ex_doc, "~> 0.22.1", only: :dev, runtime: false} ] end