From 8962c56251f490e753f34b19cf84737a0d676ee1 Mon Sep 17 00:00:00 2001 From: zeo Date: Sun, 21 Jun 2026 18:33:18 +0500 Subject: [PATCH] Add channel mixer state API --- libopenmpt/libopenmpt.h | 71 ++++++++++++++++++++++++++++++++++ libopenmpt/libopenmpt_c.cpp | 64 ++++++++++++++++++++++++++++++ libopenmpt/libopenmpt_impl.cpp | 65 +++++++++++++++++++++++++++++++ libopenmpt/libopenmpt_impl.hpp | 27 +++++++++++++ 4 files changed, 227 insertions(+) diff --git a/libopenmpt/libopenmpt.h b/libopenmpt/libopenmpt.h index 486cada5b05..4ac037ef416 100644 --- a/libopenmpt/libopenmpt.h +++ b/libopenmpt/libopenmpt.h @@ -659,6 +659,40 @@ LIBOPENMPT_API int openmpt_probe_file_header_from_stream( uint64_t flags, openmp */ typedef struct openmpt_module openmpt_module; +/*! \brief Playback state of a channel + * + * State information for an active channel in the module mixer. + */ +typedef struct openmpt_module_current_channel_state { + int32_t volume; + int32_t final_volume; + int32_t pan; + int32_t instrument; + int32_t key; + int32_t period; + int32_t position; + int64_t increment; + int32_t pitchbend; + int32_t note; + int32_t sample; + int32_t muted; +} openmpt_module_current_channel_state; + +/*! \brief Properties and metadata of a sample slot + * + * State information, a pointer to raw audio data, and properties for a loaded sample. + */ +typedef struct openmpt_module_sample_state { + const void * data; + int32_t length; + int32_t loop_start; + int32_t loop_end; + int32_t flags; + int32_t c5speed; + int32_t channels; + int32_t bits_per_sample; +} openmpt_module_sample_state; + typedef struct openmpt_module_initial_ctl { const char * ctl; const char * value; @@ -1275,6 +1309,43 @@ LIBOPENMPT_API float openmpt_module_get_current_channel_vu_rear_left( openmpt_mo */ LIBOPENMPT_API float openmpt_module_get_current_channel_vu_rear_right( openmpt_module * mod, int32_t channel ); +/*! \brief Get the current state of a channel + * + * Retrieves the detailed real-time playback state of a specific mixer channel. + * \param mod The module handle to work on. + * \param channel The channel index to query (0-based). Must be in the range [0, openmpt_module_get_num_channels(mod) - 1]. + * \param state Pointer to an openmpt_module_current_channel_state structure that receives the channel's current playback state. + * \return 1 on success, 0 on failure (e.g., if mod or state is NULL, or channel is out of bounds). + * \sa openmpt_module_get_num_channels + */ +LIBOPENMPT_API int openmpt_module_get_current_channel_state( openmpt_module * mod, int32_t channel, openmpt_module_current_channel_state * state ); + +/*! \brief Get the current state of a sample + * + * Retrieves metadata, sample properties, and raw data pointer for a specific sample slot. + * \param mod The module handle to work on. + * \param sample The sample index to query (1-based, from 1 to openmpt_module_get_num_samples(mod)). + * \param state Pointer to an openmpt_module_sample_state structure that receives the sample properties. + * \return 1 on success, 0 on failure. + * \remarks This function returns 0 (failure) if the sample index is out of bounds, if the sample has no associated sample data, or if it represents an Adlib/OPL sample. + * \sa openmpt_module_get_num_samples + */ +LIBOPENMPT_API int openmpt_module_get_sample_state( openmpt_module * mod, int32_t sample, openmpt_module_sample_state * state ); + +/*! \brief Mute or unmute a specific channel + * + * Modifies or queries the mute state of a playback channel. + * \param mod The module handle to work on. + * \param channel The channel index to modify (0-based). Must be in the range [0, openmpt_module_get_num_channels(mod) - 1]. + * \param status The action to perform: + * - 0: Unmute the channel. + * - 1: Mute the channel. + * - -1: Query the current mute status without changing it. + * \return The mute state of the channel *before* the operation (1 if previously muted, 0 if previously unmuted), or -1 on error (e.g., if the channel index is out of bounds). + * \sa openmpt_module_get_num_channels + */ +LIBOPENMPT_API int32_t openmpt_module_channel_mute( openmpt_module * mod, int32_t channel, int32_t status ); + /*! \brief Get the number of sub-songs * * \param mod The module handle to work on. diff --git a/libopenmpt/libopenmpt_c.cpp b/libopenmpt/libopenmpt_c.cpp index e61bff27b78..3dde183e35d 100644 --- a/libopenmpt/libopenmpt_c.cpp +++ b/libopenmpt/libopenmpt_c.cpp @@ -1111,6 +1111,70 @@ float openmpt_module_get_current_channel_vu_rear_right( openmpt_module * mod, in return 0.0; } +int openmpt_module_get_current_channel_state( openmpt_module * mod, int32_t channel, openmpt_module_current_channel_state * state ) { + try { + openmpt::interface::check_soundfile( mod ); + if ( !state ) { + throw openmpt::interface::argument_null_pointer(); + } + openmpt::module_impl::current_channel_state cpp_state; + if ( !mod->impl->get_current_channel_state( channel, cpp_state ) ) { + return 0; + } + state->volume = cpp_state.volume; + state->final_volume = cpp_state.final_volume; + state->pan = cpp_state.pan; + state->instrument = cpp_state.instrument; + state->key = cpp_state.key; + state->period = cpp_state.period; + state->position = cpp_state.position; + state->increment = cpp_state.increment; + state->pitchbend = cpp_state.pitchbend; + state->note = cpp_state.note; + state->sample = cpp_state.sample; + state->muted = cpp_state.muted; + return 1; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} + +int openmpt_module_get_sample_state( openmpt_module * mod, int32_t sample, openmpt_module_sample_state * state ) { + try { + openmpt::interface::check_soundfile( mod ); + if ( !state ) { + throw openmpt::interface::argument_null_pointer(); + } + openmpt::module_impl::sample_state cpp_state; + if ( !mod->impl->get_sample_state( sample, cpp_state ) ) { + return 0; + } + state->data = cpp_state.data; + state->length = cpp_state.length; + state->loop_start = cpp_state.loop_start; + state->loop_end = cpp_state.loop_end; + state->flags = cpp_state.flags; + state->c5speed = cpp_state.c5speed; + state->channels = cpp_state.channels; + state->bits_per_sample = cpp_state.bits_per_sample; + return 1; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} + +int32_t openmpt_module_channel_mute( openmpt_module * mod, int32_t channel, int32_t status ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->set_channel_mute( channel, status ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return -1; +} + int32_t openmpt_module_get_num_subsongs( openmpt_module * mod ) { try { openmpt::interface::check_soundfile( mod ); diff --git a/libopenmpt/libopenmpt_impl.cpp b/libopenmpt/libopenmpt_impl.cpp index 4b826fca5c2..78217ffc93f 100644 --- a/libopenmpt/libopenmpt_impl.cpp +++ b/libopenmpt/libopenmpt_impl.cpp @@ -1408,6 +1408,71 @@ float module_impl::get_current_channel_vu_rear_right( std::int32_t channel ) con return m_sndFile->m_PlayState.Chn[channel].dwFlags[OpenMPT::CHN_SURROUND] ? m_sndFile->m_PlayState.Chn[channel].nRightVU * (1.0f/128.0f) : 0.0f; } +bool module_impl::get_current_channel_state( std::int32_t channel, current_channel_state & state ) const { + if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) { + return false; + } + const OpenMPT::ModChannel & chn = m_sndFile->m_PlayState.Chn[channel]; + state.volume = std::clamp( chn.nVolume / 4, 0, 64 ); + state.final_volume = std::clamp( static_cast( std::round( get_current_channel_vu_mono( channel ) * 64.0f ) ), 0, 64 ); + state.pan = std::clamp( chn.nPan, 0, 256 ); + state.instrument = chn.nNewIns > 0 ? static_cast( chn.nNewIns ) - 1 : -1; + state.key = OpenMPT::ModCommand::IsNote( chn.nNote ) ? static_cast( chn.nNote ) - 1 : -1; + state.period = chn.nPeriod; + state.position = static_cast( chn.position.GetUInt() ); + state.increment = chn.increment.GetRaw(); + state.pitchbend = chn.GetMIDIPitchBend(); + state.note = chn.nNote; + state.sample = 0; + if ( chn.pModSample != nullptr ) { + for ( OpenMPT::SAMPLEINDEX i = 1; i <= m_sndFile->GetNumSamples(); ++i ) { + if ( chn.pModSample == &m_sndFile->GetSample( i ) ) { + state.sample = i; + if ( state.instrument < 0 ) { + state.instrument = static_cast( i ) - 1; + } + break; + } + } + } + state.muted = chn.dwFlags[OpenMPT::CHN_MUTE] ? 1 : 0; + return true; +} + +bool module_impl::get_sample_state( std::int32_t sample, sample_state & state ) const { + if ( sample <= 0 || sample > m_sndFile->GetNumSamples() ) { + return false; + } + const OpenMPT::ModSample & smp = m_sndFile->GetSample( static_cast( sample ) ); + if ( !smp.HasSampleData() || smp.uFlags[OpenMPT::CHN_ADLIB] ) { + return false; + } + state.data = smp.samplev(); + state.length = static_cast( smp.nLength ); + state.loop_start = static_cast( smp.nLoopStart ); + state.loop_end = static_cast( smp.nLoopEnd ); + state.flags = static_cast( static_cast( smp.uFlags ) ); + state.c5speed = static_cast( smp.nC5Speed ); + state.channels = static_cast( smp.GetNumChannels() ); + state.bits_per_sample = static_cast( smp.GetElementarySampleSize() * 8 ); + return true; +} + +std::int32_t module_impl::set_channel_mute( std::int32_t channel, std::int32_t status ) { + if ( channel < 0 || channel >= m_sndFile->GetNumChannels() ) { + return -1; + } + OpenMPT::ModChannel & chn = m_sndFile->m_PlayState.Chn[channel]; + const bool muted = chn.dwFlags[OpenMPT::CHN_MUTE] || m_sndFile->ChnSettings[channel].dwFlags[OpenMPT::CHN_MUTE]; + if ( status < 0 ) { + return muted ? 1 : 0; + } + const bool setMuted = status != 0; + chn.dwFlags.set( OpenMPT::CHN_MUTE, setMuted ); + m_sndFile->ChnSettings[channel].dwFlags.set( OpenMPT::CHN_MUTE, setMuted ); + return muted ? 1 : 0; +} + std::int32_t module_impl::get_num_subsongs() const { std::unique_ptr subsongs_temp = has_subsongs_inited() ? std::unique_ptr() : std::make_unique( get_subsongs() ); const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp; diff --git a/libopenmpt/libopenmpt_impl.hpp b/libopenmpt/libopenmpt_impl.hpp index 7e5faac4b04..3d2baaf1be7 100644 --- a/libopenmpt/libopenmpt_impl.hpp +++ b/libopenmpt/libopenmpt_impl.hpp @@ -239,6 +239,33 @@ class module_impl { float get_current_channel_vu_right( std::int32_t channel ) const; float get_current_channel_vu_rear_left( std::int32_t channel ) const; float get_current_channel_vu_rear_right( std::int32_t channel ) const; + struct current_channel_state { + std::int32_t volume = 0; + std::int32_t final_volume = 0; + std::int32_t pan = 128; + std::int32_t instrument = -1; + std::int32_t key = -1; + std::int32_t period = 0; + std::int32_t position = 0; + std::int64_t increment = 0; + std::int32_t pitchbend = 0; + std::int32_t note = 0; + std::int32_t sample = 0; + std::int32_t muted = 0; + }; + struct sample_state { + const void * data = nullptr; + std::int32_t length = 0; + std::int32_t loop_start = 0; + std::int32_t loop_end = 0; + std::int32_t flags = 0; + std::int32_t c5speed = 0; + std::int32_t channels = 0; + std::int32_t bits_per_sample = 0; + }; + bool get_current_channel_state( std::int32_t channel, current_channel_state & state ) const; + bool get_sample_state( std::int32_t sample, sample_state & state ) const; + std::int32_t set_channel_mute( std::int32_t channel, std::int32_t status ); std::int32_t get_num_subsongs() const; std::int32_t get_num_channels() const; std::int32_t get_num_orders() const;