Merge commit '54102d4940d06ebf201b4b9329dd1d6211810107'
This commit is contained in:
parent
d04db6d1d7
commit
6eb78f0ea1
|
@ -9,6 +9,12 @@ release channel, you can take advantage of these new features and fixes.
|
|||
pages, we now use a new search tool called Meilisearch that allows for very fast, very accurate search results, as
|
||||
well as more complex search queries (and other goodies, like typo correction).
|
||||
|
||||
- **Master_me and Post-Processing Tweaks:** We now have built-in support
|
||||
for [master_me](https://github.com/trummerschlunk/master_me), an open-source audio mastering tool that helps add
|
||||
polish and "punch" to your streams. Its functionality is similar to Stereo Tool, but because it's open-source, we
|
||||
include it in every AzuraCast installation. You can now also customize whether our post-processing step includes your
|
||||
live DJ performances.
|
||||
|
||||
## Code Quality/Technical Changes
|
||||
|
||||
- **Initial Podman Support**: Podman is an increasingly popular drop-in replacement for Docker, originally from the
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -32,6 +32,23 @@ RUN curl -fsSL https://github.com/meilisearch/meilisearch/archive/refs/tags/v1.1
|
|||
#
|
||||
FROM mariadb:10.9-jammy AS mariadb
|
||||
|
||||
#
|
||||
# master_me build step
|
||||
#
|
||||
FROM debian:bookworm AS masterme
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl build-essential ca-certificates git faust ladspa-sdk \
|
||||
&& mkdir -p /tmp/masterme \
|
||||
&& mkdir -p /usr/lib/ladspa
|
||||
|
||||
WORKDIR /tmp/masterme
|
||||
|
||||
RUN curl -sSL https://github.com/trummerschlunk/master_me/archive/refs/tags/1.1.0.tar.gz -o master_me.tar.gz \
|
||||
&& tar -xvzf master_me.tar.gz --strip-components=1 \
|
||||
&& faust2ladspa master_me.dsp \
|
||||
&& mv master_me.so /usr/lib/ladspa/master_me.so
|
||||
|
||||
#
|
||||
# Final build image
|
||||
#
|
||||
|
@ -67,6 +84,9 @@ COPY ./util/docker/stations /bd_build/stations/
|
|||
RUN bash /bd_build/stations/setup.sh \
|
||||
&& rm -rf /bd_build/stations
|
||||
|
||||
# Add master_me
|
||||
COPY --from=masterme /usr/lib/ladspa/master_me.so /usr/lib/ladspa/master_me.so
|
||||
|
||||
COPY ./util/docker/web /bd_build/web/
|
||||
RUN bash /bd_build/web/setup.sh \
|
||||
&& rm -rf /bd_build/web
|
||||
|
|
|
@ -67,81 +67,161 @@
|
|||
</template>
|
||||
</b-wrapped-form-group>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<b-wrapped-form-group
|
||||
id="edit_form_backend_config_audio_processing_method"
|
||||
class="col-md-12"
|
||||
:field="form.backend_config.audio_processing_method"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Audio Processing Method') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Choose a method to use for processing audio which produces a more uniform and "full" sound for your station.')
|
||||
}}
|
||||
</template>
|
||||
<template #default="slotProps">
|
||||
<b-form-radio-group
|
||||
:id="slotProps.id"
|
||||
v-model="slotProps.field.$model"
|
||||
stacked
|
||||
:options="audioProcessingOptions"
|
||||
/>
|
||||
</template>
|
||||
</b-wrapped-form-group>
|
||||
</div>
|
||||
</b-form-fieldset>
|
||||
|
||||
<b-form-fieldset v-if="isStereoToolEnabled && isStereoToolInstalled">
|
||||
<template #label>
|
||||
{{ $gettext('Stereo Tool') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Stereo Tool is an industry standard for software audio processing. For more information on how to configure it, please refer to the')
|
||||
}}
|
||||
<a
|
||||
href="https://www.thimeo.com/stereo-tool/"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $gettext('Stereo Tool documentation.') }}
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<b-form-fieldset v-if="isBackendEnabled">
|
||||
<b-form-fieldset>
|
||||
<div class="form-row">
|
||||
<b-wrapped-form-group
|
||||
id="edit_form_backend_stereo_tool_license_key"
|
||||
class="col-md-7"
|
||||
:field="form.backend_config.stereo_tool_license_key"
|
||||
input-type="text"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Stereo Tool License Key') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Provide a valid license key from Thimeo. Functionality is limited without a license key.')
|
||||
}}
|
||||
</template>
|
||||
</b-wrapped-form-group>
|
||||
<template #label>
|
||||
{{ $gettext('Audio Post-processing') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Post-processing allows you to apply audio processors (like compressors, limiters, or equalizers) to your stream to create a more uniform sound or enhance the listening experience. Post-processing requires extra CPU resources, so it may slow down your server.')
|
||||
}}
|
||||
</template>
|
||||
|
||||
<b-form-markup
|
||||
id="edit_form_backend_stereo_tool_config"
|
||||
class="col-md-5"
|
||||
>
|
||||
<b-form-fieldset>
|
||||
<div class="form-row">
|
||||
<b-wrapped-form-group
|
||||
id="edit_form_backend_config_audio_processing_method"
|
||||
class="col-md-6"
|
||||
:field="form.backend_config.audio_processing_method"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Audio Post-processing Method') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Select an option here to apply post-processing using an easy preset or tool. You can also manually apply post-processing by editing your Liquidsoap configuration manually.')
|
||||
}}
|
||||
</template>
|
||||
<template #default="slotProps">
|
||||
<b-form-radio-group
|
||||
:id="slotProps.id"
|
||||
v-model="slotProps.field.$model"
|
||||
stacked
|
||||
:options="audioProcessingOptions"
|
||||
/>
|
||||
</template>
|
||||
</b-wrapped-form-group>
|
||||
|
||||
<template v-if="isPostProcessingEnabled">
|
||||
<b-wrapped-form-checkbox
|
||||
id="edit_form_backend_config_post_processing_include_live"
|
||||
class="col-md-6"
|
||||
:field="form.backend_config.post_processing_include_live"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Apply Post-processing to Live Streams') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Check this box to apply post-processing to all audio, including live streams. Uncheck this box to only apply post-processing to the AutoDJ.')
|
||||
}}
|
||||
</template>
|
||||
</b-wrapped-form-checkbox>
|
||||
</template>
|
||||
</div>
|
||||
</b-form-fieldset>
|
||||
|
||||
<b-form-fieldset v-if="isMasterMeEnabled">
|
||||
<b-form-markup id="master_me_info">
|
||||
<template #label>
|
||||
{{ $gettext('Upload Stereo Tool Configuration') }}
|
||||
{{ $gettext('About Master_me') }}
|
||||
</template>
|
||||
|
||||
<p class="card-text">
|
||||
{{
|
||||
$gettext('Upload a Stereo Tool configuration file from the "Broadcasting" submenu in the station profile.')
|
||||
$gettext('Master_me is an open-source automatic mastering plugin for streaming, podcasts and Internet radio.')
|
||||
}}
|
||||
</p>
|
||||
<p class="card-text">
|
||||
<a
|
||||
href="https://github.com/trummerschlunk/master_me"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $gettext('Master_me Project Homepage') }}
|
||||
</a>
|
||||
</p>
|
||||
</b-form-markup>
|
||||
</div>
|
||||
|
||||
<b-form-fieldset>
|
||||
<div class="form-row">
|
||||
<b-wrapped-form-group
|
||||
id="edit_form_backend_master_me_preset"
|
||||
class="col-md-7"
|
||||
:field="form.backend_config.master_me_preset"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Master_me Preset') }}
|
||||
</template>
|
||||
<template #default="slotProps">
|
||||
<b-form-radio-group
|
||||
:id="slotProps.id"
|
||||
v-model="slotProps.field.$model"
|
||||
stacked
|
||||
:options="masterMePresetOptions"
|
||||
/>
|
||||
</template>
|
||||
</b-wrapped-form-group>
|
||||
</div>
|
||||
</b-form-fieldset>
|
||||
</b-form-fieldset>
|
||||
|
||||
<b-form-fieldset v-if="isStereoToolEnabled && isStereoToolInstalled">
|
||||
<b-form-markup id="stereo_tool_info">
|
||||
<template #label>
|
||||
{{ $gettext('Stereo Tool') }}
|
||||
</template>
|
||||
|
||||
<p class="card-text">
|
||||
{{
|
||||
$gettext('Stereo Tool is an industry standard for software audio processing. For more information on how to configure it, please refer to the')
|
||||
}}
|
||||
<a
|
||||
href="https://www.thimeo.com/stereo-tool/"
|
||||
target="_blank"
|
||||
>
|
||||
{{ $gettext('Stereo Tool documentation.') }}
|
||||
</a>
|
||||
</p>
|
||||
</b-form-markup>
|
||||
|
||||
<b-form-fieldset>
|
||||
<div class="form-row">
|
||||
<b-wrapped-form-group
|
||||
id="edit_form_backend_stereo_tool_license_key"
|
||||
class="col-md-7"
|
||||
:field="form.backend_config.stereo_tool_license_key"
|
||||
input-type="text"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Stereo Tool License Key') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('Provide a valid license key from Thimeo. Functionality is limited without a license key.')
|
||||
}}
|
||||
</template>
|
||||
</b-wrapped-form-group>
|
||||
|
||||
<b-form-markup
|
||||
id="edit_form_backend_stereo_tool_config"
|
||||
class="col-md-5"
|
||||
>
|
||||
<template #label>
|
||||
{{ $gettext('Upload Stereo Tool Configuration') }}
|
||||
</template>
|
||||
|
||||
<p class="card-text">
|
||||
{{
|
||||
$gettext('Upload a Stereo Tool configuration file from the "Broadcasting" submenu in the station profile.')
|
||||
}}
|
||||
</p>
|
||||
</b-form-markup>
|
||||
</div>
|
||||
</b-form-fieldset>
|
||||
</b-form-fieldset>
|
||||
</b-form-fieldset>
|
||||
</b-form-fieldset>
|
||||
|
||||
|
@ -293,11 +373,16 @@
|
|||
import BFormFieldset from "~/components/Form/BFormFieldset.vue";
|
||||
import BWrappedFormGroup from "~/components/Form/BWrappedFormGroup.vue";
|
||||
import {
|
||||
AUDIO_PROCESSING_LIQUIDSOAP,
|
||||
AUDIO_PROCESSING_NONE,
|
||||
AUDIO_PROCESSING_STEREO_TOOL,
|
||||
BACKEND_LIQUIDSOAP,
|
||||
BACKEND_NONE
|
||||
AUDIO_PROCESSING_LIQUIDSOAP,
|
||||
AUDIO_PROCESSING_MASTER_ME,
|
||||
AUDIO_PROCESSING_NONE,
|
||||
AUDIO_PROCESSING_STEREO_TOOL,
|
||||
BACKEND_LIQUIDSOAP,
|
||||
BACKEND_NONE,
|
||||
MASTER_ME_PRESET_APPLE_PODCASTS,
|
||||
MASTER_ME_PRESET_EBU_R128, MASTER_ME_PRESET_MUSIC_GENERAL,
|
||||
MASTER_ME_PRESET_SPEECH_GENERAL,
|
||||
MASTER_ME_PRESET_YOUTUBE
|
||||
} from "~/components/Entity/RadioAdapters";
|
||||
import BWrappedFormCheckbox from "~/components/Form/BWrappedFormCheckbox.vue";
|
||||
import BFormMarkup from "~/components/Form/BFormMarkup.vue";
|
||||
|
@ -331,6 +416,14 @@ const isStereoToolEnabled = computed(() => {
|
|||
return props.form.backend_config.audio_processing_method.$model === AUDIO_PROCESSING_STEREO_TOOL;
|
||||
});
|
||||
|
||||
const isMasterMeEnabled = computed(() => {
|
||||
return props.form.backend_config.audio_processing_method.$model === AUDIO_PROCESSING_MASTER_ME;
|
||||
});
|
||||
|
||||
const isPostProcessingEnabled = computed(() => {
|
||||
return props.form.backend_config.audio_processing_method.$model !== AUDIO_PROCESSING_NONE;
|
||||
});
|
||||
|
||||
const {$gettext} = useTranslate();
|
||||
|
||||
const backendTypeOptions = computed(() => {
|
||||
|
@ -366,17 +459,21 @@ const crossfadeOptions = computed(() => {
|
|||
const audioProcessingOptions = computed(() => {
|
||||
const audioProcessingOptions = [
|
||||
{
|
||||
text: $gettext('Liquidsoap'),
|
||||
text: $gettext('No Post-processing'),
|
||||
value: AUDIO_PROCESSING_NONE,
|
||||
},
|
||||
{
|
||||
text: $gettext('Basic Normalization and Compression'),
|
||||
value: AUDIO_PROCESSING_LIQUIDSOAP,
|
||||
},
|
||||
{
|
||||
text: $gettext('Disable Processing'),
|
||||
value: AUDIO_PROCESSING_NONE,
|
||||
}
|
||||
text: $gettext('Master_me Post-processing'),
|
||||
value: AUDIO_PROCESSING_MASTER_ME,
|
||||
},
|
||||
];
|
||||
|
||||
if (props.isStereoToolInstalled) {
|
||||
audioProcessingOptions.splice(1, 0,
|
||||
audioProcessingOptions.push(
|
||||
{
|
||||
text: $gettext('Stereo Tool'),
|
||||
value: AUDIO_PROCESSING_STEREO_TOOL,
|
||||
|
@ -394,6 +491,31 @@ const charsetOptions = computed(() => {
|
|||
];
|
||||
});
|
||||
|
||||
const masterMePresetOptions = computed(() => {
|
||||
return [
|
||||
{
|
||||
text: $gettext('Music General'),
|
||||
value: MASTER_ME_PRESET_MUSIC_GENERAL
|
||||
},
|
||||
{
|
||||
text: $gettext('Speech General'),
|
||||
value: MASTER_ME_PRESET_SPEECH_GENERAL
|
||||
},
|
||||
{
|
||||
text: $gettext('EBU R128'),
|
||||
value: MASTER_ME_PRESET_EBU_R128
|
||||
},
|
||||
{
|
||||
text: $gettext('Apple Podcasts'),
|
||||
value: MASTER_ME_PRESET_APPLE_PODCASTS
|
||||
},
|
||||
{
|
||||
text: $gettext('YouTube'),
|
||||
value: MASTER_ME_PRESET_YOUTUBE
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const performanceModeOptions = computed(() => {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -139,7 +139,12 @@ import AdminStationsHlsForm from "./Form/HlsForm.vue";
|
|||
import AdminStationsRequestsForm from "./Form/RequestsForm.vue";
|
||||
import AdminStationsStreamersForm from "./Form/StreamersForm.vue";
|
||||
import {decimal, numeric, required, url} from '@vuelidate/validators';
|
||||
import {AUDIO_PROCESSING_NONE, BACKEND_LIQUIDSOAP, FRONTEND_ICECAST} from "~/components/Entity/RadioAdapters";
|
||||
import {
|
||||
AUDIO_PROCESSING_NONE,
|
||||
BACKEND_LIQUIDSOAP,
|
||||
FRONTEND_ICECAST,
|
||||
MASTER_ME_PRESET_MUSIC_GENERAL
|
||||
} from "~/components/Entity/RadioAdapters";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useNotify} from "~/vendor/bootstrapVue";
|
||||
import {useAxios} from "~/vendor/axios";
|
||||
|
@ -196,6 +201,8 @@ const buildForm = () => {
|
|||
crossfade_type: {},
|
||||
crossfade: {decimal},
|
||||
audio_processing_method: {},
|
||||
post_processing_include_live: {},
|
||||
master_me_preset: {},
|
||||
stereo_tool_license_key: {},
|
||||
record_streams: {},
|
||||
record_streams_format: {},
|
||||
|
@ -257,6 +264,8 @@ const buildForm = () => {
|
|||
crossfade_type: 'normal',
|
||||
crossfade: 2,
|
||||
audio_processing_method: AUDIO_PROCESSING_NONE,
|
||||
post_processing_include_live: true,
|
||||
master_me_preset: MASTER_ME_PRESET_MUSIC_GENERAL,
|
||||
stereo_tool_license_key: '',
|
||||
record_streams: false,
|
||||
record_streams_format: 'mp3',
|
||||
|
|
|
@ -5,9 +5,16 @@ export const FRONTEND_REMOTE = 'remote';
|
|||
export const BACKEND_LIQUIDSOAP = 'liquidsoap';
|
||||
export const BACKEND_NONE = 'none';
|
||||
|
||||
export const AUDIO_PROCESSING_LIQUIDSOAP = 'nrj';
|
||||
export const AUDIO_PROCESSING_STEREO_TOOL = 'stereo_tool';
|
||||
export const AUDIO_PROCESSING_NONE = 'none';
|
||||
export const AUDIO_PROCESSING_LIQUIDSOAP = 'nrj';
|
||||
export const AUDIO_PROCESSING_MASTER_ME = 'master_me';
|
||||
export const AUDIO_PROCESSING_STEREO_TOOL = 'stereo_tool';
|
||||
|
||||
export const MASTER_ME_PRESET_MUSIC_GENERAL = 'music_general';
|
||||
export const MASTER_ME_PRESET_SPEECH_GENERAL = 'speech_general';
|
||||
export const MASTER_ME_PRESET_EBU_R128 = 'ebu_r128';
|
||||
export const MASTER_ME_PRESET_APPLE_PODCASTS = 'apple_podcasts';
|
||||
export const MASTER_ME_PRESET_YOUTUBE = 'youtube';
|
||||
|
||||
export const REMOTE_SHOUTCAST1 = 'shoutcast1';
|
||||
export const REMOTE_SHOUTCAST2 = 'shoutcast2';
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Entity;
|
|||
use App\Entity\Enums\StationBackendPerformanceModes;
|
||||
use App\Radio\Enums\AudioProcessingMethods;
|
||||
use App\Radio\Enums\CrossfadeModes;
|
||||
use App\Radio\Enums\MasterMePresets;
|
||||
use App\Radio\Enums\StreamFormats;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
|
@ -166,6 +167,11 @@ class StationBackendConfiguration extends AbstractStationConfiguration
|
|||
?? AudioProcessingMethods::default();
|
||||
}
|
||||
|
||||
public function isAudioProcessingEnabled(): bool
|
||||
{
|
||||
return AudioProcessingMethods::None !== $this->getAudioProcessingMethodEnum();
|
||||
}
|
||||
|
||||
public function setAudioProcessingMethod(?string $method): void
|
||||
{
|
||||
if (null !== $method) {
|
||||
|
@ -179,6 +185,18 @@ class StationBackendConfiguration extends AbstractStationConfiguration
|
|||
$this->set(self::AUDIO_PROCESSING_METHOD, $method);
|
||||
}
|
||||
|
||||
public const POST_PROCESSING_INCLUDE_LIVE = 'post_processing_include_live';
|
||||
|
||||
public function getPostProcessingIncludeLive(): bool
|
||||
{
|
||||
return $this->get(self::POST_PROCESSING_INCLUDE_LIVE, false);
|
||||
}
|
||||
|
||||
public function setPostProcessingIncludeLive(bool $postProcessingIncludeLive): void
|
||||
{
|
||||
$this->set(self::POST_PROCESSING_INCLUDE_LIVE, $postProcessingIncludeLive);
|
||||
}
|
||||
|
||||
public const STEREO_TOOL_LICENSE_KEY = 'stereo_tool_license_key';
|
||||
|
||||
public function getStereoToolLicenseKey(): ?string
|
||||
|
@ -203,6 +221,32 @@ class StationBackendConfiguration extends AbstractStationConfiguration
|
|||
$this->set(self::STEREO_TOOL_CONFIGURATION_PATH, $stereoToolConfigurationPath);
|
||||
}
|
||||
|
||||
public const MASTER_ME_PRESET = 'master_me_preset';
|
||||
|
||||
public function getMasterMePreset(): ?string
|
||||
{
|
||||
return $this->get(self::MASTER_ME_PRESET);
|
||||
}
|
||||
|
||||
public function getMasterMePresetEnum(): MasterMePresets
|
||||
{
|
||||
return MasterMePresets::tryFrom($this->get(self::MASTER_ME_PRESET) ?? '')
|
||||
?? MasterMePresets::default();
|
||||
}
|
||||
|
||||
public function setMasterMePreset(?string $masterMePreset): void
|
||||
{
|
||||
if (null !== $masterMePreset) {
|
||||
$masterMePreset = strtolower($masterMePreset);
|
||||
}
|
||||
|
||||
if (null !== $masterMePreset && null === MasterMePresets::tryFrom($masterMePreset)) {
|
||||
throw new InvalidArgumentException('Invalid master_me preset specified.');
|
||||
}
|
||||
|
||||
$this->set(self::MASTER_ME_PRESET, $masterMePreset);
|
||||
}
|
||||
|
||||
public const USE_REPLAYGAIN = 'enable_replaygain_metadata';
|
||||
|
||||
public function useReplayGain(): bool
|
||||
|
|
|
@ -80,6 +80,78 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
}
|
||||
}
|
||||
|
||||
public function writePostProcessingSection(WriteLiquidsoapConfiguration $event): void
|
||||
{
|
||||
$station = $event->getStation();
|
||||
$settings = $station->getBackendConfig();
|
||||
|
||||
switch ($settings->getAudioProcessingMethodEnum()) {
|
||||
case AudioProcessingMethods::Liquidsoap:
|
||||
// NRJ normalization
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
# Normalization and Compression
|
||||
radio = normalize(target = 0., window = 0.03, gain_min = -16., gain_max = 0., radio)
|
||||
radio = compress.exponential(radio, mu = 1.0)
|
||||
LIQ
|
||||
);
|
||||
break;
|
||||
|
||||
case AudioProcessingMethods::MasterMe:
|
||||
// MasterMe Presets
|
||||
|
||||
$lines = [
|
||||
'radio = ladspa.master_me(',
|
||||
];
|
||||
|
||||
$preset = $settings->getMasterMePresetEnum();
|
||||
foreach ($preset->getOptions() as $presetKey => $presetVal) {
|
||||
if (is_numeric($presetVal)) {
|
||||
$presetVal = self::toFloat($presetVal);
|
||||
} elseif (is_bool($presetVal)) {
|
||||
$presetVal = ($presetVal) ? 'true' : 'false';
|
||||
}
|
||||
|
||||
$lines[] = ' ' . $presetKey . ' = ' . $presetVal . ',';
|
||||
}
|
||||
|
||||
$lines[] = ' radio';
|
||||
$lines[] = ')';
|
||||
|
||||
$event->appendLines($lines);
|
||||
break;
|
||||
|
||||
case AudioProcessingMethods::StereoTool:
|
||||
// Stereo Tool processing
|
||||
if (!StereoTool::isReady($station)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stereoToolBinary = StereoTool::getBinaryPath();
|
||||
|
||||
$stereoToolConfiguration = $station->getRadioConfigDir()
|
||||
. DIRECTORY_SEPARATOR . $settings->getStereoToolConfigurationPath();
|
||||
$stereoToolProcess = $stereoToolBinary . ' --silent - - -s ' . $stereoToolConfiguration;
|
||||
|
||||
$stereoToolLicenseKey = $settings->getStereoToolLicenseKey();
|
||||
if (!empty($stereoToolLicenseKey)) {
|
||||
$stereoToolProcess .= ' -k "' . $stereoToolLicenseKey . '"';
|
||||
}
|
||||
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
# Stereo Tool Pipe
|
||||
radio = pipe(replay_delay=1.0, process='{$stereoToolProcess}', radio)
|
||||
LIQ
|
||||
);
|
||||
break;
|
||||
|
||||
case AudioProcessingMethods::None:
|
||||
// Noop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getDividerString(): string
|
||||
{
|
||||
return chr(7);
|
||||
|
@ -767,6 +839,10 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
LS
|
||||
);
|
||||
}
|
||||
|
||||
if ($settings->isAudioProcessingEnabled() && !$settings->getPostProcessingIncludeLive()) {
|
||||
$this->writePostProcessingSection($event);
|
||||
}
|
||||
}
|
||||
|
||||
public function writeHarborConfiguration(WriteLiquidsoapConfiguration $event): void
|
||||
|
@ -953,39 +1029,8 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
LIQ
|
||||
);
|
||||
|
||||
// NRJ normalization
|
||||
if (AudioProcessingMethods::Liquidsoap === $settings->getAudioProcessingMethodEnum()) {
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
# Normalization and Compression
|
||||
radio = normalize(target = 0., window = 0.03, gain_min = -16., gain_max = 0., radio)
|
||||
radio = compress.exponential(radio, mu = 1.0)
|
||||
LIQ
|
||||
);
|
||||
}
|
||||
|
||||
// Stereo Tool processing
|
||||
if (
|
||||
AudioProcessingMethods::StereoTool === $settings->getAudioProcessingMethodEnum()
|
||||
&& StereoTool::isReady($station)
|
||||
) {
|
||||
$stereoToolBinary = StereoTool::getBinaryPath();
|
||||
|
||||
$stereoToolConfiguration = $station->getRadioConfigDir()
|
||||
. DIRECTORY_SEPARATOR . $settings->getStereoToolConfigurationPath();
|
||||
$stereoToolProcess = $stereoToolBinary . ' --silent - - -s ' . $stereoToolConfiguration;
|
||||
|
||||
$stereoToolLicenseKey = $settings->getStereoToolLicenseKey();
|
||||
if (!empty($stereoToolLicenseKey)) {
|
||||
$stereoToolProcess .= ' -k "' . $stereoToolLicenseKey . '"';
|
||||
}
|
||||
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
# Stereo Tool Pipe
|
||||
radio = pipe(replay_delay=1.0, process='{$stereoToolProcess}', radio)
|
||||
LIQ
|
||||
);
|
||||
if ($settings->isAudioProcessingEnabled() && $settings->getPostProcessingIncludeLive()) {
|
||||
$this->writePostProcessingSection($event);
|
||||
}
|
||||
|
||||
// Replaygain metadata
|
||||
|
|
|
@ -6,9 +6,10 @@ namespace App\Radio\Enums;
|
|||
|
||||
enum AudioProcessingMethods: string
|
||||
{
|
||||
case Liquidsoap = 'nrj';
|
||||
case StereoTool = 'stereo_tool';
|
||||
case None = 'none';
|
||||
case Liquidsoap = 'nrj';
|
||||
case MasterMe = 'master_me';
|
||||
case StereoTool = 'stereo_tool';
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Radio\Enums;
|
||||
|
||||
enum MasterMePresets: string
|
||||
{
|
||||
case MusicGeneral = 'music_general';
|
||||
case SpeechGeneral = 'speech_general';
|
||||
case EbuR128 = 'ebu_r128';
|
||||
case ApplePodcasts = 'apple_podcasts';
|
||||
case YouTube = 'youtube';
|
||||
|
||||
public static function default(): self
|
||||
{
|
||||
return self::MusicGeneral;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
$defaults = [
|
||||
"globalbypass" => false,
|
||||
"masterme_easy_target" => -16,
|
||||
"masterme_expert_brickwall_brickwallbypass" => false,
|
||||
"masterme_expert_brickwall_brickwallceiling" => -1,
|
||||
"masterme_expert_brickwall_brickwallrelease" => 75,
|
||||
"masterme_expert_eq_eqbypass" => false,
|
||||
"masterme_expert_eq_highpass_eqhighpassfreq" => 5,
|
||||
"masterme_expert_eq_sideeq_eqsidebandwidth" => 1,
|
||||
"masterme_expert_eq_sideeq_eqsidefreq" => 600,
|
||||
"masterme_expert_eq_sideeq_eqsidegain" => 1,
|
||||
"masterme_expert_eq_tilteq_eqtiltgain" => 0,
|
||||
"masterme_expert_gate_gateattack" => 0,
|
||||
"masterme_expert_gate_gatebypass" => true,
|
||||
"masterme_expert_gate_gatehold" => 50,
|
||||
"masterme_expert_gate_gaterelease" => 430.5,
|
||||
"masterme_expert_gate_gatethreshold" => -90,
|
||||
"masterme_expert_kneecomp_kneecompattack" => 20,
|
||||
"masterme_expert_kneecomp_kneecompbypass" => false,
|
||||
"masterme_expert_kneecomp_kneecompdrywet" => 50,
|
||||
"masterme_expert_kneecomp_kneecompff_fb" => 50,
|
||||
"masterme_expert_kneecomp_kneecompknee" => 6,
|
||||
"masterme_expert_kneecomp_kneecomplink" => 60,
|
||||
"masterme_expert_kneecomp_kneecompmakeup" => 0,
|
||||
"masterme_expert_kneecomp_kneecomprelease" => 340,
|
||||
"masterme_expert_kneecomp_kneecompstrength" => 20,
|
||||
"masterme_expert_kneecomp_kneecomptar_thresh" => -4,
|
||||
"masterme_expert_leveler_levelerbrakethreshold" => -10,
|
||||
"masterme_expert_leveler_levelerbypass" => false,
|
||||
"masterme_expert_leveler_levelermax" => 10,
|
||||
"masterme_expert_leveler_levelermax_" => 10,
|
||||
"masterme_expert_leveler_levelerspeed" => 20,
|
||||
"masterme_expert_limiter_limiterattack" => 3,
|
||||
"masterme_expert_limiter_limiterbypass" => false,
|
||||
"masterme_expert_limiter_limiterff_fb" => 50,
|
||||
"masterme_expert_limiter_limiterknee" => 3,
|
||||
"masterme_expert_limiter_limitermakeup" => 0,
|
||||
"masterme_expert_limiter_limiterrelease" => 40,
|
||||
"masterme_expert_limiter_limiterstrength" => 80,
|
||||
"masterme_expert_limiter_limitertar_thresh" => 6,
|
||||
"masterme_expert_mscomp_bypass_mscompbypass" => false,
|
||||
"masterme_expert_mscomp_highband_highattack" => 8,
|
||||
"masterme_expert_mscomp_highband_highcrossover" => 8000,
|
||||
"masterme_expert_mscomp_highband_highknee" => 12,
|
||||
"masterme_expert_mscomp_highband_highlink" => 30,
|
||||
"masterme_expert_mscomp_highband_highrelease" => 30,
|
||||
"masterme_expert_mscomp_highband_highstrength" => 30,
|
||||
"masterme_expert_mscomp_highband_hightar_thresh" => -12,
|
||||
"masterme_expert_mscomp_lowband_lowattack" => 15,
|
||||
"masterme_expert_mscomp_lowband_lowcrossover" => 60,
|
||||
"masterme_expert_mscomp_lowband_lowknee" => 12,
|
||||
"masterme_expert_mscomp_lowband_lowlink" => 70,
|
||||
"masterme_expert_mscomp_lowband_lowrelease" => 150,
|
||||
"masterme_expert_mscomp_lowband_lowstrength" => 10,
|
||||
"masterme_expert_mscomp_lowband_lowtar_thresh" => -3,
|
||||
"masterme_expert_mscomp_out_makeup" => 1,
|
||||
"masterme_expert_pre_processing_dcblocker" => false,
|
||||
"masterme_expert_pre_processing_inputgain" => 0,
|
||||
"masterme_expert_pre_processing_mono" => false,
|
||||
"masterme_expert_pre_processing_phasel" => false,
|
||||
"masterme_expert_pre_processing_phaser" => false,
|
||||
"masterme_expert_pre_processing_stereocorrect" => false,
|
||||
];
|
||||
|
||||
return match ($this) {
|
||||
self::MusicGeneral => [
|
||||
...$defaults,
|
||||
],
|
||||
self::SpeechGeneral => [
|
||||
...$defaults,
|
||||
"masterme_expert_eq_highpass_eqhighpassfreq" => 20,
|
||||
"masterme_expert_eq_sideeq_eqsidegain" => 0,
|
||||
"masterme_expert_gate_gateattack" => 1,
|
||||
"masterme_expert_gate_gaterelease" => 500,
|
||||
"masterme_expert_kneecomp_kneecompattack" => 5,
|
||||
"masterme_expert_kneecomp_kneecompknee" => 9,
|
||||
"masterme_expert_kneecomp_kneecomprelease" => 50,
|
||||
"masterme_expert_kneecomp_kneecompstrength" => 15,
|
||||
"masterme_expert_kneecomp_kneecomptar_thresh" => -6,
|
||||
"masterme_expert_leveler_levelerbrakethreshold" => -20,
|
||||
"masterme_expert_leveler_levelermax" => 30,
|
||||
"masterme_expert_leveler_levelermax_" => 30,
|
||||
"masterme_expert_limiter_limiterattack" => 1,
|
||||
"masterme_expert_limiter_limiterstrength" => 80,
|
||||
"masterme_expert_limiter_limitertar_thresh" => 3,
|
||||
"masterme_expert_mscomp_highband_highattack" => 0,
|
||||
"masterme_expert_mscomp_highband_highrelease" => 50,
|
||||
"masterme_expert_mscomp_highband_highstrength" => 40,
|
||||
"masterme_expert_mscomp_highband_hightar_thresh" => -7,
|
||||
"masterme_expert_mscomp_lowband_lowattack" => 10,
|
||||
"masterme_expert_mscomp_lowband_lowrelease" => 80,
|
||||
"masterme_expert_mscomp_lowband_lowstrength" => 20,
|
||||
"masterme_expert_mscomp_lowband_lowtar_thresh" => -5,
|
||||
"masterme_expert_pre_processing_dcblocker" => true,
|
||||
],
|
||||
self::EbuR128 => [
|
||||
...$defaults,
|
||||
"masterme_easy_target" => -23,
|
||||
"masterme_expert_eq_highpass_eqhighpassfreq" => 20,
|
||||
"masterme_expert_eq_sideeq_eqsidegain" => 0,
|
||||
"masterme_expert_gate_gateattack" => 1,
|
||||
"masterme_expert_gate_gaterelease" => 500,
|
||||
"masterme_expert_kneecomp_kneecompattack" => 5,
|
||||
"masterme_expert_kneecomp_kneecompbypass" => true,
|
||||
"masterme_expert_kneecomp_kneecompknee" => 9,
|
||||
"masterme_expert_kneecomp_kneecomprelease" => 50,
|
||||
"masterme_expert_kneecomp_kneecompstrength" => 15,
|
||||
"masterme_expert_kneecomp_kneecomptar_thresh" => -6,
|
||||
"masterme_expert_leveler_levelerbrakethreshold" => -20,
|
||||
"masterme_expert_leveler_levelermax" => 30,
|
||||
"masterme_expert_leveler_levelermax_" => 30,
|
||||
"masterme_expert_leveler_levelerspeed" => 40,
|
||||
"masterme_expert_limiter_limiterattack" => 1,
|
||||
"masterme_expert_limiter_limitertar_thresh" => 3,
|
||||
"masterme_expert_mscomp_highband_highattack" => 0,
|
||||
"masterme_expert_mscomp_highband_highrelease" => 50,
|
||||
"masterme_expert_mscomp_highband_highstrength" => 40,
|
||||
"masterme_expert_mscomp_highband_hightar_thresh" => -8,
|
||||
"masterme_expert_mscomp_lowband_lowattack" => 10,
|
||||
"masterme_expert_mscomp_lowband_lowrelease" => 80,
|
||||
"masterme_expert_mscomp_lowband_lowstrength" => 20,
|
||||
"masterme_expert_mscomp_lowband_lowtar_thresh" => -6,
|
||||
"masterme_expert_pre_processing_dcblocker" => true,
|
||||
],
|
||||
self::ApplePodcasts => [
|
||||
...$defaults,
|
||||
"globalbypass" => false,
|
||||
"masterme_expert_eq_highpass_eqhighpassfreq" => 20,
|
||||
"masterme_expert_eq_sideeq_eqsidegain" => 0,
|
||||
"masterme_expert_gate_gateattack" => 1,
|
||||
"masterme_expert_gate_gaterelease" => 500,
|
||||
"masterme_expert_kneecomp_kneecompattack" => 5,
|
||||
"masterme_expert_kneecomp_kneecompbypass" => true,
|
||||
"masterme_expert_kneecomp_kneecompknee" => 9,
|
||||
"masterme_expert_kneecomp_kneecomprelease" => 50,
|
||||
"masterme_expert_kneecomp_kneecompstrength" => 15,
|
||||
"masterme_expert_kneecomp_kneecomptar_thresh" => -6,
|
||||
"masterme_expert_leveler_levelerbrakethreshold" => -20,
|
||||
"masterme_expert_leveler_levelermax" => 30,
|
||||
"masterme_expert_leveler_levelermax_" => 30,
|
||||
"masterme_expert_leveler_levelerspeed" => 50,
|
||||
"masterme_expert_limiter_limiterattack" => 1,
|
||||
"masterme_expert_limiter_limitertar_thresh" => 3,
|
||||
"masterme_expert_mscomp_highband_highattack" => 0,
|
||||
"masterme_expert_mscomp_highband_highrelease" => 50,
|
||||
"masterme_expert_mscomp_highband_highstrength" => 40,
|
||||
"masterme_expert_mscomp_highband_hightar_thresh" => -8,
|
||||
"masterme_expert_mscomp_lowband_lowattack" => 10,
|
||||
"masterme_expert_mscomp_lowband_lowrelease" => 80,
|
||||
"masterme_expert_mscomp_lowband_lowstrength" => 20,
|
||||
"masterme_expert_mscomp_lowband_lowtar_thresh" => -6,
|
||||
"masterme_expert_pre_processing_dcblocker" => true,
|
||||
],
|
||||
self::YouTube => [
|
||||
...$defaults,
|
||||
"masterme_easy_target" => -14,
|
||||
"masterme_expert_eq_highpass_eqhighpassfreq" => 20,
|
||||
"masterme_expert_eq_sideeq_eqsidegain" => 0,
|
||||
"masterme_expert_gate_gateattack" => 1,
|
||||
"masterme_expert_gate_gaterelease" => 500,
|
||||
"masterme_expert_kneecomp_kneecompattack" => 5,
|
||||
"masterme_expert_kneecomp_kneecompbypass" => true,
|
||||
"masterme_expert_kneecomp_kneecompknee" => 9,
|
||||
"masterme_expert_kneecomp_kneecomprelease" => 50,
|
||||
"masterme_expert_kneecomp_kneecompstrength" => 15,
|
||||
"masterme_expert_kneecomp_kneecomptar_thresh" => -6,
|
||||
"masterme_expert_leveler_levelerbrakethreshold" => -20,
|
||||
"masterme_expert_leveler_levelermax" => 30,
|
||||
"masterme_expert_leveler_levelermax_" => 30,
|
||||
"masterme_expert_leveler_levelerspeed" => 50,
|
||||
"masterme_expert_limiter_limiterattack" => 1,
|
||||
"masterme_expert_limiter_limitertar_thresh" => 3,
|
||||
"masterme_expert_mscomp_highband_highattack" => 0,
|
||||
"masterme_expert_mscomp_highband_highstrength" => 40,
|
||||
"masterme_expert_mscomp_highband_hightar_thresh" => -8,
|
||||
"masterme_expert_mscomp_lowband_lowattack" => 10,
|
||||
"masterme_expert_mscomp_lowband_lowrelease" => 80,
|
||||
"masterme_expert_mscomp_lowband_lowstrength" => 20,
|
||||
"masterme_expert_mscomp_lowband_lowtar_thresh" => -6,
|
||||
"masterme_expert_pre_processing_dcblocker" => true,
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue