diff --git a/.codespellignore b/.codespellignore
new file mode 100644
index 0000000000000000000000000000000000000000..93f82e5d2164a0e4f6be051ede103a98636e0fea
--- /dev/null
+++ b/.codespellignore
@@ -0,0 +1,4 @@
+HDA
+hda
+occuring
+fro
\ No newline at end of file
diff --git a/.mailmap b/.mailmap
index 6c1ec786b6b08a813df652ffbd2ed3b637f966cb..11a44f579e6f8b13817a77fc80bc220e4fc5c777 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,8 +1,7 @@
 Gottfried Gaisbauer <gogo@servus.at> Gottfried <gogo@servus.at>
 Ingo Leindecker <ingo.leindecker@fro.at> ingo <ingo.leindecker@fro.at>
-David Trattnig <david.trattnig@gmail.com> <david@subsquare.at>
-David Trattnig <david.trattnig@gmail.com> <david.trattnig@o94.at>
-David Trattnig <david.trattnig@gmail.com> david
+David Trattnig <david.trattnig@o94.at> <david@subsquare.at>
+David Trattnig <david.trattnig@o94.at> <david.trattnig@gmail.com>
 Christian Pointner <equinox@helsinki.at> Christian Pointner <equinox@spreadspace.org>
 Ernesto Rico Schmidt <ernesto@helsinki.at> Ernesto Rico Schmidt <e.rico.schmidt@gmail.com>
 Ernesto Rico Schmidt <ernesto@helsinki.at> nnrcschmdt <e.rico.schmidt@gmail.com>
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 1e438a0ede58e843abe16f7f415172729ced45f2..e66d048e8dfbadf466ce1b257a648514217def69 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,7 @@
 {
     "search.exclude": {
         "**/.git": true,
-        "**/tmp": true
-    }   
+        "**/tmp": true,
+        "logs/**": true,
+    }
 }
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index bef5dddea6c922e8514f10ffdd54a95b18ab09d5..023e78a5009fc287fc3d1df060907eb032f24f06 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,31 +1,35 @@
 
-FROM savonet/liquidsoap:v1.4.4
+FROM savonet/liquidsoap:v2.1.0
 LABEL maintainer="David Trattnig <david.trattnig@subsquare.at>"
 
-
 USER root
 
 # Dependencies & Utils
-RUN apt update --allow-releaseinfo-change && apt -y install \
+RUN apt update --allow-releaseinfo-change && \
+      apt -y --no-install-recommends install \
+      build-essential \
       alsa-utils \
       libssl-dev
-      # libgstreamer-ocaml-dev
 
 # Setup Engine
+ENV TZ=Europe/Vienna
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
 RUN mkdir -p /srv/src
 RUN mkdir -p /srv/tests
 RUN mkdir -p /srv/config
 COPY src /srv/src
 COPY tests /srv/tests
-COPY run.sh /srv
-VOLUME ["/srv/socket", "/srv/logs", "/srv/audio/source", "/srv/audio/playlist", "/srv/audio/station"]
+COPY config/sample.engine-core.docker.ini /srv/config/engine-core.ini
+COPY Makefile /srv/Makefile
+COPY VERSION /srv/VERSION
+VOLUME ["/srv/socket", "/srv/logs", "/srv/audio/source", "/srv/audio/playlist", "/srv/audio/fallback"]
 #RUN chown -R liquidsoap:liquidsoap /srv
 WORKDIR /srv
 
-# User liquodsoap has uid 10000 and gid 10001
+# User Liquidsoap has UID 10000 and GID 10001
 #USER liquidsoap
 
 # Start the Engine
 EXPOSE 1234/tcp
