View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0001080 | OpenMPT | libopenmpt | public | 2018-01-25 14:54 | 2025-06-09 08:59 |
| 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) | |||||
| related to | 0001894 | closed | Saga Musix | Please add Automatic Gain Control in LibOpentMPT |
|
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 );
|
|
|
This I very wait ) and about this I ask here https://bugs.openmpt.org/view.php?id=1894 ) |
|
| 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 | |
| 2025-06-08 20:08 | Saga Musix | Relationship added | related to 0001894 |
| 2025-06-09 08:59 | OenMPT User | Note Added: 0006387 |