370 lines
9.0 KiB
C
370 lines
9.0 KiB
C
|
/*
|
||
|
* PatternCursor.h
|
||
|
* ---------------
|
||
|
* Purpose: Class for storing pattern cursor information.
|
||
|
* Notes : (currently none)
|
||
|
* Authors: OpenMPT Devs
|
||
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include "openmpt/all/BuildSettings.hpp"
|
||
|
|
||
|
#include "../soundlib/Snd_defs.h"
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
class PatternCursor
|
||
|
{
|
||
|
friend class PatternRect;
|
||
|
|
||
|
public:
|
||
|
|
||
|
enum Columns
|
||
|
{
|
||
|
firstColumn = 0,
|
||
|
noteColumn = firstColumn,
|
||
|
instrColumn,
|
||
|
volumeColumn,
|
||
|
effectColumn,
|
||
|
paramColumn,
|
||
|
lastColumn = paramColumn,
|
||
|
};
|
||
|
|
||
|
protected:
|
||
|
// Pattern cursor structure (MSB to LSB):
|
||
|
// | 16 bits - row | 13 bits - channel | 3 bits - channel component |
|
||
|
// As you can see, the highest 16 bits contain a row index.
|
||
|
// It is followed by a channel index, which is 13 bits wide.
|
||
|
// The lowest 3 bits are used for addressing the components of a channel.
|
||
|
// They are *not* used as a bit set, but treated as one of the Columns enum entries that can be found above.
|
||
|
uint32 cursor;
|
||
|
|
||
|
static_assert(MAX_BASECHANNELS <= 0x1FFF, "Check: Channel index in class PatternCursor is only 13 bits wide!");
|
||
|
static_assert(MAX_PATTERN_ROWS <= 0xFFFF, "Check: Row index in class PatternCursor is only 16 bits wide!");
|
||
|
|
||
|
public:
|
||
|
|
||
|
// Construct cursor from given coordinates.
|
||
|
PatternCursor(ROWINDEX row = 0, CHANNELINDEX channel = 0, Columns column = firstColumn)
|
||
|
{
|
||
|
Set(row, channel, column);
|
||
|
};
|
||
|
|
||
|
// Construct cursor from a given row and another cursor's horizontal position (channel + column).
|
||
|
PatternCursor(ROWINDEX row, const PatternCursor &other)
|
||
|
{
|
||
|
Set(row, other);
|
||
|
};
|
||
|
|
||
|
// Get the pattern row the cursor is located in.
|
||
|
ROWINDEX GetRow() const
|
||
|
{
|
||
|
return static_cast<ROWINDEX>(cursor >> 16);
|
||
|
};
|
||
|
|
||
|
// Get the pattern channel the cursor is located in.
|
||
|
CHANNELINDEX GetChannel() const
|
||
|
{
|
||
|
return static_cast<CHANNELINDEX>((cursor & 0xFFFF) >> 3);
|
||
|
};
|
||
|
|
||
|
// Get the pattern channel column the cursor is located in.
|
||
|
Columns GetColumnType() const
|
||
|
{
|
||
|
return static_cast<Columns>((cursor & 0x07));
|
||
|
};
|
||
|
|
||
|
// Set the cursor to given coordinates.
|
||
|
void Set(ROWINDEX row, CHANNELINDEX channel, Columns column = firstColumn)
|
||
|
{
|
||
|
cursor = (row << 16) | ((channel << 3) & 0x1FFF) | (column & 0x07);
|
||
|
};
|
||
|
|
||
|
// Set the cursor to the same position as another curosr.
|
||
|
void Set(const PatternCursor &other)
|
||
|
{
|
||
|
*this = other;
|
||
|
};
|
||
|
|
||
|
// Set the cursor to a given row and another cursor's horizontal position (channel + column).
|
||
|
void Set(ROWINDEX row, const PatternCursor &other)
|
||
|
{
|
||
|
cursor = (row << 16) | (other.cursor & 0xFFFF);
|
||
|
};
|
||
|
|
||
|
// Only update the row of a cursor.
|
||
|
void SetRow(ROWINDEX row)
|
||
|
{
|
||
|
Set(row, *this);
|
||
|
};
|
||
|
|
||
|
// Only update the horizontal position of a cursor.
|
||
|
void SetColumn(CHANNELINDEX chn, Columns col)
|
||
|
{
|
||
|
Set(GetRow(), chn, col);
|
||
|
};
|
||
|
|
||
|
// Move the cursor relatively.
|
||
|
void Move(int rows, int channels, int columns)
|
||
|
{
|
||
|
int row = std::max(int(0), static_cast<int>(GetRow()) + rows);
|
||
|
int chn = static_cast<int>(GetChannel()) + channels;
|
||
|
int col = static_cast<int>(GetColumnType() + columns);
|
||
|
|
||
|
// Boundary checking
|
||
|
if(chn < 0)
|
||
|
{
|
||
|
// Moving too far left
|
||
|
chn = 0;
|
||
|
col = firstColumn;
|
||
|
}
|
||
|
|
||
|
if(col < firstColumn)
|
||
|
{
|
||
|
// Extending beyond first column
|
||
|
col = lastColumn;
|
||
|
chn--;
|
||
|
} else if(col > lastColumn)
|
||
|
{
|
||
|
// Extending beyond last column
|
||
|
col = firstColumn;
|
||
|
chn++;
|
||
|
}
|
||
|
|
||
|
Set(static_cast<ROWINDEX>(row), static_cast<CHANNELINDEX>(chn), static_cast<Columns>(col));
|
||
|
}
|
||
|
|
||
|
|
||
|
// Remove the channel column type from the cursor position.
|
||
|
void RemoveColType()
|
||
|
{
|
||
|
cursor &= ~0x07;
|
||
|
}
|
||
|
|
||
|
// Compare the row of two cursors.
|
||
|
// Returns -1 if this cursor is above of the other cursor,
|
||
|
// 1 if it is below of the other cursor and 0 if they are at the same vertical position.
|
||
|
int CompareRow(const PatternCursor &other) const
|
||
|
{
|
||
|
if(GetRow() < other.GetRow()) return -1;
|
||
|
if(GetRow() > other.GetRow()) return 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// Compare the column (channel + sub column) of two cursors.
|
||
|
// Returns -1 if this cursor is left of the other cursor,
|
||
|
// 1 if it is right of the other cursor and 0 if they are at the same horizontal position.
|
||
|
int CompareColumn(const PatternCursor &other) const
|
||
|
{
|
||
|
if((cursor & 0xFFFF) < (other.cursor & 0xFFFF)) return -1;
|
||
|
if((cursor & 0xFFFF) > (other.cursor & 0xFFFF)) return 1;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Check whether the cursor is placed on the first channel and first column.
|
||
|
bool IsInFirstColumn() const
|
||
|
{
|
||
|
return (cursor & 0xFFFF) == 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Ensure that the point lies within a given pattern size.
|
||
|
void Sanitize(ROWINDEX maxRows, CHANNELINDEX maxChans)
|
||
|
{
|
||
|
ROWINDEX row = std::min(GetRow(), static_cast<ROWINDEX>(maxRows - 1));
|
||
|
CHANNELINDEX chn = GetChannel();
|
||
|
Columns col = GetColumnType();
|
||
|
|
||
|
if(chn >= maxChans)
|
||
|
{
|
||
|
chn = maxChans - 1;
|
||
|
col = lastColumn;
|
||
|
} else if(col > lastColumn)
|
||
|
{
|
||
|
col = lastColumn;
|
||
|
}
|
||
|
Set(row, chn, col);
|
||
|
};
|
||
|
|
||
|
bool operator == (const PatternCursor &other) const
|
||
|
{
|
||
|
return cursor == other.cursor;
|
||
|
}
|
||
|
|
||
|
bool operator != (const PatternCursor &other) const
|
||
|
{
|
||
|
return !(*this == other);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
class PatternRect
|
||
|
{
|
||
|
protected:
|
||
|
PatternCursor upperLeft, lowerRight;
|
||
|
|
||
|
public:
|
||
|
|
||
|
// Construct selection from two pattern cursors.
|
||
|
PatternRect(const PatternCursor &p1, const PatternCursor &p2)
|
||
|
{
|
||
|
if(p1.CompareColumn(p2) <= 0)
|
||
|
{
|
||
|
upperLeft.cursor = (p1.cursor & 0xFFFF);
|
||
|
lowerRight.cursor = (p2.cursor & 0xFFFF);
|
||
|
} else
|
||
|
{
|
||
|
upperLeft.cursor = (p2.cursor & 0xFFFF);
|
||
|
lowerRight.cursor = (p1.cursor & 0xFFFF);
|
||
|
}
|
||
|
|
||
|
if(p1.CompareRow(p2) <= 0)
|
||
|
{
|
||
|
upperLeft.cursor |= (p1.GetRow() << 16);
|
||
|
lowerRight.cursor |= (p2.GetRow() << 16);
|
||
|
} else
|
||
|
{
|
||
|
upperLeft.cursor |= (p2.GetRow() << 16);
|
||
|
lowerRight.cursor |= (p1.GetRow() << 16);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Construct empty rect.
|
||
|
PatternRect()
|
||
|
{
|
||
|
upperLeft.Set(0);
|
||
|
lowerRight.Set(0);
|
||
|
}
|
||
|
|
||
|
// Return upper-left corner of selection.
|
||
|
PatternCursor GetUpperLeft() const
|
||
|
{
|
||
|
return upperLeft;
|
||
|
};
|
||
|
|
||
|
// Return lower-right corner of selection.
|
||
|
PatternCursor GetLowerRight() const
|
||
|
{
|
||
|
return lowerRight;
|
||
|
};
|
||
|
|
||
|
// Check if a given point is within the horizontal boundaries of the rect
|
||
|
bool ContainsHorizontal(const PatternCursor &point) const
|
||
|
{
|
||
|
return point.CompareColumn(upperLeft) >= 0 && point.CompareColumn(lowerRight) <= 0;
|
||
|
}
|
||
|
|
||
|
// Check if a given point is within the vertical boundaries of the rect
|
||
|
bool ContainsVertical(const PatternCursor &point) const
|
||
|
{
|
||
|
return point.CompareRow(upperLeft) >= 0 && point.CompareRow(lowerRight) <= 0;
|
||
|
}
|
||
|
|
||
|
// Check if a given point is within the rect
|
||
|
bool Contains(const PatternCursor &point) const
|
||
|
{
|
||
|
return ContainsHorizontal(point) && ContainsVertical(point);
|
||
|
}
|
||
|
|
||
|
// Ensure that the selection doesn't exceed a given pattern size.
|
||
|
void Sanitize(ROWINDEX maxRows, CHANNELINDEX maxChans)
|
||
|
{
|
||
|
upperLeft.Sanitize(maxRows, maxChans);
|
||
|
lowerRight.Sanitize(maxRows, maxChans);
|
||
|
};
|
||
|
|
||
|
|
||
|
// Get first row of selection
|
||
|
ROWINDEX GetStartRow() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
|
||
|
return upperLeft.GetRow();
|
||
|
}
|
||
|
|
||
|
// Get last row of selection
|
||
|
ROWINDEX GetEndRow() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
|
||
|
return lowerRight.GetRow();
|
||
|
}
|
||
|
|
||
|
// Get first channel of selection
|
||
|
CHANNELINDEX GetStartChannel() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
|
||
|
return upperLeft.GetChannel();
|
||
|
}
|
||
|
|
||
|
// Get last channel of selection
|
||
|
CHANNELINDEX GetEndChannel() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
|
||
|
return lowerRight.GetChannel();
|
||
|
}
|
||
|
|
||
|
PatternCursor::Columns GetStartColumn() const
|
||
|
{
|
||
|
ASSERT((upperLeft.cursor & 0xFFFF) <= (lowerRight.cursor & 0xFFFF));
|
||
|
return upperLeft.GetColumnType();
|
||
|
}
|
||
|
|
||
|
PatternCursor::Columns GetEndColumn() const
|
||
|
{
|
||
|
ASSERT((upperLeft.cursor & 0xFFFF) <= (lowerRight.cursor & 0xFFFF));
|
||
|
return lowerRight.GetColumnType();
|
||
|
}
|
||
|
|
||
|
// Create a bitset of the selected columns of a channel. If a column is selected, the corresponding bit is set.
|
||
|
// Example: If the first and second column of the channel are selected, the bits 00000011 would be returned.
|
||
|
uint8 GetSelectionBits(CHANNELINDEX chn) const
|
||
|
{
|
||
|
const CHANNELINDEX startChn = GetStartChannel(), endChn = GetEndChannel();
|
||
|
uint8 bits = 0;
|
||
|
|
||
|
if(chn >= startChn && chn <= endChn)
|
||
|
{
|
||
|
// All columns could be selected (unless this is the first or last channel).
|
||
|
bits = uint8_max;
|
||
|
|
||
|
if(chn == startChn)
|
||
|
{
|
||
|
// First channel: Remove columns left of the start column type.
|
||
|
bits <<= GetUpperLeft().GetColumnType();
|
||
|
}
|
||
|
if(chn == endChn)
|
||
|
{
|
||
|
// Last channel: Remove columns right of the end column type.
|
||
|
bits &= (2 << GetLowerRight().GetColumnType()) - 1;
|
||
|
}
|
||
|
}
|
||
|
return (bits & 0x1F);
|
||
|
}
|
||
|
|
||
|
// Get number of rows in selection
|
||
|
ROWINDEX GetNumRows() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetRow() <= lowerRight.GetRow());
|
||
|
return 1 + GetEndRow() - GetStartRow();
|
||
|
}
|
||
|
|
||
|
// Get number of channels in selection
|
||
|
CHANNELINDEX GetNumChannels() const
|
||
|
{
|
||
|
ASSERT(upperLeft.GetChannel() <= lowerRight.GetChannel());
|
||
|
return 1 + GetEndChannel() - GetStartChannel();
|
||
|
}
|
||
|
|
||
|
bool operator == (const PatternRect &other) const
|
||
|
{
|
||
|
return upperLeft == other.upperLeft && lowerRight == other.lowerRight;
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|