From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- .../openmpt-trunk/soundlib/pattern.cpp | 643 +++++++++++++++++++++ 1 file changed, 643 insertions(+) create mode 100644 Src/external_dependencies/openmpt-trunk/soundlib/pattern.cpp (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/pattern.cpp') diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/pattern.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/pattern.cpp new file mode 100644 index 00000000..b94b0ea9 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/pattern.cpp @@ -0,0 +1,643 @@ +/* + * Pattern.cpp + * ----------- + * Purpose: Module Pattern header class + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "pattern.h" +#include "patternContainer.h" +#include "../common/serialization_utils.h" +#include "../common/version.h" +#include "ITTools.h" +#include "Sndfile.h" +#include "mod_specifications.h" +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" + + +OPENMPT_NAMESPACE_BEGIN + + +CSoundFile& CPattern::GetSoundFile() { return m_rPatternContainer.GetSoundFile(); } +const CSoundFile& CPattern::GetSoundFile() const { return m_rPatternContainer.GetSoundFile(); } + + +CHANNELINDEX CPattern::GetNumChannels() const +{ + return GetSoundFile().GetNumChannels(); +} + + +// Check if there is any note data on a given row. +bool CPattern::IsEmptyRow(ROWINDEX row) const +{ + if(m_ModCommands.empty() || !IsValidRow(row)) + { + return true; + } + + PatternRow data = GetRow(row); + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++, data++) + { + if(!data->IsEmpty()) + { + return false; + } + } + return true; +} + + +bool CPattern::SetSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure) +{ + if(rowsPerBeat < 1 + || rowsPerBeat > GetSoundFile().GetModSpecifications().patternRowsMax + || rowsPerMeasure < rowsPerBeat + || rowsPerMeasure > GetSoundFile().GetModSpecifications().patternRowsMax) + { + return false; + } + m_RowsPerBeat = rowsPerBeat; + m_RowsPerMeasure = rowsPerMeasure; + return true; +} + + +// Add or remove rows from the pattern. +bool CPattern::Resize(const ROWINDEX newRowCount, bool enforceFormatLimits, bool resizeAtEnd) +{ + CSoundFile &sndFile = GetSoundFile(); + + if(newRowCount == m_Rows || newRowCount < 1 || newRowCount > MAX_PATTERN_ROWS) + { + return false; + } + if(enforceFormatLimits) + { + auto &specs = sndFile.GetModSpecifications(); + if(newRowCount > specs.patternRowsMax || newRowCount < specs.patternRowsMin) return false; + } + + try + { + size_t count = ((newRowCount > m_Rows) ? (newRowCount - m_Rows) : (m_Rows - newRowCount)) * GetNumChannels(); + + if(newRowCount > m_Rows) + m_ModCommands.insert(resizeAtEnd ? m_ModCommands.end() : m_ModCommands.begin(), count, ModCommand::Empty()); + else if(resizeAtEnd) + m_ModCommands.erase(m_ModCommands.end() - count, m_ModCommands.end()); + else + m_ModCommands.erase(m_ModCommands.begin(), m_ModCommands.begin() + count); + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + return false; + } + + m_Rows = newRowCount; + return true; +} + + +void CPattern::ClearCommands() +{ + std::fill(m_ModCommands.begin(), m_ModCommands.end(), ModCommand::Empty()); +} + + +bool CPattern::AllocatePattern(ROWINDEX rows) +{ + size_t newSize = GetNumChannels() * rows; + if(rows == 0) + { + return false; + } else if(rows == GetNumRows() && m_ModCommands.size() == newSize) + { + // Re-use allocated memory + ClearCommands(); + return true; + } else + { + // Do this in two steps in order to keep the old pattern data in case of OOM + decltype(m_ModCommands) newPattern(newSize, ModCommand::Empty()); + m_ModCommands = std::move(newPattern); + } + m_Rows = rows; + return true; +} + + +void CPattern::Deallocate() +{ + m_Rows = m_RowsPerBeat = m_RowsPerMeasure = 0; + m_ModCommands.clear(); + m_PatternName.clear(); +} + + +CPattern& CPattern::operator= (const CPattern &pat) +{ + m_ModCommands = pat.m_ModCommands; + m_Rows = pat.m_Rows; + m_RowsPerBeat = pat.m_RowsPerBeat; + m_RowsPerMeasure = pat.m_RowsPerMeasure; + m_tempoSwing = pat.m_tempoSwing; + m_PatternName = pat.m_PatternName; + return *this; +} + + + +bool CPattern::operator== (const CPattern &other) const +{ + return GetNumRows() == other.GetNumRows() + && GetNumChannels() == other.GetNumChannels() + && GetOverrideSignature() == other.GetOverrideSignature() + && GetRowsPerBeat() == other.GetRowsPerBeat() + && GetRowsPerMeasure() == other.GetRowsPerMeasure() + && GetTempoSwing() == other.GetTempoSwing() + && m_ModCommands == other.m_ModCommands; +} + + +#ifdef MODPLUG_TRACKER + +bool CPattern::Expand() +{ + const ROWINDEX newRows = m_Rows * 2; + const CHANNELINDEX nChns = GetNumChannels(); + + if(m_ModCommands.empty() + || newRows > GetSoundFile().GetModSpecifications().patternRowsMax) + { + return false; + } + + decltype(m_ModCommands) newPattern; + try + { + newPattern.assign(m_ModCommands.size() * 2, ModCommand::Empty()); + } catch(mpt::out_of_memory e) + { + mpt::delete_out_of_memory(e); + return false; + } + + for(auto mSrc = m_ModCommands.begin(), mDst = newPattern.begin(); mSrc != m_ModCommands.end(); mSrc += nChns, mDst += 2 * nChns) + { + std::copy(mSrc, mSrc + nChns, mDst); + } + + m_ModCommands = std::move(newPattern); + m_Rows = newRows; + + return true; +} + + +bool CPattern::Shrink() +{ + if (m_ModCommands.empty() + || m_Rows < GetSoundFile().GetModSpecifications().patternRowsMin * 2) + { + return false; + } + + m_Rows /= 2; + const CHANNELINDEX nChns = GetNumChannels(); + + for(ROWINDEX y = 0; y < m_Rows; y++) + { + const PatternRow srcRow = GetRow(y * 2); + const PatternRow nextSrcRow = GetRow(y * 2 + 1); + PatternRow destRow = GetRow(y); + + for(CHANNELINDEX x = 0; x < nChns; x++) + { + const ModCommand &src = srcRow[x]; + const ModCommand &srcNext = nextSrcRow[x]; + ModCommand &dest = destRow[x]; + dest = src; + + if(dest.note == NOTE_NONE && !dest.instr) + { + // Fill in data from next row if field is empty + dest.note = srcNext.note; + dest.instr = srcNext.instr; + if(srcNext.volcmd != VOLCMD_NONE) + { + dest.volcmd = srcNext.volcmd; + dest.vol = srcNext.vol; + } + if(dest.command == CMD_NONE) + { + dest.command = srcNext.command; + dest.param = srcNext.param; + } + } + } + } + m_ModCommands.resize(m_Rows * nChns); + + return true; +} + + +#endif // MODPLUG_TRACKER + + +bool CPattern::SetName(const std::string &newName) +{ + m_PatternName = newName; + return true; +} + + +bool CPattern::SetName(const char *newName, size_t maxChars) +{ + if(newName == nullptr || maxChars == 0) + { + return false; + } + const auto nameEnd = std::find(newName, newName + maxChars, '\0'); + m_PatternName.assign(newName, nameEnd); + return true; +} + + +// Write some kind of effect data to the pattern. Exact data to be written and write behaviour can be found in the EffectWriter object. +bool CPattern::WriteEffect(EffectWriter &settings) +{ + // First, reject invalid parameters. + if(m_ModCommands.empty() + || settings.m_row >= GetNumRows() + || (settings.m_channel >= GetNumChannels() && settings.m_channel != CHANNELINDEX_INVALID)) + { + return false; + } + + CHANNELINDEX scanChnMin = settings.m_channel, scanChnMax = settings.m_channel; + + // Scan all channels + if(settings.m_channel == CHANNELINDEX_INVALID) + { + scanChnMin = 0; + scanChnMax = GetNumChannels() - 1; + } + + ModCommand * const baseCommand = GetpModCommand(settings.m_row, scanChnMin); + ModCommand *m; + + // Scan channel(s) for same effect type - if an effect of the same type is already present, exit. + if(!settings.m_allowMultiple) + { + m = baseCommand; + for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++) + { + if(!settings.m_isVolEffect && m->command == settings.m_command) + return true; + if(settings.m_isVolEffect && m->volcmd == settings.m_volcmd) + return true; + } + } + + // Easy case: check if there's some space left to put the effect somewhere + m = baseCommand; + for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++) + { + if(!settings.m_isVolEffect && m->command == CMD_NONE) + { + m->command = settings.m_command; + m->param = settings.m_param; + return true; + } + if(settings.m_isVolEffect && m->volcmd == VOLCMD_NONE) + { + m->volcmd = settings.m_volcmd; + m->vol = settings.m_vol; + return true; + } + } + + // Ok, apparently there's no space. If we haven't tried already, try to map it to the volume column or effect column instead. + if(settings.m_retry) + { + const bool isS3M = (GetSoundFile().GetType() & MOD_TYPE_S3M); + + // Move some effects that also work in the volume column, so there's place for our new effect. + if(!settings.m_isVolEffect) + { + m = baseCommand; + for(CHANNELINDEX i = scanChnMin; i <= scanChnMax; i++, m++) + { + switch(m->command) + { + case CMD_VOLUME: + if(!GetSoundFile().GetModSpecifications().HasVolCommand(VOLCMD_VOLUME)) + { + break; + } + m->volcmd = VOLCMD_VOLUME; + m->vol = m->param; + m->command = settings.m_command; + m->param = settings.m_param; + return true; + + case CMD_PANNING8: + if(isS3M && m->param > 0x80) + { + break; + } + + m->volcmd = VOLCMD_PANNING; + m->command = settings.m_command; + + if(isS3M) + m->vol = (m->param + 1u) / 2u; + else + m->vol = (m->param + 2u) / 4u; + + m->param = settings.m_param; + return true; + + default: + break; + } + } + } + + // Let's try it again by writing into the "other" effect column. + if(settings.m_isVolEffect) + { + // Convert volume effect to normal effect + ModCommand::COMMAND newCommand = CMD_NONE; + ModCommand::PARAM newParam = settings.m_vol; + switch(settings.m_volcmd) + { + case VOLCMD_PANNING: + newCommand = CMD_PANNING8; + newParam = mpt::saturate_cast(settings.m_vol * (isS3M ? 2u : 4u)); + break; + case VOLCMD_VOLUME: + newCommand = CMD_VOLUME; + break; + default: + break; + } + + if(newCommand != CMD_NONE) + { + settings.m_command = static_cast(newCommand); + settings.m_param = newParam; + settings.m_retry = false; + } + } else + { + // Convert normal effect to volume effect + ModCommand::VOLCMD newVolCmd = VOLCMD_NONE; + ModCommand::VOL newVol = settings.m_param; + if(settings.m_command == CMD_PANNING8 && isS3M) + { + // This needs some manual fixing. + if(settings.m_param <= 0x80) + { + // Can't have surround in volume column, only normal panning + newVolCmd = VOLCMD_PANNING; + newVol /= 2u; + } + } else + { + newVolCmd = settings.m_command; + if(!ModCommand::ConvertVolEffect(newVolCmd, newVol, true)) + { + // No Success :( + newVolCmd = VOLCMD_NONE; + } + } + + if(newVolCmd != CMD_NONE) + { + settings.m_volcmd = static_cast(newVolCmd); + settings.m_vol = newVol; + settings.m_retry = false; + } + } + + if(!settings.m_retry) + { + settings.m_isVolEffect = !settings.m_isVolEffect; + if(WriteEffect(settings)) + { + return true; + } + } + } + + // Try in the next row if possible (this may also happen if we already retried) + if(settings.m_retryMode == EffectWriter::rmTryNextRow && settings.m_row + 1 < GetNumRows()) + { + settings.m_row++; + settings.m_retry = true; + return WriteEffect(settings); + } else if(settings.m_retryMode == EffectWriter::rmTryPreviousRow && settings.m_row > 0) + { + settings.m_row--; + settings.m_retry = true; + return WriteEffect(settings); + } + + return false; +} + + +//////////////////////////////////////////////////////////////////////// +// +// Pattern serialization functions +// +//////////////////////////////////////////////////////////////////////// + + +enum maskbits +{ + noteBit = (1 << 0), + instrBit = (1 << 1), + volcmdBit = (1 << 2), + volBit = (1 << 3), + commandBit = (1 << 4), + effectParamBit = (1 << 5), + extraData = (1 << 6) +}; + +void WriteData(std::ostream& oStrm, const CPattern& pat); +void ReadData(std::istream& iStrm, CPattern& pat, const size_t nSize = 0); + +void WriteModPattern(std::ostream& oStrm, const CPattern& pat) +{ + srlztn::SsbWrite ssb(oStrm); + ssb.BeginWrite(FileIdPattern, Version::Current().GetRawVersion()); + ssb.WriteItem(pat, "data", &WriteData); + // pattern time signature + if(pat.GetOverrideSignature()) + { + ssb.WriteItem(pat.GetRowsPerBeat(), "RPB."); + ssb.WriteItem(pat.GetRowsPerMeasure(), "RPM."); + } + if(pat.HasTempoSwing()) + { + ssb.WriteItem(pat.GetTempoSwing(), "SWNG", TempoSwing::Serialize); + } + ssb.FinishWrite(); +} + + +void ReadModPattern(std::istream& iStrm, CPattern& pat, const size_t) +{ + srlztn::SsbRead ssb(iStrm); + ssb.BeginRead(FileIdPattern, Version::Current().GetRawVersion()); + if ((ssb.GetStatus() & srlztn::SNT_FAILURE) != 0) + return; + ssb.ReadItem(pat, "data", &ReadData); + // pattern time signature + uint32 rpb = 0, rpm = 0; + ssb.ReadItem(rpb, "RPB."); + ssb.ReadItem(rpm, "RPM."); + pat.SetSignature(rpb, rpm); + TempoSwing swing; + ssb.ReadItem(swing, "SWNG", TempoSwing::Deserialize); + if(!swing.empty()) + swing.resize(pat.GetRowsPerBeat()); + pat.SetTempoSwing(swing); +} + + +static uint8 CreateDiffMask(const ModCommand &chnMC, const ModCommand &newMC) +{ + uint8 mask = 0; + if(chnMC.note != newMC.note) + mask |= noteBit; + if(chnMC.instr != newMC.instr) + mask |= instrBit; + if(chnMC.volcmd != newMC.volcmd) + mask |= volcmdBit; + if(chnMC.vol != newMC.vol) + mask |= volBit; + if(chnMC.command != newMC.command) + mask |= commandBit; + if(chnMC.param != newMC.param) + mask |= effectParamBit; + return mask; +} + + +// Writes pattern data. Adapted from SaveIT. +void WriteData(std::ostream& oStrm, const CPattern& pat) +{ + if(!pat.IsValid()) + return; + + const ROWINDEX rows = pat.GetNumRows(); + const CHANNELINDEX chns = pat.GetNumChannels(); + std::vector lastChnMC(chns); + + for(ROWINDEX r = 0; r(c+1); + if(diffmask != 0) + chval |= IT_bitmask_patternChanEnabled_c; + + mpt::IO::WriteIntLE(oStrm, chval); + + if(diffmask) + { + lastChnMC[c] = m; + mpt::IO::WriteIntLE(oStrm, diffmask); + if(diffmask & noteBit) mpt::IO::WriteIntLE(oStrm, m.note); + if(diffmask & instrBit) mpt::IO::WriteIntLE(oStrm, m.instr); + if(diffmask & volcmdBit) mpt::IO::WriteIntLE(oStrm, m.volcmd); + if(diffmask & volBit) mpt::IO::WriteIntLE(oStrm, m.vol); + if(diffmask & commandBit) mpt::IO::WriteIntLE(oStrm, m.command); + if(diffmask & effectParamBit) mpt::IO::WriteIntLE(oStrm, m.param); + } + } + mpt::IO::WriteIntLE(oStrm, 0); // Write end of row marker. + } +} + + +#define READITEM(itembit,id) \ +if(diffmask & itembit) \ +{ \ + mpt::IO::ReadIntLE(iStrm, temp); \ + if(ch < chns) \ + lastChnMC[ch].id = temp; \ +} \ +if(ch < chns) \ + m.id = lastChnMC[ch].id; + + +void ReadData(std::istream& iStrm, CPattern& pat, const size_t) +{ + if (!pat.IsValid()) // Expecting patterns to be allocated and resized properly. + return; + + const CHANNELINDEX chns = pat.GetNumChannels(); + const ROWINDEX rows = pat.GetNumRows(); + + std::vector lastChnMC(chns); + + ROWINDEX row = 0; + while(row < rows && iStrm.good()) + { + uint8 t = 0; + mpt::IO::ReadIntLE(iStrm, t); + if(t == 0) + { + row++; + continue; + } + + CHANNELINDEX ch = (t & IT_bitmask_patternChanField_c); + if(ch > 0) + ch--; + + uint8 diffmask = 0; + if((t & IT_bitmask_patternChanEnabled_c) != 0) + mpt::IO::ReadIntLE(iStrm, diffmask); + uint8 temp = 0; + + ModCommand dummy = ModCommand::Empty(); + ModCommand& m = (ch < chns) ? *pat.GetpModCommand(row, ch) : dummy; + + READITEM(noteBit, note); + READITEM(instrBit, instr); + READITEM(volcmdBit, volcmd); + READITEM(volBit, vol); + READITEM(commandBit, command); + READITEM(effectParamBit, param); + if(diffmask & extraData) + { + //Ignore additional data. + uint8 size; + mpt::IO::ReadIntLE(iStrm, size); + iStrm.ignore(size); + } + } +} + +#undef READITEM + + +OPENMPT_NAMESPACE_END -- cgit