mirror of
https://github.com/liuk7071/ChonkyStation.git
synced 2025-04-02 10:52:38 -04:00
402 lines
10 KiB
C++
402 lines
10 KiB
C++
#include "../zep/include/zep/mcommon/logger.h"
|
|
|
|
#include "../zep/include/zep/buffer.h"
|
|
#include "../zep/include/zep/window.h"
|
|
|
|
#include "orca.h"
|
|
#include "syntax_orca.h"
|
|
|
|
#include <mutils/time/profiler.h>
|
|
|
|
using namespace MUtils;
|
|
using namespace std::chrono;
|
|
|
|
namespace Zep
|
|
{
|
|
|
|
Orca::Orca()
|
|
{
|
|
}
|
|
|
|
Orca::~Orca()
|
|
{
|
|
Quit();
|
|
|
|
mbuf_reusable_deinit(&m_mbuf_r);
|
|
oevent_list_deinit(&m_oevent_list);
|
|
field_deinit(&m_field);
|
|
|
|
TimeProvider::Instance().UnRegisterConsumer(this);
|
|
}
|
|
|
|
void Orca::Init(ZepEditor& editor)
|
|
{
|
|
m_pEditor = &editor;
|
|
field_init(&m_field);
|
|
oevent_list_init(&m_oevent_list);
|
|
mbuf_reusable_init(&m_mbuf_r);
|
|
|
|
TimeProvider::Instance().RegisterConsumer(this);
|
|
}
|
|
|
|
long Orca::FieldIndex(long x, long y)
|
|
{
|
|
return long(y * long(m_field.width) + x);
|
|
}
|
|
|
|
long Orca::StateIndex(long x, long y, long xOff, long yOff)
|
|
{
|
|
auto xNew = x + xOff;
|
|
auto yNew = y + yOff;
|
|
if (xNew < 0 || yNew < 0 || xNew >= m_field.width || yNew >= m_field.height)
|
|
{
|
|
return -1;
|
|
}
|
|
return (yNew * (m_field.width + 1)) + xNew;
|
|
};
|
|
|
|
long Orca::StateIndex(long x, long y)
|
|
{
|
|
if (x < 0 || y < 0 || x >= m_field.width || y >= m_field.height)
|
|
{
|
|
return -1;
|
|
}
|
|
return (y * (m_field.width + 1)) + x;
|
|
};
|
|
|
|
Glyph Orca::ReadField(long x, long y, Glyph failVal)
|
|
{
|
|
if (x < m_field.width && y < m_field.height && x >= 0 && y >= 0)
|
|
{
|
|
return m_field.buffer[FieldIndex(x, y)];
|
|
}
|
|
return failVal;
|
|
};
|
|
|
|
void Orca::WriteField(long x, long y, Glyph val)
|
|
{
|
|
if (x < m_field.width && y < m_field.height && x >= 0 && y >= 0)
|
|
{
|
|
m_field.buffer[FieldIndex(x, y)] = val;
|
|
}
|
|
};
|
|
|
|
void Orca::ReadFromBuffer(ZepBuffer* pBuffer)
|
|
{
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
ByteRange range;
|
|
pBuffer->GetLineOffsets(0, range);
|
|
|
|
// We don't have a pending buffer update to show if we just updated from the buffer
|
|
m_updated.store(false);
|
|
|
|
// Update field sizes
|
|
field_resize_raw_if_necessary(&m_field, pBuffer->GetLineCount(), (range.second - range.first - 1));
|
|
if (m_lastField.size() < (m_field.width * m_field.height))
|
|
{
|
|
m_lastField.assign(m_field.width * m_field.height, 0);
|
|
}
|
|
|
|
m_size = NVec2i(m_field.width, m_field.height);
|
|
|
|
// Copy the buffer into the field
|
|
auto& text = pBuffer->GetWorkingBuffer();
|
|
auto sz = text.size();
|
|
for (int y = 0; y < m_field.height; y++)
|
|
{
|
|
for (int x = 0; x < m_field.width; x++)
|
|
{
|
|
auto sourceIndex = y * (m_field.width + 1) + x;
|
|
if (sourceIndex < sz)
|
|
{
|
|
auto ch = text[sourceIndex];
|
|
// Replace things we inserted that aren't valid Orca syntax
|
|
if (ch == '+' || ch == ' ')
|
|
{
|
|
ch = '.';
|
|
}
|
|
m_field.buffer[y * m_field.width + x] = ch;
|
|
}
|
|
}
|
|
}
|
|
|
|
mbuf_reusable_ensure_size(&m_mbuf_r, m_field.height, m_field.width);
|
|
}
|
|
|
|
void Orca::WriteToBuffer(ZepBuffer* pBuffer, ZepWindow& window)
|
|
{
|
|
if (!m_updated.load() && window.BufferToDisplay() == m_lastCursorPos)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PROFILE_SCOPE(Orca_WriteToBuffer);
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
|
|
m_lastCursorPos = window.BufferToDisplay();
|
|
|
|
// Copy the calculated buffer data from the orca buffer
|
|
auto& text = pBuffer->GetMutableWorkingBuffer();
|
|
for (int y = 0; y < m_field.height; y++)
|
|
{
|
|
for (int x = 0; x < m_field.width; x++)
|
|
{
|
|
auto targetIndex = y * (m_field.width + 1) + x;
|
|
auto ch = ReadField(x, y);
|
|
// Special case + marker for grid bounds
|
|
if (y % GridModulo == 0 && x % GridModulo == 0 && ch == '.')
|
|
{
|
|
ch = '+';
|
|
}
|
|
text[targetIndex] = ch;
|
|
}
|
|
}
|
|
|
|
// Copy the precalculated syntax data to the buffer's syntax view
|
|
auto pSyntax = dynamic_cast<ZepSyntax_Orca*>(pBuffer->GetSyntax());
|
|
if (pSyntax)
|
|
{
|
|
BuildSyntax();
|
|
pSyntax->UpdateSyntax(m_syntax);
|
|
}
|
|
|
|
m_updated.store(false);
|
|
}
|
|
|
|
// Generate the syntax information for the whole buffer
|
|
void Orca::BuildSyntax()
|
|
{
|
|
PROFILE_SCOPE(Orca_BuildSyntax);
|
|
|
|
m_syntax.resize((m_field.width + 1) * m_field.height);
|
|
|
|
for (long y = 0; y < m_field.height; y++)
|
|
{
|
|
bool inComment = false;
|
|
for (long x = 0; x < m_field.width; x++)
|
|
{
|
|
auto cursorGrid = m_lastCursorPos / NVec2i(GridModulo, GridModulo);
|
|
auto valGrid = NVec2i(x, y) / NVec2i(GridModulo, GridModulo);
|
|
|
|
// For highlighting the cursor area
|
|
bool near = false;
|
|
if (x > (((m_lastCursorPos.x / GridModulo) * GridModulo) - 1) && x <= ((1 + (m_lastCursorPos.x / GridModulo)) * GridModulo) && y > (((m_lastCursorPos.y / GridModulo) * GridModulo) - 1) && y <= ((1 + (m_lastCursorPos.y / GridModulo)) * GridModulo))
|
|
{
|
|
// This cell is near the cursor and needs the highlight
|
|
if (x % 2 == 0 && y % 2 == 0)
|
|
near = true;
|
|
}
|
|
|
|
// Copy the mark into the bottom of the state
|
|
auto index = FieldIndex(x, y);
|
|
auto mark = (uint32_t)m_mbuf_r.buffer[index];
|
|
|
|
auto glyph = m_field.buffer[index];
|
|
auto glyphOld = m_lastField[index];
|
|
|
|
// TODO: Do I still want/need this?
|
|
if (glyph != glyphOld)
|
|
{
|
|
mark |= OrcaFlags::Changed;
|
|
}
|
|
|
|
if (y % GridModulo == 0 && x % GridModulo == 0 && glyph == '.')
|
|
{
|
|
glyph = Glyph_class_grid_marker;
|
|
}
|
|
else if (near && glyph == '.')
|
|
{
|
|
glyph = Glyph_class_grid_marker;
|
|
}
|
|
else if (glyph == '#')
|
|
{
|
|
glyph = Glyph_class_comment;
|
|
inComment = !inComment;
|
|
}
|
|
|
|
if (inComment)
|
|
{
|
|
glyph = Glyph_class_comment;
|
|
}
|
|
|
|
BuildSyntax(x, y, mark, glyph);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Orca::BuildSyntax(int x, int y, uint32_t m, Glyph glyph)
|
|
{
|
|
auto& res = m_syntax[StateIndex(x, y)];
|
|
res.background = ThemeColor::Background;
|
|
res.foreground = ThemeColor::None;
|
|
|
|
const NVec4f cyan = NVec4f(0.4f, 0.85f, 0.86f, 1.0f);
|
|
|
|
if (glyph == Glyph_class_comment)
|
|
{
|
|
res.foreground = ThemeColor::Comment;
|
|
return;
|
|
}
|
|
|
|
// Locked are initially Dim
|
|
if (m & Mark_flag_lock)
|
|
{
|
|
res.foreground = ThemeColor::TextDim;
|
|
}
|
|
|
|
// Haste inputs always cyan
|
|
if (m & Mark_flag_haste_input)
|
|
{
|
|
res.foreground = ThemeColor::Custom;
|
|
res.customForegroundColor = cyan;
|
|
return;
|
|
}
|
|
else if (m & Mark_flag_input)
|
|
{
|
|
res.foreground = ThemeColor::Text;
|
|
return;
|
|
}
|
|
|
|
// Outputs always black on white
|
|
if (m & Mark_flag_output)
|
|
{
|
|
res.foreground = ThemeColor::Background;
|
|
res.background = ThemeColor::Text;
|
|
return;
|
|
}
|
|
|
|
if (m & Mark_flag_lock)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (glyph == Glyph_class_grid || glyph == Glyph_class_grid_marker)
|
|
{
|
|
res.foreground = ThemeColor::Whitespace;
|
|
return;
|
|
}
|
|
|
|
switch (glyph_class_of(glyph))
|
|
{
|
|
case Glyph_class_unknown:
|
|
res.foreground = ThemeColor::Error;
|
|
return;
|
|
break;
|
|
case Glyph_class_grid:
|
|
res.foreground = ThemeColor::Background;
|
|
return;
|
|
break;
|
|
case Glyph_class_grid_marker:
|
|
res.foreground = ThemeColor::Whitespace;
|
|
return;
|
|
break;
|
|
case Glyph_class_lowercase:
|
|
res.foreground = ThemeColor::TextDim;
|
|
return;
|
|
case Glyph_class_uppercase:
|
|
case Glyph_class_message:
|
|
res.background = ThemeColor::Custom;
|
|
res.customBackgroundColor = cyan;
|
|
res.foreground = ThemeColor::Background;
|
|
return;
|
|
case Glyph_class_movement:
|
|
res.foreground = ThemeColor::TextDim;
|
|
return;
|
|
break;
|
|
case Glyph_class_bang:
|
|
res.background = ThemeColor::FlashColor;
|
|
res.foreground = ThemeColor::Text;
|
|
return;
|
|
break;
|
|
default:
|
|
res.foreground = ThemeColor::TextDim;
|
|
res.background = ThemeColor::Background;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Orca::SetTestField(const NVec2i& fieldSize)
|
|
{
|
|
ZEP_UNUSED(fieldSize);
|
|
}
|
|
|
|
void Orca::Enable(bool enable)
|
|
{
|
|
m_enable.store(enable);
|
|
}
|
|
|
|
void Orca::Step()
|
|
{
|
|
// Single step
|
|
m_step.store(true);
|
|
}
|
|
|
|
void Orca::Quit()
|
|
{
|
|
TimeProvider::Instance().UnRegisterConsumer(this);
|
|
}
|
|
|
|
void Orca::Tick()
|
|
{
|
|
if (!m_enable.load() && !m_step.load())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PROFILE_SCOPE(Orca_Tick);
|
|
|
|
auto& tp = TimeProvider::Instance();
|
|
|
|
m_zeroQuantum = false;
|
|
auto beat = tp.GetBeat();
|
|
if ((beat - m_lastBeat) > tp.GetQuantum())
|
|
{
|
|
m_lastBeat = beat;
|
|
m_zeroQuantum = true;
|
|
}
|
|
|
|
m_step.store(false);
|
|
|
|
// Lock zone
|
|
{
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
TIME_SCOPE(OrcaUpdate)
|
|
mbuffer_clear(m_mbuf_r.buffer, m_field.height, m_field.width);
|
|
oevent_list_clear(&m_oevent_list);
|
|
orca_run(m_field.buffer, m_mbuf_r.buffer, m_field.height, m_field.width, m_frame++, &m_oevent_list, 0);
|
|
|
|
for (uint32_t i = 0; i < m_oevent_list.count; i++)
|
|
{
|
|
auto& ev = m_oevent_list.buffer[i];
|
|
|
|
if (ev.any.oevent_type == Oevent_type_midi_note)
|
|
{
|
|
// Note information
|
|
auto note = ev.midi_note.note + ((ev.midi_note.octave + 1) * 12);
|
|
auto velocity = (ev.midi_note.velocity > 0) ? ((float)ev.midi_note.velocity / 127.0f) : 1.0;
|
|
auto duration = std::max(ev.midi_note.duration, (uint8_t)1) * tp.GetTimePerBeat();
|
|
|
|
// Max MIDI note allowed
|
|
note = std::min<int>(127, note);
|
|
|
|
// TODO: For now, play at next beat
|
|
// We know that m_time is 'on' the engine's beat
|
|
// Note: need time to be unique??!
|
|
auto time = TimeProvider::Instance().Now() + nanoseconds(i) + tp.GetTimePerBeat() * 4;
|
|
|
|
// Submit event
|
|
sigPlayNote(std::chrono::duration_cast<std::chrono::milliseconds>(duration), float(velocity), note);
|
|
}
|
|
}
|
|
|
|
m_lastField.assign(m_field.buffer, m_field.buffer + size_t(m_field.width * m_field.height));
|
|
m_updated.store(true);
|
|
|
|
// This call is thread safe
|
|
m_pEditor->RequestRefresh();
|
|
}
|
|
}
|
|
|
|
} // namespace Zep
|