Skip to content

v0.3.0:大重构:预留chunithm转谱引入的空间#1

Merged
Starrah merged 7 commits intomasterfrom
refactor-for-chu
Apr 30, 2026
Merged

v0.3.0:大重构:预留chunithm转谱引入的空间#1
Starrah merged 7 commits intomasterfrom
refactor-for-chu

Conversation

@Starrah
Copy link
Copy Markdown
Collaborator

@Starrah Starrah commented Apr 30, 2026

Summary by Sourcery

引入通用的基础谱面抽象,并将现有所有与 Mai 谱面解析、生成以及 note/slide/statistics 相关的逻辑迁移到专用的 MuConvert.mai 命名空间,同时相应更新测试和程序装配。

增强内容:

  • 重构谱面表示,使用通用的 BaseChart<TNote> 接口和抽象基类,为未来支持多种游戏格式做准备。
  • 将 Mai 专用的谱面类型(ChartMaiChart,以及 note/slide/statistics/duration 类)重命名并迁移到专用的 MuConvert.mai 命名空间中。
  • 将解析器和生成器接口(IParserIGenerator)泛型化以支持不同谱面类型,使 MA2/Simai 的解析器和生成器与 MaiChart 对齐。
  • 改进警报时间元数据,将基于小节(bar)的时间字段和基于秒的时间字段分离,并增强本地化格式化消息。
  • 调整 MA2 和 Simai 的解析器/生成器以及 BPM/statistics 逻辑,使其使用统一的音符和滑条结束时间计算。

文档:

  • 扩展本地化资源,为仅小节(bar-only)、仅时间(time-only)以及时间+小节(time+bar)的组合信息提供独立格式,以支持新的警报时间模型。

测试:

  • 重组测试结构,将与 Mai 相关的测试归类到 tests/mai 下,并添加用于定位 Mai 测试集目录的辅助方法。
  • 添加占位用的 Chu 测试和测试集目录,为未来的 Chunithm 相关支持预留空间。
Original summary in English

Summary by Sourcery

Introduce a generic base chart abstraction and migrate all existing Mai chart parsing, generation, and note/slide/statistics logic to a dedicated MuConvert.mai namespace while updating tests and program wiring accordingly.

Enhancements:

  • Refactor chart representation to use a generic BaseChart interface and abstract base class for future support of multiple game formats.
  • Rename and relocate Mai-specific chart types (Chart → MaiChart, note/slide/statistics/duration classes) into a dedicated MuConvert.mai namespace.
  • Generalize parser and generator interfaces (IParser, IGenerator) to be generic over chart types, aligning MA2/Simai parsers and generators with MaiChart.
  • Improve alert time metadata by separating bar-based and second-based timing fields and enhancing localized formatting messages.
  • Adjust MA2 and Simai parsers/generators and BPM/statistics logic to use unified end-time calculations for notes and slides.

Documentation:

  • Extend localization resources with separate formats for bar-only, time-only, and combined time+bar messages to support the new alert timing model.

Tests:

  • Restructure test layout to group Mai-related tests under tests/mai and add helpers for locating the Mai testset directory.
  • Add a placeholder Chu test and testset directory to reserve space for future Chunithm-related support.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 30, 2026

审阅者指南

重构项目以引入通用的 BaseChart 抽象和面向 Mai 的 MaiChart/Note 流水线;更新解析器和生成器,使其在谱面类型上具备类型安全;并改进提示(Alert)的时间元数据以及测试/目录结构,为将来的 Chunithm 谱面支持做准备。

新 BaseChart 抽象与 Mai 谱面模型的类图

classDiagram
class IBaseChart

class BaseChart_TNote {
  List~TNote~ Notes
  <<abstract>>
  +StartBpm decimal
  +StartTime decimal
  +EndTime decimal
  +TotalNotes int
}

IBaseChart <|.. BaseChart_TNote

class MaiChart {
  +BPMList BpmList
  +string DefaultTouchSize
  +int ClockCount
  +List~Rational~ ExplicitClocks
  +decimal StartBpm
  +decimal StartTime
  +decimal EndTime
  +int TotalNotes
  +Statistics Statistics
  +void Sort()
  +Rational FirstNoteTime
  +void Shift(Rational offset, decimal? bpm)
}

BaseChart_TNote <|-- MaiChart

class Note {
  +MaiChart Chart
  +Rational Time
  +int Key
  +Duration Duration
  +virtual Rational EndTime
  +string DebuggerDisplay()
}

MaiChart "1" --> "*" Note : Notes

class Tap
class Hold {
  +override Duration Duration
}
class Touch {
  +TouchSeries TouchSeries
}
class TouchHold {
  +override Duration Duration
}
class Slide {
  +int StartKey
  +int EndKey
  +List~SlideSegment~ segments
  +Duration WaitTime
  +override Rational EndTime
}
class SlideSegment
class Star
class PseudoNote
class Duration
class Statistics

