Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions libopenmpt/libopenmpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,31 @@ 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 per-channel audio output for the most recently rendered block, mixed to mono.
*
* \param mod The module handle to work on.
* \param channel The channel index (0-based).
* \param count Number of frames to retrieve. Must not exceed the count passed to the most recent openmpt_module_read_*() call.
* \param buf Output buffer of at least \p count floats. Values are in the range [-1.0, 1.0].
* \return The number of frames written to \p buf, or 0 if capture is not enabled or the channel is out of range.
* \remarks Enable capture first via openmpt_module_ctl_set_boolean(mod, "render.channel_capture", 1).
* Works for both PCM and OPL/FM channels. Must be called after openmpt_module_read_*() and before the next read call.
*/
LIBOPENMPT_API size_t openmpt_module_get_current_channel_audio_mono( openmpt_module * mod, int32_t channel, size_t count, float * buf );

/*! \brief Get the per-channel audio output for the most recently rendered block, as separate left/right channels.
*
* \param mod The module handle to work on.
* \param channel The channel index (0-based).
* \param count Number of frames to retrieve. Must not exceed the count passed to the most recent openmpt_module_read_*() call.
* \param buf_left Output buffer of at least \p count floats for the left channel.
* \param buf_right Output buffer of at least \p count floats for the right channel.
* \return The number of frames written, or 0 if capture is not enabled or the channel is out of range.
* \remarks Enable capture first via openmpt_module_ctl_set_boolean(mod, "render.channel_capture", 1).
* Works for both PCM and OPL/FM channels. Must be called after openmpt_module_read_*() and before the next read call.
*/
LIBOPENMPT_API size_t openmpt_module_get_current_channel_audio_stereo( openmpt_module * mod, int32_t channel, size_t count, float * buf_left, float * buf_right );

/*! \brief Get the number of sub-songs
*
* \param mod The module handle to work on.
Expand Down
23 changes: 23 additions & 0 deletions libopenmpt/libopenmpt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,29 @@ class LIBOPENMPT_CXX_API_CLASS module {
*/
LIBOPENMPT_CXX_API_MEMBER float get_current_channel_vu_rear_right( std::int32_t channel ) const;

//! Get the per-channel audio output for the most recently rendered block, mixed to mono.
/*!
\param channel The channel index (0-based).
\param count Number of frames to retrieve. Must not exceed the count passed to the most recent read() call.
\param buf Output buffer of at least \p count floats. Values are in the range [-1.0, 1.0].
\return The number of frames written to \p buf, or 0 if capture is not enabled or the channel is out of range.
\remarks Enable capture first via ctl_set_boolean("render.channel_capture", true).
Works for both PCM and OPL/FM channels. Must be called after read() and before the next read() call.
*/
LIBOPENMPT_CXX_API_MEMBER std::size_t get_current_channel_audio_mono( std::int32_t channel, std::size_t count, float * buf ) const;

//! Get the per-channel audio output for the most recently rendered block, as separate left/right channels.
/*!
\param channel The channel index (0-based).
\param count Number of frames to retrieve. Must not exceed the count passed to the most recent read() call.
\param buf_left Output buffer of at least \p count floats for the left channel.
\param buf_right Output buffer of at least \p count floats for the right channel.
\return The number of frames written, or 0 if capture is not enabled or the channel is out of range.
\remarks Enable capture first via ctl_set_boolean("render.channel_capture", true).
Works for both PCM and OPL/FM channels. Must be called after read() and before the next read() call.
*/
LIBOPENMPT_CXX_API_MEMBER std::size_t get_current_channel_audio_stereo( std::int32_t channel, std::size_t count, float * buf_left, float * buf_right ) const;

