diff --git a/Pulsus/Audio/AudioEngine.cs b/Pulsus/Audio/AudioEngine.cs index cbcc665..6918a72 100644 --- a/Pulsus/Audio/AudioEngine.cs +++ b/Pulsus/Audio/AudioEngine.cs @@ -51,8 +51,12 @@ public SoundInstanceInternal(SoundInstance instance) long buffersMixedCount = 0; float volume = 1.0f; - public AudioEngine(string audioDevice = null, AudioDriver driver = AudioDriver.Default, int sampleRate = 44100, int bufferLength = 1024) + private ResampleQuality resampleQuality; + + public AudioEngine(string audioDevice = null, AudioDriver driver = AudioDriver.Default, int sampleRate = 44100, int bufferLength = 1024, ResampleQuality resampleQuality = ResampleQuality.High) { + this.resampleQuality = resampleQuality; + SDL.SDL_InitSubSystem(SDL.SDL_INIT_AUDIO); audioCallback = AudioCallback; @@ -261,7 +265,7 @@ public uint GetBufferOffset() public SoundData LoadFromFile(string path) { return new SoundData(FFmpeg.FFmpegHelper.SoundFromFileResample(path, - audioSpec.freq, audioSpec.channels, audioSpec.format)); + audioSpec.freq, audioSpec.channels, audioSpec.format, resampleQuality)); } public void Play(SoundInstance soundInstance, int polyphony) diff --git a/Pulsus/FFmpeg/FFmpegContext.cs b/Pulsus/FFmpeg/FFmpegContext.cs index 926e308..dd3eae1 100644 --- a/Pulsus/FFmpeg/FFmpegContext.cs +++ b/Pulsus/FFmpeg/FFmpegContext.cs @@ -296,7 +296,7 @@ private void SetupCodecContext(AVStream* stream) format = (int)codecContext->sample_fmt; } - public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int channels) + public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int channels, ResampleQuality resampleQuality = ResampleQuality.High) { if (format == (int)sampleFormat && this.sampleRate == sampleRate && @@ -312,6 +312,7 @@ public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int cha int channelLayout = (int)ffmpeg.av_get_default_channel_layout(channels); swrContext = ffmpeg.swr_alloc(); + ffmpeg.av_opt_set_int(swrContext, "in_channel_layout", (int)codecContext->channel_layout, 0); ffmpeg.av_opt_set_int(swrContext, "out_channel_layout", channelLayout, 0); ffmpeg.av_opt_set_int(swrContext, "in_channel_count", codecContext->channels, 0); @@ -321,6 +322,25 @@ public void ConvertToFormat(AVSampleFormat sampleFormat, int sampleRate, int cha ffmpeg.av_opt_set_sample_fmt(swrContext, "in_sample_fmt", codecContext->sample_fmt, 0); ffmpeg.av_opt_set_sample_fmt(swrContext, "out_sample_fmt", sampleFormat, 0); + switch (resampleQuality) + { + case ResampleQuality.Low: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 0, 0); + ffmpeg.av_opt_set_int(swrContext, "phase_shift", 0, 0); + break; + case ResampleQuality.Medium: + // default ffmpeg settings + break; + case ResampleQuality.High: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 128, 0); + ffmpeg.av_opt_set_double(swrContext, "cutoff", 1.0, 0); + break; + case ResampleQuality.Highest: + ffmpeg.av_opt_set_int(swrContext, "filter_size", 256, 0); + ffmpeg.av_opt_set_double(swrContext, "cutoff", 1.0, 0); + break; + } + if (ffmpeg.swr_init(swrContext) != 0) throw new ApplicationException("Failed init SwrContext: " + FFmpegHelper.logLastLine); } diff --git a/Pulsus/FFmpeg/FFmpegHelper.cs b/Pulsus/FFmpeg/FFmpegHelper.cs index 62d1616..6fe8c72 100644 --- a/Pulsus/FFmpeg/FFmpegHelper.cs +++ b/Pulsus/FFmpeg/FFmpegHelper.cs @@ -134,7 +134,7 @@ public static byte[] SoundFromFile(string path, out int sampleRate, out int chan } } - public static byte[] SoundFromFileResample(string path, int sampleRate, int channels, ushort sampleFormatSDL) + public static byte[] SoundFromFileResample(string path, int sampleRate, int channels, ushort sampleFormatSDL, ResampleQuality resampleQuality = ResampleQuality.High) { AVSampleFormat targetFormat2; switch (sampleFormatSDL) @@ -157,7 +157,7 @@ public static byte[] SoundFromFileResample(string path, int sampleRate, int chan ffContext.SelectStream(AVMediaType.AVMEDIA_TYPE_AUDIO); // setup resamplers and other format converters if needed - ffContext.ConvertToFormat(targetFormat2, sampleRate, channels); + ffContext.ConvertToFormat(targetFormat2, sampleRate, channels, resampleQuality); // FFmpeg only approximates stream durations but is // usually not far from the real duration. diff --git a/Pulsus/Game.cs b/Pulsus/Game.cs index e787674..af3f4e2 100644 --- a/Pulsus/Game.cs +++ b/Pulsus/Game.cs @@ -34,7 +34,9 @@ public Game() FFmpegHelper.Init(); Log.Info("Initializing Audio..."); - audio = new AudioEngine(null, settings.audio.driver, (int)settings.audio.sampleRate, (int)settings.audio.bufferLength); + audio = new AudioEngine(null, settings.audio.driver, + (int)settings.audio.sampleRate, (int)settings.audio.bufferLength, + settings.audio.resampleQuality); audio.SetVolume(Math.Min(settings.audio.volume, 100) / 100.0f); Log.Info("Audio driver: " + audio.audioDriver.ToString()); diff --git a/Pulsus/Pulsus.csproj b/Pulsus/Pulsus.csproj index 3a824da..281039a 100644 --- a/Pulsus/Pulsus.csproj +++ b/Pulsus/Pulsus.csproj @@ -199,6 +199,7 @@ + diff --git a/Pulsus/Shared/ResampleQuality.cs b/Pulsus/Shared/ResampleQuality.cs new file mode 100644 index 0000000..d75b725 --- /dev/null +++ b/Pulsus/Shared/ResampleQuality.cs @@ -0,0 +1,10 @@ +namespace Pulsus +{ + public enum ResampleQuality + { + Low, + Medium, + High, + Highest, + } +} diff --git a/Pulsus/Shared/Settings.cs b/Pulsus/Shared/Settings.cs index ad10be9..b75eddb 100644 --- a/Pulsus/Shared/Settings.cs +++ b/Pulsus/Shared/Settings.cs @@ -1,11 +1,11 @@ -using Jil; +using System; +using System.Collections.Generic; +using Jil; using Pulsus.Audio; using Pulsus.Gameplay; using Pulsus.Graphics; using Pulsus.Input; using SDL2; -using System.Collections.Generic; -using System; namespace Pulsus { @@ -82,10 +82,10 @@ public class VideoSettings public class AudioSettings { public AudioDriver driver = AudioDriver.Default; - public uint volume = 30; public uint sampleRate = 44100; public uint bufferLength = 768; + public ResampleQuality resampleQuality = ResampleQuality.Low; } public class InputSettings diff --git a/Pulsus/Shared/SettingsParser.cs b/Pulsus/Shared/SettingsParser.cs index 2e82cf6..cd4fb5c 100644 --- a/Pulsus/Shared/SettingsParser.cs +++ b/Pulsus/Shared/SettingsParser.cs @@ -32,6 +32,8 @@ private Settings settings } } + private bool resampleQualityOverridden = false; + public Settings Parse(string[] args) { for (int i = 1; i < args.Length; i++) @@ -67,12 +69,17 @@ private static void PrintHelp() Tuple.Create("-m VALUE, --measure VALUE", "Starts the chart from measure number VALUE [0-999]"), Tuple.Create("", ""), Tuple.Create("--render OUTPUT.wav", "Renders all audio of CHARTFILE to file"), + Tuple.Create("--resample-quality [low|medium|high|highest]", + "Changes resampling quality of audio samples"), Tuple.Create("--dump-timestamps OUTPUT", "Dumps all generated note event timestamps of CHARTFILE"), }; foreach (var option in options) { - Console.WriteLine(" {0,-35}{1}", option.Item1, option.Item2); + if (option.Item1.Length < 35) + Console.WriteLine(" {0,-35}{1}", option.Item1, option.Item2); + else + Console.WriteLine(" {0,-35}\n {2,-35}{1}", option.Item1, option.Item2, ""); } Console.Write("\n"); @@ -127,6 +134,20 @@ public bool ParseArg(string key, string value) settings.audio.volume = 100; settings.outputPath = value; + if (!resampleQualityOverridden) + settings.audio.resampleQuality = ResampleQuality.Highest; + break; + case "--resample-quality": + { + ResampleQuality resampleQuality; + if (Enum.TryParse(value, true, out resampleQuality)) + { + settings.audio.resampleQuality = resampleQuality; + resampleQualityOverridden = true; + } + else + Log.Warning("Unknown resample quality value: " + value); + } break; case "--dump-timestamps": settings.outputMode = OutputMode.DumpTimestamps;