View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001080 | OpenMPT | libopenmpt | public | 2018-01-25 14:54 | 2022-01-14 18:15 |
Reporter | Saga Musix | Assigned To | |||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | new | Resolution | open | ||
Target Version | OpenMPT 1.?? (libopenmpt 1.0) (goals) | ||||
Summary | 0001080: Auto-normalize | ||||
Description | Add a ctl to libopenmpt that allows to auto-normalize module playback, like BASS/XMPlay. | ||||
Tags | No tags attached. | ||||
Attached Files | |||||
Has the bug occurred in previous versions? | |||||
Tested code revision (in case you know it) | |||||
Proof-of-concept patch added. It works surprisingly well already, but there are some things to consider. For example, we might want to offer different quality levels for the auto-normalization. Right now, it works at 1000 Hz / mono, but doing it in stereo at a higher sample rate can be more accurate. Dropping plugins from the scan process can also increase speed, but make the result less accurate. auto-normalize.patch (5,596 bytes)
Index: libopenmpt/libopenmpt_impl.cpp =================================================================== --- libopenmpt/libopenmpt_impl.cpp (revision 9521) +++ libopenmpt/libopenmpt_impl.cpp (working copy) @@ -450,6 +450,7 @@ m_current_subsong = 0; m_currentPositionSeconds = 0.0; m_Gain = 1.0f; + m_AutoGain = 1.0f; m_ctl_play_at_end = song_end_action::fadeout_song; m_ctl_load_skip_samples = false; m_ctl_load_skip_patterns = false; @@ -493,6 +494,7 @@ for ( const auto & ctl : ctls ) { ctl_set( ctl.first, ctl.second, false ); } + update_autogain(); } bool module_impl::is_loaded() const { return m_loaded; @@ -503,7 +505,7 @@ std::size_t count_read = 0; while ( count > 0 ) { std::int16_t * const buffers[4] = { left + count_read, right + count_read, rear_left + count_read, rear_right + count_read }; - AudioReadTargetGainBuffer<std::int16_t> target(*m_Dither, 0, buffers, m_Gain); + AudioReadTargetGainBuffer<std::int16_t> target(*m_Dither, 0, buffers, m_Gain * m_AutoGain); std::size_t count_chunk = m_sndFile->Read( static_cast<CSoundFile::samplecount_t>( std::min<std::uint64_t>( count, std::numeric_limits<CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ), // safety margin / samplesize / channels target @@ -526,7 +528,7 @@ std::size_t count_read = 0; while ( count > 0 ) { float * const buffers[4] = { left + count_read, right + count_read, rear_left + count_read, rear_right + count_read }; - AudioReadTargetGainBuffer<float> target(*m_Dither, 0, buffers, m_Gain); + AudioReadTargetGainBuffer<float> target(*m_Dither, 0, buffers, m_Gain * m_AutoGain); std::size_t count_chunk = m_sndFile->Read( static_cast<CSoundFile::samplecount_t>( std::min<std::uint64_t>( count, std::numeric_limits<CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ), // safety margin / samplesize / channels target @@ -548,7 +550,7 @@ m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; while ( count > 0 ) { - AudioReadTargetGainBuffer<std::int16_t> target(*m_Dither, interleaved + count_read * channels, 0, m_Gain); + AudioReadTargetGainBuffer<std::int16_t> target(*m_Dither, interleaved + count_read * channels, 0, m_Gain * m_AutoGain); std::size_t count_chunk = m_sndFile->Read( static_cast<CSoundFile::samplecount_t>( std::min<std::uint64_t>( count, std::numeric_limits<CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ), // safety margin / samplesize / channels target @@ -570,7 +572,7 @@ m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; while ( count > 0 ) { - AudioReadTargetGainBuffer<float> target(*m_Dither, interleaved + count_read * channels, 0, m_Gain); + AudioReadTargetGainBuffer<float> target(*m_Dither, interleaved + count_read * channels, 0, m_Gain * m_AutoGain); std::size_t count_chunk = m_sndFile->Read( static_cast<CSoundFile::samplecount_t>( std::min<std::uint64_t>( count, std::numeric_limits<CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ), // safety margin / samplesize / channels target @@ -1658,4 +1660,42 @@ } } +void module_impl::update_autogain() { + std::int32_t current_subsong = get_selected_subsong(); + select_subsong( all_subsongs); + + std::uint64_t samples = Util::Round<std::uint64_t>( get_duration_seconds() * 1000.0 ); + float buffer[512]; + auto old_dither = m_Dither->GetMode(); + m_Dither->SetMode( DitherNone ); + auto orig_settings = m_sndFile->m_Resampler.m_Settings, new_settings = m_sndFile->m_Resampler.m_Settings; + new_settings.emulateAmiga = false; + new_settings.SrcMode = SRCMODE_NEAREST; + m_sndFile->SetResamplerSettings( new_settings ); + + apply_mixer_settings( 1000, 1 ); + float peak = 0.0f; + while ( samples > 0 ) { + AudioReadTargetBuffer<float> target(*m_Dither, buffer, nullptr ); + std::size_t count_chunk = m_sndFile->Read( + static_cast<std::uint32_t>( std::min<std::uint64_t>( mpt::size(buffer), samples ) ), + target + ); + if(count_chunk == 0) { + break; + } + auto minmax = std::minmax_element( std::begin( buffer ), std::begin( buffer ) + count_chunk ); + peak = std::max( { peak, mpt::abs(*minmax.first), mpt::abs(*minmax.second) } ); + samples -= count_chunk; + } + + m_Dither->SetMode( old_dither ); + m_sndFile->SetResamplerSettings( orig_settings ); + select_subsong( current_subsong ); + + if ( peak > 0.0f ) { + m_AutoGain = 1.0f / peak; + } +} + } // namespace openmpt Index: libopenmpt/libopenmpt_impl.hpp =================================================================== --- libopenmpt/libopenmpt_impl.hpp (revision 9521) +++ libopenmpt/libopenmpt_impl.hpp (working copy) @@ -98,7 +98,7 @@ bool m_loaded; std::unique_ptr<OpenMPT::Dither> m_Dither; subsongs_type m_subsongs; - float m_Gain; + float m_Gain, m_AutoGain; song_end_action m_ctl_play_at_end; bool m_ctl_load_skip_samples; bool m_ctl_load_skip_patterns; @@ -126,6 +126,7 @@ std::pair< std::string, std::string > format_and_highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int command ) const; std::pair< std::string, std::string > format_and_highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const; static double could_open_probability( const OpenMPT::FileReader & file, double effort, std::unique_ptr<log_interface> log ); + void update_autogain(); public: static std::vector<std::string> get_supported_extensions(); static bool is_extension_supported( const char * extension ); |
|
auto-normalize-2.patch (6,810 bytes)
Index: libopenmpt/libopenmpt_impl.cpp =================================================================== --- libopenmpt/libopenmpt_impl.cpp (revision 16515) +++ libopenmpt/libopenmpt_impl.cpp (working copy) @@ -468,6 +468,7 @@ m_current_subsong = 0; m_currentPositionSeconds = 0.0; m_Gain = 1.0f; + m_AutoGain = 1.0f; m_ctl_play_at_end = song_end_action::fadeout_song; m_ctl_load_skip_samples = false; m_ctl_load_skip_patterns = false; @@ -511,6 +512,7 @@ for ( const auto & ctl : ctls ) { ctl_set( ctl.first, ctl.second, false ); } + update_autogain(); } bool module_impl::is_loaded() const { return m_loaded; @@ -520,7 +522,7 @@ m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; std::int16_t * const buffers[4] = { left, right, rear_left, rear_right }; - OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<std::int16_t>> target( mpt::audio_span_planar<std::int16_t>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain ); + OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<std::int16_t>> target( mpt::audio_span_planar<std::int16_t>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain * m_AutoGain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels @@ -543,7 +545,7 @@ m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; float * const buffers[4] = { left, right, rear_left, rear_right }; - OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<float>> target( mpt::audio_span_planar<float>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain ); + OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_planar<float>> target( mpt::audio_span_planar<float>( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain * m_AutoGain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels @@ -565,7 +567,7 @@ m_sndFile->ResetMixStat(); m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; - OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<std::int16_t>> target( mpt::audio_span_interleaved<std::int16_t>( interleaved, channels, count ), *m_Dithers, m_Gain ); + OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<std::int16_t>> target( mpt::audio_span_interleaved<std::int16_t>( interleaved, channels, count ), *m_Dithers, m_Gain * m_AutoGain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels @@ -587,7 +589,7 @@ m_sndFile->ResetMixStat(); m_sndFile->m_bIsRendering = ( m_ctl_play_at_end != song_end_action::fadeout_song ); std::size_t count_read = 0; - OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<float>> target( mpt::audio_span_interleaved<float>( interleaved, channels, count ), *m_Dithers, m_Gain ); + OpenMPT::AudioTargetBufferWithGain<mpt::audio_span_interleaved<float>> target( mpt::audio_span_interleaved<float>( interleaved, channels, count ), *m_Dithers, m_Gain * m_AutoGain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( static_cast<OpenMPT::CSoundFile::samplecount_t>( std::min( static_cast<std::uint64_t>( count ), static_cast<std::uint64_t>( std::numeric_limits<OpenMPT::CSoundFile::samplecount_t>::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels @@ -2141,4 +2143,42 @@ } } +void module_impl::update_autogain() { + std::int32_t current_subsong = get_selected_subsong(); + select_subsong( all_subsongs); + + std::uint64_t samples = Util::Round<std::uint64_t>( get_duration_seconds() * 1000.0 ); + float buffer[512]; + auto old_dither = m_Dither->GetMode(); + m_Dither->SetMode( DitherNone ); + auto orig_settings = m_sndFile->m_Resampler.m_Settings, new_settings = m_sndFile->m_Resampler.m_Settings; + new_settings.emulateAmiga = false; + new_settings.SrcMode = SRCMODE_NEAREST; + m_sndFile->SetResamplerSettings( new_settings ); + + apply_mixer_settings( 1000, 1 ); + float peak = 0.0f; + while ( samples > 0 ) { + AudioReadTargetBuffer<float> target(*m_Dither, buffer, nullptr ); + std::size_t count_chunk = m_sndFile->Read( + static_cast<std::uint32_t>( std::min<std::uint64_t>( mpt::size(buffer), samples ) ), + target + ); + if(count_chunk == 0) { + break; + } + auto minmax = std::minmax_element( std::begin( buffer ), std::begin( buffer ) + count_chunk ); + peak = std::max( { peak, mpt::abs(*minmax.first), mpt::abs(*minmax.second) } ); + samples -= count_chunk; + } + + m_Dither->SetMode( old_dither ); + m_sndFile->SetResamplerSettings( orig_settings ); + select_subsong( current_subsong ); + + if ( peak > 0.0f ) { + m_AutoGain = 1.0f / peak; + } +} + } // namespace openmpt Index: libopenmpt/libopenmpt_impl.hpp =================================================================== --- libopenmpt/libopenmpt_impl.hpp (revision 16515) +++ libopenmpt/libopenmpt_impl.hpp (working copy) @@ -131,7 +131,7 @@ bool m_mixer_initialized; std::unique_ptr<OpenMPT::DithersWrapperOpenMPT> m_Dithers; subsongs_type m_subsongs; - float m_Gain; + float m_Gain, m_AutoGain; song_end_action m_ctl_play_at_end; amiga_filter_type m_ctl_render_resampler_emulate_amiga_type = amiga_filter_type::auto_filter; bool m_ctl_load_skip_samples; @@ -162,6 +162,7 @@ std::pair< std::string, std::string > format_and_highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int command ) const; std::pair< std::string, std::string > format_and_highlight_pattern_row_channel( std::int32_t p, std::int32_t r, std::int32_t c, std::size_t width, bool pad ) const; static double could_open_probability( const OpenMPT::FileCursor & file, double effort, std::unique_ptr<log_interface> log ); + void update_autogain(); public: static std::vector<std::string> get_supported_extensions(); static bool is_extension_supported( std::string_view extension ); |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2018-01-25 14:54 | Saga Musix | New Issue | |
2018-01-25 14:55 | manx | Target Version | => OpenMPT 1.?? (libopenmpt 1.0) (goals) |
2018-01-25 19:35 | Saga Musix | File Added: auto-normalize.patch | |
2018-01-25 19:35 | Saga Musix | Note Added: 0003396 | |
2022-01-14 18:15 | Saga Musix | Note Added: 0004997 | |
2022-01-14 18:15 | Saga Musix | File Added: auto-normalize-2.patch |