//! Get the number of sub-songs
/*!
\return The number of sub-songs in the module. This includes any "hidden" songs (songs that share the same sequence, but start at different order indices) and "normal" sub-songs or "sequences" (if the format supports them).
Expand Down
18 changes: 18 additions & 0 deletions libopenmpt/libopenmpt_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,24 @@ float openmpt_module_get_current_channel_vu_rear_right( openmpt_module * mod, in
}
return 0.0;
}
size_t openmpt_module_get_current_channel_audio_mono( openmpt_module * mod, int32_t channel, size_t count, float * buf ) {
try {
openmpt::interface::check_soundfile( mod );
return mod->impl->get_current_channel_audio_mono( channel, count, buf );
} catch ( ... ) {
openmpt::report_exception( __func__, mod );
}
return 0;
}
size_t openmpt_module_get_current_channel_audio_stereo( openmpt_module * mod, int32_t channel, size_t count, float * buf_left, float * buf_right ) {
try {
openmpt::interface::check_soundfile( mod );
return mod->impl->get_current_channel_audio_stereo( channel, count, buf_left, buf_right );
} catch ( ... ) {
openmpt::report_exception( __func__, mod );
}
return 0;
}

int32_t openmpt_module_get_num_subsongs( openmpt_module * mod ) {
try {
Expand Down
7 changes: 7 additions & 0 deletions libopenmpt/libopenmpt_cxx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,13 @@ float module::get_current_channel_vu_rear_right( std::int32_t channel ) const {
return impl->get_current_channel_vu_rear_right( channel );
}

std::size_t module::get_current_channel_audio_mono( std::int32_t channel, std::size_t count, float * buf ) const {
return impl->get_current_channel_audio_mono( channel, count, buf );
}
std::size_t module::get_current_channel_audio_stereo( std::int32_t channel, std::size_t count, float * buf_left, float * buf_right ) const {
return impl->get_current_channel_audio_stereo( channel, count, buf_left, buf_right );
}

std::int32_t module::get_num_subsongs() const {
return impl->get_num_subsongs();
}
Expand Down
76 changes: 76 additions & 0 deletions libopenmpt/libopenmpt_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "common/FileReader.h"
#include "common/Logging.h"
#include "soundlib/Sndfile.h"
#include "soundlib/OPL.h"
#include "soundlib/mod_specifications.h"
#include "soundlib/AudioReadTarget.h"

Expand Down Expand Up @@ -1407,6 +1408,75 @@ 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;
}
std::size_t module_impl::get_current_channel_audio_mono( std::int32_t channel, std::size_t count, float * buf ) const {
if ( !buf || count == 0 || channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
return 0;
}
if ( !m_sndFile->m_channelCaptureEnabled || m_sndFile->m_channelCaptureBuf.empty() ) {
return 0;
}
const OpenMPT::CHANNELINDEX ch = static_cast<OpenMPT::CHANNELINDEX>( channel );
if ( ch >= m_sndFile->m_channelCaptureNumChannels ) {
return 0;
}
const std::size_t n = std::min( count, static_cast<std::size_t>( MIXBUFFERSIZE ) );
const float scale = m_sndFile->m_channelCaptureGlobalVolScale / OpenMPT::MIXING_SCALEF;
const OpenMPT::mixsample_t * src = m_sndFile->m_channelCaptureBuf.data() + static_cast<std::size_t>( ch ) * MIXBUFFERSIZE * 2;
for ( std::size_t s = 0; s < n; ++s ) {
buf[s] = static_cast<float>( src[s * 2] ) * scale;
}
// Add OPL voice contributions for this tracker channel
if ( m_sndFile->m_opl && m_sndFile->m_opl->IsVoiceCaptureEnabled() ) {
const OpenMPT::OPL * opl = m_sndFile->m_opl.get();
const int nf = std::min( opl->GetCapturedFrameCount(), static_cast<int>( n ) );
constexpr float invMixScale = 1.0f / OpenMPT::MIXING_SCALEF;
const float oplScale = m_sndFile->m_channelCaptureGlobalVolScale * invMixScale;
for ( int v = 0; v < OpenMPT::OPL::NUM_OPL_VOICES; ++v ) {
if ( opl->GetVoiceTrackerChannel( v ) != ch ) continue;
const float * vf = opl->GetVoiceFrames( v );
if ( !vf ) continue;
for ( int s = 0; s < nf; ++s )
buf[s] += vf[s] * oplScale;
}
}
return n;
}
std::size_t module_impl::get_current_channel_audio_stereo( std::int32_t channel, std::size_t count, float * buf_left, float * buf_right ) const {
if ( !buf_left || !buf_right || count == 0 || channel < 0 || channel >= m_sndFile->GetNumChannels() ) {
return 0;
}
if ( !m_sndFile->m_channelCaptureEnabled || m_sndFile->m_channelCaptureBuf.empty() ) {
return 0;
}
const OpenMPT::CHANNELINDEX ch = static_cast<OpenMPT::CHANNELINDEX>( channel );
if ( ch >= m_sndFile->m_channelCaptureNumChannels ) {
return 0;
}
const std::size_t n = std::min( count, static_cast<std::size_t>( MIXBUFFERSIZE ) );
const float scale = m_sndFile->m_channelCaptureGlobalVolScale / OpenMPT::MIXING_SCALEF;
const OpenMPT::mixsample_t * src = m_sndFile->m_channelCaptureBuf.data() + static_cast<std::size_t>( ch ) * MIXBUFFERSIZE * 2;
for ( std::size_t s = 0; s < n; ++s ) {
buf_left[s] = static_cast<float>( src[s * 2] ) * scale;
buf_right[s] = static_cast<float>( src[s * 2 + 1] ) * scale;
}
// Add OPL voice contributions for this tracker channel
if ( m_sndFile->m_opl && m_sndFile->m_opl->IsVoiceCaptureEnabled() ) {
const OpenMPT::OPL * opl = m_sndFile->m_opl.get();
const int nf = std::min( opl->GetCapturedFrameCount(), static_cast<int>( n ) );
constexpr float invMixScale = 1.0f / OpenMPT::MIXING_SCALEF;
const float oplScale = m_sndFile->m_channelCaptureGlobalVolScale * invMixScale;
for ( int v = 0; v < OpenMPT::OPL::NUM_OPL_VOICES; ++v ) {
if ( opl->GetVoiceTrackerChannel( v ) != ch ) continue;
const float * vf = opl->GetVoiceFrames( v );
if ( !vf ) continue;
for ( int s = 0; s < nf; ++s ) {
buf_left[s] += vf[s] * oplScale;
buf_right[s] += vf[s] * oplScale;
}
}
}
return n;
}

std::int32_t module_impl::get_num_subsongs() const {
std::unique_ptr<subsongs_type> subsongs_temp = has_subsongs_inited() ? std::unique_ptr<subsongs_type>() : std::make_unique<subsongs_type>( get_subsongs() );
Expand Down Expand Up @@ -1734,6 +1804,7 @@ std::pair<const module_impl::ctl_info *, const module_impl::ctl_info *> module_i
{ "render.resampler.emulate_amiga", ctl_type::boolean },
{ "render.resampler.emulate_amiga_type", ctl_type::text },
{ "render.opl.volume_factor", ctl_type::floatingpoint },
{ "render.channel_capture", ctl_type::boolean },
{ "dither", ctl_type::integer }
};
return std::make_pair(std::begin(ctl_infos), std::end(ctl_infos));
Expand Down Expand Up @@ -1831,6 +1902,8 @@ bool module_impl::ctl_get_boolean( std::string_view ctl, bool throw_if_unknown )
return m_ctl_seek_sync_samples;
} else if ( ctl == "render.resampler.emulate_amiga" ) {
return ( m_sndFile->m_Resampler.m_Settings.emulateAmiga != OpenMPT::Resampling::AmigaFilter::Off );
} else if ( ctl == "render.channel_capture" ) {
return m_ctl_render_channel_capture;
} else {
MPT_ASSERT_NOTREACHED();
return false;
Expand Down Expand Up @@ -2062,6 +2135,9 @@ void module_impl::ctl_set_boolean( std::string_view ctl, bool value, bool throw_
if ( newsettings != m_sndFile->m_Resampler.m_Settings ) {
m_sndFile->SetResamplerSettings( newsettings );
}
} else if ( ctl == "render.channel_capture" ) {
m_ctl_render_channel_capture = value;
m_sndFile->SetChannelCaptureEnabled( value, m_sndFile->GetNumChannels() );
} else {
MPT_ASSERT_NOTREACHED();
}
Expand Down
3 changes: 3 additions & 0 deletions libopenmpt/libopenmpt_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class module_impl {
bool m_ctl_load_skip_plugins;
bool m_ctl_load_skip_subsongs_init;
bool m_ctl_seek_sync_samples;
bool m_ctl_render_channel_capture = false;
std::vector<std::string> m_loaderMessages;
public:
void PushToCSoundFileLog( const std::string & text ) const;
Expand Down Expand Up @@ -239,6 +240,8 @@ 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;
std::size_t get_current_channel_audio_mono( std::int32_t channel, std::size_t count, float * buf ) const;
std::size_t get_current_channel_audio_stereo( std::int32_t channel, std::size_t count, float * buf_left, float * buf_right ) const;
std::int32_t get_num_subsongs() const;
std::int32_t get_num_channels() const;
std::int32_t get_num_orders() const;
Expand Down
35 changes: 34 additions & 1 deletion soundlib/Fastmix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,46 @@ void CSoundFile::CreateStereoMix(int count)
if(m_MixerSettings.gnChannels > 2)
StereoFill(MixRearBuffer, count, m_surroundROfsVol, m_surroundLOfsVol);

if(m_channelCaptureEnabled && !m_channelCaptureBuf.empty())
{
std::fill(m_channelCaptureBuf.begin(), m_channelCaptureBuf.end(), mixsample_t{});
m_channelCaptureGlobalVolScale = m_PlayConfig.getGlobalVolumeAppliesToMaster()
? static_cast<float>(m_PlayState.m_nGlobalVolume) / static_cast<float>(MAX_GLOBAL_VOLUME)
: 1.0f;
}

// Channels that are actually mixed and not skipped (because they are paused or muted)
CHANNELINDEX numChannelsMixed = 0;

for(uint32 nChn = 0; nChn < m_nMixChannels; nChn++)
{
if(MixChannel(count, m_PlayState.Chn[m_PlayState.ChnMix[nChn]], m_PlayState.ChnMix[nChn], numChannelsMixed < m_MixerSettings.m_nMaxMixChannels))
const CHANNELINDEX absIdx = m_PlayState.ChnMix[nChn];

int captureTarget = -1;
mixsample_t snapBuf[MIXBUFFERSIZE * 2];
if(m_channelCaptureEnabled && !m_channelCaptureBuf.empty())
{
if(absIdx < m_channelCaptureNumChannels)
captureTarget = static_cast<int>(absIdx);
else
{
const CHANNELINDEX master = m_PlayState.Chn[absIdx].nMasterChn;
if(master > 0 && master <= m_channelCaptureNumChannels)
captureTarget = static_cast<int>(master - 1);
}
if(captureTarget >= 0)
std::memcpy(snapBuf, MixSoundBuffer, count * 2 * sizeof(mixsample_t));
}

if(MixChannel(count, m_PlayState.Chn[absIdx], absIdx, numChannelsMixed < m_MixerSettings.m_nMaxMixChannels))
numChannelsMixed++;

if(m_channelCaptureEnabled && captureTarget >= 0)
{
mixsample_t *dest = m_channelCaptureBuf.data() + static_cast<std::size_t>(captureTarget) * MIXBUFFERSIZE * 2;
for(int s = 0; s < count * 2; ++s)
dest[s] += MixSoundBuffer[s] - snapBuf[s];
}
}
m_nMixStat = std::max(m_nMixStat, numChannelsMixed);
}
Expand Down
16 changes: 16 additions & 0 deletions soundlib/OPL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,33 @@ void OPL::Mix(int32 *target, size_t count, uint32 volumeFactorQ16)

// This factor causes a sample voice to be more or less as loud as an OPL voice
const int32 factor = Util::muldiv_unsigned(volumeFactorQ16, 6169, (1 << 16));
if(m_captureEnabled)
m_captureFrameCount = 0;
while(count--)
{
int16 l, r;
m_opl->Sample(&l, &r);
target[0] += l * factor;
target[1] += r * factor;
target += 2;
if(m_captureEnabled && m_captureFrameCount < MAX_CAPTURE_FRAMES)
{
const int16_t *voiceOut = m_opl->GetVoiceOutput();
for(int v = 0; v < OPL_CHANNELS; ++v)
m_voiceFrames[v][m_captureFrameCount] = static_cast<float>(voiceOut[v]) * static_cast<float>(factor);
++m_captureFrameCount;
}
}
}


void OPL::SetupVoiceCapture(bool enable) noexcept
{
m_captureEnabled = enable;
m_captureFrameCount = 0;
}


OPL::Register OPL::ChannelToRegister(uint8 oplCh)
{
if(oplCh < 9)
Expand Down
14 changes: 14 additions & 0 deletions soundlib/OPL.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ class OPL
void Initialize(uint32 sampleRate);
void Mix(int32 *buffer, size_t count, uint32 volumeFactorQ16);

// Per-voice audio capture — opt-in, enabled via "render.channel_capture" CTL.
// Must be called before Mix(); data is valid until the next Mix() call.
void SetupVoiceCapture(bool enable) noexcept;
bool IsVoiceCaptureEnabled() const noexcept { return m_captureEnabled; }
int GetCapturedFrameCount() const noexcept { return m_captureFrameCount; }
const float* GetVoiceFrames(int voice) const noexcept { return m_captureEnabled ? m_voiceFrames[voice] : nullptr; }
CHANNELINDEX GetVoiceTrackerChannel(int voice) const noexcept { return m_OPLtoChan[voice]; }
static constexpr int MAX_CAPTURE_FRAMES = 512;
static constexpr int NUM_OPL_VOICES = 18;

void NoteOff(CHANNELINDEX c);
void NoteCut(CHANNELINDEX c, bool unassign = true);
void Frequency(CHANNELINDEX c, uint32 milliHertz, bool keyOff, bool beatingOscillators);
Expand Down Expand Up @@ -130,6 +140,10 @@ class OPL
std::array<OPLPatch, OPL_CHANNELS> m_Patches;

bool m_isActive = false;

bool m_captureEnabled{false};
int m_captureFrameCount{0};
float m_voiceFrames[OPL_CHANNELS][MAX_CAPTURE_FRAMES]{};
};

OPENMPT_NAMESPACE_END
16 changes: 16 additions & 0 deletions soundlib/Sndfile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2192,4 +2192,20 @@ void TempoSwing::Deserialize(std::istream &iStrm, TempoSwing &swing, const size_
}


void CSoundFile::SetChannelCaptureEnabled(bool enable, CHANNELINDEX numChannels)
{
m_channelCaptureEnabled = enable;
m_channelCaptureNumChannels = enable ? numChannels : 0;
if(enable)
m_channelCaptureBuf.assign(static_cast<std::size_t>(numChannels) * MIXBUFFERSIZE * 2, mixsample_t{});
else
{
m_channelCaptureBuf.clear();
m_channelCaptureBuf.shrink_to_fit();
}
if(m_opl)
m_opl->SetupVoiceCapture(enable);
}


OPENMPT_NAMESPACE_END
12 changes: 12 additions & 0 deletions soundlib/Sndfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,18 @@ class CSoundFile
// Interleaved Front Mix Buffer (Also room for interleaved rear mix)
mixsample_t MixSoundBuffer[MIXBUFFERSIZE * 4];
mixsample_t MixRearBuffer[MIXBUFFERSIZE * 2];

public:
// Per-channel audio capture — enabled via "render.channel_capture" CTL.
// Allocated on enable, freed on disable - Interleaved stereo: [ch * MIXBUFFERSIZE * 2].
bool m_channelCaptureEnabled{false};
CHANNELINDEX m_channelCaptureNumChannels{0};
float m_channelCaptureGlobalVolScale{1.0f};
std::vector<mixsample_t> m_channelCaptureBuf;

void SetChannelCaptureEnabled(bool enable, CHANNELINDEX numChannels);

private:
// Non-interleaved plugin processing buffer
float MixFloatBuffer[2][MIXBUFFERSIZE];
mixsample_t MixInputBuffer[NUMMIXINPUTBUFFERS][MIXBUFFERSIZE];
Expand Down
Loading