Note <|-- Tap
Tap <|-- Hold
Note <|-- Touch
Touch <|-- TouchHold
Note <|-- Slide
Tap <|-- Star
Note <|-- PseudoNote

Slide --> "*" SlideSegment
Note "1" o-- "1" Duration : Duration
Slide "1" o-- "1" Duration : WaitTime

MaiChart --> Statistics : creates
Duration --> Note : owner
Statistics --> MaiChart : reads
SlideSegment --> Slide : owner
Loading

针对 MaiChart 的通用解析器与生成器类图

classDiagram
class IBaseChart

class IParser_TChart {
  <<interface>>
  +(TChart,List~Alert~) Parse(string text)
}

class IGenerator_TChart {
  <<interface>>
  +(string,List~Alert~) Generate(TChart chart)
}

IParser_TChart ..> IBaseChart : where TChart
IGenerator_TChart ..> IBaseChart : where TChart

class MaiChart

class SimaiParser {
  +StrictLevelEnum StrictLevel
  +MaiChart chart
  +List~Alert~ alerts
  +(MaiChart,List~Alert~) Parse(string text)
}

class MA2Parser {
  +MaiChart chart
  +List~Alert~ alerts
  +(MaiChart,List~Alert~) Parse(string text)
}

class SimaiGenerator {
  +bool BigtouchIsAllowed
  +bool UseExplicitTime
  +MaiChart chart
  +List~Alert~ alerts
  +(string,List~Alert~) Generate(MaiChart chart)
}

class MA2Generator {
  +bool IsUtage
  +int MA2Version
  +int RSL
  +MaiChart chart
  +List~Alert~ alerts
  +(string,List~Alert~) Generate(MaiChart chart)
}

class MA2_103Generator

SimaiParser ..|> IParser_TChart
MA2Parser ..|> IParser_TChart
SimaiGenerator ..|> IGenerator_TChart
MA2Generator ..|> IGenerator_TChart
MA2_103Generator --|> MA2Generator

SimaiParser --> MaiChart : produces
MA2Parser --> MaiChart : produces
SimaiGenerator --> MaiChart : consumes
MA2Generator --> MaiChart : consumes
MA2_103Generator --> MaiChart : consumes

class Alert
SimaiParser --> Alert : collects
MA2Parser --> Alert : collects
SimaiGenerator --> Alert : collects
MA2Generator --> Alert : collects
MA2_103Generator --> Alert : collects
Loading

更新后的 Alert 时间元数据与本地化字符串类图

classDiagram
class Alert {
  +LEVEL Level
  +Rational? TimeInBar
  +double? TimeInSeconds
  +int? Line
  +string? RelevantNote
  +string Description
  +Alert(LEVEL level,string description)
  +Alert(LEVEL level,string description,int? line,string? relevantNote)
  +Alert(LEVEL level,string description,Rational? timeInBar,double? timeInSeconds,int? line,string? relevantNote)
  +Alert(LEVEL level,string description,mai_MaiChart chart,Rational time,int? line,string? relevantNote)
  +string ToString()
}

class LEVEL {
  <<enumeration>>
  Error
  Warning
  Info
  Debug
}

class Locale {
  +string MessageAt
  +string MessageBar
  +string MessageTime
  +string MessageTimeAndBar
  +string MessageParsing
  +string MA2NoteSentenceTooManyParam
  +string InvalidMA2Sentence
  +string InvalidMA2SentenceWarning
}

Alert ..> Locale : formats_messages

class MaiChart
Alert ..> MaiChart : optional_barTime_input
Loading

Mai 谱面解析与生成流水线的流程图

flowchart LR
  subgraph Inputs
    SimaiText[Simai text]
    MA2Text[MA2 text]
  end

  subgraph Parsers
    SP[SimaiParser]
    MP[MA2Parser]
  end

  subgraph Model
    MC[MaiChart]
  end

  subgraph Generators
    SG[SimaiGenerator]
    MG[MA2Generator]
    MG103[MA2_103Generator]
  end

  subgraph Outputs
    SimaiOut[Simai text output]
    MA2Out[MA2 105 text output]
    MA2103Out[MA2 103 text output]
  end

  Alerts[(Alert list)]

  SimaiText --> SP
  MA2Text --> MP

  SP --> MC
  MP --> MC

  MC --> SG
  MC --> MG
  MG --> MG103

  SG --> SimaiOut
  MG --> MA2Out
  MG103 --> MA2103Out

  SP --> Alerts
  MP --> Alerts
  SG --> Alerts
  MG --> Alerts
  MG103 --> Alerts
