Skip to content

Commit

Permalink
Updates for v0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
karlsson committed Jun 16, 2020
1 parent 3502913 commit e33d765
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 8 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ 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.

The frames are to be in a binary array of 32 bit floats for the C api. The Xalsa module holds a helper function to convert from an Elixir list of floats.

## 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
Expand All @@ -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
Expand All @@ -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.
14 changes: 13 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit e33d765

Please sign in to comment.