/* * libopenmpt_ext_impl.cpp * ----------------------- * Purpose: libopenmpt extensions - implementation * Notes : * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "common/stdafx.h" #include "libopenmpt_internal.h" #include "libopenmpt_ext.hpp" #include "libopenmpt_ext_impl.hpp" #include #include "soundlib/Sndfile.h" using namespace OpenMPT; namespace openmpt { module_ext_impl::module_ext_impl( callback_stream_wrapper stream, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( stream, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( std::istream & stream, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( stream, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const std::vector & data, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const std::vector & data, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const std::vector & data, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const std::byte * data, std::size_t size, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, size, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const std::uint8_t * data, std::size_t size, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, size, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const char * data, std::size_t size, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, size, std::move(log), ctls ) { ctor(); } module_ext_impl::module_ext_impl( const void * data, std::size_t size, std::unique_ptr log, const std::map< std::string, std::string > & ctls ) : module_impl( data, size, std::move(log), ctls ) { ctor(); } void module_ext_impl::ctor() { /* add stuff here */ } module_ext_impl::~module_ext_impl() { /* add stuff here */ } void * module_ext_impl::get_interface( const std::string & interface_id ) { if ( interface_id.empty() ) { return 0; } else if ( interface_id == ext::pattern_vis_id ) { return dynamic_cast< ext::pattern_vis * >( this ); } else if ( interface_id == ext::interactive_id ) { return dynamic_cast< ext::interactive * >( this ); /* add stuff here */ } else { return 0; } } // pattern_vis module_ext_impl::effect_type module_ext_impl::get_pattern_row_channel_volume_effect_type( std::int32_t pattern, std::int32_t row, std::int32_t channel ) const { std::uint8_t byte = get_pattern_row_channel_command( pattern, row, channel, module::command_volumeffect ); switch ( ModCommand::GetVolumeEffectType( byte ) ) { case EFFECT_TYPE_NORMAL : return effect_general; break; case EFFECT_TYPE_GLOBAL : return effect_global ; break; case EFFECT_TYPE_VOLUME : return effect_volume ; break; case EFFECT_TYPE_PANNING: return effect_panning; break; case EFFECT_TYPE_PITCH : return effect_pitch ; break; default: return effect_unknown; break; } } module_ext_impl::effect_type module_ext_impl::get_pattern_row_channel_effect_type( std::int32_t pattern, std::int32_t row, std::int32_t channel ) const { std::uint8_t byte = get_pattern_row_channel_command( pattern, row, channel, module::command_effect ); switch ( ModCommand::GetEffectType( byte ) ) { case EFFECT_TYPE_NORMAL : return effect_general; break; case EFFECT_TYPE_GLOBAL : return effect_global ; break; case EFFECT_TYPE_VOLUME : return effect_volume ; break; case EFFECT_TYPE_PANNING: return effect_panning; break; case EFFECT_TYPE_PITCH : return effect_pitch ; break; default: return effect_unknown; break; } } // interactive void module_ext_impl::set_current_speed( std::int32_t speed ) { if ( speed < 1 || speed > 65535 ) { throw openmpt::exception("invalid tick count"); } m_sndFile->m_PlayState.m_nMusicSpeed = speed; } void module_ext_impl::set_current_tempo( std::int32_t tempo ) { if ( tempo < 32 || tempo > 512 ) { throw openmpt::exception("invalid tempo"); } m_sndFile->m_PlayState.m_nMusicTempo.Set( tempo ); } void module_ext_impl::set_tempo_factor( double factor ) { if ( factor <= 0.0 || factor > 4.0 ) { throw openmpt::exception("invalid tempo factor"); } m_sndFile->m_nTempoFactor = mpt::saturate_round( 65536.0 / factor ); m_sndFile->RecalculateSamplesPerTick(); } double module_ext_impl::get_tempo_factor( ) const { return 65536.0 / m_sndFile->m_nTempoFactor; } void module_ext_impl::set_pitch_factor( double factor ) { if ( factor <= 0.0 || factor > 4.0 ) { throw openmpt::exception("invalid pitch factor"); } m_sndFile->m_nFreqFactor = mpt::saturate_round( 65536.0 * factor ); m_sndFile->RecalculateSamplesPerTick(); } double module_ext_impl::get_pitch_factor( ) const { return m_sndFile->m_nFreqFactor / 65536.0; } void module_ext_impl::set_global_volume( double volume ) { if ( volume < 0.0 || volume > 1.0 ) { throw openmpt::exception("invalid global volume"); } m_sndFile->m_PlayState.m_nGlobalVolume = mpt::saturate_round( volume * MAX_GLOBAL_VOLUME ); } double module_ext_impl::get_global_volume( ) const { return m_sndFile->m_PlayState.m_nGlobalVolume / static_cast( MAX_GLOBAL_VOLUME ); } void module_ext_impl::set_channel_volume( std::int32_t channel, double volume ) { if ( channel < 0 || channel >= get_num_channels() ) { throw openmpt::exception("invalid channel"); } if ( volume < 0.0 || volume > 1.0 ) { throw openmpt::exception("invalid global volume"); } m_sndFile->m_PlayState.Chn[channel].nGlobalVol = mpt::saturate_round(volume * 64.0); } double module_ext_impl::get_channel_volume( std::int32_t channel ) const { if ( channel < 0 || channel >= get_num_channels() ) { throw openmpt::exception("invalid channel"); } return m_sndFile->m_PlayState.Chn[channel].nGlobalVol / 64.0; } void module_ext_impl::set_channel_mute_status( std::int32_t channel, bool mute ) { if ( channel < 0 || channel >= get_num_channels() ) { throw openmpt::exception("invalid channel"); } m_sndFile->ChnSettings[channel].dwFlags.set( CHN_MUTE | CHN_SYNCMUTE , mute ); m_sndFile->m_PlayState.Chn[channel].dwFlags.set( CHN_MUTE | CHN_SYNCMUTE , mute ); // Also update NNA channels for ( CHANNELINDEX i = m_sndFile->GetNumChannels(); i < MAX_CHANNELS; i++) { if ( m_sndFile->m_PlayState.Chn[i].nMasterChn == channel + 1) { m_sndFile->m_PlayState.Chn[i].dwFlags.set( CHN_MUTE | CHN_SYNCMUTE, mute ); } } } bool module_ext_impl::get_channel_mute_status( std::int32_t channel ) const { if ( channel < 0 || channel >= get_num_channels() ) { throw openmpt::exception("invalid channel"); } return m_sndFile->m_PlayState.Chn[channel].dwFlags[CHN_MUTE]; } void module_ext_impl::set_instrument_mute_status( std::int32_t instrument, bool mute ) { const bool instrument_mode = get_num_instruments() != 0; const std::int32_t max_instrument = instrument_mode ? get_num_instruments() : get_num_samples(); if ( instrument < 0 || instrument >= max_instrument ) { throw openmpt::exception("invalid instrument"); } if ( instrument_mode ) { if ( m_sndFile->Instruments[instrument + 1] != nullptr ) { m_sndFile->Instruments[instrument + 1]->dwFlags.set( INS_MUTE, mute ); } } else { m_sndFile->GetSample( static_cast( instrument + 1 ) ).uFlags.set( CHN_MUTE, mute ) ; } } bool module_ext_impl::get_instrument_mute_status( std::int32_t instrument ) const { const bool instrument_mode = get_num_instruments() != 0; const std::int32_t max_instrument = instrument_mode ? get_num_instruments() : get_num_samples(); if ( instrument < 0 || instrument >= max_instrument ) { throw openmpt::exception("invalid instrument"); } if ( instrument_mode ) { if ( m_sndFile->Instruments[instrument + 1] != nullptr ) { return m_sndFile->Instruments[instrument + 1]->dwFlags[INS_MUTE]; } return true; } else { return m_sndFile->GetSample( static_cast( instrument + 1 ) ).uFlags[CHN_MUTE]; } } std::int32_t module_ext_impl::play_note( std::int32_t instrument, std::int32_t note, double volume, double panning ) { const bool instrument_mode = get_num_instruments() != 0; const std::int32_t max_instrument = instrument_mode ? get_num_instruments() : get_num_samples(); if ( instrument < 0 || instrument >= max_instrument ) { throw openmpt::exception("invalid instrument"); } note += NOTE_MIN; if ( note < NOTE_MIN || note > NOTE_MAX ) { throw openmpt::exception("invalid note"); } // Find a free channel CHANNELINDEX free_channel = m_sndFile->GetNNAChannel( CHANNELINDEX_INVALID ); if ( free_channel == CHANNELINDEX_INVALID ) free_channel = MAX_CHANNELS - 1; ModChannel &chn = m_sndFile->m_PlayState.Chn[free_channel]; chn.Reset(ModChannel::resetTotal, *m_sndFile, CHANNELINDEX_INVALID, CHN_MUTE); chn.nMasterChn = 0; // remove NNA association chn.nNewNote = chn.nLastNote = static_cast(note); chn.ResetEnvelopes(); m_sndFile->InstrumentChange(chn, instrument + 1); chn.nFadeOutVol = 0x10000; m_sndFile->NoteChange(chn, note, false, true, true); chn.nPan = mpt::saturate_round( Clamp( panning * 128.0, -128.0, 128.0 ) + 128.0 ); chn.nVolume = mpt::saturate_round( Clamp( volume * 256.0, 0.0, 256.0 ) ); // Remove channel from list of mixed channels to fix https://bugs.openmpt.org/view.php?id=209 // This is required because a previous note on the same channel might have just stopped playing, // but the channel is still in the mix list. // Since the channel volume / etc is only updated every tick in CSoundFile::ReadNote, and we // do not want to duplicate mixmode-dependant logic here, CSoundFile::CreateStereoMix may already // try to mix our newly set up channel at volume 0 if we don't remove it from the list. auto mix_begin = std::begin( m_sndFile->m_PlayState.ChnMix ); auto mix_end = std::remove( mix_begin, mix_begin + m_sndFile->m_nMixChannels, free_channel ); m_sndFile->m_nMixChannels = static_cast( std::distance( mix_begin, mix_end ) ); return free_channel; } void module_ext_impl::stop_note( std::int32_t channel ) { if ( channel < 0 || channel >= MAX_CHANNELS ) { throw openmpt::exception("invalid channel"); } ModChannel &chn = m_sndFile->m_PlayState.Chn[channel]; chn.nLength = 0; chn.pCurrentSample = nullptr; } /* add stuff here */ } // namespace openmpt