Loading

文件级变更

变更 详情 文件
引入通用的 BaseChart 抽象,并重构现有 Mai 谱面模型以使用该抽象。
  • 新增 IBaseChartBaseChart<TNote>,在其中共享 Notes 列表,并将 StartBpm/StartTime/EndTime/TotalNotes 抽象化。
  • MuConvert.mai 命名空间下将 Chart 重命名为 MaiChart,并让其继承自 BaseChart<Note>
  • 将相关谱面类(NoteSlideDurationStatistics 等)移动并更新至 MuConvert.mai 命名空间,并与 MaiChart 连接,包括为音符和 Slide 新增 EndTime 重写实现。
chart/BaseChart.cs
chart/mai/MaiChart.cs
chart/mai/Note.cs
chart/mai/Slide.cs
chart/mai/Duration.cs
chart/mai/Statistics.cs
使解析器与生成器在谱面类型上泛型化,并将现有 MA2/Simai 实现绑定到 MaiChart。
  • IParserIGenerator 改为以 IBaseChart 为约束的泛型接口。
  • SimaiParserMA2Parser 移至 MuConvert.mai 命名空间,并实现 IParser<MaiChart>,包括更新字段类型与 Parse 签名。
  • MA2GeneratorMA2_103GeneratorSimaiGenerator 移至 MuConvert.mai 命名空间,并实现 IGenerator<MaiChart>,包括更新字段类型与 Generate 签名。
parser/IParser.cs
parser/mai/MA2Parser.cs
parser/mai/SimaiParser.cs
parser/mai/ErrorStrategy.cs
generator/IGenerator.cs
generator/mai/MA2Generator.cs
generator/mai/MA2_103Generator.cs
generator/mai/SimaiGenerator.cs
Program.cs
增强 Alert,使其能独立表示小节时间与秒数时间,并将其与具体谱面类型解耦。
  • 用独立的 Rational? TimeInBardouble? TimeInSeconds 字段替代基于 Chart/Rational 元组的 BarTime 表示。
  • 新增多个 Alert 构造函数,以支持仅行号、仅时间以及 MaiChart + Rational 等输入(通过 chart.ToSecond 计算秒数)。
  • 更新 ToString,根据可用的时间字段输出不同的本地化消息,并使用新增的 Locale 键。
utils/Error.cs
parser/mai/MA2Parser.cs
parser/mai/SimaiParser.cs
扩展本地化消息,以支持仅小节、仅秒数以及小节+秒数组合格式的输出。
  • Locale.Designer 中新增 MessageBarMessageTimeAndBar 资源访问器。
  • MessageTime 调整为仅表示秒数;MessageTimeAndBar 用于同时格式化小节时间与秒数,并相应更新 Alert.ToString 的使用逻辑。
i18n/Locale.Designer.cs
i18n/Locale.resx
i18n/Locale.ja.resx
i18n/Locale.ko.resx
i18n/Locale.zh.resx
i18n/Locale.zh-hant.resx
重构 Mai 测试及测试数据布局,并为未来的 Chu 测试添加占位。
  • 将现有测试移动到 MuConvert.Tests.mai 命名空间及 tests/mai 目录下,调整引用与谱面类型(从 Chart 改为 MaiChart)。
  • 用基于 MuConvert.Tests.csprojtests/mai/testset 布局的 “测试集根目录” 发现机制,替代基于仓库根目录的发现方式。
  • 新增一个最小的 Chu 测试和占位用的 testset 目录,以确保 Chu 测试树被纳入版本控制。
tests/mai/TestUtils.cs
tests/mai/BPMList测试.cs
tests/mai/ChartShift测试.cs
tests/mai/MA2_103测试.cs
tests/mai/MA2转Simai测试.cs
tests/mai/Maidata读取测试.cs
tests/mai/Simai解析测试.cs
tests/mai/Simai语义测试.cs
tests/mai/Simai转MA2测试.cs
tests/mai/Statistics测试.cs
tests/chu/example.cs
tests/chu/testset/placeholder.txt

提示与命令

