View Issue Details

IDProjectCategoryView StatusLast Update
0001080OpenMPT[All Projects] libopenmptpublic2018-01-25 19:35
ReporterSaga MusixAssigned To 
PrioritynormalSeverityfeatureReproducibilityN/A
Status newResolutionopen 
Product Version 
Target VersionOpenMPT 1.?? (libopenmpt 1.0) (goals)Fixed in Version 
Summary0001080: Auto-normalize
Description

Add a ctl to libopenmpt that allows to auto-normalize module playback, like BASS/XMPlay.
As far as I'm aware, XMPlay renders the module at a very low mix rate (1000 Hz or so) for its length detection and normalization procedure. We could do something similar, or add the logic to GetLength, which might be less accurate but faster.

TagsNo tags attached.
Has the bug occurred in previous versions?
Tested code revision (in case you know it)

Activities

Saga Musix

Saga Musix

2018-01-25 19:35

administrator   ~0003396

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.patch (5,596 bytes)

Issue History

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