-ENTRYPOINT ["./run.sh", "prod"]
-CMD ["core"]
+ENTRYPOINT ["make"]
+CMD ["run"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..dcd7642f7ab6c5975c6d696d97afcf74d7492ff7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,76 @@
+-include build/base.Makefile
+-include build/docker.Makefile
+
+.PHONY: help
+help:
+	@echo "$(APP_NAME) targets:"
+	@echo "    init.dev        - init development environment"
+	@echo "    lint            - verify code style"
+	@echo "    spell           - check spelling of text"
+	@echo "    test            - run test suite"
+	@echo "    log             - tail log file"
+	@echo "    syslog          - tail syslog file"
+	@echo "    run             - start app"
+	@echo "    run.debug       - start app in debug mode"
+	$(call docker_help)
+
+
+# Settings
+
+AURA_ENGINE_CORE_CONFIG := ${CURDIR}/config/engine-core.docker.ini
+AURA_ENGINE_CORE_SOCKET := ${CURDIR}/socket
+AURA_AUDIO_STORE := ${CURDIR}/audio
+AURA_AUDIO_STORE_SOURCE := ${AURA_AUDIO_STORE}/source
+AURA_AUDIO_STORE_FALLBACK := ${AURA_AUDIO_STORE}/fallback
+AURA_AUDIO_STORE_PLAYLIST := ${AURA_AUDIO_STORE}/playlist
+
+DOCKER_RUN = @docker run \
+		--name $(APP_NAME) \
+		--network="host" \
+		--mount type=tmpfs,destination=/tmp \
+		--device /dev/snd \
+		--group-add audio \
+		-v "$(AURA_ENGINE_CORE_CONFIG)":"/srv/config/engine-core.ini":ro \
+		-v "$(AURA_ENGINE_CORE_SOCKET)":"/srv/socket" \
+		-v "$(AURA_AUDIO_STORE_SOURCE)":"/var/audio/source":ro \
+		-v "$(AURA_AUDIO_STORE_PLAYLIST)":"/var/audio/playlist":ro \
+		-v "$(AURA_AUDIO_STORE_FALLBACK)":"/var/audio/fallback":ro \
+		-u $(UID):$(GID) \
+		$(DOCKER_ENTRY_POINT) \
+		autoradio/$(APP_NAME)
+
+# Targets
+
+.PHONY: lint
+lint:
+	liquidsoap --check src/engine.liq
+
+.PHONY: spell
+spell: SPELL_PATHS := $(wildcard *.md) docs src tests config contrib
+spell: SPELL_PATHS_SKIP := src/archive
+spell: SPELL_IGNORE_FILE := .codespellignore
+spell:
+	codespell --ignore-words "$(SPELL_IGNORE_FILE)" --skip "$(SPELL_PATHS_SKIP)" $(SPELL_PATHS)
+
+.PHONY: init.dev
+init.dev:
+	sudo apt install -y
+		codespell
+
+.PHONY: test
+test:
+	(cd tests && liquidsoap engine_test_suite.liq)
+
+.PHONY: log
+log:
+	tail -f logs/$(APP_NAME).log
+
+.PHONY: run
+run:
+	(cd src && liquidsoap ./engine.liq)
+
+.PHONY: run.debug
+run.debug:
+	(cd src && liquidsoap --verbose --debug ./engine.liq)
+
+
diff --git a/README.md b/README.md
index 3d60bdc336cce5228c93296a0a85e479bbde324f..e24c3d9b0657702c2b1578272b186d324e817f17 100644
--- a/README.md
+++ b/README.md
@@ -1,58 +1,42 @@
-
-
 # AURA Engine Core
 
-<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/assets/images/aura-engine.png" width="250" align="right" />
-
-AURA Engine Core is a play-out server for radio stations based on [Liquidsoap](https://www.liquidsoap.info/).
-
-For full utilization Engine Core might be combined with [Engine](https://gitlab.servus.at/aura/engine) providing a managment, monitoring and scheduling layer atop. Engine Core provides following features out of the box:
-
-- **Play audio from various sources** including files, streams and analog live audio
-- **Analog audio or digital stream output** which is able to connect to an Icecast Server. It allows to stream to multiple Icecast Servers simultaniousely including sending of meta information using the *Icy* protocol.
-- **Auto DJ** triggered by a silence detector to avoid [Dead Air](https://en.wikipedia.org/wiki/Dead_air). Play randomized music from a folder or M3U playlist.
-- **ReplayGain** normalization done using passed [ReplayGain](https://en.wikipedia.org/wiki/ReplayGain) meta data.
-- **Track Service** notifications when used in conjunction with [Engine API](https://gitlab.servus.at/aura/engine-api)
+<img src="https://gitlab.servus.at/autoradio/meta/-/raw/main/assets/images/aura-engine.png" width="250" align="right" />
 
-This project is part of [Aura Radio Software Suite](https://gitlab.servus.at/aura/meta), specifically build for the requirements of community radio stations.
+AURA Engine Core is a multi-channel playout server for radio stations based on [Liquidsoap](https://www.liquidsoap.info/).
 
-<!-- TOC -->
+This documentation is meant for developers. For using the AURA Community Radio Suite check out the [aura.radio](https://docs.aura.radio/)
 
-1. [AURA Engine Core](#aura-engine-core)
-   1. [Prerequisites](#prerequisites)
-   2. [Quickstart](#quickstart)
-   3. [Audio Store](#audio-store)
-   4. [Advanced Configuration](#advanced-configuration)
-   5. [Logging](#logging)
-   6. [Other AURA Components](#other-aura-components)
-   7. [Read more](#read-more)
-   8. [About](#about)
+## Prerequisites
 
-<!-- /TOC -->
+Before you begin, ensure you have met the following requirements:
 
-## Prerequisites
+- Operating system: Debian 11, Ubuntu 20.04 or newer
+- [Liquidsoap 2.1.x](https://www.liquidsoap.info/) installed using [OPAM (OCaml Package Manager)](https://opam.ocaml.org/)
+- git
+- make
 
-To [install the AURA Suite](https://gitlab.servus.at/aura/meta) we recommend using [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/).
+## Installation
 
-> IMPORTANT: For live audio input inside Docker there are currently buffer and latency issues. After the [move the Liquidsoap 2.0](https://gitlab.servus.at/aura/engine-core/-/issues/4) it should be somewhat better. Also looking for tips on how to solve these issues. Therefore we right now recommend to do a bare-metal installation for *Engine Core*. If you don't require analog audio input you can safely use Docker. All other AURA components can be deployed using Docker though.
+Install system dependencies:
 
-You'll need to have following installed before proceeding:
+```bash
+apt install curl alsa-utils libasound2-dev libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libflac-dev libjack-dev libpulse-dev libswresample-dev libswscale-dev libssl-dev ffmpeg opam
+```
 
-- [git](https://git-scm.com/)
-- `apt-get install curl alsa-utils libasound2-dev libavcodec-dev libavdevice-dev libavformat-dev libavutil-dev libflac-dev libjack-dev libpulse-dev libswresample-dev libswscale-dev libssl-dev ffmpeg opam`
-- [Liquidsoap 1.4.4](https://www.liquidsoap.info/doc-1.4.4/install.html) installed using [OPAM (OCaml Package Manager)](https://opam.ocaml.org/) and these additional dependencies:
-    - `opam depext alsa pulseaudio bjack ssl ffmpeg samplerate flac taglib mad lame vorbis flac opus cry liquidsoap.1.4.4 -y`
-    - `opam install alsa pulseaudio bjack ssl ffmpeg samplerate flac taglib mad lame vorbis flac opus cry liquidsoap.1.4.4 -y`
+Build Liquidsoap with additional libraries:
 
-Download a zipped version from the [*Engine Core* repository](https://gitlab.servus.at/aura/engine-core) page or clone some specific branch or tag using `git` to a local `aura/engine-core` directory. In order to allow easy updating we recommend the git approach.
+```bash
+opam depext alsa pulseaudio bjack ffmpeg samplerate flac taglib mad lame vorbis flac opus cry ocurl liquidsoap -y
+opam install alsa pulseaudio bjack ffmpeg samplerate flac taglib mad lame vorbis flac opus cry ocurl liquidsoap -y
+```
 
 ## Quickstart
 
-Perform following commands inside the `engine-core` directory:
+After cloning the project perform following commands inside the project directory:
 
-1. Create an initial configuration file based on an sample config: `cp config/sample-production.engine-core.ini config/engine-core.ini`
-2. Create the folder structure `./audio/fallback/` in the project root and populate `fallback` with some music files. This folder is picked up as a so-called *Station Fallback* in case no other music is scheduled or if silence is detected.
-3. Execute `./run.sh` to get the *Engine Core* server running. Voilá, you should hear some music!
+1. Create an initial configuration file based on an sample config: `cp config/sample.engine-core.ini config/engine-core.ini`
+2. Create the folder structure `./audio/fallback/` in the project root and populate `fallback` with some music files. This folder is picked up as a so-called _Station Fallback_ in case no other music is scheduled or if silence is detected.
+3. Execute `make run` to get the _Engine Core_ server running. Voilá, you should hear some music!
 
 If the audio device desired for playback is set as `default`, the Engine now should be ready to play sound. If you are not hearing any sound set a working output device for `output_device_0` in `engine-core.ini` and carefully review the logs.
 
@@ -62,26 +46,38 @@ After this is working you might be ready for a more sophisticated setup of the e
 
 ## Audio Store
 
-The aformentioned `audio` folder is the base for retrieving audio files. Engine Core is referencing three folders from this so-called *Audio Store*:
+The aforementioned `audio` folder is the base for retrieving audio files. Engine Core is referencing three folders from this so-called _Audio Store_:
 
-- **`audio/fallback/`**: A local folder for any emergency playback, also called *Station Fallback*. All audio files inside are played in a randomized order, if no actually scheduled music is played by the engine. The folder is being watched for changes. So you can add/remove audio on the fly. This fallback feature is enabled by default.
+- **`audio/fallback/`**: A local folder for any emergency playback, also called _Station Fallback_. All audio files inside are played in a randomized order, if no actually scheduled music is played by the engine. The folder is being watched for changes. So you can add/remove audio on the fly. This fallback feature is enabled by default.
 - **`audio/playlist/`**: Put a file `station-fallback-playlist.m3u` in here and it has the same effect as the fallback folder.
-he playlist is being watched for changes. Set `fallback_type="playlist"` to enable this instead of the fallback folder.
-- **`audio/source/`**: This is the location for audio files provided by [Tank](https://gitlab.servus.at/aura/tank). Usually any audio files which are part of the scheduled programme are read for their broadcast from here. If you are running all AURA components on a single machine you should be fine with just creating a symbolic link to the relevant Tank folder (`ln -s ../engine/audio ./audio`). But in some [distributed and redundant production scenario](https://gitlab.servus.at/aura/meta/-/blob/master/docs/administration/installation-guide.md) you might think about more advanced options on how to sync your audio files between machines. You can find some ideas in the doc " [Setting up the Audio Store](https://gitlab.servus.at/aura/meta/-/blob/master/docs/administration/setup-audio-store.md)".
+  he playlist is being watched for changes. Set `fallback_type="playlist"` to enable this instead of the fallback folder.
+- **`audio/source/`**: This is the location for audio files provided by [Tank](https://gitlab.servus.at/aura/tank). Usually any audio files which are part of the scheduled programme are read from here to perform broadcasts. If you are running all AURA components on a single machine you should be fine with just creating a symbolic link to the relevant Tank folder (`ln -s ../engine/audio ./audio`). But in some [distributed and redundant production scenario](https://docs.aura.radio/en/latest/administration/deployment-scenarios.html) you might think about more advanced options on how to sync your audio files between machines. You can find some ideas in the doc " [Setting up the Audio Store](https://docs.aura.radio/en/latest/administration/setup-audio-store.html)".
 
-## Advanced Configuration
+## Configuration
 
 By default only audio output is enabled using the systems default device. If you want to use another audio interface or enable live audio, check the [Audio Device Configuration](docs/audio-device-configuration.md) document.
 
 Also review the other settings in your `engine-core.ini` to fine-tune the heart of your engine. If you are experiencing issues also check out the [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md).
 
-## Logging
-
-The Engine Core logs can be found under `./logs`.
-
-## Other AURA Components
-
-After successfully running Engine Core you might look into getting started with [the other Engine Components](https://gitlab.servus.at/aura/engine) and [AURA Web](https://gitlab.servus.at/aura/meta).
+## Channel Routing
+
+Following diagram shows existing channels and their routing.
+
+```mermaid
+graph TD
+    iq0[Queue A] -->|in_queue_0| mix
+    iq1[Queue B] -->|in_queue_1| mix
+    is0[Stream A] -->|in_stream_0| mix
+    is1[Stream B] -->|in_stream_1| mix
+    il0[Line In 1-5] -->|in_line_0..4| mix
+    ff[Fallback Folder] -->|fallback_folder| which_fallback
+    fpls[Fallback Playlist] -->|fallback_playlist| which_fallback
+    mix["  Mixer  "] --> silence_detector
+    which_fallback{or} -->| | silence_detector{Silence Detector}
+    silence_detector -->| | output[Output]
+    output --> |output.alsa| C[fa:fa-play Audio Interface]
+    output --> |output.icecast| D[fa:fa-play Icecast]
+```
 
 ## Read more
 
@@ -90,10 +86,8 @@ After successfully running Engine Core you might look into getting started with
 
 ## About
 
-[<img src="https://gitlab.servus.at/autoradio/meta/-/raw/master/assets/images/aura-logo.png" width="150" />](https://gitlab.servus.at/aura/meta)
+[<img src="https://gitlab.servus.at/autoradio/meta/-/raw/main/assets/images/aura-logo.png" width="150" />](https://aura.radio)
 
-AURA stands for Automated Radio and is a swiss army knife for community radio stations. Beside the Engine it provides Steering (Admin Interface for the radio station), Dashboard (Collaborative scheduling and programme coordination), Tank (Audio uploading, pre-processing and delivery). Read more in the [Aura Meta](https://gitlab.servus.at/aura/meta) repository or on the specific project pages.
+```
 
-| [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-steering.png" width="150" align="left" />](https://gitlab.servus.at/aura/steering)  |  [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-dashboard.png" width="150" align="left" />](https://gitlab.servus.at/aura/dashboard)  |  [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-tank.png" width="150" align="left" />](https://gitlab.servus.at/aura/tank) | [<img src="https://gitlab.servus.at/aura/meta/-/raw/master/assets/images/aura-engine.png" width="150" align="left" />](https://gitlab.servus.at/aura/engine)  |
-|---|---|---|---|
-| [Steering](https://gitlab.servus.at/aura/steering)  | [Dashboard](https://gitlab.servus.at/aura/dashboard)<br/>[Dashboard Clock](https://gitlab.servus.at/aura/dashboard-clock)  | [Tank](https://gitlab.servus.at/aura/tank)  | [Engine](https://gitlab.servus.at/aura/engine)<br/>[Engine Core](https://gitlab.servus.at/aura/engine-core)<br/>[Engine API](https://gitlab.servus.at/aura/engine-api)  |
+```
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..ba7730be7dd1e509ec9c3dd47bcfd41f78739a23
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0-alpha1-dev
\ No newline at end of file
diff --git a/build/base.Makefile b/build/base.Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..9968cbe47e335a4e3fb0803dad2b50cc66f5cc2e
--- /dev/null
+++ b/build/base.Makefile
@@ -0,0 +1,6 @@
+# Base config for AURA Makefiles
+# Include this at the top of other Makesfiles
+
+.DEFAULT_GOAL := help
+
+APP_NAME := $(shell basename $(dir $(abspath $(dir $$PWD/Makefile))))
\ No newline at end of file
diff --git a/build/docker.Makefile b/build/docker.Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..bf16b627c76692a11e40fa83fdfba8832d3b6169
--- /dev/null
+++ b/build/docker.Makefile
@@ -0,0 +1,67 @@
+# Docker targets for AURA Makefiles
+
+
+# Help
+
+define docker_help
+	@echo "    docker.build    - build docker image"
+	@echo "    docker.push     - push docker image"
+	@echo "    docker.run      - start app in container"
+	@echo "    docker.run.i    - start app in container (interactive mode)"
+	@echo "    docker.run.bash - start bash in container"
+	@echo "    docker.restart  - restart container"
+	@echo "    docker.stop     - stop container"
+	@echo "    docker.rm       - stop and remove container"
+	@echo "    docker.log      - container logs for app"
+	@echo "    docker.bash     - enter bash in running container"
+endef
+
+# Dependencies
+
+docker.deps:
+	@which docker
+
+# Targets
+
+.PHONY: docker.build
+docker.build: docker.deps
+	@docker build -t autoradio/$(APP_NAME) .
+
+.PHONY: docker.push
+docker/push: docker.deps
+	@docker push autoradio/$(APP_NAME)
+
+.PHONY: docker.run
+docker.run: DOCKER_ENTRY_POINT := -d
+docker.run: docker.deps
+	$(DOCKER_RUN)
+
+.PHONY: docker.run.i
+docker.run.i: DOCKER_ENTRY_POINT := -it
+docker.run.i: docker.deps
+	$(DOCKER_RUN)
+
+.PHONY: docker.run.bash
+docker.run.bash: DOCKER_ENTRY_POINT := -v "$(CURDIR)":"/srv" --entrypoint bash -it
+docker.run.bash: docker.deps
+	$(DOCKER_RUN)
+
+.PHONY: docker.restart
+docker.restart: docker.deps
+	@docker restart $(APP_NAME)
+
+.PHONY: docker.stop
+docker.stop: docker.deps
+	@docker stop $(APP_NAME)
+
+.PHONY: docker.rm
+docker.rm: docker.stop
+	@docker rm $(APP_NAME)
+
+.PHONY: docker.log
+docker.log: docker.deps
+	@docker logs $(APP_NAME) -f
+
+.PHONY: docker.bash
+docker.bash: docker.deps
+	@docker exec -it $(APP_NAME) bash
diff --git a/config/sample-development.engine-core.ini b/config/sample-development.engine-core.ini
deleted file mode 100644
index cdef57e8c7f8ebde693ea96e0992f3643a9fa498..0000000000000000000000000000000000000000
--- a/config/sample-development.engine-core.ini
+++ /dev/null
@@ -1,166 +0,0 @@
-##############################################
-#         Engine-Core Configuration          #
-##############################################
-# Note: Paths are relative to the 'src' folder
-
-[general]
-# File Socket to control engine-core externally (e.g. by 'engine')
-socketdir="../socket"
-# Host and port of the engine backchannel (Network Socket for e.g. sending track service updates)
-engine_control_host="localhost:1337"
-# Log directory absolute or relative from the "src" dir
-logdir="../logs"
-# Possible values are from "1" (INFO) to "5" (DEBUG)
-log_level="3"
-# Allow Liquidsoap to run as root (required for Docker)
-liquidsoap_as_root="false"
-
-[audio]
-# This is the folder where fallback playlists are read from (optional)
-audio_playlist_folder="../audio/playlist"
-# Sets the time how long we have to fade in and out, when we select another mixer input values are in seconds
-fade_in_time="1.5"
-fade_out_time="1.5"
-# Fallback type: "folder", "playlist" or "none"
-fallback_type="folder"
-# A playlist holding music for Station Fallbacks (optional)
-fallback_music_playlist= "station-fallback-playlist.m3u"
-# A folder holding music for Station Fallbacks (optional)
-fallback_music_folder="../audio/fallback"
-# The time in seconds how often the folder should be re-scanned
-# Do not reload too often when using large folders
-fallback_music_folder_reload="300"
-# Maximum time of blank from source (defaults to 20., seconds, float)
-fallback_max_blank="15."
-# Minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
-fallback_min_noise="0."
-# Power in dB under which the stream is considered silent (defaults to -40., float)
-fallback_threshold="-80."
-
-[soundcard]
-# Choose your weapon: "alsa" (1 input/output), "pulseaudio" (multi input/output) or "jack" (multi input/output)
-soundsystem="alsa"
-# With 'alsa' you have to write the devicenames like 'default' to use the system default or 'hw:0' to use the 1st audio device directly.
-# With Pulse Audio and Jack => an non empty value means it is used
-# Devices with empty string are ignored and not used
-input_device_0=""
-input_device_1=""
-input_device_2=""
-input_device_3=""
-input_device_4=""
-# Same same, but different
-output_device_0="default"
-output_device_1=""
-output_device_2=""
-output_device_3=""
-output_device_4=""
-
-# ADVANCED ALSA SETTINGS - Defaults are recommended; you might need to tweak these values if you're hearing jitter, cracklings or other artifacts or having generally bad latency
-#
-# Buffered audio is quite simple to setup but has some latency; Unbuffered provides low latency but requires a valid frame size.
-alsa_use_buffer="true"
-# Usually provided by your device
-alsa_sample_rate=""
-# Set to 0 to use the ALSA default. If using hardware directly (hw:0) start with 8192. Higher values give higher latency and lower CPU usage.
-alsa_buffer="8192"
-# Only used for buffered ALSA I/O, and affects latency. Probably not wanted for live audio. Defaults to "1".
-alsa_buffer_length="10"
-# Set to 0 or leave empty to use the ALSA default.
-alsa_periods=""
-# Frame Size ("frame.audio.size") is provided by ALSA and is specific to your Audio Device. Needs to manually obtained and set correctly.
-alsa_frame_size="2048"
-# Tentative frame duration in seconds, defaults to "0.04" (Float). This setting is used as a hint for the duration,
-# when ‘frame_size’ is not provided. It's required for getting things in sync, but it's actually tricky to set correctly.
-alsa_frame_duration=""
-
-[stream]
-# defines enabled or not
-stream_0="n"
-# possible values: flac, mp3, ogg, opus (depending on which liquidsoap plugins you've installed)
-stream_0_encoding="ogg"
-# bitrate (with encoding types without bitrate like flac or ogg it is substituted. 32 => very poor quality. 320 => very high quality)
-stream_0_bitrate="128"
-# how many channels? everything else than 2 is considered as mono
-stream_0_channels="2"
-# to where we are streaming..?
-stream_0_host="localhost"
-# and which port?
-stream_0_port="8888"
-# the name of the mountpoint
-stream_0_mountpoint="aura-test-0.ogg"
-# username
-stream_0_user="source"
-# and the password
-stream_0_password="source"
-# stream url
-stream_0_url="http://www.fro.at"
-# the name of the stream
-stream_0_name="AURA Test Stream 0"
-# the genre of the stream
-stream_0_genre="mixed"
-# description of the stream
-stream_0_description="Test Stream 0"
-
-stream_1="n"
-stream_1_encoding="flac"
-stream_1_bitrate="128"
-stream_1_channels="2"
-
-stream_1_host="localhost"
-stream_1_port="8888"
-stream_1_mountpoint="aura-test-1.flac"
-stream_1_user="source"
-stream_1_password="source"
-
-stream_1_url="http://www.fro.at"
-stream_1_name="AURA Test Stream 1"
-stream_1_genre="mixed"
-stream_1_description="Test Stream 1"
-
-stream_2="n"
-stream_2_encoding="mp3"
-stream_2_bitrate="64"
-stream_2_channels="2"
-
-stream_2_host="localhost"
-stream_2_port="8888"
-stream_2_mountpoint="aura-test-2.mp3"
-stream_2_user="source"
-stream_2_password="source"
-
-stream_2_url="http://www.fro.at"
-stream_2_name="AURA Test Stream 2"
-stream_2_genre="mixed"
-stream_2_description="Test Stream 2"
-
-stream_3="n"
-stream_3_encoding="ogg"
-stream_3_bitrate="64"
-stream_3_channels="2"
-
-stream_3_host="localhost"
-stream_3_port="8888"
-stream_3_mountpoint="aura-test-3.ogg"
-stream_3_user="source"
-stream_3_password="source"
-
-stream_3_url="http://www.fro.at"
-stream_3_name="AURA Test Stream 3"
-stream_3_genre="mixed"
-stream_3_description="Test Stream 3"
-
-stream_4="n"
-stream_4_encoding="opus"
-stream_4_bitrate="64"
-stream_4_channels="2"
-
-stream_4_host="localhost"
-stream_4_port="8888"
-stream_4_mountpoint="aura-test-4.opus"
-stream_4_user="source"
-stream_4_password="source"
-
-stream_4_url="http://www.fro.at"
-stream_4_name="AURA Test Stream 3"
-stream_4_genre="mixed"
-stream_4_description="Test Stream 3"
diff --git a/config/sample-docker.engine-core.ini b/config/sample-docker.engine-core.ini
deleted file mode 100644
index c46a3836134cfaa955a6a40a5b1004bdcc728b2e..0000000000000000000000000000000000000000
--- a/config/sample-docker.engine-core.ini
+++ /dev/null
@@ -1,166 +0,0 @@
-##############################################
-#         Engine-Core Configuration          #
-##############################################
-# Note: Paths are relative to the 'src' folder
-
-[general]
-# File Socket to control engine-core externally (e.g. by 'engine')
-socketdir="../socket"
-# Host and port of the engine backchannel (Network Socket for e.g. sending track service updates)
-engine_control_host="0.0.0.0:1337"
-# Log directory absolute or relative from the "src" dir
-logdir="../logs"
-# Possible values are from "1" (INFO) to "5" (DEBUG)
-log_level="3"
-# Allow Liquidsoap to run as root (required for Docker)
-liquidsoap_as_root="true"
-
-[audio]
-# This is the folder where fallback playlists are read from (optional)
-audio_playlist_folder="../audio/playlist"
-# Sets the time how long we have to fade in and out, when we select another mixer input values are in seconds
-fade_in_time="1.5"
-fade_out_time="1.5"
-# Fallback type: "folder", "playlist" or "none"
-fallback_type="folder"
-# A playlist holding music for Station Fallbacks (optional)
-fallback_music_playlist= "station-fallback-playlist.m3u"
-# A folder holding music for Station Fallbacks (optional)
-fallback_music_folder="../audio/fallback"
-# The time in seconds how often the folder should be re-scanned
-# Do not reload too often when using large folders
-fallback_music_folder_reload="300"
-# Maximum time of blank from source (defaults to 20., seconds, float)
-fallback_max_blank="15."
-# Minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
-fallback_min_noise="0."
-# Power in dB under which the stream is considered silent (defaults to -40., float)
-fallback_threshold="-80."
-
-[soundcard]
-# Choose your weapon: "alsa" (1 input/output), "pulseaudio" (multi input/output) or "jack" (multi input/output)
-soundsystem="alsa"
-# With 'alsa' you have to write the devicenames like 'default' to use the system default or 'hw:0' to use the 1st audio device directly.
-# With Pulse Audio and Jack => an non empty value means it is used
-# Devices with empty string are ignored and not used
-input_device_0=""
-input_device_1=""
-input_device_2=""
-input_device_3=""
-input_device_4=""
-# Same same, but different
-output_device_0="default"
-output_device_1=""
-output_device_2=""
-output_device_3=""
-output_device_4=""
-
-# ADVANCED ALSA SETTINGS - Defaults are recommended; you might need to tweak these values if you're hearing jitter, cracklings or other artifacts or having generally bad latency
-#
-# Buffered audio is quite simple to setup but has some latency; Unbuffered provides low latency but requires a valid frame size.
-alsa_use_buffer="true"
-# Usually provided by your device
-alsa_sample_rate=""
-# Set to 0 to use the ALSA default. If using hardware directly (hw:0) start with 8192. Higher values give higher latency and lower CPU usage.
-alsa_buffer="8192"
-# Only used for buffered ALSA I/O, and affects latency. Probably not wanted for live audio. Defaults to "1".
-alsa_buffer_length="10"
-# Set to 0 or leave empty to use the ALSA default.
-alsa_periods=""
-# Frame Size ("frame.audio.size") is provided by ALSA and is specific to your Audio Device. Needs to manually obtained and set correctly.
-alsa_frame_size="2048"
-# Tentative frame duration in seconds, defaults to "0.04" (Float). This setting is used as a hint for the duration,
-# when ‘frame_size’ is not provided. It's required for getting things in sync, but it's actually tricky to set correctly.
-alsa_frame_duration=""
-
-[stream]
-# defines enabled or not
-stream_0="n"
-# possible values: flac, mp3, ogg, opus (depending on which liquidsoap plugins you've installed)
-stream_0_encoding="ogg"
-# bitrate (with encoding types without bitrate like flac or ogg it is substituted. 32 => very poor quality. 320 => very high quality)
-stream_0_bitrate="128"
-# how many channels? everything else than 2 is considered as mono
-stream_0_channels="2"
-# to where we are streaming..?
-stream_0_host="localhost"
-# and which port?
-stream_0_port="8888"
-# the name of the mountpoint
-stream_0_mountpoint="aura-test-0.ogg"
-# username
-stream_0_user="source"
-# and the password
-stream_0_password="source"
-# stream url
-stream_0_url="http://www.fro.at"
-# the name of the stream
-stream_0_name="AURA Test Stream 0"
-# the genre of the stream
-stream_0_genre="mixed"
-# description of the stream
-stream_0_description="Test Stream 0"
-
-stream_1="n"
-stream_1_encoding="flac"
-stream_1_bitrate="128"
-stream_1_channels="2"
-
-stream_1_host="localhost"
-stream_1_port="8888"
-stream_1_mountpoint="aura-test-1.flac"
-stream_1_user="source"
-stream_1_password="source"
-
-stream_1_url="http://www.fro.at"
-stream_1_name="AURA Test Stream 1"
-stream_1_genre="mixed"
-stream_1_description="Test Stream 1"
-
-stream_2="n"
-stream_2_encoding="mp3"
-stream_2_bitrate="64"
-stream_2_channels="2"
-
-stream_2_host="localhost"
-stream_2_port="8888"
-stream_2_mountpoint="aura-test-2.mp3"
-stream_2_user="source"
-stream_2_password="source"
-
-stream_2_url="http://www.fro.at"
-stream_2_name="AURA Test Stream 2"
-stream_2_genre="mixed"
-stream_2_description="Test Stream 2"
-
-stream_3="n"
-stream_3_encoding="ogg"
-stream_3_bitrate="64"
-stream_3_channels="2"
-
-stream_3_host="localhost"
-stream_3_port="8888"
-stream_3_mountpoint="aura-test-3.ogg"
-stream_3_user="source"
-stream_3_password="source"
-
-stream_3_url="http://www.fro.at"
-stream_3_name="AURA Test Stream 3"
-stream_3_genre="mixed"
-stream_3_description="Test Stream 3"
-
-stream_4="n"
-stream_4_encoding="opus"
-stream_4_bitrate="64"
-stream_4_channels="2"
-
-stream_4_host="localhost"
-stream_4_port="8888"
-stream_4_mountpoint="aura-test-4.opus"
-stream_4_user="source"
-stream_4_password="source"
-
-stream_4_url="http://www.fro.at"
-stream_4_name="AURA Test Stream 3"
-stream_4_genre="mixed"
-stream_4_description="Test Stream 3"
diff --git a/config/sample-production.engine-core.ini b/config/sample-production.engine-core.ini
deleted file mode 100644
index cdef57e8c7f8ebde693ea96e0992f3643a9fa498..0000000000000000000000000000000000000000
--- a/config/sample-production.engine-core.ini
+++ /dev/null
@@ -1,166 +0,0 @@
-##############################################
-#         Engine-Core Configuration          #
-##############################################
-# Note: Paths are relative to the 'src' folder
-
-[general]
-# File Socket to control engine-core externally (e.g. by 'engine')
-socketdir="../socket"
-# Host and port of the engine backchannel (Network Socket for e.g. sending track service updates)
-engine_control_host="localhost:1337"
-# Log directory absolute or relative from the "src" dir
-logdir="../logs"
-# Possible values are from "1" (INFO) to "5" (DEBUG)
-log_level="3"
-# Allow Liquidsoap to run as root (required for Docker)
-liquidsoap_as_root="false"
-
-[audio]
-# This is the folder where fallback playlists are read from (optional)
-audio_playlist_folder="../audio/playlist"
-# Sets the time how long we have to fade in and out, when we select another mixer input values are in seconds
-fade_in_time="1.5"
-fade_out_time="1.5"
-# Fallback type: "folder", "playlist" or "none"
-fallback_type="folder"
-# A playlist holding music for Station Fallbacks (optional)
-fallback_music_playlist= "station-fallback-playlist.m3u"
-# A folder holding music for Station Fallbacks (optional)
-fallback_music_folder="../audio/fallback"
-# The time in seconds how often the folder should be re-scanned
-# Do not reload too often when using large folders
-fallback_music_folder_reload="300"
-# Maximum time of blank from source (defaults to 20., seconds, float)
-fallback_max_blank="15."
-# Minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
-fallback_min_noise="0."
-# Power in dB under which the stream is considered silent (defaults to -40., float)
-fallback_threshold="-80."
-
-[soundcard]
-# Choose your weapon: "alsa" (1 input/output), "pulseaudio" (multi input/output) or "jack" (multi input/output)
-soundsystem="alsa"
-# With 'alsa' you have to write the devicenames like 'default' to use the system default or 'hw:0' to use the 1st audio device directly.
-# With Pulse Audio and Jack => an non empty value means it is used
-# Devices with empty string are ignored and not used
-input_device_0=""
-input_device_1=""
-input_device_2=""
-input_device_3=""
-input_device_4=""
-# Same same, but different
-output_device_0="default"
-output_device_1=""
-output_device_2=""
-output_device_3=""
-output_device_4=""
-
-# ADVANCED ALSA SETTINGS - Defaults are recommended; you might need to tweak these values if you're hearing jitter, cracklings or other artifacts or having generally bad latency
-#
-# Buffered audio is quite simple to setup but has some latency; Unbuffered provides low latency but requires a valid frame size.
-alsa_use_buffer="true"
-# Usually provided by your device
-alsa_sample_rate=""
-# Set to 0 to use the ALSA default. If using hardware directly (hw:0) start with 8192. Higher values give higher latency and lower CPU usage.
-alsa_buffer="8192"
-# Only used for buffered ALSA I/O, and affects latency. Probably not wanted for live audio. Defaults to "1".
-alsa_buffer_length="10"
-# Set to 0 or leave empty to use the ALSA default.
-alsa_periods=""
-# Frame Size ("frame.audio.size") is provided by ALSA and is specific to your Audio Device. Needs to manually obtained and set correctly.
-alsa_frame_size="2048"
-# Tentative frame duration in seconds, defaults to "0.04" (Float). This setting is used as a hint for the duration,
-# when ‘frame_size’ is not provided. It's required for getting things in sync, but it's actually tricky to set correctly.
-alsa_frame_duration=""
-
-[stream]
-# defines enabled or not
-stream_0="n"
-# possible values: flac, mp3, ogg, opus (depending on which liquidsoap plugins you've installed)
-stream_0_encoding="ogg"
-# bitrate (with encoding types without bitrate like flac or ogg it is substituted. 32 => very poor quality. 320 => very high quality)
-stream_0_bitrate="128"
-# how many channels? everything else than 2 is considered as mono
-stream_0_channels="2"
-# to where we are streaming..?
-stream_0_host="localhost"
-# and which port?
-stream_0_port="8888"
-# the name of the mountpoint
-stream_0_mountpoint="aura-test-0.ogg"
-# username
-stream_0_user="source"
-# and the password
-stream_0_password="source"
-# stream url
-stream_0_url="http://www.fro.at"
-# the name of the stream
-stream_0_name="AURA Test Stream 0"
-# the genre of the stream
-stream_0_genre="mixed"
-# description of the stream
-stream_0_description="Test Stream 0"
-
-stream_1="n"
-stream_1_encoding="flac"
-stream_1_bitrate="128"
-stream_1_channels="2"
-
-stream_1_host="localhost"
-stream_1_port="8888"
-stream_1_mountpoint="aura-test-1.flac"
-stream_1_user="source"
-stream_1_password="source"
-
-stream_1_url="http://www.fro.at"
-stream_1_name="AURA Test Stream 1"
-stream_1_genre="mixed"
-stream_1_description="Test Stream 1"
-
-stream_2="n"
-stream_2_encoding="mp3"
-stream_2_bitrate="64"
-stream_2_channels="2"
-
-stream_2_host="localhost"
-stream_2_port="8888"
-stream_2_mountpoint="aura-test-2.mp3"
-stream_2_user="source"
-stream_2_password="source"
-
-stream_2_url="http://www.fro.at"
-stream_2_name="AURA Test Stream 2"
-stream_2_genre="mixed"
-stream_2_description="Test Stream 2"
-
-stream_3="n"
-stream_3_encoding="ogg"
-stream_3_bitrate="64"
-stream_3_channels="2"
-
-stream_3_host="localhost"
-stream_3_port="8888"
-stream_3_mountpoint="aura-test-3.ogg"
-stream_3_user="source"
-stream_3_password="source"
-
-stream_3_url="http://www.fro.at"
-stream_3_name="AURA Test Stream 3"
-stream_3_genre="mixed"
-stream_3_description="Test Stream 3"
-
-stream_4="n"
-stream_4_encoding="opus"
-stream_4_bitrate="64"
-stream_4_channels="2"
-
-stream_4_host="localhost"
-stream_4_port="8888"
-stream_4_mountpoint="aura-test-4.opus"
-stream_4_user="source"
-stream_4_password="source"
-
-stream_4_url="http://www.fro.at"
-stream_4_name="AURA Test Stream 3"
-stream_4_genre="mixed"
-stream_4_description="Test Stream 3"
diff --git a/config/sample.engine-core.docker.ini b/config/sample.engine-core.docker.ini
new file mode 100644
index 0000000000000000000000000000000000000000..a6ad9f4e7cd8cbc7f76a9352c63852ea0604ee93
--- /dev/null
+++ b/config/sample.engine-core.docker.ini
@@ -0,0 +1,142 @@
+##################################################################################################
+# Engine Core Configuration                                                                      #
+##################################################################################################
+
+
+[general]
+##################################################################################################
+# Engine ID
+; engine_id="1"
+# File Socket to control engine-core by engine
+; socket_dir="../socket"
+# Log directory absolute or relative from the "src" dir
+; log_dir="../logs"
+# Possible values are from "1" (INFO) to "5" (DEBUG)
+; log_level="3"
+# Allow Liquidsoap to run as root (required for Docker)
+liquidsoap_as_root="true"
+# API endpoint to POST playlogs to
+api_url_playlog="http://0.0.0.0:8008/api/v1/playlog"
+# This is the folder where fallback playlists are read from (optional)
+audio_playlist_folder="/var/audio/playlist"
+
+
+[fallback]
+##################################################################################################
+# Fallback audio played in scenarios where all other channels are silent. Fallback channels are
+# activated using a silence detector. You can use a fallback folder or playlist to feed audio
+# into the fallback player.
+#
+# A folder holding music for Station Fallbacks (optional)
+fallback_music_folder="/var/audio/fallback"
+# Fallback Show Settings
+; fallback_show_name="Station Fallback"
+; fallback_show_id="-1"
+# Fallback type: "folder", "playlist" or "none"
+; fallback_type="folder"
+# A playlist holding music for Station Fallbacks (optional)
+; fallback_music_playlist= "station-fallback-playlist.m3u"
+# The time in seconds how often the folder should be re-scanned
+# Do not reload too often when using large folders
+; fallback_music_folder_reload="300"
+# Maximum time of blank from source (defaults to 20., seconds, float)
+; fallback_max_blank="15."
+# Minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
+; fallback_min_noise="0."
+# Power in dB under which the stream is considered silent (defaults to -40., float)
+; fallback_threshold="-80."
+
+
+[audio]
+##################################################################################################
+# Sound Server
+#
+# Choose your weapon: "alsa" (1 input/output), "pulseaudio" (multi input/output)
+# or "jack" (multi input/output)
+soundsystem="alsa"
+
+##################################################################################################
+# Input / Output
+#
+# With 'alsa' you have to write the device names like 'default' to use the system default
+# or 'hw:0' to use the 1st audio device directly. With Pulse Audio and Jack => an non empty value
+# means it is used. Devices with empty string are ignored and not used
+#
+output_device_0="default"
+; output_device_1=""
+
+; input_device_0=""
+; input_device_1=""
+
+##################################################################################################
+# Frame Settings
+#
+# Sample rate in Hz
+; frame_audio_sample_rate="44100"
+
+# Frame Size ("frame.audio.size") is provided by ALSA and is specific to your Audio Device. Needs
+# to be manually obtained and set correctly.
+; frame_audio_size="1764"
+; frame_audio_size="7056"
+
+# Tentative frame duration in seconds, defaults to "0.04" (Float). This setting is used as a hint
+# for the duration, when frame_audio_size is not provided. It's required for getting things in
+# sync, but it's actually tricky to set correctly.
+; frame_duration=""
+
+##################################################################################################
+# Advanced ALSA Settings
+#
+# Defaults are recommended, but you might need to tweak these values if you're
+# hearing jitter, cracklings or other artifacts or having generally bad latency
+#
+# Buffered audio is quite simple to setup but has some latency; Unbuffered provides low latency but
+# requires a valid frame size.
+; alsa_buffered_input="true"
+; alsa_buffered_output="true"
+#
+# Set to 0 to use the ALSA default. If using hardware directly (hw:0) start with 8192. Higher values
+# give higher latency and lower CPU usage.
+; alsa_buffer="8192"
+#
+# Only used for buffered ALSA I/O, and affects latency. Probably not wanted for live audio.
+# Defaults to "1".
+; alsa_buffer_length="10"
+#
+# Set to 0 or leave empty to use the ALSA default.
+; alsa_periods=""
+
+##################################################################################################
+# ReplayGain Track Gain Normalization
+#
+# Audio files are automatically normalized as soon ReplayGain metadata is provided. If your audio
+# files do not provide such metadata, it's possible to compute it on the fly for all files
+# automatically. Keep in mind this is a computation costly task.
+;enable_replaygain_resolver=true
+
+
+[stream]
+##################################################################################################
+# Icecast output
+#
+# Repeat this section for additional outgoing streams, like "stream_1", "stream_2" and so on.
+
+# Enabled: (y)es or (n)o
+; stream_0="n"
+# Possible values: flac, mp3, ogg, opus
+# (depending on the installed Liquidsoap)
+; stream_0_encoding="ogg"
+; stream_0_bitrate="128"
+# Stereo (2) or Mono (any other value)
+; stream_0_channels="2"
+# Icecast host details
+; stream_0_host="localhost"
+; stream_0_port="8888"
+; stream_0_mountpoint="aura-radio.ogg"
+; stream_0_user="source"
+; stream_0_password="source"
+; stream_0_url="https://aura.radio"
+# Stream Metadata
+; stream_0_name="AURA Radio"
+; stream_0_genre="music"
+; stream_0_description="Music from the community radio software suite"
diff --git a/config/sample.engine-core.ini b/config/sample.engine-core.ini
new file mode 100644
index 0000000000000000000000000000000000000000..721a792c7a04ae868221e21023ff0930779a7968
--- /dev/null
+++ b/config/sample.engine-core.ini
@@ -0,0 +1,142 @@
+##################################################################################################
+# Engine Core Configuration                                                                      #
+##################################################################################################
+
+
+[general]
+##################################################################################################
+# Engine ID
+; engine_id="1"
+# File Socket to control engine-core by engine
+; socket_dir="../socket"
+# Log directory absolute or relative from the "src" dir
+; log_dir="../logs"
+# Possible values are from "1" (INFO) to "5" (DEBUG)
+; log_level="3"
+# Allow Liquidsoap to run as root (required for Docker)
+; liquidsoap_as_root="false"
+# API endpoint to POST playlogs to
+api_url_playlog="http://127.0.0.1:8008/api/v1/playlog"
+# This is the folder where fallback playlists are read from (optional)
+; audio_playlist_folder="../audio/playlist"
+
+
+[fallback]
+##################################################################################################
+# Fallback audio played in scenarios where all other channels are silent. Fallback channels are
+# activated using a silence detector. You can use a fallback folder or playlist to feed audio
+# into the fallback player.
+#
+# A folder holding music for Station Fallbacks (optional)
+; fallback_music_folder="../audio/fallback"
+# Fallback Show Settings
+; fallback_show_name="Station Fallback"
+; fallback_show_id="-1"
+# Fallback type: "folder", "playlist" or "none"
+; fallback_type="folder"
+# A playlist holding music for Station Fallbacks (optional)
+; fallback_music_playlist= "station-fallback-playlist.m3u"
+# The time in seconds how often the folder should be re-scanned
+# Do not reload too often when using large folders
+; fallback_music_folder_reload="300"
+# Maximum time of blank from source (defaults to 20., seconds, float)
+; fallback_max_blank="15."
+# Minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
+; fallback_min_noise="0."
+# Power in dB under which the stream is considered silent (defaults to -40., float)
+; fallback_threshold="-80."
+
+
+[audio]
+##################################################################################################
+# Sound Server
+#
+# Choose your weapon: "alsa" (1 input/output), "pulseaudio" (multi input/output)
+# or "jack" (multi input/output)
+soundsystem="alsa"
+
+##################################################################################################
+# Input / Output
+#
+# With 'alsa' you have to write the device names like 'default' to use the system default
+# or 'hw:0' to use the 1st audio device directly. With Pulse Audio and Jack => an non empty value
+# means it is used. Devices with empty string are ignored and not used
+#
+output_device_0="default"
+; output_device_1=""
+
+; input_device_0=""
+; input_device_1=""
+
+##################################################################################################
+# Frame Settings
+#
+# Sample rate in Hz
+; frame_audio_sample_rate="44100"
+
+# Frame Size ("frame.audio.size") is provided by ALSA and is specific to your Audio Device. Needs
+# to be manually obtained and set correctly.
+; frame_audio_size="1764"
+; frame_audio_size="7056"
+
+# Tentative frame duration in seconds, defaults to "0.04" (Float). This setting is used as a hint
+# for the duration, when frame_audio_size is not provided. It's required for getting things in
+# sync, but it's actually tricky to set correctly.
+; frame_duration=""
+
+##################################################################################################
+# Advanced ALSA Settings
+#
+# Defaults are recommended, but you might need to tweak these values if you're
+# hearing jitter, cracklings or other artifacts or having generally bad latency
+#
+# Buffered audio is quite simple to setup but has some latency; Unbuffered provides low latency but
+# requires a valid frame size.
+; alsa_buffered_input="true"
+; alsa_buffered_output="true"
+#
+# Set to 0 to use the ALSA default. If using hardware directly (hw:0) start with 8192. Higher values
+# give higher latency and lower CPU usage.
+; alsa_buffer="8192"
+#
+# Only used for buffered ALSA I/O, and affects latency. Probably not wanted for live audio.
+# Defaults to "1".
+; alsa_buffer_length="10"
+#
+# Set to 0 or leave empty to use the ALSA default.
+; alsa_periods=""
+
+##################################################################################################
+# ReplayGain Track Gain Normalization
+#
+# Audio files are automatically normalized as soon ReplayGain metadata is provided. If your audio
+# files do not provide such metadata, it's possible to compute it on the fly for all files
+# automatically. Keep in mind this is a computation costly task.
+;enable_replaygain_resolver=true
+
+
+[stream]
+##################################################################################################
+# Icecast output
+#
+# Repeat this section for additional outgoing streams, like "stream_1", "stream_2" and so on.
+
+# Enabled: (y)es or (n)o
+; stream_0="n"
+# Possible values: flac, mp3, ogg, opus
+# (depending on the installed Liquidsoap)
+; stream_0_encoding="ogg"
+; stream_0_bitrate="128"
+# Stereo (2) or Mono (any other value)
+; stream_0_channels="2"
+# Icecast host details
+; stream_0_host="localhost"
+; stream_0_port="8888"
+; stream_0_mountpoint="aura-radio.ogg"
+; stream_0_user="source"
+; stream_0_password="source"
+; stream_0_url="https://aura.radio"
+# Stream Metadata
+; stream_0_name="AURA Radio"
+; stream_0_genre="music"
+; stream_0_description="Music from the community radio software suite"
diff --git a/docs/audio-device-configuration.md b/docs/audio-device-configuration.md
index d27f12139db99788c64e0f79c7b1a8b9fcba1210..f314f6ff162eabd9bd5813dbc6866a5aa4cde829 100644
--- a/docs/audio-device-configuration.md
+++ b/docs/audio-device-configuration.md
@@ -1,4 +1,3 @@
-
 # Audio Device Configuration
 
 This document outlines the steps on how to configure an audio interface with Engine Core.
@@ -7,20 +6,19 @@ This document outlines the steps on how to configure an audio interface with Eng
 
 <!-- TOC -->
 
-- [Audio Device Configuration](#audio-device-configuration)
-    - [ALSA](#alsa)
-        - [Disabe an existing Pulse Audio Server](#disabe-an-existing-pulse-audio-server)
-        - [Defining the default audio device](#defining-the-default-audio-device)
-        - [Setting the output device using a device ID](#setting-the-output-device-using-a-device-id)
-        - [Setting the input device](#setting-the-input-device)
-            - [(A) Buffered audio](#a-buffered-audio)
-            - [(B) Unbuffered audio](#b-unbuffered-audio)
-    - [Pulse Audio](#pulse-audio)
-    - [JACK](#jack)
+1. [Audio Device Configuration](#audio-device-configuration)
+   1. [ALSA](#alsa)
+      1. [Disable an existing Pulse Audio Server](#disabe-an-existing-pulse-audio-server)
+      2. [Setting the output device using a device ID](#setting-the-output-device-using-a-device-id)
+      3. [Setting the input device](#setting-the-input-device)
+         1. [(A) Buffered audio](#a-buffered-audio)
+         2. [(B) Unbuffered audio](#b-unbuffered-audio)
+   2. [Pulse Audio](#pulse-audio)
+   3. [JACK](#jack)
 
 <!-- /TOC -->
 
-Following three audio drivers & servers are supported by *Engine Core*:
+Following three audio drivers & servers are supported by _Engine Core_:
 
 1. ALSA: Recommended for native hardware access. Only one input and output per channel pair is available.
 2. PulseAudio: This provides the most simple setup, but is not recommended for live audio input due to latencies.
@@ -32,8 +30,9 @@ Out of the box Engine Core is configured to use ALSA `default` output. The input
 
 When using your audio device with ALSA you are pretty close to the actual hardware. It can be relatively hard to find some good settings though.
 
-**Documentation:** While the [*Official ALSA*](https://www.alsa-project.org/wiki/Documentation) documentation is not an easy read, there is a superb [*ALSA, exposed!*](https://rendaw.gitlab.io/blog/2125f09a85f2.html#alsa-exposed) documentation available too.
-### Disabe an existing Pulse Audio Server
+**Documentation:** While the [_Official ALSA_](https://www.alsa-project.org/wiki/Documentation) documentation is not an easy read, there is a superb [_ALSA, exposed!_](https://rendaw.gitlab.io/blog/2125f09a85f2.html#alsa-exposed) documentation available too.
+
+### Disable an existing Pulse Audio Server
 
 Before starting, you need to ensure not having a Pulse Audio server running on your system. On Linux distributions like Ubuntu, PulseAudio is pre-installed and it can be tricky turning it off.
 
@@ -43,7 +42,7 @@ Having PulseAudio running in the background while thinking running native ALSA c
 
 First stop PulseAudio from restarting automatically
 
-```conf
+````conf
 nano ~/.pulse/client.conf
 (autospawn = no)
 ```conf
@@ -51,7 +50,7 @@ nano ~/.pulse/client.conf
 ```bash
 systemctl --user stop pulseaudio.socket
 systemctl --user stop pulseaudio.service
-```
+````
 
 You might even think about uninstalling PulseAudio altogether, as there are scenarios where the driver gets restarted upon ALSA device requests. Yes, even with having the PulseAudio service disabled and `autospawn = no` set in your PulseAudio configuration.
 
@@ -61,7 +60,7 @@ If you only want to do a quick ALSA test, without removing your nicely working P
 
 Audio output can be set by defining the default audio device or some specific device ID. If you prefer the latter you can skip this chapter.
 
-After first installation, Engine Core is configured to use the audio device set as `default`. You can set the default device in `/etc/asound.conf` or in a per user configuration in `~/asoundrc`. A simple defintion can look like this:
+After first installation, Engine Core is configured to use the audio device set as `default`. You can set the default device in `/etc/asound.conf` or in a per user configuration in `~/asoundrc`. A simple definition can look like this:
 
 ```ini
 defaults.pcm.card 2
@@ -83,7 +82,7 @@ cat /proc/asound/cards
                       Native Instruments Komplete Audio 6 at usb-0000:00:14.0-5.2.1, high speed
 ```
 
-The `asoundrc` configuration format allows the defintion of not only hardware devices but also complex virtual cards. For example it's possible to do live conversion using ALSA plugins or combine multiple devices into one.
+The `asoundrc` configuration format allows the definition of not only hardware devices but also complex virtual cards. For example it's possible to do live conversion using ALSA plugins or combine multiple devices into one.
 
 > This file allows you to have more advanced control over your card/device. The .asoundrc file consists of definitions of the various cards available in your system. It also gives you access to the pcm plugins in alsa-lib. These allow you to do tricky things like combine your cards into one or access multiple I/Os on your multichannel card.
 >
@@ -127,14 +126,14 @@ output_device_0="default:PCH"
 
 Disadvantages can be:
 
-- Modern distributions configure the ALSA devices with *dmix*, so that several devices can use the sound card at the same time. This can lead to unintentional audio being played independently of engine-core, e.g. if a desktop is running on the same computer.
+- Modern distributions configure the ALSA devices with _dmix_, so that several devices can use the sound card at the same time. This can lead to unintentional audio being played independently of engine-core, e.g. if a desktop is running on the same computer.
 - The default configuration might have higher latencies than would be possible. However, the latency of the default devices is like using PulseAudio and quite sufficient.
 
-This can be avoided by directly addressing the hardware and setting the parameters buffer size and periods in `engine-ini`. However, this requires a deep understanding of ALSA and experimentally finding the right parameters, so it is not recommended. A good starting point is to set only the parameter for the buffer size. Liquidsoap will adjust the remaining parameters automatically:
+This can be avoided by directly addressing the hardware and setting the parameters buffer size and frame size in `engine-ini`. However, this requires a deep understanding of ALSA and experimentally finding the right parameters, so it is not recommended. A good starting point is to set only the parameter for the buffer size. Liquidsoap will adjust the remaining parameters automatically:
 
 ```ini
 output_device_0="hw:2"
-alsa_buffer="8192"
+alsa_buffer="7056"
 ```
 
 > #TODO Check if we should remove the alsa buffer recommendation here? According to my experience the frame-size is enough, as it also sets the buffer indirectly.
@@ -145,7 +144,7 @@ See [alsa-project.org/wiki/FramesPeriods](https://www.alsa-project.org/wiki/Fram
 
 Live analog audio input is disabled by default in Engine Core. Configuring live audio with low latencies can be tricky and depends on your system, Liquidsoap version, ALSA version and last but not least your audio interface.
 
-First you'll need to set the input device ID. You can either set the `default` device or one specific device ID. The approach to get the correct device ID is similar to the one for the output device as outlined above. Here, all three example defintions are valid settings for the same card:
+First you'll need to set the input device ID. You can either set the `default` device or one specific device ID. The approach to get the correct device ID is similar to the one for the output device as outlined above. Here, all three example definitions are valid settings for the same card:
 
 ```ini
 input_device_0="default"
@@ -157,7 +156,6 @@ input_device_0="sysdefault:CARD=K6"
 
 Next you need to connect some external audio to the analog inputs of your audio interface. Double check if there is enough amplitude to avoid unnecessary searches for an error. Now you can try starting the engine with `run.sh`. Do you hear some sound? It might work, but probably you hear some audio glitches and see plenty buffer underruns in the logs. That's because you need to specific the proper frame-size, individual to you soundcard and ALSA driver.
 
-
 Next you need to decide if you want:
 
 - **(A) Buffered audio**: There's some latency involved, but it is relatively easy to configure.
@@ -170,33 +168,34 @@ Buffered audio is relatively easy to configure, but introduce some latency. By t
 First check if buffered audio is enabled. But it's the default anyway.
 
 ```ini
-alsa_use_buffer="true"
+alsa_buffered_input="true"
+alsa_buffered_output="true"
 ```
 
 In that case you might need to tweak following settings, where the defaults are a good start. Maybe you are lucky and it works out of the box? :-)
 
 ```ini
-alsa_buffer="8192"
+alsa_buffer="7056"
 alsa_buffer_length="10"
-alsa_frame_size="2048"
+frame_audio_size="1764"
 ```
 
-If you don't experience any buffer underruns in the logs you can try to decrease the buffer size to for example `4096` or `2048`.
+If you don't experience any buffer underruns in the logs you can try to decrease the buffer size to for example `3528` or `1764`.
 
 In case you get plenty of buffer unterruns then try to increase the buffer size and possibly the frame size to twice the given value.
 
 #### (B) Unbuffered audio
 
-When enabling unbuffered audio you can access the (almost ;) real-time capabilities of ALSA.
+When enabling unbuffered audio you can access the (almost) real-time capabilities of ALSA.
 
 ```ini
-alsa_use_buffer="false"
+alsa_buffered_input="false"
 ```
 
 In that case you need to tweak at least following setting, but it's one with some huge impact.
 
 ```ini
-alsa_frame_size="2048"
+frame_audio_size="1764"
 ```
 
 **Detecting the correct frame-size**
@@ -209,23 +208,23 @@ Now perform following steps to find the correct frame-size:
 
 1. Run the script with the initial `frame.audio.size` of zero: `engine-core/tests$ liquidsoap -v test_alsa_framesize.liq`
 2. This causes plenty buffer underruns.
-3. Liquidsoap reports the "correct" frame-size of e.g. 1881 in the logs: `[analog_input:3] Could not set buffer size to 'frame.size' (1764 samples), got 1881.`.
-4. Now adapt the `frame.audio.size` in the script to `1881`.
+3. Liquidsoap reports the "correct" frame-size of e.g. 1764 in the logs: `[analog_input:3] Could not set buffer size to 'frame.size' (1764 samples), got 1881.`.
+4. Now adapt the `frame.audio.size` in the script to `1764`.
 5. This may have caused buffer underruns too
-6. Now try to set the frame size to a multiple of "1881". For example frame size to 2 x 1881 = 3762. Run the script again.
-7. Liquidsoap logs another mismatch in the logs: `[analog_input:3] Could not set buffer size to 'frame.size' (3762 samples), got 3763.`
-8. Now the value is only off by 1. This might be because of a resolution problem. So now try to set the frame size to 3762 + 1 = 3763
+6. Now try to set the frame size to a multiple of "1764". For example frame size to 2 x 1764 = 3528. Run the script again.
+7. Liquidsoap logs another mismatch in the logs: `[analog_input:3] Could not set buffer size to 'frame.size' (3528 samples), got 3529.`
+8. Now the value is only off by 1. This might be because of a resolution problem. So now try to set the frame size to 3528 + 1 = 3529
 9. Run the script again. Now the logs don't report an error like "Could not not set buffer size" anymore.
-10. Watch the logs for a while. If there are still some buffer underruns try a higher number. Try another multiple by setting frame size to 2 x 3763 = 7526.
+10. Watch the logs for a while. If there are still some buffer underruns try a higher number. Try another multiple by setting frame size to 2 x 3528 = 7056.
 11. Run the script again. If there are no buffer underruns anymore and you hear clean sound without any glitches you have found an optimal value. Also watchout for any "Could not set buffer size" logs, they should be gone too.
 
 The total steps needed and resulting values might be different based on your system and audio interface.
 
-After you have found your own personal frame-size, use the value for the `alsa_frame_size` in the `engine-core.ini` config. Then start the engine and double-check the logs there.
+After you have found your own personal frame-size, use the value for the `frame_audio_size` in the `engine-core.ini` config. Then start the engine and double-check the logs there.
 
 ## Pulse Audio
 
-> *More testing and documentation on PulseAudio usage to be provided at a later stage. Feedback and tips are welcome.*
+> _More testing and documentation on PulseAudio usage to be provided at a later stage. Feedback and tips are welcome._
 
 A system which is already configured for PulseAudio needs no further configuration. The `default` ALSA device is emulated automatically using a PulseAudio ALSA plugin. The signal flow looks like following:
 
@@ -235,12 +234,10 @@ Obviously this is the reason for increased latencies. This should not be an issu
 
     Audio Interface <--> ALSA <--> Pulse Audio <--> Audio Stream
 
-**Documentation:** Find more information in the [*PulseAudio*](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/) documentation.
-
-
+**Documentation:** Find more information in the [_PulseAudio_](https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/) documentation.
 
 ## JACK
 
-> *Supported, but not yet tested. Documentation will be updated as soon we have more information on the actual usage. We are happy for contributions on configuration and best practices.*
+> _Supported, but not yet tested. Documentation will be updated as soon we have more information on the actual usage. We are happy for contributions on configuration and best practices._
 
-**Documentation:** Find more information in the [*JACK Audio Connection Kit*](https://jackaudio.org/) documentation.
+**Documentation:** Find more information in the [_JACK Audio Connection Kit_](https://jackaudio.org/) documentation.
diff --git a/docs/frequently-asked-questions.md b/docs/frequently-asked-questions.md
index ba67e7513ad666895a1bce1f5c86792b4a374276..f5bad074bd660f3d509b8d1cea8bde548e34784f 100755
--- a/docs/frequently-asked-questions.md
+++ b/docs/frequently-asked-questions.md
@@ -1,4 +1,3 @@
-
 # Frequently Asked Questions
 
 <!-- TOC -->
@@ -8,7 +7,7 @@
    2. [How can I find the audio output device IDs, required for settings in engine-core.ini?](#how-can-i-find-the-audio-output-device-ids-required-for-settings-in-engine-coreini)
    3. [I have configured an audio device in my `engine-core.ini` but still hear no sound](#i-have-configured-an-audio-device-in-my-engine-coreini-but-still-hear-no-sound)
    4. [I have configured an audio device in my `docker.engine-core.ini` but still hear no sound](#i-have-configured-an-audio-device-in-my-dockerengine-coreini-but-still-hear-no-sound)
-   5. [I'm getting [clock.wallclock_alsa:2] Error when starting output lineout: Failure("Error while setting open_pcm: No such file or directory")!**](#im-getting-clockwallclock_alsa2-error-when-starting-output-lineout-failureerror-while-setting-open_pcm-no-such-file-or-directory)
+   5. [I'm getting [clock.wallclock_alsa:2] Error when starting output lineout: Failure("Error while setting open_pcm: No such file or directory")!\*\*](#im-getting-clockwallclock_alsa2-error-when-starting-output-lineout-failureerror-while-setting-open_pcm-no-such-file-or-directory)
    6. [How to solve 'Error when starting output output_lineout_0: Failure("Error while setting open_pcm: Device or resource busy")!'?](#how-to-solve-error-when-starting-output-output_lineout_0-failureerror-while-setting-open_pcm-device-or-resource-busy)
    7. [How to avoid stutter, hangs, artifacts or in general glitchy sound?](#how-to-avoid-stutter-hangs-artifacts-or-in-general-glitchy-sound)
    8. [Are there any commands to directly control the playout-server for development, debugging or testing?](#are-there-any-commands-to-directly-control-the-playout-server-for-development-debugging-or-testing)
@@ -32,6 +31,7 @@ Check out the [Audio Device Configuration](docs/audio-device-configuration.md) p
 ## I have configured an audio device in my `engine-core.ini` but still hear no sound
 
 To test if you device is able to output audio at all, independently from Engine Core, try executing `speaker-test`. Also checkout out the `-D` argument to test specific devices. If you system doesn't provided `speaker-test` you have to install or use your preferred way of testing also audio.
+
 ## I have configured an audio device in my `docker.engine-core.ini` but still hear no sound
 
 If you are running Engine Core using Docker, run the aforementioned `speaker-test` from within your docker container by perform following:
@@ -41,12 +41,11 @@ If you are running Engine Core using Docker, run the aforementioned `speaker-tes
 3. Now run `speaker-test`. It that's working, you now know that your audio device is at least available from within Docker and you'll need to further check your Liquidsoap device configuration.
 4. Next you can run `liquidsoap tests/test_alsa_default.liq`. This is a basic script which tries to play the supplied MP3 using the default ALSA device.
 
+## I'm getting [clock.wallclock_alsa:2] Error when starting output lineout: Failure("Error while setting open_pcm: No such file or directory")!\*\*
 
-## I'm getting [clock.wallclock_alsa:2] Error when starting output lineout: Failure("Error while setting open_pcm: No such file or directory")!**
+Assure you have set the correct device ID. To do so check the aforementioned question. Check the audio interface configuration section in `engine-core.ini`. Verify if the default settings `input_device_0` and `output_device_0` are valid device IDs.
 
-Assure you have set the correct device ID. To do so check the aformentioned question. Check the audio interface configuration section in `engine-core.ini`. Verify if the default settings `input_device_0` and `output_device_0` are valid device IDs.
-
-In case your are *not* running Engine Core within Docker, also check if your executing user (è.g. `engineuser`) belongs to the group `audio`.
+In case your are _not_ running Engine Core within Docker, also check if your executing user (è.g. `engineuser`) belongs to the group `audio`.
 
 ## How to solve 'Error when starting output output_lineout_0: Failure("Error while setting open_pcm: Device or resource busy")!'?
 
@@ -58,7 +57,7 @@ This can have various reasons, but first of all it's good to check the `engine-c
 
 **Incorrect ALSA buffer settings**: If the ALSA settings provided by your system are not working cleanly the `engine-core.ini` settings provide to option to override parameters such as `alsa_buffer`. The correct settings are individual to the used soundcard but in general this is a tricky topic. In our case we had more success using PulseAudio or JACK instead. Recommendations are welcome.
 
-**These problems occurr while having Icecast streaming enabled**: Try to reduce the quality of the stream, especially when you are experiencing hangs on the stream. Check your Icecast connection. Is it up and running? Maybe there is some authentication issue or an [Icecast limitation for max clients](ttps://github.com/savonet/liquidsoap/issues/524).
+**These problems occur while having Icecast streaming enabled**: Try to reduce the quality of the stream, especially when you are experiencing hangs on the stream. Check your Icecast connection. Is it up and running? Maybe there is some authentication issue or an [Icecast limitation for max clients](ttps://github.com/savonet/liquidsoap/issues/524).
 
 **The hardware is hitting its limits**: Also check the relevant logs and the system utilization. Are there other processes using up the machines resources? You might even be hitting the performance limit of your hardware. Maybe using a realtime linux kernel could help too.
 
@@ -78,7 +77,7 @@ List all available channels
 
 List all input channels connected to the mixer
 
-`mixer.input`
+`mixer.inputs`
 
 Set the volume of mixer `input 0` to `100%`
 
@@ -88,8 +87,7 @@ Push some audio file to the filesystem `in_filesystem_0`
 
 `in_filesystem_0.push /path/to/your/file.mp3`
 
-
 ## Read More
 
 - [Overview](/README.md)
-- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
+- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
diff --git a/meta.py b/meta.py
deleted file mode 100644
index c6cdd94f5b8a58b332631049b865ed80140d46d6..0000000000000000000000000000000000000000
--- a/meta.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Meta
-__author__ = "David Trattnig and Gottfried Gaisbauer"
-__copyright__ = "Copyright 2017-2020, Aura Engine Team"
-__credits__ = ["Steffen Müller", "Michael Liebler"]
-__license__ = "GNU Affero General Public License (AGPL) Version 3"
-__version__ = "0.9.9"
-__version_info__ = (0, 9, 9)
-__maintainer__ = "David Trattnig"
-__email__ = "david.trattnig@subsquare.at"
-__status__ = "Development"
\ No newline at end of file
diff --git a/run.sh b/run.sh
deleted file mode 100755
index 35e84871e021c609eaea85e3f94106d3ac6b25a6..0000000000000000000000000000000000000000
--- a/run.sh
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/bin/bash
-mode="dev"
-docker="false"
-
-#
-# Run Script for AURA Engine
-#
-# Call with one of these parameters:
-#
-# - dev
-# - prod
-# - debug
-# - log
-
-# - docker:dev
-# - docker:debug
-# - docker:build
-# - docker:push
-#
-
-if [[ $* =~ ^(dev|prod|debug|log)$ ]]; then
-	mode=$1
-fi
-
-if [[ "$1" == *"docker:"* ]]; then
-	docker="true"
-	mode=${1#*:}
-fi
-
-
-echo "[ Run mode=$mode ]"
-echo "[ Docker=$docker ]"
-
-
-
-# +++ DEFAULT COMMANDS +++ #
-
-if [[ $docker == "false" ]]; then
-
-	### Runs Engine Core in Development (Liquidsoap) ###
-
-	if [[ $mode == "dev" ]]; then
-		(cd src && liquidsoap ./engine.liq)
-	fi
-
-	### Runs Engine Core in Production (Liquidsoap) ###
-
-	if [[ $mode == "prod" ]]; then
-		(cd src && liquidsoap ./engine.liq)
-	fi
-
-	### Runs Engine Core (Verbose & debug output) ###
-
-	if [[ $mode == "debug" ]]; then
-		(cd src && liquidsoap  --verbose --debug ./engine.liq)
-	fi
-
-	### Tails the log file only (Used for Docker debugging) ###
-
-	if [[ $mode == "log" ]]; then
-		tail -f logs/engine-core.log
-	fi
-fi
-
-
-# +++ DOCKER COMMANDS +++ #
-
-if [[ $docker == "true" ]]; then
-	BASE_DIR=$(readlink -f .)
-	AUDIO_DIR=$(readlink -f ./audio)
-	echo "Absolute base dir: " $BASE_DIR
-	echo "Absolute audio dir: " $AUDIO_DIR
-
-	### Runs Engine Core ###
-
-	if [[ $mode == "dev" ]]; then
-		exec sudo docker run \
-			--network="host" \
-			--name aura-engine-core \
-			--rm \
-			-u $UID:$GID \
-			-v "$BASE_DIR/config/engine-core.docker.ini":"/srv/config/engine-core.ini":ro \
-			-v "$BASE_DIR/socket":"/srv/socket" \
-			-v "$AUDIO_DIR/source":"/var/audio/source":ro \
-			-v "$AUDIO_DIR/playlist":"/var/audio/playlist":ro \
-			-v "$AUDIO_DIR/station":"/var/audio/station":ro \
-			-v "$BASE_DIR/logs":"/srv/logs" \
-			-v "/etc/asound.conf":"/etc/asound.conf" \
-			--mount type=tmpfs,destination=/tmp \
-			--device /dev/snd \
-			--group-add audio \
-			autoradio/engine-core
-	fi
-
-	### Debugging mode: only tails the log file and enter the container manually ###
-
-	if [[ $mode == "debug" ]]; then
-		exec sudo docker run \
-			--network="host" \
-			--name aura-engine-core \
-			--rm \
-			-u $UID:$GID \
-			-v "$BASE_DIR/config/engine-core.docker.ini":"/srv/config/engine-core.ini":ro \
-			-v "$BASE_DIR/socket":"/srv/socket" \
-			-v "$AUDIO_DIR/source":"/var/audio/source":ro \
-			-v "$AUDIO_DIR/playlist":"/var/audio/playlist":ro \
-			-v "$AUDIO_DIR/station":"/var/audio/station":ro \
-			-v "$BASE_DIR/contrib":"/srv/contrib" \
-			-v "$BASE_DIR/tests":"/srv/tests" \
-			-v "$BASE_DIR/logs":"/srv/logs" \
-			-v "$BASE_DIR/contrib":"/srv/contrib" \
-			-v "$BASE_DIR/tests":"/srv/tests" \
-			-v "/etc/asound.conf":"/etc/asound.conf" \
-			--privileged \
-			--memory=8g --memory-reservation=4g \
-			--cpus=4.0 --cpu-shares=3000 \
-			--mount type=tmpfs,destination=/tmp \
-			--device /dev/snd \
-			--group-add audio \
-			autoradio/engine-core log
-	fi
-
-	### Create Docker Image from local project ###
-
-	if [[ $mode == "build" ]]; then
-		docker build -t autoradio/engine-core .
-	fi
-
-	### Pushes the latest Docker Image to Docker Hub ###
-
-	if [[ $mode == "push" ]]; then
-		docker push autoradio/engine-core
-	fi
-fi
\ No newline at end of file
diff --git a/src/archive/in_fallback_scheduled.liq b/src/archive/in_fallback_scheduled.liq
index 730646c052c6386a177c4851884eaeee109427c4..84d7b4a0022f30e2531a9fffa9e78092bbd25e31 100644
--- a/src/archive/in_fallback_scheduled.liq
+++ b/src/archive/in_fallback_scheduled.liq
@@ -25,16 +25,16 @@ fallback_inputs = ref []
 
 
 # Create Sources
-input_fallback_scheduled_0 = request.equeue(id="in_fallback_scheduled_0")
-input_fallback_scheduled_1 = request.equeue(id="in_fallback_scheduled_1")
+input_fallback_scheduled_0 = request.queue(id="in_fallback_scheduled_0")
+input_fallback_scheduled_1 = request.queue(id="in_fallback_scheduled_1")
 
 # Apply ReplayGain Normalization
 input_fallback_scheduled_0 = amplify(id="in_fallback_scheduled_0", 1., override="replay_gain", input_fallback_scheduled_0)
 input_fallback_scheduled_1 = amplify(id="in_fallback_scheduled_1", 1., override="replay_gain", input_fallback_scheduled_1)
 
 # Add Event Handlers
-input_fallback_scheduled_0 = on_metadata(id="in_fallback_scheduled_0", on_metadata_notification, input_fallback_scheduled_0)
-input_fallback_scheduled_1 = on_metadata(id="in_fallback_scheduled_1", on_metadata_notification, input_fallback_scheduled_1)
+input_fallback_scheduled_0.on_metadata(on_metadata_notification)
+input_fallback_scheduled_1.on_metadata(on_metadata_notification)
 
 # Mixer for more control of scheduled fallbacks
 fallback_mixer = mix(id="mixer_fallback",
@@ -47,8 +47,8 @@ fallback_mixer = mix(id="mixer_fallback",
         )
     )
 
-stripped_fallback_mixer = strip_blank(
-        id="fallback_strip_blank",
+stripped_fallback_mixer = blank.strip(
+        id="fallback_stripped",
         track_sensitive=false,
         max_blank=fallback_max_blank,
         min_noise=fallback_min_noise,
diff --git a/src/archive/unused_fallback.liq b/src/archive/unused_fallback.liq
index 1e531b2f04dcb01b5f0514ad87019e037bf7374d..257d401f0d2ad72581852545b01b721f65340c43 100644
--- a/src/archive/unused_fallback.liq
+++ b/src/archive/unused_fallback.liq
@@ -20,9 +20,9 @@
 
 
 
-# Crossfade between tracks, 
-# taking the respective volume levels 
-# into account in the choice of the 
+# Crossfade between tracks,
+# taking the respective volume levels
+# into account in the choice of the
 # transition.
 # @category Source / Track Processing
 # @param ~start_next   Crossing duration, if any.
@@ -45,25 +45,25 @@ def crossfade (~start_next=5.,~fade_in=3.,
 
   def transition(a,b,ma,mb,sa,sb)
 
-    list.iter(fun(x)-> 
+    list.iter(fun(x)->
        log(level=4,"Before: #{x}"),ma)
-    list.iter(fun(x)-> 
+    list.iter(fun(x)->
        log(level=4,"After : #{x}"),mb)
 
     if
-      # If A and B and not too loud and close, 
+      # If A and B and not too loud and close,
       # fully cross-fade them.
-      a <= medium and 
-      b <= medium and 
+      a <= medium and
+      b <= medium and
       abs(a - b) <= margin
     then
       log("Transition: crossed, fade-in, fade-out.")
       add(fade.out(sa),fade.in(sb))
 
     elsif
-      # If B is significantly louder than A, 
+      # If B is significantly louder than A,
       # only fade-out A.
-      # We don't want to fade almost silent things, 
+      # We don't want to fade almost silent things,
       # ask for >medium.
       b >= a + margin and a >= medium and b <= high
     then
@@ -84,23 +84,23 @@ def crossfade (~start_next=5.,~fade_in=3.,
       log("Transition: crossed, fade-in.")
       add(sa,fade.in(sb))
 
-    # What to do with a loud end and 
+    # What to do with a loud end and
     # a quiet beginning ?
-    # A good idea is to use a jingle to separate 
+    # A good idea is to use a jingle to separate
     # the two tracks, but that's another story.
 
     else
-      # Otherwise, A and B are just too loud 
-      # to overlap nicely, or the difference 
-      # between them is too large and 
-      # overlapping would completely mask one 
+      # Otherwise, A and B are just too loud
+      # to overlap nicely, or the difference
+      # between them is too large and
+      # overlapping would completely mask one
       # of them.
       log("No transition: just sequencing.")
       sequence([sa, sb])
     end
   end
 
-  cross(width=width, duration=start_next, 
+  cross(width=width, duration=start_next,
         conservative=conservative,
         transition,s)
 end
@@ -205,7 +205,7 @@ def fallback_create(~skip=true, name, requestor)
     system('#{list.assoc(default="", "install_dir", ini)}/guru.py --on_play "#{filename}"')
   end
   source = on_metadata(do_meta, source)
-  
+
 
   log("channel created")
 
@@ -220,24 +220,24 @@ end
 
 def create_playlist() =
   log("requesting next song for PLAYLIST")
-  result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for "playlist" --quiet')
+  result = process.read.lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for "playlist" --quiet')
   create_dynamic_playlist(result)
 end
 
 def create_station_fallback() =
-  result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for station --quiet')
+  result = process.read.lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for station --quiet')
   log("next song for STATION fallback is: #{result}")
   create_dynamic_playlist(result)
 end
 
 def create_show_fallback() =
-  result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for show --quiet')
+  result = process.read.lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for show --quiet')
   log("next song for SHOW fallback is: #{result}")
   create_dynamic_playlist(result)
 end
 
 def create_timeslot_fallback() =
-  result = get_process_lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for timeslot --quiet')
+  result = process.read.lines('#{list.assoc(default="", "install_dir", ini)}/guru.py --get-next-file-for timeslot --quiet')
   log("next song for TIMESLOT fallback is: #{result}")
   create_dynamic_playlist(result)
 end
diff --git a/src/archive/unused_record.liq b/src/archive/unused_record.liq
index 50148093dfa8abc55382d7e741deef85f1d646b0..54f3486504cbeb8c81d0c928c159fb8721e5d8fc 100644
--- a/src/archive/unused_record.liq
+++ b/src/archive/unused_record.liq
@@ -51,7 +51,7 @@ def stop_dump() =
 end
 
 def on_start()
-    recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
+    recordingfile := list.hd(default="", process.read.lines("date +#{!filenamepattern}"))
 end
 
 # Wav header fixen und ggf. die Aufzeichnung beenden
@@ -61,10 +61,10 @@ def on_close(filename)
 
     # if list.assoc(default="", "rec_filetype", ini) == 'wav'
     #     # Korrekten WAV-Header schreiben
-    #     system("qwavheaderdump -F #{filename}")
+    #     process.run("qwavheaderdump -F #{filename}")
 
     # Naechsten Dateinamen vormerken
-    recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
+    recordingfile := list.hd(default="", process.read.lines("date +#{!filenamepattern}"))
 end
 
 # Der input wie oben definiert
@@ -108,7 +108,7 @@ end
 def currecording()
     curfile = !recordingfile
     if curfile != "" then
-        percent_done = default="", get_process_lines("echo $(($(stat -c%s "^curfile^")/3174777))"))
+        percent_done = default="", process.read.lines("echo $(($(stat -c%s "^curfile^")/3174777))"))
         "#{curfile}, #{percent_done}%"
     else
         "Nothing is being recorded now"
@@ -122,11 +122,11 @@ def start_dump() =
     # don't record twice is_record_active
     if !is_record_active == false then
         is_record_active := true
-        
+
         log('starting to record')
 
         record = get_output()
-        
+
         log('record defined')
         # Die Stopfunkton zeigt nun auf die Shutdown-Funktion der aktuellen Source
         stop_f := fun () -> begin
@@ -138,7 +138,7 @@ def start_dump() =
                                 # Variable zurücksetzen
                                 recordingfile := ""
                             end
-    else 
+    else
         log("recorder already active")
     end
 end
diff --git a/src/archive/unused_recorder.liq b/src/archive/unused_recorder.liq
index b62ca5c448f21359248ff73adabe4688fd72153d..fedda545420b411e02a34a6b9a62b667802d8119 100644
--- a/src/archive/unused_recorder.liq
+++ b/src/archive/unused_recorder.liq
@@ -41,10 +41,10 @@ def start_wav_output(recfile, filenamepattern, recorder_number)
     ignore(samplesize)
 
 #    def on_start()
-#        recfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
+#        recfile := list.hd(default="", process.read.lines("date +#{!filenamepattern}"))
 #    end
 #    def on_close(filename)
-#        recordingfile := list.hd(default="", get_process_lines("date +#{!filenamepattern}"))
+#        recordingfile := list.hd(default="", process.read.lines("date +#{!filenamepattern}"))
 #    end
 
     print(channels)
@@ -55,13 +55,8 @@ def start_wav_output(recfile, filenamepattern, recorder_number)
 
     output.dummy(id="wav_dummy_recording", blank())
 
-#    if channels == 2 then
-#        output.file(id="recorder", %wav(stereo=true, channels=2, samplesize=8, header=true), perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, !filenamepattern, audio_to_stereo(output_source))
-##        out_wav(output_source)
-#    else
-#        output.file(id="recorder", %wav(stereo=true, channels=1, samplesize=8, header=true), perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, !filenamepattern, output_source)
-    #    out_wav(output_source)
-#    end
+    # output.file(id="recorder", %wav(stereo=true, channels=1, samplesize=8, header=true), perm = 0o664, on_start=on_start, on_close=on_close, reopen_when={ int_of_float(gettimeofday()/60.) mod duration == 0 }, !filenamepattern, output_source)
+    # out_wav(output_source)
 
     #if channels == 2 then
     #    if samplesize < 12 then
@@ -89,10 +84,10 @@ def start_flac_output(recorder_number, filenamepattern, duration)
     recfile = ref ''
 
     def on_start()
-        recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
+        recfile := list.hd(default="", process.read.lines("date +#{filenamepattern}"))
     end
     def on_close(filename)
-        recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
+        recfile := list.hd(default="", process.read.lines("date +#{filenamepattern}"))
     end
 
     # dumbass liquidsoap cannot handle one output definition for mono and stereo
@@ -149,7 +144,7 @@ end
 def currecording()
     curfile = !recordingfile
     if curfile != "" then
-        bytes_written = list.hd(default="", get_process_lines("echo $(($(stat -c%s "^curfile^")))"))
+        bytes_written = list.hd(default="", process.read.lines("echo $(($(stat -c%s "^curfile^")))"))
         "#{curfile}, #{bytes_written}B"
     else
         ""
@@ -168,7 +163,7 @@ end
 # shows current file and how many bytes were written so far
 def currecording(recfile)
     if recfile != "" then
-        bytes_written = list.hd(default="", get_process_lines("echo $(($(stat -c%s "^recfile^")))"))
+        bytes_written = list.hd(default="", process.read.lines("echo $(($(stat -c%s "^recfile^")))"))
         "#{recfile}, #{bytes_written}B"
     else
         ""
@@ -183,11 +178,11 @@ def start_recorder(folder, duration, encoding, bitrate, channels, filenamepatter
     recfile = ref ''
     def on_start()
         is_recording := true
-        recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
+        recfile := list.hd(default="", process.read.lines("date +#{filenamepattern}"))
     end
     def on_close(filename)
         is_recording := false
-        recfile := list.hd(default="", get_process_lines("date +#{filenamepattern}"))
+        recfile := list.hd(default="", process.read.lines("date +#{filenamepattern}"))
     end
     def on_stop()
         is_recording := false
diff --git a/src/engine.liq b/src/engine.liq
index 33209529f3ef2ff54f4697fccbad4fbad938082d..6627542a4fec2ee391fc6a7a0d24ca5d0ef1dc7d 100644
--- a/src/engine.liq
+++ b/src/engine.liq
@@ -20,32 +20,24 @@
 
 
 
+# Initialize
 icecast_vorbis_metadata = false
-inputs = ref []
-
+inputs = ref ([])
+engine_state = {
+    is_fallback = ref(false)
+}
 
 # Load settings from ini file
 %include "settings.liq"
 
-# Include some functions
+# Include library functions
 %include "library.liq"
 
-#####################################
-#              EVENTS               #
-#####################################
+# Include dependency-free functions
+%include "functions.liq"
 
-# Called when some new metadata info is available
-def on_metadata_notification(meta) =
-    filename = meta["filename"]
-    track_duration = request.duration(filename)
-    json_data = json_of(meta, compact=true)
-    json_data = url.encode(json_data)
-    json_data = '{ "action": "on_metadata", "meta": "#{json_data}", "track_duration": "#{track_duration}" }'
-    # There's currently an issue with Liquidsoap http.post requests (should be gone with Liquidsoap 2):
-    #headers = [("Content-Type","application/json; charset=utf-8")]
-    #ignore(http.post(headers=headers, data="#{json_data}", "localhost:1337"))
-    ignore(system("curl -X POST -H 'Content-Type: application/json' --data '#{json_data}' #{engine_control}"))
-end
+# Handle events
+%include "events.liq"
 
 #####################################
 #              INPUTS               #
@@ -68,23 +60,20 @@ end
 #             ROUTING               #
 #####################################
 
-
 mixer = mix(id="mixer",
         list.append(
             [
                 input_filesystem_0,
                 input_filesystem_1,
                 input_http_0,
-                input_http_1,
-                input_https_0,
-                input_https_1
+                input_http_1
             ],
             !inputs
         )
     )
 
-stripped_stream = strip_blank(
-        id="strip_blank",
+stripped_stream = blank.strip(
+        id="stripped_stream",
         track_sensitive=false,
         max_blank=fallback_max_blank,
         min_noise=fallback_min_noise,
@@ -94,6 +83,7 @@ stripped_stream = strip_blank(
 
 output_source = attach_fallback_source(stripped_stream)
 
+
 #####################################
 #             OUTPUTS               #
 #####################################
diff --git a/src/events.liq b/src/events.liq
new file mode 100644
index 0000000000000000000000000000000000000000..8ff9817504b130f449cbabc26f58de2475d1e041
--- /dev/null
+++ b/src/events.liq
@@ -0,0 +1,67 @@
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+
+# Called when some new metadata info is available
+def on_metadata_notification(meta) =
+    # FIXME For some reason stream channels do not pass the `source` property in meta
+    channel_name = meta["source"]
+    source_type = eval_source_type(channel_name)
+
+    log(level=3, label="metadata", "Raw metadata before POST:\n\n #{meta}\n\n")
+
+    show_name = ref(list.assoc(default="", "show_name", meta))
+    show_id = ref(list.assoc(default="", "show_id", meta))
+    timeslot_id = list.assoc(default="-1", "timeslot_id", meta)
+    playlist_id = list.assoc(default="-1", "playlist_id", meta)
+    playlist_track_num = list.assoc(default="", "playlist_item", meta)
+    track_start = list.assoc(default=meta["track_start"], "on_air", meta)
+    track_duration = get_meta_track_duration(meta)
+    track_type = eval_track_type(meta["track_type"], meta["source"])
+    track_artist = list.assoc(default=meta["track_artist"], "artist", meta)
+    track_album = list.assoc(default=meta["track_album"], "album", meta)
+    track_title = list.assoc(default=meta["track_title"], "title", meta)
+
+    if source_type == "fallback" then
+        log(level=3, label="metadata", "Detected FALLBACK channel `#{channel_name}` playing \
+            (Show ID: #{!fallback_show_id})")
+        show_name := !fallback_show_name
+        show_id := !fallback_show_id
+    end
+
+    playlog = [
+        ("log_source", engine_id),
+        ("show_name", !show_name),
+        ("show_id", !show_id),
+        ("timeslot_id", timeslot_id),
+        ("playlist_id", playlist_id),
+        ("track_type", track_type),
+        ("track_start", track_start),
+        ("track_duration", "#{track_duration}"),
+        ("track_title", track_title),
+        ("track_album", track_album),
+        ("track_artist", track_artist),
+        ("track_num", playlist_track_num)
+    ]
+    if playlog["show_id"] == "" then
+        log(level=3, label="metadata", "Skip posting playlog because of missing show ID!")
+    else
+        post_playlog(engine_api_playlog, playlog)
+    end
+end
\ No newline at end of file
diff --git a/src/functions.liq b/src/functions.liq
new file mode 100644
index 0000000000000000000000000000000000000000..a9c523fc03c279b76a0842e493ff2e64c9f8798a
--- /dev/null
+++ b/src/functions.liq
@@ -0,0 +1,236 @@
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-2020 - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+
+#
+# METADATA
+#
+
+# Merge with previous metadata, avoid duplicates
+def merge_meta(last_meta, meta) =
+    log(level=5, label="metadata", "Merge | last metadata: #{last_meta}")
+    log(level=5, label="metadata", "Merge | current metadata: #{meta}")
+    merged = ref(last_meta)
+
+    def add_meta_entry(entry) =
+        let (k, _) = entry
+        if list.assoc.mem(k, !merged) then
+            log(level=5, label="metadata", "Remove existing entry #{entry}")
+            merged := list.assoc.remove(k, !merged)
+        end
+        merged := list.add(entry, !merged)
+    end
+
+    list.iter(add_meta_entry, (meta))
+    log(level=5, label="metadata", "Merge | resulting metadata: #{!merged}")
+    !merged
+end
+
+# Checks for the existence of show-specific metadata
+def has_show_meta(meta) =
+    list.assoc.mem(engine_meta_key_show_id, meta) ? true : false
+end
+
+# Checks if the show ID in two metadata objects matches
+def is_same_show(last_meta, current_meta) =
+    last_meta[engine_meta_key_show_id] == current_meta[engine_meta_key_show_id] ? true : false
+end
+
+# Checks if the current show metadata is same as the previous one
+def is_same_show(last_meta, current_meta) =
+    if has_show_meta(last_meta) then
+        if not has_show_meta(current_meta) then
+            # No current show meta: handle as same show
+            true
+        elsif is_same_show(last_meta, current_meta) then
+            true
+        else
+            # A new show has started
+            false
+        end
+    else
+        # Last show has no show meta
+        if not has_show_meta(current_meta) then
+            # And the current one either: handle as same show
+            true
+        else
+            # Treat missing last show info as the same show
+            true
+        end
+    end
+end
+
+# Handles either insert or merge & insert of metadata, depending on the show ID
+def do_meta_insert(last_meta_callback, insert_meta_callback, meta) =
+    lm = (last_meta_callback() ?? [])
+    if is_same_show(lm, meta) then
+        lm = (last_meta_callback() ?? [])
+        merged = merge_meta(lm, meta)
+        insert_meta_callback(merged)
+    else
+        insert_meta_callback(meta)
+    end
+end
+
+# Builds a metadata object from data passed as JSON
+def build_metadata(json_string) =
+    let json.parse (data : {
+            show_name: string,
+            show_id: int,
+            timeslot_id: int,
+            playlist_id: int,
+            playlist_item: string,
+            track_type: int,
+            track_start: string?,
+            track_duration: int?,
+            track_title: string?,
+            track_album: string?,
+            track_artist: string?
+        }) = json_string
+    [
+        ("show_name", data.show_name),
+        ("show_id", "#{data.show_id}"),
+        ("timeslot_id", "#{data.timeslot_id}"),
+        ("playlist_id", "#{data.playlist_id}"),
+        ("playlist_item", "#{data.playlist_item}"),
+        ("track_type", "#{data.track_type}"),
+        ("track_start", "#{data.track_start}"),
+        ("track_duration", "#{data.track_duration}"),
+        ("track_title", "#{data.track_title}"),
+        ("track_album", "#{data.track_album}"),
+        ("track_artist", "#{data.track_artist}"),
+    ]
+end
+
+# Reads the track duration
+#   a.) when available from the file
+#   b.) as a fallback from the meta field "track_duration"
+#
+# Returns
+#   (int) duration in seconds
+def get_meta_track_duration(meta) =
+    track_duration = int_of_float(request.duration(meta["filename"]))
+    if track_duration != -1 then
+        track_duration
+    else
+        int_of_string(meta["track_duration"])
+    end
+end
+
+# Posts a playlog to the Engine API
+def post_playlog(api_url, data) =
+    json_data = json()
+
+    json_data.add("log_source", int_of_string(list.assoc("log_source", data)))
+    json_data.add("show_name", list.assoc("show_name", data))
+    json_data.add("show_id", int_of_string(list.assoc("show_id", data)))
+    json_data.add("timeslot_id", int_of_string(list.assoc("timeslot_id", data)))
+    json_data.add("playlist_id", int_of_string(list.assoc("playlist_id", data)))
+    json_data.add("track_type", int_of_string(list.assoc("track_type", data)))
+    json_data.add("track_start", list.assoc("track_start", data))
+    json_data.add("track_duration", int_of_string(list.assoc("track_duration", data)))
+    json_data.add("track_title", list.assoc("track_title", data))
+    json_data.add("track_album", list.assoc("track_album", data))
+    json_data.add("track_artist", list.assoc("track_artist", data))
+    if list.assoc("track_num", data) != "" then
+        json_data.add("track_num", int_of_string(list.assoc("track_num", data)))
+    end
+
+    playlog = json.stringify(json_data)
+    log("Posting playlog to '#{api_url}': #{playlog}")
+    headers = [("Content-Type","application/json")]
+    result = http.post(headers=headers, data="#{playlog}", "#{api_url}")
+
+    if result.status_code < 400 then
+        log("Successfully posted playlog to Engine API.")
+    else
+        log("ERROR during playlog POST: #{result.status_code} | #{result.status_message}")
+    end
+end
+
+
+#
+# SOURCE
+#
+
+
+# Evaluate the type of source
+#
+# Returns
+#   "fallback", "queue", "stream", "analog_in", "unknown_source"
+def eval_source_type(source_id) =
+    type_mapping = [
+        ("fallback_folder", "fallback"),
+        ("fallback_playlist", "fallback"),
+        ("in_filesystem_0", "queue"),
+        ("in_filesystem_0", "queue"),
+        ("in_http_0", "stream"),
+        ("in_http_1", "stream"),
+        ("linein_0", "analog_in"),
+        ("linein_1", "analog_in"),
+        ("linein_2", "analog_in"),
+        ("linein_3", "analog_in"),
+        ("linein_4", "analog_in")
+    ]
+    let source_type = list.assoc(
+        default="unknown_source",
+        source_id,
+        type_mapping
+    )
+    source_type
+end
+
+# Evaluates the track type based on the given:
+#   a.) "meta.track_type" passed as annotation, and if not available on
+#   b.) "engine_current_track_type" passed via server function
+#   c.) "meta.source" and if not available on
+#   d.) configured default track type setting
+#
+# Returns:
+#   0=QUEUE/FILE, 1=STREAM, 2=LIVE ANALOG, 3=PLAYLIST
+#
+def eval_track_type(meta_track_type, meta_source) =
+    type_mapping = [
+        ("fallback_folder", "0"),
+        ("fallback_playlist", "3"),
+        ("in_filesystem_0", "0"),
+        ("in_filesystem_0", "0"),
+        ("in_http_0", "1"),
+        ("in_http_1", "1"),
+        ("linein_0", "2"),
+        ("linein_1", "2"),
+        ("linein_2", "2"),
+        ("linein_3", "2"),
+        ("linein_4", "2")
+    ]
+
+    track_type = list.assoc(
+        default=engine_default_track_type,
+        meta_source,
+        type_mapping
+    )
+
+    if meta_track_type != "" then
+        meta_track_type
+    elsif track_type != "" then
+        track_type
+    else
+        engine_default_track_type
+    end
+end
\ No newline at end of file
diff --git a/src/in_fallback.liq b/src/in_fallback.liq
index 55635baaf6b22365efc50a12094c6c4669ff1b7f..61f6f9899645b87550300eb2f78724c93d86e2de 100644
--- a/src/in_fallback.liq
+++ b/src/in_fallback.liq
@@ -22,8 +22,8 @@
 #             SOURCES               #
 #####################################
 
-fallback_folder = ref blank()
-fallback_playlist = ref blank()
+fallback_folder = ref (blank())
+fallback_playlist = ref (blank())
 
 
 #####################################
@@ -33,7 +33,7 @@ fallback_playlist = ref blank()
 # When some regular playout is happening and it is returned to the fallback,
 # we don't want to resume the previous fallback track
 def on_track_change(s) =
-
+    ignore(s)
     if fallback_type == "folder" then
         log("Skipping track in fallback folder ...")
         source.skip(!fallback_folder)
@@ -43,6 +43,16 @@ def on_track_change(s) =
     end
 end
 
+# Called when a fallback source is actively playing
+def on_fallback_notify(metadata) =
+    engine_state.is_fallback := true
+    on_metadata_notification(metadata)
+end
+
+# Called when a fallback source stopped playing
+def on_fallback_leave_notify() =
+    engine_state.is_fallback := false
+end
 
 #####################################
 #            FUNCTIONS              #
@@ -59,12 +69,14 @@ def attach_fallback_source(main_stream)
                 reload=fallback_station_dir_reload,
                 reload_mode="seconds")
 
-            s = amplify(id="fallback_folder", 1., override="replay_gain", s)
-            s = on_metadata(id="fallback_playlist", on_metadata_notification, s)
+            s = replaygain(s)
+            s = source.on_track(id="fallback_folder", s, on_fallback_notify)
             s = mksafe(s)
+            source.on_leave(s, on_fallback_leave_notify)
 
             fallback_folder := s
-            [ on_track(on_track_change, main_stream), !fallback_folder ]
+            main_stream.on_track(on_track_change)
+            [ (main_stream:source), (!fallback_folder) ]
 
         elsif fallback_type == "playlist" then
             log("Fallback Type: PLAYLIST")
@@ -76,15 +88,17 @@ def attach_fallback_source(main_stream)
                 reload_mode="watch",
                 reload=0)
 
-            s = amplify(id="fallback_playlist", 1., override="replay_gain", s)
-            s = on_metadata(id="fallback_playlist", on_metadata_notification, s)
+            s = replaygain(s)
+            s = source.on_track(id="fallback_playlist", s, on_fallback_notify)
             s = mksafe(s)
+            source.on_leave(s, on_fallback_leave_notify)
 
             fallback_playlist := s
-            [ on_track(on_track_change, main_stream), !fallback_playlist ]
+            main_stream.on_track(on_track_change)
+            [ (main_stream:source), (!fallback_playlist:source) ]
         else
             log("Fallback Type: NONE")
-            [ mksafe(main_stream) ]
+            [ (mksafe(main_stream):source) ]
         end
 
     fallback(
diff --git a/src/in_queue.liq b/src/in_queue.liq
index 6181080b0376f0eac09be39dad01a7702e10c061..d7a6b8b562fcffcb2df2b359607d99f991ff9b08 100644
--- a/src/in_queue.liq
+++ b/src/in_queue.liq
@@ -23,17 +23,16 @@
 #####################################
 
 # Create Sources
-input_filesystem_0 = request.equeue(id="in_filesystem_0")
-input_filesystem_1 = request.equeue(id="in_filesystem_1")
+input_filesystem_0 = request.queue(id="in_filesystem_0")
+input_filesystem_1 = request.queue(id="in_filesystem_1")
 
-# Apply ReplayGain Normalization
-input_filesystem_0 = amplify(id="in_filesystem_0", 1., override="replay_gain", input_filesystem_0)
-input_filesystem_1 = amplify(id="in_filesystem_1", 1., override="replay_gain", input_filesystem_1)
+# Apply ReplayGain normalization, if metadata available
+input_filesystem_0 = replaygain(input_filesystem_0)
+input_filesystem_1 = replaygain(input_filesystem_1)
 
 # Add Event Handlers
-input_filesystem_0 = on_metadata(id="in_filesystem_0", on_metadata_notification, input_filesystem_0)
-input_filesystem_1 = on_metadata(id="in_filesystem_1", on_metadata_notification, input_filesystem_1)
-
+input_filesystem_0.on_metadata(on_metadata_notification)
+input_filesystem_1.on_metadata(on_metadata_notification)
 
 #####################################
 #          SERVER FUNCTIONS         #
@@ -46,10 +45,11 @@ server.register(namespace=source.id(input_filesystem_0),
         usage="clear",
         "clear",
 
-    fun (s) -> 
-        begin 
-            clear_queue(input_filesystem_0) 
-            "Clearing done."     
+    fun (s) ->
+        begin
+            ignore(s)
+            clear_queue(input_filesystem_0)
+            "Clearing done."
         end
     )
 
@@ -59,10 +59,11 @@ server.register(namespace=source.id(input_filesystem_1),
         usage="clear",
         "clear",
 
-    fun (s) -> 
-        begin 
-            clear_queue(input_filesystem_1) 
-            "Clearing done." 
+    fun (s) ->
+        begin
+            ignore(s)
+            clear_queue(input_filesystem_1)
+            "Clearing done."
         end
     )
 
@@ -72,7 +73,7 @@ server.register(namespace = source.id(input_filesystem_0),
     usage = "seek <duration in seconds>",
     "seek",
 
-    fun (t) -> 
+    fun (t) ->
         begin
             log("Seeking #{t} sec")
             t = float_of_string(default=0.,t)
@@ -87,7 +88,7 @@ server.register(namespace = source.id(input_filesystem_1),
     usage = "seek <duration in seconds>",
     "seek",
 
-    fun (t) -> 
+    fun (t) ->
         begin
             log("Seeking #{t} sec")
             t = float_of_string(default=0.,t)
diff --git a/src/in_soundcard.liq b/src/in_soundcard.liq
index cf98414216676389dc8c55f0fefa6fc6d21e4701..af361492ebbdeaa66adccaef34e36ce11ea52135 100644
--- a/src/in_soundcard.liq
+++ b/src/in_soundcard.liq
@@ -18,27 +18,88 @@
 
 
 
+in_line_insert_callbacks = ref([])
+
+
+#####################################
+#            FUNCTIONS              #
+#####################################
+
+usage_set_track_metadata = "set_track_metadata { \
+        \"show_name\": \"Analog Ambient\", \
+        \"show_id\": 111, \
+        \"timeslot_id\": 222, \
+        \"playlist_id\": 333, \
+        \"playlist_item\": \"\", \
+        \"track_type\": 2, \
+        \"track_start\": \"2022/02/22 22:02:22\", \
+        \"track_duration\": 808, \
+        \"track_title\": \"Lorem Ipsum\", \
+        \"track_album\": \"\", \
+        \"track_artist\": \"\" \
+    }"
+
+def get_input_line(source_id, device) =
+    if use_alsa == true then
+        input.alsa(id=source_id, device=device, bufferize=!alsa_buffered_input)
+    elsif use_jack == true then
+        input.jack(id=source_id)
+    else
+        input.pulseaudio(id=source_id, client="aura_engine_#{source_id}")
+    end
+end
+
+
+def create_input_line(source_id, device) =
+    in_line = get_input_line(source_id, device)
+
+    # Enable metadata insertion and store callbacks
+    in_line = insert_metadata(id=source_id, in_line)
+    in_line_insert_callbacks := list.append([("#{source_id}", in_line.insert_metadata)], !in_line_insert_callbacks)
+
+    # Old Liquidsoap approach:
+    # in_line.on_metadata(on_metadata_notification)
+    # New Liquidsoap 2.1 approach, which should not trigger when inactive:
+    in_line = source.on_metadata(id=source_id, in_line, on_metadata_notification)
+    inputs := list.append([in_line], !inputs)
+
+    server.register(namespace=source.id(in_line),
+        description="Sets the current track metadata for a channel",
+        usage=usage_set_track_metadata,
+        "set_track_metadata",
+
+        fun (json_string) -> begin
+            log("Received JSON to set track metadata on channel \
+                'linein_0' to:\n #{json_string}")
+            metadata = build_metadata(json_string)
+            insert_callback = list.assoc(source_id, !in_line_insert_callbacks)
+            insert_callback(metadata)
+            "OK"
+        end
+    )
+end
+
+
+#####################################
+#             SOURCES               #
+#####################################
+
 if a0_in != "" then
-    # we can ignore the result, since it is stored in the list 'inputs'
-    set_input(a0_in, "linein_0")
+    create_input_line("linein_0", a0_in)
 end
 
 if a1_in != "" then
-    ignore(set_input(a1_in, "linein_1"))
+    create_input_line("linein_1", a1_in)
 end
 
 if a2_in != "" then
-    ignore(set_input(a2_in, "linein_2"))
+    create_input_line("linein_2", a2_in)
 end
 
 if a3_in != "" then
-    ignore(set_input(a3_in, "linein_3"))
+    create_input_line("linein_3", a3_in)
 end
 
 if a4_in != "" then
-    ignore(set_input(a4_in, "linein_4"))
-
-#    input_4 = ref output.dummy(blank())
-#    set_input(input_4, a4_in, "linein_4")
-#    inputs := list.append([!input_4], !inputs)
-end
\ No newline at end of file
+    create_input_line("linein_4", a4_in)
+end
diff --git a/src/in_stream.liq b/src/in_stream.liq
index 129510607d1417e39577440dd3414557a2ad5712..3b2cba3626446ccf79091995843854e7415274d8 100644
--- a/src/in_stream.liq
+++ b/src/in_stream.liq
@@ -17,31 +17,112 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-
-
 # Pre-population of stream input sources, as Liquidsoap needs it to initialize this input.
 # This is overwritten as soon as some other Stream is scheduled.
+# #TODO Check if this is still required for Liquidsoap 2
+
+
+
+#####################################
+#             SOURCES               #
+#####################################
+
+# STREAM 1
+input_http_0 = input.http(
+    id="in_http_0",
+    max_buffer=input_stream_max_buffer,
+    timeout=input_stream_timeout,
+    start=false,
+    ""
+)
+
+# Enable metadata insertion and store callbacks
+input_http_0 = insert_metadata(id="in_http_0", input_http_0)
+in_stream_insert_meta_0 = input_http_0.insert_metadata
+in_stream_last_meta_0 = input_http_0.last_metadata
+
+# Map metadata
+def on_stream_meta_0(meta) =
+    lm = (in_stream_last_meta_0() ?? [])
+    merge_meta(lm, meta)
+end
+input_http_0 = metadata.map(id="in_http_0", on_stream_meta_0, input_http_0)
+
+# Old Liquidsoap approach:
+# input_http_0.on_metadata(on_metadata_notification)
+
+# New Liquidsoap 2.1 approach, which should not trigger when inactive,
+# but actually does trigger before stream is ready:
+input_http_0 = source.on_metadata(id="in_http_0", input_http_0, on_metadata_notification)
+
+#####################################
+
+# STREAM 2
+input_http_1 = input.http(
+    id="in_http_1",
+    max_buffer=input_stream_max_buffer,
+    timeout=input_stream_timeout,
+    start=false,
+    ""
+)
+
+# Enable metadata insertion and store callbacks
+input_http_1 = insert_metadata(id="in_http_1", input_http_1)
+in_stream_insert_meta_1 = input_http_1.insert_metadata
+in_stream_last_meta_1 = input_http_1.last_metadata
+
+# Old approach:
+# input_http_1.on_metadata(on_metadata_notification)
+
+# New Liquidsoap 2.1 approach, which should not trigger when inactive,
+# but actually does trigger before stream is ready:
+input_http_1 = source.on_metadata(id="in_http_1", input_http_1, on_metadata_notification)
+
 
-# http_starturl = "http://stream01.kapper.net:8001/live.mp3"
-# http_starturl = "http://stream.fro.at/fro-128.ogg"
-http_starturl = "http://trance.out.airtime.pro:8000/trance_a"
-# http_starturl = "http://chill.out.airtime.pro:8000/chill_a"
-# http_starturl = "http://212.89.182.114:8008/frf"
+#####################################
+#          SERVER FUNCTIONS         #
+#####################################
 
-https_starturl = "https://securestream.o94.at/live.mp3"
-# https_starturl = "https://live.helsinki.at:8088/live160.ogg"
-# https_starturl = "https://stream.fro.at/fro-128.ogg"
+usage_set_track_metadata = "set_track_metadata { \
+        \"show_name\": \"Laser Music 2000\", \
+        \"show_id\": 123, \
+        \"timeslot_id\": 123, \
+        \"playlist_id\": 123, \
+        \"playlist_item\": \"\", \
+        \"track_type\": 1, \
+        \"track_start\": \"2022/02/22 22:02:22\", \
+        \"track_duration\": 303, \
+        \"track_title\": \"Bar\", \
+        \"track_album\": \"\", \
+        \"track_artist\": \"Foo\" \
+    }"
 
+# Set current track metadata for "input_http_0"
+server.register(namespace=source.id(input_http_0),
+    description="Sets the current track metadata for a channel",
+    usage=usage_set_track_metadata,
+    "set_track_metadata",
 
+    fun (json_string) -> begin
+        log("Received JSON to set track metadata on channel \
+            '#{source.id(input_http_0)}' to:\n #{json_string}")
+        metadata = build_metadata(json_string)
+        do_meta_insert(in_stream_last_meta_0, in_stream_insert_meta_0, metadata)
+        "OK"
+    end
+)
 
-input_http_0 = input.http(id="in_http_0", buffer=input_stream_buffer, max=60.0, timeout=60.0, autostart=false, http_starturl)
-input_http_1 = input.http(id="in_http_1", buffer=input_stream_buffer, max=60.0, timeout=60.0, autostart=false, http_starturl)
-input_https_0 = input.https(id="in_https_0", buffer=input_stream_buffer, max=60.0, timeout=60.0, autostart=false, https_starturl)
-input_https_1 = input.https(id="in_https_1", buffer=input_stream_buffer, max=60.0, timeout=60.0, autostart=false, https_starturl)
+# Set current track metadata for "input_http_1"
+server.register(namespace=source.id(input_http_1),
+    description="Sets the current track metadata for a channel",
+    usage=usage_set_track_metadata,
+    "set_track_metadata",
 
-# Route input stream to an dummy output to avoid buffer-overrun messages
-# output.dummy(id="SPAM_HTTP_OUTPUT_0", fallible=true, input_http_0)
-# output.dummy(id="SPAM_HTTP_OUTPUT_1", fallible=true, input_http_1)
-# output.dummy(id="SPAM_HTTPS_OUTPUT_0", fallible=true, input_https_0)
-# output.dummy(id="SPAM_HTTPS_OUTPUT_1", fallible=true, input_https_1)
-# output.dummy(blank())
\ No newline at end of file
+    fun (json_string) -> begin
+        log("Received JSON to set track metadata on channel \
+            '#{source.id(input_http_1)}' to:\n #{json_string}")
+        metadata = build_metadata(json_string)
+        do_meta_insert(in_stream_last_meta_1, in_stream_insert_meta_1, metadata)
+        "OK"
+    end
+)
\ No newline at end of file
diff --git a/src/library.liq b/src/library.liq
index 18d8714e1034fcdef9caeb5ff063628476990674..e395f50c3a30a13c5d25a9eb0f77bacdf75aada2 100644
--- a/src/library.liq
+++ b/src/library.liq
@@ -46,8 +46,8 @@ end
 #####################################
 
 def create_dynamic_source(~skip=true, name)
-    log("Creating dynamic source '#{name}'")
-    track = get_process_lines("cat "^string.quote("next-track.txt"))
+    log("Creating dynamic source '#{name}' (skip:#{skip})")
+    track = process.read.lines("cat "^string.quote("next-track.txt"))
     track = list.hd(default="", track)
     dyn_source = request.dynamic.list(
       { [request.create(track)] })
@@ -60,7 +60,7 @@ end
 #####################
 
 def stream_to_icecast(id, encoding, bitrate, host, port, pass, mount_point, url, description, genre, user, stream, streamnumber, connected, name, channels) =
-    source = ref stream
+    source = ref (stream)
 
     def on_error(msg)
         connected := "false"
@@ -72,68 +72,32 @@ def stream_to_icecast(id, encoding, bitrate, host, port, pass, mount_point, url,
         log("Successfully connected to stream_#{streamnumber}")
     end
 
-    stereo = (int_of_string(channels) >= 2)
-
-    user_ref = ref user
+    user_ref = ref (user)
     if user == "" then
         user_ref := "source"
     end
 
-    # Liquidsoap cannot handle one output definition for mono and stereo
-    output_icecast_mono   = output.icecast(id = id, host = host, port = port, password = pass, mount = mount_point, fallible = true, url = url, description = description, name = name, genre = genre, user = !user_ref, on_error = on_error, on_connect = on_connect, icy_metadata = "true")
-    output_icecast_stereo = output.icecast(id = id, host = host, port = port, password = pass, mount = mount_point, fallible = true, url = url, description = description, name = name, genre = genre, user = !user_ref, on_error = on_error, on_connect = on_connect, icy_metadata = "true")
-
-#    %ifencoder %aac
-#    if encoding == "aac" then
-#        log("ENABLING AAC to ICECAST")
-#        %include "outgoing_streams/aac.liq"
-#    end
-#    %endif
-#
-#    %ifencoder %flac
-#    if encoding == "flac" then
-#        log("ENABLING FLAC to ICECAST")
-#        %include "outgoing_streams/flac.liq"
-#    end
-#    %endif
-
+    # TODO Refactor all outgoing stream formats this way
+    let stereo = (int_of_string(channels) >= 2)
+    let format = %mp3(bitrate = 128, stereo = true)
     if encoding == "mp3" then
-        log("ENABLING Mp3 to ICECAST")
         %include "outgoing_streams/mp3.liq"
     end
-
     if encoding == "ogg" then
-        log("ENABLING OGG to ICECAST")
         %include "outgoing_streams/ogg.liq"
     end
 
-#    %ifencoder %opus
-#    if encoding == "opus" then
-#        log("ENABLING OPUS to ICECAST")
-#        %include "outgoing_streams/opus.liq"
-#    end
-#    %endif
-end
-
-###########
-# line in #
-###########
-
-def set_input(device, name) =
-    if use_alsa == true then
-        alsa_in = input.alsa(id=name, device=a0_in, clock_safe=false, bufferize=!alsa_use_buffer)
-        inputs := list.append([alsa_in], !inputs)
-
-    elsif use_jack == true then
-        jack_in = input.jack(id=name, clock_safe=false)
-        inputs := list.append([jack_in], !inputs)
+    log("Icecast output format: #{encoding} #{bitrate}")
+    # Liquidsoap cannot handle one output definition for mono and stereo
+    # FIXME should be working since Liquidsoap 2
+    output_icecast_mono   = output.icecast(id = id, host = host, port = port, password = pass, mount = mount_point, fallible = true, url = url, description = description, name = name, genre = genre, user = !user_ref, on_error = on_error, on_connect = on_connect, icy_metadata = "true", format, !source)
+    output_icecast_stereo = output.icecast(id = id, host = host, port = port, password = pass, mount = mount_point, fallible = true, url = url, description = description, name = name, genre = genre, user = !user_ref, on_error = on_error, on_connect = on_connect, icy_metadata = "true", format, !source)
+    ignore(output_icecast_mono)
+    ignore(output_icecast_stereo)
 
-    else
-        pulse_in = input.pulseaudio(id=name, client="AuraEngine Line IN")
-        inputs := list.append([pulse_in], !inputs)
-    end
 end
 
+
 ############
 # line out #
 ############
@@ -142,11 +106,7 @@ def get_output(source, device, name) =
     if device != "" then
         if use_alsa == true then
             log("--- Set ALSA Output ---")
-            if device == "default" then
-                output.alsa(id="lineout", bufferize=!alsa_use_buffer, source)
-            else
-                output.alsa(id=name, device=device, bufferize=!alsa_use_buffer, source)
-            end
+            output.alsa(id=name, device=device, bufferize=!alsa_buffered_output, source)
         elsif use_jack == true then
             log("--- Set JACK AUDIO Output ---")
             output.jack(id=name, source)
@@ -155,7 +115,8 @@ def get_output(source, device, name) =
             output.pulseaudio(id=name, client="AuraEngine Line OUT", source)
         end
     else
-        log("OUTPUT DUMMY")
-        output.dummy(id=name^"_DUMMY", blank())
+        output_name = "#{name}_DUMMY"
+        log("Using dummy output: #{output_name}")
+        output.dummy(id=output_name, blank())
     end
 end
diff --git a/src/out_stream.liq b/src/out_stream.liq
index f5ca38e22c67d69273a90bfafcd7816596763b90..015f38406e781b3bd3c01cecac3a6dd8574d7c6d 100644
--- a/src/out_stream.liq
+++ b/src/out_stream.liq
@@ -91,36 +91,51 @@ s4_genre = list.assoc(default="", "stream_4_genre", ini)
 s4_name = list.assoc(default="", "stream_4_name", ini)
 s4_channels = list.assoc(default="", "stream_4_channels", ini)
 
-s0_connected = ref ''
-s1_connected = ref ''
-s2_connected = ref ''
-s3_connected = ref ''
-s4_connected = ref ''
+s0_connected = ref ('')
+s1_connected = ref ('')
+s2_connected = ref ('')
+s3_connected = ref ('')
+s4_connected = ref ('')
 
 if s0_enable == true then
     # enable connection status for that stream
-    server.register(namespace="out_http_0", "connected", fun (s) -> begin !s0_connected end)
+    server.register(namespace="out_http_0", "connected", fun (s) -> begin
+        ignore(s)
+        !s0_connected
+    end)
 
     # aaand stream
     stream_to_icecast("out_http_0", s0_encoding, s0_bitrate, s0_host, s0_port, s0_pass, s0_mount, s0_url, s0_desc, s0_genre, s0_user, output_source, "0", s0_connected, s0_name, s0_channels)
 end
 
 if s1_enable == true then
-    server.register(namespace="out_http_1", "connected", fun (s) -> begin !s1_connected end)
+    server.register(namespace="out_http_1", "connected", fun (s) -> begin
+        ignore(s)
+        !s1_connected
+    end)
     stream_to_icecast("out_http_1", s1_encoding, s1_bitrate, s1_host, s1_port, s1_pass, s1_mount, s1_url, s1_desc, s1_genre, s1_user, output_source, "1", s1_connected, s1_name, s1_channels)
 end
 
 if s2_enable == true then
-    server.register(namespace="out_http_2", "connected", fun (s) -> begin !s2_connected end)
+    server.register(namespace="out_http_2", "connected", fun (s) -> begin
+        ignore(s)
+        !s2_connected
+    end)
     stream_to_icecast("out_http_2", s2_encoding, s2_bitrate, s2_host, s2_port, s2_pass, s2_mount, s2_url, s2_desc, s2_genre, s2_user, output_source, "2", s2_connected, s2_name, s2_channels)
 end
 
 if s3_enable == true then
-    server.register(namespace="out_http_3", "connected", fun (s) -> begin !s3_connected end)
+    server.register(namespace="out_http_3", "connected", fun (s) -> begin
+        ignore(s)
+        !s3_connected
+    end)
     stream_to_icecast("out_http_3", s3_encoding, s3_bitrate, s3_host, s3_port, s3_pass, s3_mount, s3_url, s3_desc, s3_genre, s3_user, output_source, "3", s3_connected, s3_name, s3_channels)
 end
 
 if s4_enable == true then
-    server.register(namespace="out_http_4", "connected", fun (s) -> begin !s4_connected end)
+    server.register(namespace="out_http_4", "connected", fun (s) -> begin
+        ignore(s)
+        !s4_connected
+    end)
     stream_to_icecast("out_http_4", s4_encoding, s4_bitrate, s4_host, s4_port, s4_pass, s4_mount, s4_url, s4_desc, s4_genre, s4_user, output_source, "4", s4_connected, s4_name, s4_channels)
 end
diff --git a/src/outgoing_streams/mp3.liq b/src/outgoing_streams/mp3.liq
index 70218feb23b94ce3dd6bf6615353864656e5e96a..8f8ce70d74cb107df99546f1310807c6c3e83045 100644
--- a/src/outgoing_streams/mp3.liq
+++ b/src/outgoing_streams/mp3.liq
@@ -19,69 +19,91 @@
 
 if bitrate == 24 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 24, stereo = true), !source))
+        format = %mp3(bitrate = 24, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 24, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 24, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 32 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 32, stereo = true), !source))
+        format = %mp3(bitrate = 32, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 32, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 32, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 48 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 48, stereo = true), !source))
+        format = %mp3(bitrate = 48, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 48, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 48, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 64 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 64, stereo = true), !source))
+        format = %mp3(bitrate = 64, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 64, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 64, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 96 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 96, stereo = true), !source))
+        format = %mp3(bitrate = 96, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 96, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 96, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 128 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 128, stereo = true), !source))
+        format = %mp3(bitrate = 128, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 128, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 128, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 160 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 160, stereo = true), !source))
+        format = %mp3(bitrate = 160, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 160, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 160, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 192 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 192, stereo = true), !source))
+        format = %mp3(bitrate = 192, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 192, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 192, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 224 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 224, stereo = true), !source))
+        format = %mp3(bitrate = 224, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 224, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 224, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 256 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 256, stereo = true), !source))
+        format = %mp3(bitrate = 256, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 256, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 256, stereo = false)
+        ignore(format)
     end
 elsif bitrate == 320 then
     if stereo then
-        ignore(output_icecast_stereo(%mp3(bitrate = 320, stereo = true), !source))
+        format = %mp3(bitrate = 320, stereo = true)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%mp3(bitrate = 320, stereo = false), mean(!source)))
+        format = %mp3(bitrate = 320, stereo = false)
+        ignore(format)
     end
 end
 
diff --git a/src/outgoing_streams/ogg.liq b/src/outgoing_streams/ogg.liq
index a8a6e624ef82c7c7be5018b2482ebe5014c2c948..7c790d1f76a078887914af93b3aca06b345d6108 100644
--- a/src/outgoing_streams/ogg.liq
+++ b/src/outgoing_streams/ogg.liq
@@ -23,57 +23,75 @@ end
 
 if bitrate == 24 or bitrate == 32 or bitrate == 48 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=-0.1, channels = 2), !source))
+        format = %vorbis(quality=-0.1, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=-0.1, channels = 1), mean(!source)))
+        format = %vorbis(quality=-0.1, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 64 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0, channels = 2), !source))
+        format = %vorbis(quality=0, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0, channels = 1), mean(!source)))
+        format = %vorbis(quality=0, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 96 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.2, channels = 2), !source))
+        format = %vorbis(quality=0.2, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.2, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.2, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 128 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.4, channels = 2), !source))
+        format = %vorbis(quality=0.4, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.4, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.4, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 160 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.5, channels = 2), !source))
+        format = %vorbis(quality=0.5, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.5, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.5, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 192 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.6, channels = 2), !source))
+        format = %vorbis(quality=0.6, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.6, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.6, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 224 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.7, channels = 2), !source))
+        format = %vorbis(quality=0.7, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.7, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.7, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 256 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.8, channels = 2), !source))
+        format = %vorbis(quality=0.8, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.8, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.8, channels = 1)
+        ignore(format)
     end
 elsif bitrate == 320 then
     if stereo then
-        ignore(output_icecast_stereo(%vorbis(quality=0.9, channels = 2), !source))
+        format = %vorbis(quality=0.9, channels = 2)
+        ignore(format)
     else
-        ignore(output_icecast_mono(%vorbis(quality=0.9, channels = 1), mean(!source)))
+        format = %vorbis(quality=0.9, channels = 1)
+        ignore(format)
     end
 end
 
diff --git a/src/readini.liq b/src/readini.liq
index fc71cb8111eec0be70258778209e84a571491262..64a3b2d87df87616db3186a4854d0553f8ec1813 100644
--- a/src/readini.liq
+++ b/src/readini.liq
@@ -22,31 +22,36 @@
 # Read INI File #
 #################
 
-
-
-debug = false
+debug = ref(false)
 
 def read_ini(file)
   # read ini file
-  settings_file_content = get_process_lines("cat "^file )
+  settings_file_content = process.read.lines("cat "^file )
   # one entry stored as ["setting_name", "setting"]
-  settings_map = list.map(string.split(separator="="), settings_file_content)
+  # settings_map = list.map(string.split(separator="="), settings_file_content)
+
+  def split_line(line)
+    string.split(line, separator="=")
+  end
+  settings_map = list.map(split_line, settings_file_content)
+
+  # settings_map = list.map(string.split("#{settings_file_content}", separator="="))
 
   def filter_pair(setting_pair) =
-    if debug then
+    if !debug then
       print(" +++ IN FILTER_PAIR +++")
     end
 
     # get head of settings_pair
     setting_name = list.hd(default="", setting_pair)
-    if debug then
+    if !debug then
       print(" -- setting_name:")
       print(setting_name)
     end
 
     # settings in ini are with '"', so read them with high comma
     setting_unfiltered = list.nth(default="", setting_pair, 1)
-    if debug then
+    if !debug then
       print(" -- setting_unfiltered:")
       print(setting_unfiltered)
     end
@@ -54,13 +59,13 @@ def read_ini(file)
     # filter high comma out. why the hell comes an array ["1", setting] returned?
     # the filter patterns are perl regex
     setting = string.extract(pattern='"(.*)"', setting_unfiltered)
-    if debug then
+    if !debug then
       print(" -- setting ( after string.extract):")
-      print(setting['1'])
+      print(setting[1])
     end
 
-    filtered_pair = [(setting_name, setting['1'])]
-    if debug then
+    filtered_pair = [(setting_name, setting[1])]
+    if !debug then
       print(" -- filter_pair returning: --")
       print(filtered_pair)
     end
@@ -70,7 +75,7 @@ def read_ini(file)
   end
 
   def filter_map(filled_list, next_element) =
-#    if debug then
+#    if !debug then
 #      print(" +++ IN FILTER_MAP +++")
 #      print(" .. length of filled_list: ")
 #      print(list.length(filled_list))
@@ -80,7 +85,7 @@ def read_ini(file)
 
     # the normal case: settingname and its setting
     if list.length(next_element) >= 2 then
-      if debug then
+      if !debug then
         print(" ===> LENGTH list to insert in settings_list is equal TWO! <===")
         print(" -- next_element")
         print(next_element)
@@ -93,7 +98,7 @@ def read_ini(file)
       list.append(setting_pair, filled_list)
     else
       if list.length(next_element) >= 1 then
-        if debug then
+        if !debug then
           print(" ===> LENGTH of list to insert in settings_list is equal ONE! <===")
           print(" -- next_element")
           print(next_element)
@@ -103,7 +108,7 @@ def read_ini(file)
         list.append([(list.hd(default="",next_element), "")], filled_list)
       else
 
-        if debug then
+        if !debug then
           print(" ===> LENGTH of list to insert in settings_list is greater then two or less than one <===")
           print(" -- next_element")
           print(next_element)
@@ -116,7 +121,7 @@ def read_ini(file)
   end
 
   # get install dir
-  pwd = get_process_lines("pwd") # returns an array
+  pwd = process.read.lines("pwd") # returns an array
   absolute_path = list.hd(default="", pwd) # grabs head of array (pwd only returns one line which is our desired result)
   install_dir = '"' ^ path.dirname(path.dirname(absolute_path)) ^ '"' # pre and append " for filter_pair function. otherwise this setting would be lost through regex and fold function below would be imho much more difficult to read
 
@@ -134,14 +139,14 @@ def read_ini(file)
 
   #settings = list.fold(filter_map, [], settings_map)
 
-  if debug then
+  if !debug then
     print(settings)
   end
 
   settings
 end
 
-if debug then
+if !debug then
     ini = read_ini("/etc/aura/engine-core.ini")
     print(ini)
 
diff --git a/src/serverfunctions.liq b/src/serverfunctions.liq
index 73e51d6599558bfb62648113cde7cf7c35a46e95..619c7a0bbfef67b880c38ec3244298b6072f9ca6 100644
--- a/src/serverfunctions.liq
+++ b/src/serverfunctions.liq
@@ -18,6 +18,162 @@
 
 
 
+#
+# Namespace: aura_engine
+#
+
+# Get state of the inputs/outputs as JSON
+server.register(namespace = "aura_engine",
+    description="Get status info (uptime and fallback state)",
+    usage="status",
+    "status",
+    fun(s) -> begin
+        ignore(s)
+        uptime = list.nth(server.execute("uptime"), 0)
+        json_data = json()
+        json_data.add("uptime", uptime)
+        json_data.add("is_fallback", !engine_state.is_fallback)
+        json.stringify(json_data)
+    end
+)
+
+# Updates engine config
+server.register(namespace="aura_engine",
+    description="Update the engine configuration",
+    usage="update_config { \
+        \"fallback_show_id\": \"-1\", \
+        \"fallback_show_name\": \"Station Fallback\" \
+     }",
+    "update_config",
+
+    fun (s) -> begin
+        log("Received JSON to update config: #{s}")
+        let json.parse (data : {
+                fallback_show_id: int,
+                fallback_show_name: string
+            }) = s
+
+        fallback_show_id := "#{data.fallback_show_id}"
+        log("Set Fallback Show ID to '#{!fallback_show_id}'")
+        fallback_show_name := data.fallback_show_name
+        log("Set Fallback Show Name to '#{!fallback_show_name}'")
+        "OK"
+    end
+)
+
+# Get version
+server.register(namespace="aura_engine",
+    description="Return the version of Engine Core",
+    usage="version",
+    "version",
+    fun (s) -> begin
+        ignore(s)
+        engine_version
+    end
+)
+
+# Shutdown server
+server.register(namespace="aura_engine",
+    description="Shutdown play-out server",
+    usage="stop",
+    "stop",
+    fun (s) -> begin
+        ignore(s)
+        shutdown(code=0)
+        "OK"
+    end
+)
+
+
+#
+# Namespace: mixer
+#
+
+# Get connection state of outgoing streams #TODO refactor
+server.register(namespace="mixer",
+    description="Return all active outputs",
+    usage="outputs",
+    "outputs",
+    fun (s) -> begin
+        ignore(s)
+        log("executing: mixer.outputs")
+
+        so = []
+        so = s0_enable ? list.add(("out_line_0", ("connected", "#{!s0_connected")), so) : so
+        so = s1_enable ? list.add(("out_line_1", ("connected", "#{!s1_connected")), so) : so
+        so = s2_enable ? list.add(("out_line_2", ("connected", "#{!s2_connected")), so) : so
+        so = s3_enable ? list.add(("out_line_3", ("connected", "#{!s3_connected")), so) : so
+        so = s4_enable ? list.add(("out_line_4", ("connected", "#{!s4_connected")), so) : so
+
+        lo = []
+        lo = a0_out != '' ? list.add("out_line_0", lo) : lo
+        lo = a1_out != '' ? list.add("out_line_1", lo) : lo
+        lo = a2_out != '' ? list.add("out_line_2", lo) : lo
+        lo = a3_out != '' ? list.add("out_line_3", lo) : lo
+        lo = a4_out != '' ? list.add("out_line_4", lo) : lo
+
+        json_data = json()
+        json_data.add("line", lo)
+        json_data.add("stream", so)
+        json.stringify(json_data)
+    end
+)
+
+# Activate a source by selecting it and setting the volume to 100 (or vice versa)
+server.register(namespace = "mixer",
+    description = "Select a source and set the volume to 100",
+    usage = "activate <source number> <true|false>",
+    "activate",
+    fun(p) -> begin
+        params=string.split(separator=" ", p)
+        if list.length(params) < 2 then
+            print(p)
+            "Usage: mixer.activate <source nb> <true|false>"
+        else
+            source_number = list.nth(default="0", params, 0)
+            source_enable = list.nth(default="false", params, 1)
+
+            if source_enable == "true" then
+                r = server.execute("mixer.select #{source_number} true")
+                print(r)
+                r = server.execute("mixer.volume #{source_number} 1")
+                print(r)
+            else
+                r = server.execute("mixer.volume #{source_number} 0")
+                print(r)
+                r = server.execute("mixer.select #{source_number} false")
+                print(r)
+            end
+            "OK"
+        end
+    end
+)
+
+
+#
+# Namespace: misc #TODO: refactor
+#
+
+def fadeTo(source_number) =
+    if source_number == "" then
+        print(source_number)
+        "Usage: mixer.fadeto <source nb> #{source_number}"
+    else
+        r = server.execute("mixer.select #{source_number} true")
+        print(r)
+        "OK"
+    end
+end
+
+# enable fadeTo for the mixer
+server.register(namespace = "mixer",
+    description = "is fading from one mixer input to another",
+    usage = "fadeto <source number>",
+    "fadeto",
+    fadeTo
+)
+
+
 def icy_update(v) =
 
   # Parse the argument
@@ -32,7 +188,7 @@ def icy_update(v) =
   end
   meta = list.fold(split,[],l)
 
-  # Update metadata 
+  # Update metadata
   if s0_enable == true then
     icy.update_metadata(
         mount=s0_mount,
@@ -88,148 +244,10 @@ def icy_update(v) =
         )
   end
 
-  "Done!"
+  "OK"
 end
 
 server.register("update", namespace="metadata",
                  description="Update metadata",
                  usage="update title=foo,album=bar, ...",
-                 icy_update)
-
-
-# shutdown server function
-#server.register(namespace='server',
-#    description="shutdown server",
-#    usage="stop",
-#    "stop",
-#    fun(x,y) -> shutdown )
-
-
-
-#
-# to reduce complexity of lqs => query 'mixer.inputs' over socket and parse it in python
-#server.register(namespace="auraengine",
-#    "enabled_lineins",
-#    fun (s) -> begin
-#        log("auraengine.enabled_lineins")
-#        "0:#{!linein_0_enabled}, 1:#{!linein_1_enabled}, 2:#{!linein_2_enabled}, 3:#{!linein_3_enabled}, 4:#{!linein_4_enabled}"
-#    end
-#)
-
-#server.register(namespace="auraengine",
-#    "enabled_lineouts",
-#    fun(s) -> begin
-#        log("auraengine.enabled_lineouts")
-#        "0:#{!lineout_0_enabled}, 1:#{!lineout_1_enabled}, 2:#{!lineout_2_enabled}, 3:#{!lineout_3_enabled}, 4:#{!lineout_4_enabled}"
-#    end
-#)
-
-# are outgoing streams connected?
-server.register(namespace="auraengine",
-    description="returns if outgoing streams are connected",
-    usage="out_streams_connected",
-    "out_streams_connected",
-    fun (s) -> begin
-        log("streams.connection_status")
-        "0:#{!s0_connected}, 1:#{!s1_connected}, 2:#{!s2_connected}, 3:#{!s3_connected}, 4:#{!s4_connected}"
-    end
-)
-
-# return a state of the inputs/outputs of the soundserver as JSON
-server.register(namespace = "auraengine",
-    description="returns enabled lineouts/lineins, connected outgoing streams, and recorder. Also returns fallbacksettings.",
-    usage="state",
-    "state",
-    fun(s) -> begin
-        log("auraengine.state")
-
-        ret = '{'
-        ret = ret^'"streams": {'
-        ret = ret^'"stream_0": {"enabled": #{s0_enable}, "connected": #{!s0_connected}},'
-        ret = ret^'"stream_1": {"enabled": #{s1_enable}, "connected": #{!s1_connected}},'
-        ret = ret^'"stream_2": {"enabled": #{s2_enable}, "connected": #{!s2_connected}},'
-        ret = ret^'"stream_3": {"enabled": #{s3_enable}, "connected": #{!s3_connected}},'
-        ret = ret^'"stream_4": {"enabled": #{s4_enable}, "connected": #{!s4_connected}}'
-        ret = ret^'},'
-        ret = ret^'"linein": {'
-        ret = ret^'"linein_0": {"enabled": #{a0_in != ""}},'
-        ret = ret^'"linein_1": {"enabled": #{a1_in != ""}},'
-        ret = ret^'"linein_2": {"enabled": #{a2_in != ""}},'
-        ret = ret^'"linein_3": {"enabled": #{a3_in != ""}},'
-        ret = ret^'"linein_4": {"enabled": #{a4_in != ""}}'
-        ret = ret^'},'
-        ret = ret^'"lineout": {'
-        ret = ret^'"lineout_0": {"enabled": #{a0_out != ""}},'
-        ret = ret^'"lineout_1": {"enabled": #{a1_out != ""}},'
-        ret = ret^'"lineout_2": {"enabled": #{a2_out != ""}},'
-        ret = ret^'"lineout_3": {"enabled": #{a3_out != ""}},'
-        ret = ret^'"lineout_4": {"enabled": #{a4_out != ""}}'
-        ret = ret^'}'
-        ret = ret^'}'
-        ret
-
-        # outgoing streams enabled?
-        #ret = "stream_0_enabled:#{!s0_enable}, stream_1_enabled:#{!s1_enable}, stream_2_enabled:#{!s2_enable}, stream_3_enabled:#{!s3_enable}, stream_4_enabled:#{!s4_enable}, "       
-        #ret = ret^"linein_0_enabled:#{a0_in != ''}, linein_1_enabled:#{a1_in != ''}, linein_2_enabled:#{a2_in != ''}, linein_3_enabled:#{a3_in != ''}, linein_4_enabled:#{a4_in != ''}, "
-        #ret = ret^"lineout_0_enabled:#{a0_out != ''}, lineout_1_enabled:#{a1_out != ''}, lineout_2_enabled:#{a2_out != ''}, lineout_3_enabled:#{a3_out != ''}, lineout_4_enabled:#{a4_out != ''}, "
-        #ret = ret^"fallback_max_blank:#{fallback_max_blank}, fallback_min_noise:#{fallback_min_noise}, fallback_threshold:#{fallback_threshold}"
-
-    end
-)
-
-
-def fadeTo(source_number) =
-    if source_number == "" then
-        print(source_number)
-        "Usage: mixer.fadeto <source nb> #{source_number}"
-    else
-        r = server.execute("mixer.select #{source_number} true")
-        print(r)
-        "Donee!"
-    end
-end
-
-# enable fadeTo for the mixer
-server.register(namespace = "mixer",
-    description = "is fading from one mixer input to another",
-    usage = "fadeto <source number>",
-    "fadeto",
-    fadeTo
-)
-
-ignore(fade_in_time)
-ignore(fade_out_time)
-
-
-# Activate a source by selecting it and setting the volume to 100 (or vice versa)
-
-def activate(p) =
-    params=string.split(separator=" ", p) 
-    if list.length(params) < 2 then
-        print(p)
-        "Usage: mixer.activate <source nb> <true|false>"
-    else
-        source_number = list.nth(default="0", params, 0)
-        source_enable = list.nth(default="false", params, 1)
-
-        if source_enable == "true" then
-            r = server.execute("mixer.select #{source_number} true")
-            print(r)
-            r = server.execute("mixer.volume #{source_number} 100")
-            print(r)
-        else
-            r = server.execute("mixer.volume #{source_number} 0")
-            print(r)
-            r = server.execute("mixer.select #{source_number} false")
-            print(r)
-        end
-        "Done!"
-    end
-end
-
-server.register(namespace = "mixer",
-    description = "is selecting a source and setting the volume to 100",
-    usage = "activate <source nb> <true|false>",
-    "activate",
-    activate
-)
+                 icy_update)
\ No newline at end of file
diff --git a/src/settings.liq b/src/settings.liq
index 1bb48f34371dea21fe926afdf05280c8ddf24d8d..37b221977040203a89d08c644ea84c0ab15dc0be 100644
--- a/src/settings.liq
+++ b/src/settings.liq
@@ -20,44 +20,51 @@
 # READ INI FILE
 %include "readini.liq"
 default_config = "../config/engine-core.ini"
-config = list.hd(default=default_config, get_process_lines("ls /etc/aura/engine-core.ini"))
+config = list.hd(default=default_config, process.read.lines("ls /etc/aura/engine-core.ini"))
 log("Config file used: #{config}")
 ini = read_ini(config)
 engine_config_folder = string.split(separator="/engine-core.ini", config)
 engine_config_folder = list.nth(default="../config/", engine_config_folder, 0)
 
+# VERSION CHECK
+liq_min_version = list.assoc(default="2.1", "liquidsoap_min_version", ini)
+if not liquidsoap.version.at_least(liq_min_version) then
+    print("AURA Engine Core requires at least Liquidsoap v#{liq_min_version}")
+    exit(1)
+end
+
 # ALLOW LIQUIDSOAP RUN AS ROOT
 lqs_allow_root = list.assoc(default="false", "liquidsoap_as_root", ini)
-if lqs_allow_root == "true" then
-    set("init.allow_root", true)
-end
 
 # BASICS
-set("console.colorize","always")
-#set("request.grace_time",2.)
+settings.console.colorize.set("always")
+#settings.request.grace_time.set(2.)
 
 # TELNET SETTINGS
-set("server.telnet", true)
-set("server.telnet.bind_addr", "0.0.0.0")
-set("server.telnet.port", 1234)
+server_timeout = float_of_string(list.assoc(default="60.", "server_timeout", ini))
+settings.server.timeout.set(server_timeout)
+settings.server.telnet.set(true)
+settings.server.telnet.bind_addr.set("0.0.0.0")
+settings.server.telnet.port.set(1234)
 
 # LOGGING SETTINGS
-set("log.stdout", true)
-set("log.file", true)
+settings.log.stdout.set(true)
+settings.log.file.set(true)
 log_level = int_of_string(list.assoc(default="3", "log_level", ini))
-set("log.level", log_level)
-log_dir = list.assoc(default="../logs", "logdir", ini)
-set("log.file.path", "#{log_dir}/engine-core.log")
+settings.log.level.set(log_level)
+log_dir = list.assoc(default="../logs", "log_dir", ini)
+settings.log.file.path.set("#{log_dir}/engine-core.log")
 
 # SOCKET SETTINGS
-set("server.socket", true)
-socket_dir = list.assoc(default="../socket", "socketdir", ini)
-set("server.socket.path", "#{socket_dir}/<script>.sock")
-engine_control = list.assoc(default="localhost:1337", "engine_control_host", ini)
+settings.server.socket.set(true)
+socket_dir = list.assoc(default="../socket", "socket_dir", ini)
+socket_file = "#{socket_dir}/engine.sock"
+settings.server.socket.path.set(socket_file)
+# engine_control = list.assoc(default="localhost:1337", "engine_control_host", ini)
 
 # SOUND CARD SETTINGS
-set("audio.converter.samplerate.converters",["ffmpeg","libsamplerate","native"])
-set("decoder.file_decoders",["META","WAV","AIFF","FLAC","AAC","MP4","OGG","MAD"])
+# settings.audio.converter.samplerate.converters.set(["ffmpeg","libsamplerate","native"])
+# settings.decoder.decoders.set(["META","WAV","AIFF","FLAC","AAC","MP4","OGG","MAD"])
 
 #print(ini)
 a0_in = list.assoc(default="", "input_device_0", ini)
@@ -71,78 +78,119 @@ a2_out = list.assoc(default="", "output_device_2", ini)
 a3_out = list.assoc(default="", "output_device_3", ini)
 a4_out = list.assoc(default="", "output_device_4", ini)
 
-input_stream_buffer = float_of_string(list.assoc(default="3.0", "input_stream_buffer", ini))
+input_stream_max_buffer = float_of_string(list.assoc(default="5.0", "input_stream_max_buffer", ini))
+input_stream_timeout = float_of_string(list.assoc(default="10.0", "input_stream_timeout", ini))
 
 # AUDIO AND PLAYLIST SOURCES
-audio_playlist_folder = "#{engine_config_folder}/playlists"
+audio_playlist_folder = "../audio/playlists"
 audio_playlist_folder = list.assoc(default=audio_playlist_folder, "audio_playlist_folder", ini)
 
 # FALLBACK SETTINGS
+fallback_show_name = ref(list.assoc(default="Station Fallback", "fallback_show_name", ini))
+fallback_show_id = ref(list.assoc(default="-1", "fallback_show_id", ini))
 fallback_type = list.assoc(default="folder", "fallback_type", ini)
-
 fallback_station_playlist_name = "station-fallback-playlist.m3u"
 fallback_station_playlist_name = list.assoc(default=fallback_station_playlist_name, "fallback_music_playlist", ini)
 fallback_station_playlist_path = "#{audio_playlist_folder}/#{fallback_station_playlist_name}"
-
-fallback_station_dir = list.assoc(default="/var/audio/fallback", "fallback_music_folder", ini)
+fallback_station_dir = list.assoc(default="../audio/fallback", "fallback_music_folder", ini)
 fallback_station_dir_reload = int_of_string(list.assoc(default="300", "fallback_music_folder_reload", ini))
-fallback_max_blank = float_of_string(list.assoc(default="", "fallback_max_blank", ini))
-fallback_min_noise = float_of_string(list.assoc(default="", "fallback_min_noise", ini))
-fallback_threshold = float_of_string(list.assoc(default="", "fallback_threshold", ini))
+fallback_max_blank = float_of_string(list.assoc(default="15.", "fallback_max_blank", ini))
+fallback_min_noise = float_of_string(list.assoc(default="0.", "fallback_min_noise", ini))
+fallback_threshold = float_of_string(list.assoc(default="-80.", "fallback_threshold", ini))
 
 # FADING SETTINGS
-fade_in_time = list.assoc(default="", "fade_in_time", ini) #int_of_string(list.assoc(default="", "fade_in_time", ini))
-fade_out_time = list.assoc(default="", "fade_out_time", ini) #int_of_string(list.assoc(default="", "fade_out_time", ini))
+# fade_in_time = list.assoc(default="", "fade_in_time", ini) #int_of_string(list.assoc(default="", "fade_in_time", ini))
+# fade_out_time = list.assoc(default="", "fade_out_time", ini) #int_of_string(list.assoc(default="", "fade_out_time", ini))
 
 # ALSA / pulse settings
 soundsystem = list.assoc(default="", "soundsystem", ini)
-use_alsa    = soundsystem == "alsa"
-use_jack    = soundsystem == "jack"
+use_alsa = soundsystem == "alsa"
+use_jack = soundsystem == "jack"
 
-print("**************************************************************************************")
-print("     AURA ENGINE:CORE - LIQUIDSOAP SETTINGS                                           ")
-print("**************************************************************************************")
+engine_version = process.read("cat ../VERSION")
+print("######################################################################################")
+print("AURA Engine:Core v#{engine_version} starting ...")
+print("######################################################################################")
 
-alsa_use_buffer = ref true
+print("System Settings:")
 
-if use_alsa then
-    alsa_use_buffer := bool_of_string(list.assoc(default="true", "alsa_use_buffer", ini))
-    print("ALSA: I/O bufferize=#{!alsa_use_buffer}")
+if lqs_allow_root == "true" then
+    print("\tAllow Liquidsoap running as root user")
+    settings.init.allow_root.set(true)
+end
+
+if socket_dir != "" then
+    print("\tSocket location: #{socket_file}")
+else
+    print("\tCRITICAL: No socket directory set!")
+end
+
+print("\nAudio Settings:")
+
+# ReplayGain Settings
+enable_replaygain_resolver = bool_of_string(list.assoc(default="false", "enable_replaygain_resolver", ini))
+if enable_replaygain_resolver == true then
+    enable_replaygain_metadata()
+    print("\tReplayGain resolver enabled")
+end
+
+# Frame Settings
+frame_audio_sample_rate = int_of_string(list.assoc(default="", "frame_audio_sample_rate", ini))
+frame_duration = float_of_string(list.assoc(default="", "frame_duration", ini))
+frame_audio_size = int_of_string(list.assoc(default="", "frame_audio_size", ini))
 
-    alsa_sample_rate = int_of_string(list.assoc(default="", "alsa_sample_rate", ini))
-    alsa_frame_duration = float_of_string(list.assoc(default="", "alsa_frame_duration", ini))
-    alsa_frame_size = int_of_string(list.assoc(default="", "alsa_frame_size", ini))
+if frame_audio_sample_rate > 0 then
+    print("\tframe.audio.samplerate = #{frame_audio_sample_rate} Hz")
+    settings.frame.audio.samplerate.set(frame_audio_sample_rate)
+end
+if frame_duration > 0.0 then
+    print("\tframe.duration = #{frame_duration} seconds")
+    settings.frame.duration.set(frame_duration)
+end
+if frame_audio_size > 0 then
+    print("\tframe.audio.size = #{frame_audio_size} ticks")
+    settings.frame.audio.size.set(frame_audio_size)
+end
+
+alsa_buffered_input = ref (true)
+alsa_buffered_output = ref (true)
+
+if use_alsa then
+    print("\n\nALSA Settings:")
+    alsa_buffered_input := bool_of_string(list.assoc(default="true", "alsa_buffered_input", ini))
+    alsa_buffered_output := bool_of_string(list.assoc(default="true", "alsa_buffered_output", ini))
     alsa_buffer = int_of_string(list.assoc(default="", "alsa_buffer", ini))
     alsa_buffer_length = int_of_string(list.assoc(default="", "alsa_buffer_length", ini))
     alsa_periods = int_of_string(list.assoc(default="", "alsa_periods", ini))
 
-    if alsa_sample_rate > 0 then
-        print("ALSA: 'frame.audio.samplerate' to #{alsa_sample_rate}Hz")
-        set("frame.audio.samplerate", alsa_sample_rate)
-    end
-    if alsa_frame_duration > 0.0 then
-        print("ALSA: 'frame.duration' = #{alsa_frame_duration}s")
-        set("frame.duration", alsa_frame_duration)
-    end
-    if alsa_frame_size > 0 then
-        print("ALSA: 'frame.audio.size' = #{alsa_frame_size}")
-        set("frame.audio.size", alsa_frame_size)
-    end
+    print("\talsa_buffered_input = #{!alsa_buffered_input}")
+    print("\talsa_buffered_output = #{!alsa_buffered_output}")
+
     if alsa_buffer > 0 then
-        print("ALSA: 'alsa.alsa_buffer' = #{alsa_buffer}")
-        set("alsa.alsa_buffer", alsa_buffer)
+        print("\tInternal buffer size (alsa.alsa_buffer) = #{alsa_buffer}")
+        settings.alsa.alsa_buffer.set(alsa_buffer)
     end
     if alsa_buffer_length > 0 then
-        print("ALSA: 'alsa.buffer_length' = #{alsa_buffer_length}")
-        set("alsa.buffer_length", alsa_buffer_length)
+        print("\tBuffer size, in frames (alsa.buffer_length) = #{alsa_buffer_length}")
+        settings.alsa.buffer_length.set(alsa_buffer_length)
     end
     if alsa_periods > 0 then
-        print("ALSA: 'alsa.periods' = #{alsa_periods}")
-        set("alsa.periods", alsa_periods)
+        print("\tPeriods (alsa.periods) = #{alsa_periods}")
+        settings.alsa.periods.set(alsa_periods)
     end
 end
 
-print("Engine Configuration Folder: '#{engine_config_folder}'")
-print("Station Fallback Playlist: '#{fallback_station_playlist_path}'")
-print("Station Fallback Directory: '#{fallback_station_dir}'")
-print("**************************************************************************************")
+print("\nOther Settings:")
+
+print("\tengine_config_folder = '#{engine_config_folder}'")
+print("\tfallback_station_playlist_path = '#{fallback_station_playlist_path}'")
+print("\tfallback_station_dir = '#{fallback_station_dir}'")
+
+# Metadata Configuration
+engine_meta_key_show_id = list.assoc(default="show_id", "meta_key_show_id", ini)
+engine_default_track_type = list.assoc(default="0", "default_track_type", ini)
+engine_api_playlog = list.assoc(default="http://127.0.0.1:8008/api/v1/playlog", "api_url_playlog", ini)
+engine_id = list.assoc(default="1", "engine_id", ini) # "1" or "2" used for HA setups
+
+
+print("\n######################################################################################")
\ No newline at end of file
diff --git a/tests/base_config.liq b/tests/base_config.liq
new file mode 100644
index 0000000000000000000000000000000000000000..d4a57c94a1d74ec8442742df10209013c96e6345
--- /dev/null
+++ b/tests/base_config.liq
@@ -0,0 +1,48 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+settings.init.allow_root.set(true)
+settings.init.catch_exn.set(false)
+settings.server.timeout.set(180.)
+settings.server.telnet.set(true)
+settings.server.telnet.bind_addr.set("0.0.0.0")
+settings.server.telnet.port.set(1234)
+settings.log.level.set(1)
+# settings.log.file.path.set("../logs/<script>.log")
+# settings.ffmpeg.log.level.set(5)
+
+# Engine Defaults
+
+engine_meta_key_show_id = "show_id"
+ignore(engine_meta_key_show_id)
+engine_default_track_type = "0"
+ignore(engine_default_track_type)
+
+# Assertions
+
+assertEqualsError = error.register("AssertEqualsError")
+def assertEquals(var1, var2) =
+    if var1 != var2 then
+        error.raise(assertEqualsError, "#{var1} != #{var2}")
+        shutdown(code=1)
+    else
+        print("#{var1} == #{var2}")
+    end
+end
\ No newline at end of file
diff --git a/tests/engine_test_suite.liq b/tests/engine_test_suite.liq
new file mode 100644
index 0000000000000000000000000000000000000000..5d0c6d433825cf69df52abb73cf65a9fe2423f7c
--- /dev/null
+++ b/tests/engine_test_suite.liq
@@ -0,0 +1,33 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+
+# Engine Core Test Suite
+#
+# Only include tests which can be executed in a CI/CD pipeline
+# i.e. do not require audio output validation or long-running processes.
+
+%include "test_config.liq"
+%include "test_eval_source_type.liq"
+%include "test_eval_track_type.liq"
+%include "test_parse_json.liq"
+%include "test_metadata_file.liq"
+%include "test_metadata_duration.liq"
+%include "test_metadata_build.liq"
\ No newline at end of file
diff --git a/tests/test_alsa_file.liq b/tests/test_alsa_file.liq
old mode 100644
new mode 100755
index 93f1e8f2039c1ff219b82c18689d8204622268f1..66ba947d28d0080f2dea96e0629772b8f102df41
--- a/tests/test_alsa_file.liq
+++ b/tests/test_alsa_file.liq
@@ -18,14 +18,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-set("init.allow_root", true)
-set("log.file.path", "../logs/<script>.log")
-
-set("server.telnet", true)
-set("server.telnet.bind_addr", "0.0.0.0")
-set("server.telnet.port", 1234)
-
-set("frame.audio.samplerate", 44100)
+%include "base_config.liq"
+settings.frame.audio.samplerate.set(44100)
 
 input_fs = single(id="fs", "assets/audio.mp3")
 output.alsa(id="lineout", device="default", input_fs)
\ No newline at end of file
diff --git a/tests/test_alsa_framesize.liq b/tests/test_alsa_framesize.liq
index 5e74c016d655a1a58879bc85d7000eccf3c50e8a..4ca287124b9766933d816bf85d4c2c8b8ab77383 100755
--- a/tests/test_alsa_framesize.liq
+++ b/tests/test_alsa_framesize.liq
@@ -18,12 +18,12 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-set("init.allow_root", true)
-set("log.file.path", "../logs/<script>.log")
+%include "base_config.liq"
+settings.log.level.set(3)
 
 # Set this to the value mentioned in the logs after the first run. Watch the logs and double the value if needed.
 # Find more details here: https://gitlab.servus.at/aura/engine-core/-/blob/master/docs/audio-device-configuration.md
-set("frame.audio.size", 0)
+settings.frame.audio.size.set(0)
 
-set("frame.video.framerate", 0)
+# settings.frame.video.framerate.set(0)
 output.alsa(device="default", bufferize=false, input.alsa(device="default", bufferize=false))
\ No newline at end of file
diff --git a/tests/test_alsa_live_buffered.liq b/tests/test_alsa_live_buffered.liq
old mode 100644
new mode 100755
index d3105df710f479d40d9d6cfac55b5de53092b837..ee994fdd598566d02b75aacbd28512c1bcc78f3c
--- a/tests/test_alsa_live_buffered.liq
+++ b/tests/test_alsa_live_buffered.liq
@@ -18,18 +18,21 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-set("init.allow_root", true)
-set("init.catch_exn", false)
-set("log.file.path", "../logs/<script>.log")
-set("log.level", 5)
-set("ffmpeg.log.level", 5)
-set("audio.converter.samplerate.converters", ["ffmpeg"])
+%include "base_config.liq"
+settings.log.level.set(3)
 
 # Successfully tested with Liquidsoap 1.4.4 & ALSA 1.1.3
 # Low latency, no buffer underruns
-set("frame.audio.size", 2048)
-set("alsa.alsa_buffer", 8192)
-set("alsa.buffer_length", 10)
+# settings.audio.converter.samplerate.converters.set(["ffmpeg"])
+# settings.frame.audio.size.set(2048)
+# settings.alsa.alsa_buffer.set(8192)
+# settings.alsa.buffer_length.set(10)
+
+# Liquidsoap 2.1 and ALSA 1.2.4
+# Only short testing with PulseAudio enabled
+# Low latency & clear quality
+settings.frame.audio.size.set(1764)
+settings.alsa.alsa_buffer.set(7056)
 
 input_analog = input.alsa(device="default", bufferize=true)
 output.alsa(device="default", input_analog, bufferize=true)
\ No newline at end of file
diff --git a/tests/test_alsa_live_default.liq b/tests/test_alsa_live_default.liq
new file mode 100755
index 0000000000000000000000000000000000000000..28fa5a359dae256c79a77fba4c01e91dae587cda
--- /dev/null
+++ b/tests/test_alsa_live_default.liq
@@ -0,0 +1,27 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+settings.log.level.set(3)
+
+# Tested with Liquidsoap 2.1.x
+# Good and clear initial audio (no long testing yet)
+input_analog = input.alsa(device="default")
+output.alsa(device="default", input_analog)
\ No newline at end of file
diff --git a/tests/test_alsa_live_double_buffered.liq b/tests/test_alsa_live_double_buffered.liq
old mode 100644
new mode 100755
index 2d7eb47e139670b5d2efbc21eb3de0cbf76cd7e8..5ba94ac0914916c2a87a3548377d6dc061808976
--- a/tests/test_alsa_live_double_buffered.liq
+++ b/tests/test_alsa_live_double_buffered.liq
@@ -18,18 +18,14 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-set("init.allow_root", true)
-set("init.catch_exn", false)
-set("log.file.path", "../logs/<script>.log")
-set("log.level", 5)
-set("ffmpeg.log.level", 5)
-set("audio.converter.samplerate.converters", ["ffmpeg"])
+%include "base_config.liq"
+settings.audio.converter.samplerate.converters.set(["ffmpeg"])
 
 # Successfully tested with Liquidsoap 1.4.4 + ALSA 1.1.3
 # High latency, no buffer underruns
-set("frame.audio.size", 2048)
-set("alsa.alsa_buffer", 8192)
-set("alsa.buffer_length", 10)
+settings.frame.audio.size.set(2048)
+settings.alsa.alsa_buffer.set(8192)
+settings.alsa.buffer_length.set(10)
 
 input_analog = input.alsa(device="default", bufferize=true)
 input_analog = mksafe(buffer(input_analog))
diff --git a/tests/test_alsa_live_unbuffered.liq b/tests/test_alsa_live_unbuffered.liq
old mode 100644
new mode 100755
index 8e3ac24075ebb43a2b3a1e288c15ff60ab442e40..a1c7a76b601b2bc6ac0b2c0478a6521126e72325
--- a/tests/test_alsa_live_unbuffered.liq
+++ b/tests/test_alsa_live_unbuffered.liq
@@ -18,18 +18,14 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-set("init.allow_root", true)
-set("init.catch_exn", false)
-set("log.file.path", "../logs/<script>.log")
-set("log.level", 5)
-set("ffmpeg.log.level", 5)
-set("audio.converter.samplerate.converters", ["ffmpeg"])
+%include "base_config.liq"
+settings.audio.converter.samplerate.converters.set(["ffmpeg"])
 
 # Successfully tested with Liquidsoap 2 & ALSA 1.2.4
 # Almost no latency, no buffer underruns
 # Doesn't work in Liquidsoap 1.4 because of some calculation bug
-set("frame.audio.size", 7526)
-set("frame.video.framerate", 0)
+settings.frame.audio.size.set(7526)
+settings.frame.video.framerate.set(0)
 
 input_analog = input.alsa(device="default", bufferize=false)
 output.alsa(device="default", input_analog, bufferize=false)
\ No newline at end of file
diff --git a/tests/test_config.liq b/tests/test_config.liq
new file mode 100755
index 0000000000000000000000000000000000000000..9a169383e9d36eafe502245299118fbd8c1aad8b
--- /dev/null
+++ b/tests/test_config.liq
@@ -0,0 +1,32 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+%include "../src/readini.liq"
+
+# debug := true
+config = "../config/sample-production.engine-core.ini"
+print("Config file used: #{config}")
+ini = read_ini(config)
+# log("Read configuration:\n${ini}")
+# print(ini)
+
+soundsystem = list.assoc(default="", "soundsystem", ini)
+assertEquals(soundsystem, "alsa")
\ No newline at end of file
diff --git a/tests/test_eval_source_type.liq b/tests/test_eval_source_type.liq
new file mode 100755
index 0000000000000000000000000000000000000000..f8b685933890d104a464b18f34de9854e8759e9e
--- /dev/null
+++ b/tests/test_eval_source_type.liq
@@ -0,0 +1,50 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+# Init defaults
+engine_default_track_type = "0"
+
+%include "../src/functions.liq"
+
+
+### TEST ###
+
+
+# Case 1: Fallback
+type = eval_source_type("fallback_folder")
+assertEquals(type, "fallback")
+
+# Case 2: Fallback
+type = eval_source_type("fallback_playlist")
+assertEquals(type, "fallback")
+
+# Case 3: Stream
+type = eval_source_type("in_http_0")
+assertEquals(type, "stream")
+
+# Case 4: Analog In
+type = eval_source_type("linein_0")
+assertEquals(type, "analog_in")
+
+# Case 5: Unknown Source
+type = eval_source_type("foo_bar")
+assertEquals(type, "unknown_source")
diff --git a/tests/test_eval_track_type.liq b/tests/test_eval_track_type.liq
new file mode 100755
index 0000000000000000000000000000000000000000..7dcb990df5a173f5432ba52cabf1f6b5ddc6296b
--- /dev/null
+++ b/tests/test_eval_track_type.liq
@@ -0,0 +1,42 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+# Init defaults
+engine_default_track_type = "0"
+
+%include "../src/functions.liq"
+
+
+### TEST ###
+
+
+# Case 1: Passed meta track type => 33
+type = eval_track_type("33", "fallback_playlist")
+assertEquals(type, "33")
+
+# Case 2: Detect track type via source => 3
+type = eval_track_type("", "fallback_playlist")
+assertEquals(type, "3")
+
+# Case 3: Default track type => 0
+type = eval_track_type("", "not_existing_source")
+assertEquals(type, "0")
diff --git a/tests/test_metadata_build.liq b/tests/test_metadata_build.liq
new file mode 100755
index 0000000000000000000000000000000000000000..e481242c17cee7eb3f61d63d7b4fc48f8efb3421
--- /dev/null
+++ b/tests/test_metadata_build.liq
@@ -0,0 +1,42 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+%include "../src/functions.liq"
+
+
+json_string = "{ \
+        \"show_name\": \"Laser Music 2000\", \
+        \"show_id\": -1, \
+        \"timeslot_id\": -1, \
+        \"playlist_id\": -1, \
+        \"playlist_item\": \"\", \
+        \"track_type\": 0, \
+        \"track_start\": \"2022/02/22 22:02:22\", \
+        \"track_duration\": 303, \
+        \"track_title\": \"FooBar 2000\", \
+        \"track_album\": \"\", \
+        \"track_artist\": \"\" \
+    }"
+
+metadata = build_metadata(json_string)
+title = metadata["track_title"]
+print("Track title: #{title}")
+assertEquals(title, "FooBar 2000")
diff --git a/tests/test_metadata_duration.liq b/tests/test_metadata_duration.liq
new file mode 100755
index 0000000000000000000000000000000000000000..0caa269dcf6ecaff6ef99335057d2323a807447d
--- /dev/null
+++ b/tests/test_metadata_duration.liq
@@ -0,0 +1,42 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+settings.frame.audio.samplerate.set(44100)
+
+%include "../src/functions.liq"
+
+# Read duration from metadata field => 808
+metadata = [( "track_duration", "808" )]
+duration = get_meta_track_duration(metadata)
+print("Track duration in meta 'track_duration': #{duration}")
+assertEquals(duration, 808)
+
+# Read duration from file => 207
+input_fs = single(id="fs", "assets/audio.mp3")
+def process(metadata) =
+    duration = get_meta_track_duration(metadata)
+    print("Track duration on file: #{duration}")
+    assertEquals(duration, 207)
+    shutdown(code=0)
+end
+input_fs.on_metadata(process)
+output.dummy(id="dummy", input_fs)
+
diff --git a/tests/test_metadata_fallback_folder.liq b/tests/test_metadata_fallback_folder.liq
new file mode 100755
index 0000000000000000000000000000000000000000..2b63ab7ca81437c64db462fee1c980b6c65cbfb7
--- /dev/null
+++ b/tests/test_metadata_fallback_folder.liq
@@ -0,0 +1,60 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+
+# Test case to validate the timely execution of a metadata handler
+# in case of a fallback source triggered after `max_blank`.
+# Currently the handler is called 15 seconds before the audio
+# actually starts playing.
+#
+# See: https://github.com/savonet/liquidsoap/issues/2513
+
+
+# 1.) Metadata handler to be called when track starts
+def on_metadata_notification(meta) =
+    print("TITLE: #{meta['title']}")
+    print("ON AIR: #{meta['on_air']}")
+end
+
+# 2.) Invalid source to test fallback
+empty_source = blank.strip(
+        id="invalid_source",
+        track_sensitive=false,
+        max_blank=15.,
+        min_noise=0.,
+        threshold=-80.,
+        blank()
+    )
+
+# 3.) Fallback
+fallback_source = playlist(id="fallback_folder", "../audio/fallback", mode="randomize", reload=300, reload_mode="seconds")
+fallback_source = source.on_track(id="fallback_folder", fallback_source, on_metadata_notification)
+fallback_source = mksafe(fallback_source)
+
+# 4.) Mix and output
+main_source = fallback(
+    id="fallback-source",
+    track_sensitive=false,
+    replay_metadata=true,
+    [empty_source, fallback_source])
+output.alsa(id="lineout", device="default", main_source)
+
diff --git a/tests/test_metadata_file.liq b/tests/test_metadata_file.liq
new file mode 100755
index 0000000000000000000000000000000000000000..069f04879147c303c968706d430cb8dac4400fc1
--- /dev/null
+++ b/tests/test_metadata_file.liq
@@ -0,0 +1,32 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+settings.frame.audio.samplerate.set(44100)
+
+# Metadata on file
+input_fs = single(id="fs", "assets/audio.mp3")
+def process(metadata) =
+    # print(json.stringify(metadata))
+    assertEquals(metadata["album"], "ccMixter")
+    shutdown(code=0)
+end
+input_fs.on_metadata(process)
+output.dummy(id="dummy", input_fs)
diff --git a/tests/test_metadata_insert_merge.liq b/tests/test_metadata_insert_merge.liq
new file mode 100755
index 0000000000000000000000000000000000000000..f21d1a3a17ce123e99c5e08321864fbfa982aead
--- /dev/null
+++ b/tests/test_metadata_insert_merge.liq
@@ -0,0 +1,70 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+%include "../src/functions.liq"
+
+# Test insert metadata on stream
+url = "https://securestream.o94.at/live.mp3"
+in_stream = input.http(id="in_stream", start=true, url)
+in_stream = insert_metadata(id="in_stream", in_stream)
+def on_metadata_notification(metadata) =
+    print("Metadata Notification: \n#{metadata}")
+end
+
+# Save the callback
+last_metadata = in_stream.last_metadata
+imcb = in_stream.insert_metadata
+
+# Handle event and stream output
+in_stream = source.on_metadata(id="in_stream", in_stream, on_metadata_notification)
+output.alsa(id="lineout", device="default", mksafe(in_stream))
+
+
+# Test Case 1: Normal insert and merge
+# Provide new metadata every 3 seconds
+def case1() =
+    print("Running Test Case 1 ...")
+    meta = [("foo", "bar")]
+    do_meta_insert(last_metadata, imcb, meta)
+end
+thread.run(delay=2., { case1() })
+thread.run(delay=4., { case1() })
+thread.run(delay=8., { case1() })
+
+# Test Case 2: Insert metadata with new show
+# Provide new metadata after 23 seconds
+def case2() =
+    print("Running Test Case 2 ...")
+    meta = [("show_id", "333"), ("FooBar", "2000")]
+    do_meta_insert(last_metadata, imcb, meta)
+end
+thread.run(delay=13., { case2() })
+thread.run(delay=16., { case1() })
+
+# Test Case 3: Insert metadata with new show
+# Provide new show metadata after 42 seconds
+def case3() =
+    print("Running Test Case 3 ...")
+    meta = [("show_id", "42"), ("Something", "...completely different")]
+    do_meta_insert(last_metadata, imcb, meta)
+end
+thread.run(delay=23., { case3() })
+thread.run(delay=42., { case1() })
\ No newline at end of file
diff --git a/tests/test_metadata_stream.liq b/tests/test_metadata_stream.liq
new file mode 100755
index 0000000000000000000000000000000000000000..47fdee3952053292a83577cc78db573b68769526
--- /dev/null
+++ b/tests/test_metadata_stream.liq
@@ -0,0 +1,33 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+
+# Test insert metadata on stream
+url = "https://securestream.o94.at/live.mp3"
+in_stream = input.http(id="in_stream", start=true, url)
+in_stream = insert_metadata(id="in_stream", in_stream)
+def on_metadata_notification(metadata) =
+    print("METADATA: \n#{metadata}")
+end
+
+in_stream = source.on_metadata(id="in_stream", in_stream, on_metadata_notification)
+output.alsa(id="lineout", device="default", mksafe(in_stream))
diff --git a/tests/test_parse_json.liq b/tests/test_parse_json.liq
new file mode 100755
index 0000000000000000000000000000000000000000..990a088d51f8d4275a4303132941598906b65ffe
--- /dev/null
+++ b/tests/test_parse_json.liq
@@ -0,0 +1,61 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+# Compare: https://github.com/savonet/liquidsoap/issues/2483
+
+# Scenario 1: Doesn't work
+let json.parse data = '{"show_id": "-1", "show_name": "Random Music"}'
+print("Set current show to '#{data.show_name}' (ID: #{data.show_id})")
+assertEquals("#{data.show_name}", "null")
+
+# Scenario 2: Works, when data types of the JSON have been defined
+let json.parse (data : {
+        show_id: string,
+        show_name: string
+    }) = '{"show_id": "-1", "show_name": "Random Music"}'
+print("Set current show to '#{data.show_name}' (ID: #{data.show_id})")
+assertEquals(data.show_name, "Random Music")
+assertEquals(data.show_id, "-1")
+
+# Scenario 3: Works, even when no data types have been defined
+let json.parse data = '{"show_id": "-1", "show_name": "Random Music"}'
+print("Set current show to '" ^ data.show_name ^ "' (ID: " ^ data.show_id ^ ")")
+assertEquals(data.show_name, "Random Music")
+assertEquals(data.show_id, "-1")
+
+# Scenario 4: Works, when variables have been accessed outside the string before
+let json.parse data = '{"show_id": "-1", "show_name": "Random Music"}'
+print("Set current show to '" ^ data.show_name ^ "' (ID: " ^ data.show_id ^ ")")
+assertEquals(data.show_name, "Random Music")
+assertEquals(data.show_id, "-1")
+print("Set current show to '#{data.show_name}' (ID: #{data.show_id})")
+assertEquals(data.show_name, "Random Music")
+assertEquals(data.show_id, "-1")
+
+# Scenario 5: Allow `null` and optional values
+let json.parse (data : {
+        show_id: string,
+        show_name: string?,
+        track_title: string?
+    }) = '{"show_id": "-1", "show_name": null}'
+print("Set current show to '#{data.show_name}' (ID: #{data.show_id})")
+assertEquals(data.show_id, "-1")
diff --git a/tests/test_source.liq b/tests/test_source.liq
new file mode 100755
index 0000000000000000000000000000000000000000..758154cdefa99f7ccc5593051657cfe6d272decf
--- /dev/null
+++ b/tests/test_source.liq
@@ -0,0 +1,69 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+url = "https://orf-live.ors-shoutcast.at/oe1-q2a"
+input_stream = input.http(id="input_stream", url, start=true)
+def handle_meta1(meta) =
+    print("Called metadata handler1 and got: #{meta}")
+end
+def handle_meta2(meta) =
+    print("Called metadata handler2 and got: #{meta}")
+end
+
+
+### SOURCE ID ###
+
+# Test retrieving ID via getter
+id = input_stream.id()
+print("Source ID: #{id}")
+assertEquals(id, "input_stream")
+
+# Test retrieving ID via `source.id`
+id = source.id(input_stream)
+print("Source ID: #{id}")
+assertEquals(id, "input_stream")
+
+
+### ON_METADATA ###
+
+# Two variants of metadata event handling
+# See https://github.com/savonet/liquidsoap/discussions/2509
+
+# Variant A.) Attach `on_metadata` handler directly: handlers registered
+# directly using the source method will be called whenever the source is being consumed
+input_stream.on_metadata(handle_meta1)
+print("Source ID: #{input_stream.id()}")
+assertEquals(id, "input_stream")
+
+# Variant B.) Source returned by `on_metadata`: Handlers are only called
+# when the returned source is being used
+input_stream = source.on_metadata(id="input_stream", input_stream, handle_meta2)
+print("Source ID: #{input_stream.id()}")
+assertEquals(id, "input_stream")
+
+
+# IMPORTANT: If this is commented out, only "handle_meta1" will be called
+output.alsa(device="default", mksafe(mixer), bufferize=true)
+
+# The same is true for a mixer i.e. when the source is not selected/not audible:
+# mixer = mix(id="mixer", [input_stream])
+# output.alsa(device="default", mksafe(mixer), bufferize=true)
\ No newline at end of file
diff --git a/tests/test_stream.liq b/tests/test_stream.liq
new file mode 100755
index 0000000000000000000000000000000000000000..28444cb64afa385e843dc948a46790dcb3f5939c
--- /dev/null
+++ b/tests/test_stream.liq
@@ -0,0 +1,32 @@
+#!/usr/bin/env liquidsoap
+#
+# Aura Engine (https://gitlab.servus.at/aura/engine)
+#
+# Copyright (C) 2017-now() - The Aura Engine Team.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+%include "base_config.liq"
+
+
+url = "https://orf-live.ors-shoutcast.at/oe1-q2a"
+# url = "https://securestream.o94.at/live.mp3"
+# url = "http://stream01.kapper.net:8001/live.mp3"
+# url = "https://live.helsinki.at:8088/live160.ogg"
+# url = "https://stream.fro.at/fro-128.ogg"
+# url = "http://212.89.182.114:8008/frf"
+
+input_http = input.http(id="in_http", max_buffer=5., timeout=10.0, start=true, url)
+output.alsa(device="default", mksafe(input_http), bufferize=true)
\ No newline at end of file