与 Sourcery 交互

  • 触发新的审查: 在 pull request 上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 在某条审查评论下回复,要求 Sourcery 从该评论创建 issue。你也可以直接回复 @sourcery-ai issue 来基于该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 上评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 总结: 在 pull request 描述正文中任意位置写上 @sourcery-ai summary,即可在该位置生成 PR 总结。你也可以在 pull request 上评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成审阅者指南: 在 pull request 上评论 @sourcery-ai guide,可随时(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 上评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论并且不希望再看到它们,这会很有用。
  • 一次性忽略所有 Sourcery 审查: 在 pull request 上评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。若你想从一次全新的审查开始,尤其有用——别忘了再评论 @sourcery-ai review 触发新的审查!

自定义你的使用体验

访问你的 控制面板 以:

  • 启用或禁用诸如 Sourcery 生成的 pull request 总结、审阅者指南等审查功能。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Refactors the project to introduce a generic BaseChart abstraction and a mai-specific MaiChart/Note pipeline, updates parsers and generators to be type-safe over chart types, and improves alert time metadata and testing/layout to prepare for future Chunithm chart support.

Class diagram for new BaseChart abstraction and Mai chart model

classDiagram
class IBaseChart

class BaseChart_TNote {
  List~TNote~ Notes
  <<abstract>>
  +StartBpm decimal
  +StartTime decimal
  +EndTime decimal
  +TotalNotes int
}

IBaseChart <|.. BaseChart_TNote

class MaiChart {
  +BPMList BpmList
  +string DefaultTouchSize
  +int ClockCount
  +List~Rational~ ExplicitClocks
  +decimal StartBpm
  +decimal StartTime
  +decimal EndTime
  +int TotalNotes
  +Statistics Statistics
  +void Sort()
  +Rational FirstNoteTime
  +void Shift(Rational offset, decimal? bpm)
}

BaseChart_TNote <|-- MaiChart

class Note {
  +MaiChart Chart
  +Rational Time
  +int Key
  +Duration Duration
  +virtual Rational EndTime
  +string DebuggerDisplay()
}

MaiChart "1" --> "*" Note : Notes

class Tap
class Hold {
  +override Duration Duration
}
class Touch {
  +TouchSeries TouchSeries
}
class TouchHold {
  +override Duration Duration
}
class Slide {
  +int StartKey
  +int EndKey
  +List~SlideSegment~ segments
  +Duration WaitTime
  +override Rational EndTime
}
class SlideSegment
class Star
class PseudoNote
class Duration
class Statistics

Note <|-- Tap
Tap <|-- Hold
Note <|-- Touch
Touch <|-- TouchHold
Note <|-- Slide
Tap <|-- Star
Note <|-- PseudoNote

Slide --> "*" SlideSegment
Note "1" o-- "1" Duration : Duration
Slide "1" o-- "1" Duration : WaitTime

MaiChart --> Statistics : creates
Duration --> Note : owner
Statistics --> MaiChart : reads
SlideSegment --> Slide : owner
Loading

Class diagram for generic parsers and generators over MaiChart

classDiagram
class IBaseChart

class IParser_TChart {
  <<interface>>
  +(TChart,List~Alert~) Parse(string text)
}

class IGenerator_TChart {
  <<interface>>
  +(string,List~Alert~) Generate(TChart chart)
}

IParser_TChart ..> IBaseChart : where TChart
IGenerator_TChart ..> IBaseChart : where TChart

class MaiChart

class SimaiParser {
  +StrictLevelEnum StrictLevel
  +MaiChart chart
  +List~Alert~ alerts
  +(MaiChart,List~Alert~) Parse(string text)
}

class MA2Parser {
  +MaiChart chart
  +List~Alert~ alerts
  +(MaiChart,List~Alert~) Parse(string text)
}

class SimaiGenerator {
  +bool BigtouchIsAllowed
  +bool UseExplicitTime
  +MaiChart chart
  +List~Alert~ alerts
  +(string,List~Alert~) Generate(MaiChart chart)
}

class MA2Generator {
  +bool IsUtage
  +int MA2Version
  +int RSL
  +MaiChart chart
  +List~Alert~ alerts
  +(string,List~Alert~) Generate(MaiChart chart)
}

class MA2_103Generator

SimaiParser ..|> IParser_TChart
MA2Parser ..|> IParser_TChart
SimaiGenerator ..|> IGenerator_TChart
MA2Generator ..|> IGenerator_TChart
MA2_103Generator --|> MA2Generator

SimaiParser --> MaiChart : produces
MA2Parser --> MaiChart : produces
SimaiGenerator --> MaiChart : consumes
MA2Generator --> MaiChart : consumes
MA2_103Generator --> MaiChart : consumes

class Alert
SimaiParser --> Alert : collects
MA2Parser --> Alert : collects
SimaiGenerator --> Alert : collects
MA2Generator --> Alert : collects
MA2_103Generator --> Alert : collects
Loading

Class diagram for updated Alert time metadata and Locale strings

classDiagram
class Alert {
  +LEVEL Level
  +Rational? TimeInBar
  +double? TimeInSeconds
  +int? Line
  +string? RelevantNote
  +string Description
  +Alert(LEVEL level,string description)
  +Alert(LEVEL level,string description,int? line,string? relevantNote)
  +Alert(LEVEL level,string description,Rational? timeInBar,double? timeInSeconds,int? line,string? relevantNote)
  +Alert(LEVEL level,string description,mai_MaiChart chart,Rational time,int? line,string? relevantNote)
  +string ToString()
}

class LEVEL {
  <<enumeration>>
  Error
  Warning
  Info
  Debug
}

class Locale {
  +string MessageAt
  +string MessageBar
  +string MessageTime
  +string MessageTimeAndBar
  +string MessageParsing
  +string MA2NoteSentenceTooManyParam
  +string InvalidMA2Sentence
  +string InvalidMA2SentenceWarning
}

Alert ..> Locale : formats_messages

class MaiChart
Alert ..> MaiChart : optional_barTime_input
Loading

Flow diagram for Mai chart parsing and generation pipeline

flowchart LR
  subgraph Inputs
    SimaiText[Simai text]
    MA2Text[MA2 text]
  end

  subgraph Parsers
    SP[SimaiParser]
    MP[MA2Parser]
  end

  subgraph Model
    MC[MaiChart]
  end

  subgraph Generators
    SG[SimaiGenerator]
    MG[MA2Generator]
    MG103[MA2_103Generator]
  end

  subgraph Outputs
    SimaiOut[Simai text output]
    MA2Out[MA2 105 text output]
    MA2103Out[MA2 103 text output]
  end

  Alerts[(Alert list)]

  SimaiText --> SP
  MA2Text --> MP

  SP --> MC
  MP --> MC

  MC --> SG
  MC --> MG
  MG --> MG103

  SG --> SimaiOut
  MG --> MA2Out
  MG103 --> MA2103Out

  SP --> Alerts
  MP --> Alerts
  SG --> Alerts
  MG --> Alerts
  MG103 --> Alerts
Loading

File-Level Changes

Change Details Files
Introduce a generic BaseChart abstraction and refactor the existing Mai chart model to use it.
  • Add IBaseChart and BaseChart with shared Notes list and abstract StartBpm/StartTime/EndTime/TotalNotes.
  • Rename Chart to MaiChart under MuConvert.mai namespace and make it inherit from BaseChart.
  • Move and update related chart classes (Note, Slide, Duration, Statistics, etc.) into the MuConvert.mai namespace and wire them to MaiChart, including new EndTime overrides for notes and slides.
chart/BaseChart.cs
chart/mai/MaiChart.cs
chart/mai/Note.cs
chart/mai/Slide.cs
chart/mai/Duration.cs
chart/mai/Statistics.cs
Make parsers and generators generic over chart type and bind existing MA2/Simai implementations to MaiChart.
  • Change IParser and IGenerator to generic interfaces constrained on IBaseChart.
  • Update SimaiParser and MA2Parser to live under MuConvert.mai and implement IParser, including updated field types and Parse signatures.
  • Update MA2Generator, MA2_103Generator, and SimaiGenerator to live under MuConvert.mai and implement IGenerator, including updated field types and Generate signatures.
parser/IParser.cs
parser/mai/MA2Parser.cs
parser/mai/SimaiParser.cs
parser/mai/ErrorStrategy.cs
generator/IGenerator.cs
generator/mai/MA2Generator.cs
generator/mai/MA2_103Generator.cs
generator/mai/SimaiGenerator.cs
Program.cs
Enhance Alert to represent bar time and second time independently and decouple it from specific chart types.
  • Replace the Chart/Rational tuple-based BarTime with separate Rational? TimeInBar and double? TimeInSeconds fields.
  • Add multiple Alert constructors to support line-only, time-only, and MaiChart+Rational inputs (computing seconds via chart.ToSecond).
  • Update ToString to emit different localized messages depending on which time values are present, using new Locale keys.
utils/Error.cs
parser/mai/MA2Parser.cs
parser/mai/SimaiParser.cs
Extend localization messages to support separate bar-only, time-only, and combined time+bar formatting.
  • Add MessageBar and MessageTimeAndBar resource accessors to Locale.Designer.
  • Change MessageTime to represent seconds-only while MessageTimeAndBar formats both bar time and seconds, and adjust Alert.ToString to use them.
i18n/Locale.Designer.cs
i18n/Locale.resx
i18n/Locale.ja.resx
i18n/Locale.ko.resx
i18n/Locale.zh.resx
i18n/Locale.zh-hant.resx
Restructure mai tests and test data layout, and add a placeholder for future Chu tests.
  • Move existing tests into MuConvert.Tests.mai namespace and tests/mai directory, adjusting imports and chart types from Chart to MaiChart.
  • Replace repo-root discovery with a testset-root discovery based on MuConvert.Tests.csproj and tests/mai/testset layout.
  • Add a minimal Chu test and placeholder testset directory so that the Chu test tree exists in version control.
tests/mai/TestUtils.cs
tests/mai/BPMList测试.cs
tests/mai/ChartShift测试.cs
tests/mai/MA2_103测试.cs
tests/mai/MA2转Simai测试.cs
tests/mai/Maidata读取测试.cs
tests/mai/Simai解析测试.cs
tests/mai/Simai语义测试.cs
tests/mai/Simai转MA2测试.cs
tests/mai/Statistics测试.cs
tests/chu/example.cs
tests/chu/testset/placeholder.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey——我发现了两个问题,并留下了一些高层次的反馈:

  • Alert 构造函数现在有点过载了,可能会导致调用歧义(尤其是当 null 同时可以匹配 Rational?int? 参数时);建议将其收敛为较小的一组构造函数,并通过静态辅助方法或 builder 风格的方法来补充,这样可以让 API 对未来的调用方更安全、更清晰。
  • BaseChart<TNote> 目前将 Notes 暴露为一个可变的公有字段;既然它已经成为通用抽象层,你可能会希望将其改为属性(或只暴露只读访问),以便在后续增加更多谱面类型(例如 Chunithm)时,更好地封装不变量。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- The `Alert` constructors are getting quite overloaded and may lead to ambiguous calls (especially where `null` could match either `Rational?` or `int?` arguments); consider consolidating to a smaller set of constructors plus static helpers or builder-style methods to keep the API safer and clearer for future callers.
- `BaseChart<TNote>` currently exposes `Notes` as a mutable public field; now that it’s the common abstraction, you might want to switch this to a property (or expose read-only access) to better encapsulate invariants as more chart types (e.g. Chunithm) are added.

## Individual Comments

### Comment 1
<location path="utils/Error.cs" line_range="17-23" />
<code_context>
     public string Description;

-    public Alert(LEVEL level, string description, (Chart, Rational)? barTime = null, int? line = null, string? relevantNote = null)
+    public Alert(LEVEL level, string description)
     {
         Level = level;
         Description = description;
-        BarTime = barTime;
+    }
+    
+    public Alert(LEVEL level, string description, int? line = null, string? relevantNote = null)
+        : this(level, description)
+    {
</code_context>
<issue_to_address>
**issue (bug_risk):** The two Alert constructors are overload-ambiguous when called with just (LEVEL, string).

Because both signatures accept `(LEVEL, string)` and the extra parameters in the second ctor are optional, `new Alert(Error, "msg")` has two equally good matches and the code will not compile. To fix this, either remove the two-arg constructor, make the extra parameters non-optional (e.g. `int line` without defaults) and call that overload explicitly where needed, or move the more specific variants into a static factory method.
</issue_to_address>

### Comment 2
<location path="utils/Error.cs" line_range="37-40" />
<code_context>
+        TimeInSeconds = timeInSeconds;
+    }
+    
+    public Alert(LEVEL level, string description, (mai.MaiChart, Rational) barTime, int? line = null, string? relevantNote = null)
+        : this(level, description, line, relevantNote)
+    {
+        var (chart, time) = barTime; 
+        TimeInBar = time; 
+        if (chart.BpmList.Count > 0) TimeInSeconds = (double)chart.ToSecond(time);
</code_context>
<issue_to_address>
**issue (bug_risk):** The `mai.MaiChart` type reference is likely invalid without a corresponding using-alias.

This constructor uses `(mai.MaiChart, Rational)` and then deconstructs it as `var (chart, time) = barTime;`, but `mai` is not defined in this file. Please either add a `using mai = MuConvert.mai;` alias or use the fully qualified `MuConvert.mai.MaiChart` type so the code compiles.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会基于你的反馈改进后续评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The Alert constructors are getting quite overloaded and may lead to ambiguous calls (especially where null could match either Rational? or int? arguments); consider consolidating to a smaller set of constructors plus static helpers or builder-style methods to keep the API safer and clearer for future callers.
  • BaseChart<TNote> currently exposes Notes as a mutable public field; now that it’s the common abstraction, you might want to switch this to a property (or expose read-only access) to better encapsulate invariants as more chart types (e.g. Chunithm) are added.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `Alert` constructors are getting quite overloaded and may lead to ambiguous calls (especially where `null` could match either `Rational?` or `int?` arguments); consider consolidating to a smaller set of constructors plus static helpers or builder-style methods to keep the API safer and clearer for future callers.
- `BaseChart<TNote>` currently exposes `Notes` as a mutable public field; now that it’s the common abstraction, you might want to switch this to a property (or expose read-only access) to better encapsulate invariants as more chart types (e.g. Chunithm) are added.

## Individual Comments

### Comment 1
<location path="utils/Error.cs" line_range="17-23" />
<code_context>
     public string Description;

-    public Alert(LEVEL level, string description, (Chart, Rational)? barTime = null, int? line = null, string? relevantNote = null)
+    public Alert(LEVEL level, string description)
     {
         Level = level;
         Description = description;
-        BarTime = barTime;
+    }
+    
+    public Alert(LEVEL level, string description, int? line = null, string? relevantNote = null)
+        : this(level, description)
+    {
</code_context>
<issue_to_address>
**issue (bug_risk):** The two Alert constructors are overload-ambiguous when called with just (LEVEL, string).

Because both signatures accept `(LEVEL, string)` and the extra parameters in the second ctor are optional, `new Alert(Error, "msg")` has two equally good matches and the code will not compile. To fix this, either remove the two-arg constructor, make the extra parameters non-optional (e.g. `int line` without defaults) and call that overload explicitly where needed, or move the more specific variants into a static factory method.
</issue_to_address>

### Comment 2
<location path="utils/Error.cs" line_range="37-40" />
<code_context>
+        TimeInSeconds = timeInSeconds;
+    }
+    
+    public Alert(LEVEL level, string description, (mai.MaiChart, Rational) barTime, int? line = null, string? relevantNote = null)
+        : this(level, description, line, relevantNote)
+    {
+        var (chart, time) = barTime; 
+        TimeInBar = time; 
+        if (chart.BpmList.Count > 0) TimeInSeconds = (double)chart.ToSecond(time);
</code_context>
<issue_to_address>
**issue (bug_risk):** The `mai.MaiChart` type reference is likely invalid without a corresponding using-alias.

This constructor uses `(mai.MaiChart, Rational)` and then deconstructs it as `var (chart, time) = barTime;`, but `mai` is not defined in this file. Please either add a `using mai = MuConvert.mai;` alias or use the fully qualified `MuConvert.mai.MaiChart` type so the code compiles.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread utils/Error.cs
Comment on lines +17 to +23
public Alert(LEVEL level, string description)
{
Level = level;
Description = description;
BarTime = barTime;
}

public Alert(LEVEL level, string description, int? line = null, string? relevantNote = null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 当仅以 (LEVEL, string) 调用时,这两个 Alert 构造函数存在重载歧义。

由于两个签名都接受 (LEVEL, string),且第二个构造函数中的额外参数是可选的,所以 new Alert(Error, "msg") 会匹配到两个同样合适的重载,导致代码无法编译。为了解决这个问题,可以选择移除只带两个参数的构造函数,或者让额外参数变为必填(例如使用不带默认值的 int line),并在需要的地方显式调用该重载;或者将这些更具体的变体移到一个静态工厂方法中。

Original comment in English

issue (bug_risk): The two Alert constructors are overload-ambiguous when called with just (LEVEL, string).

Because both signatures accept (LEVEL, string) and the extra parameters in the second ctor are optional, new Alert(Error, "msg") has two equally good matches and the code will not compile. To fix this, either remove the two-arg constructor, make the extra parameters non-optional (e.g. int line without defaults) and call that overload explicitly where needed, or move the more specific variants into a static factory method.

Comment thread utils/Error.cs
Comment on lines +37 to +40
public Alert(LEVEL level, string description, (mai.MaiChart, Rational) barTime, int? line = null, string? relevantNote = null)
: this(level, description, line, relevantNote)
{
var (chart, time) = barTime;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 如果没有对应的 using 别名,mai.MaiChart 这个类型引用很可能是无效的。

这个构造函数使用 (mai.MaiChart, Rational),并通过 var (chart, time) = barTime; 进行解构,但在这个文件中并未定义 mai。请添加 using mai = MuConvert.mai; 别名,或者直接使用完整限定名 MuConvert.mai.MaiChart,以确保代码可以正确编译。

Original comment in English

issue (bug_risk): The mai.MaiChart type reference is likely invalid without a corresponding using-alias.

This constructor uses (mai.MaiChart, Rational) and then deconstructs it as var (chart, time) = barTime;, but mai is not defined in this file. Please either add a using mai = MuConvert.mai; alias or use the fully qualified MuConvert.mai.MaiChart type so the code compiles.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the codebase to introduce a generic chart and parser/generator architecture, moving maimai-specific logic into a dedicated mai namespace and introducing BaseChart<TNote>. It also updates localization strings and refactors the Alert utility. Several critical issues were identified, including a syntax error in the IBaseChart declaration, potential runtime exceptions in EndTime calculations for both charts and notes, and significant performance inefficiencies in the TotalNotes property. Furthermore, the refactor introduced a circular dependency between the utils and mai namespaces, which compromises the architectural layering.

Comment thread chart/BaseChart.cs
@@ -0,0 +1,36 @@
namespace MuConvert.chart;

public interface IBaseChart;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In C#, interfaces must have a body enclosed in braces { }, even if they are empty. The semicolon-only syntax ; is not valid for interface declarations and will cause a compilation error.

public interface IBaseChart { }

Comment thread chart/mai/MaiChart.cs
}
}
public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This property has two issues:

  1. Correctness: If the Notes list is empty, Max() will throw an InvalidOperationException. You should handle the empty case (e.g., using DefaultIfEmpty).
  2. Efficiency: This is an $O(N)$ operation. If EndTime is accessed frequently, it could become a performance bottleneck. Consider caching this value or calculating it only when the chart is modified.
    public override decimal EndTime => Notes.Count == 0 ? 0 : (decimal)ToSecond(Notes.Max(x => x.EndTime));

Comment thread chart/mai/MaiChart.cs
}
public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
public override int TotalNotes => Statistics.Total;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The TotalNotes property is extremely inefficient. It calls the Statistics property, which creates a new Statistics instance and iterates through all notes in the chart every time it is accessed. This makes TotalNotes an $O(N)$ operation. It should be cached or calculated once.

    public override int TotalNotes => Notes.Count;

Comment thread chart/mai/Note.cs

internal virtual string DebuggerDisplay() => "";

public virtual Rational EndTime => Time + Duration.Bar;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This will throw a NullReferenceException for note types that do not initialize the Duration property (such as Tap or Touch). Since Duration is a class and is only initialized in the Hold and TouchHold constructors, you should add a null check.

    public virtual Rational EndTime => Time + (Duration?.Bar ?? 0);

Comment thread chart/BaseChart.cs
/**
* 所有音符构成的列表
*/
public List<TNote> Notes = [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Exposing a List<T> as a public field is generally discouraged in C# as it breaks encapsulation. It is better to use a property with a private setter or an auto-property.

    public List<TNote> Notes { get; set; } = [];

Comment thread utils/Error.cs
Comment on lines +37 to +43
public Alert(LEVEL level, string description, (mai.MaiChart, Rational) barTime, int? line = null, string? relevantNote = null)
: this(level, description, line, relevantNote)
{
var (chart, time) = barTime;
TimeInBar = time;
if (chart.BpmList.Count > 0) TimeInSeconds = (double)chart.ToSecond(time);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This constructor introduces a circular dependency between the MuConvert.utils and MuConvert.mai namespaces, which breaks the architectural layering intended by the refactor. Additionally, it ties the general Alert class to a specific chart implementation (MaiChart). It would be better to pass the pre-calculated time in seconds or have IBaseChart provide a way to convert bar time to seconds.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 297 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritises the most important files to review.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="chart/mai/MaiChart.cs">

<violation number="1" location="chart/mai/MaiChart.cs:43">
P2: `StartTime` assumes at least one note; empty charts will throw when accessing `Notes[0]` through `FirstNoteTime`. Add an empty-check fallback.</violation>

<violation number="2" location="chart/mai/MaiChart.cs:44">
P2: `EndTime` calls `Max()` on `Notes` without guarding empty charts, which will throw at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread chart/mai/MaiChart.cs
}
}
public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: EndTime calls Max() on Notes without guarding empty charts, which will throw at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chart/mai/MaiChart.cs, line 44:

<comment>`EndTime` calls `Max()` on `Notes` without guarding empty charts, which will throw at runtime.</comment>

<file context>
@@ -33,13 +33,16 @@ public void Sort()
         }
     }
+    public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
+    public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
+    public override int TotalNotes => Statistics.Total;
 
</file context>
Suggested change
public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
public override decimal EndTime => Notes.Count == 0 ? 0m : (decimal)ToSecond(Notes.Select(x => x.EndTime).Max());

Comment thread chart/mai/MaiChart.cs
return BpmList[0].Bpm;
}
}
public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: StartTime assumes at least one note; empty charts will throw when accessing Notes[0] through FirstNoteTime. Add an empty-check fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chart/mai/MaiChart.cs, line 43:

<comment>`StartTime` assumes at least one note; empty charts will throw when accessing `Notes[0]` through `FirstNoteTime`. Add an empty-check fallback.</comment>

<file context>
@@ -33,13 +33,16 @@ public void Sort()
             return BpmList[0].Bpm;
         }
     }
+    public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
+    public override decimal EndTime => (decimal)ToSecond(Notes.Select(x=>x.EndTime).Max());
+    public override int TotalNotes => Statistics.Total;
</file context>
Suggested change
public override decimal StartTime => (decimal)FirstNoteTime.Seconds;
public override decimal StartTime => Notes.Count == 0 ? 0m : (decimal)FirstNoteTime.Seconds;

@Starrah Starrah merged commit 462a414 into master Apr 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant