Skip to content

Commit f6a1360

Browse files
authored
Merge pull request #50 from Starrah/feat-clock_count-demo_len
feat: 导入导出实现对&clock_count(MET)、&demo_seek、&demo_len的支持
2 parents 9e8cc36 + e395ba2 commit f6a1360

7 files changed

Lines changed: 80 additions & 13 deletions

File tree

MaiChartManager/Controllers/Charts/ImportChartController.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.RegularExpressions;
2+
using MaiChartManager.Controllers.Music;
23
using MaiChartManager.Services;
34
using Microsoft.AspNetCore.Mvc;
45
using SimaiSharp;
@@ -11,7 +12,7 @@ namespace MaiChartManager.Controllers.Charts;
1112
public class ImportChartController(StaticSettings settings, ILogger<StaticSettings> logger,
1213
MaidataImportService importService) : ControllerBase
1314
{
14-
public record ImportChartCheckResult(bool Accept, IEnumerable<ImportChartMessage> Errors, Dictionary<ShiftMethod, float> chartPaddings, bool IsDx, string? Title, float first);
15+
public record ImportChartCheckResult(bool Accept, IEnumerable<ImportChartMessage> Errors, Dictionary<ShiftMethod, float> chartPaddings, bool IsDx, string? Title, float first, CueConvertController.SetAudioPreviewRequest? previewTime);
1516

1617
[HttpPost]
1718
public ImportChartCheckResult ImportChartCheck(IFormFile file, [FromForm] bool isReplacement = false)
@@ -99,7 +100,7 @@ public ImportChartCheckResult ImportChartCheck(IFormFile file, [FromForm] bool i
99100
{
100101
errors.Add(new ImportChartMessage(Locale.MusicNoCharts, MessageLevel.Fatal));
101102
fatal = true;
102-
return new ImportChartCheckResult(!fatal, errors, new Dictionary<ShiftMethod, float>(), false, title, 0);
103+
return new ImportChartCheckResult(!fatal, errors, new Dictionary<ShiftMethod, float>(), false, title, 0, null);
103104
}
104105

105106
float.TryParse(maiData.GetValueOrDefault("first"), out var first);
@@ -142,14 +143,22 @@ public ImportChartCheckResult ImportChartCheck(IFormFile file, [FromForm] bool i
142143

143144
var chartPaddings = MaidataImportService.CalcChartPadding(maiCharts, out _);
144145

145-
return new ImportChartCheckResult(!fatal, errors, chartPaddings, isDx, title, first);
146+
CueConvertController.SetAudioPreviewRequest? previewTime = null;
147+
if (float.TryParse(maiData.GetValueOrDefault("demo_seek"), out var demo_seek))
148+
{
149+
// 当只有demo_seek没有demo_len时,则把demo_len设为一个很大的数,表示preview直到音频结尾;SetAudioPreviewApi中会自动把实际的loopEnd限制到音频长度以内。
150+
if (!float.TryParse(maiData.GetValueOrDefault("demo_len"), out var demo_len)) demo_len = 10000f;
151+
previewTime = new CueConvertController.SetAudioPreviewRequest(demo_seek, demo_seek + demo_len);
152+
}
153+
154+
return new ImportChartCheckResult(!fatal, errors, chartPaddings, isDx, title, first, previewTime);
146155
}
147156
catch (Exception e)
148157
{
149158
logger.LogError(e, "解析谱面失败(大)");
150159
errors.Add(new ImportChartMessage(Locale.ChartParseFailedGlobal, MessageLevel.Fatal));
151160
fatal = true;
152-
return new ImportChartCheckResult(!fatal, errors, new Dictionary<ShiftMethod, float>(), false, "", 0);
161+
return new ImportChartCheckResult(!fatal, errors, new Dictionary<ShiftMethod, float>(), false, "", 0, null);
153162
}
154163
}
155164

MaiChartManager/Controllers/Music/MusicTransferController.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -563,25 +563,31 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
563563
var version = StaticSettings.VersionList.FirstOrDefault(it => it.Id == music.AddVersionId);
564564
if (version is not null)
565565
simaiFile.AppendLine($"&version={version.GenreName}");
566-
simaiFile.AppendLine($"&chartconverter=MaiChartManager v{Application.ProductVersion}");
567-
simaiFile.AppendLine("&ChartConvertTool=MaiChartManager");
568-
simaiFile.AppendLine($"&ChartConvertToolVersion={Application.ProductVersion}");
569-
570566

571567
// demo_seek
572568
try
573569
{
574570
if (AudioConvert.TryResolveAcbAwb(GetAudioCandidateIds(music), out _, out var previewAcb, out _) && previewAcb is not null)
575571
{
576572
var previewTime = CriUtils.GetAudioPreviewTime(previewAcb);
577-
if (previewTime.StartTime >= 0)
573+
if (previewTime.StartTime >= 0 && previewTime.EndTime > previewTime.StartTime)
574+
{
578575
simaiFile.AppendLine($"&demo_seek={previewTime.StartTime}");
576+
simaiFile.AppendLine($"&demo_len={previewTime.EndTime - previewTime.StartTime}");
577+
}
579578
}
580579
}
581580
catch
582581
{
583582
// ignore preview time errors
584583
}
584+
585+
var ma2Contents = new List<(int, string[])>();
586+
587+
// 关于clock_count功能,我决定不走MaiLib了,而是我们自己解析。因为ma2.Compose返回的是裸谱面inote中的内容,没有办法合理的把clock信息插进去。因此,我们自己解析吧。
588+
// 选用最难的一张有效谱面的MET值作为全曲的&clock_count
589+
int clockCount = 0;
590+
585591
for (var i = 0; i < music.Charts.Length; i++)
586592
{
587593
var chart = music.Charts[i];
@@ -596,8 +602,26 @@ public async Task ExportAsMaidata(int id, string assetDir, bool ignoreVideo = fa
596602
}
597603

598604
var ma2Content = await System.IO.File.ReadAllLinesAsync(chartPath);
605+
ma2Contents.Add((i, ma2Content));
606+
607+
// 从谱面内容中寻找MET行
608+
var metLine = ma2Content.FirstOrDefault(it => it.StartsWith("MET\t"));
609+
if (metLine is not null && int.TryParse(metLine.Split('\t')[4], out var v)) clockCount = v;
610+
}
611+
612+
if (clockCount > 0) simaiFile.AppendLine($"&clock_count={clockCount}");
613+
614+
simaiFile.AppendLine($"&chartconverter=MaiChartManager v{Application.ProductVersion}");
615+
simaiFile.AppendLine("&ChartConvertTool=MaiChartManager");
616+
simaiFile.AppendLine($"&ChartConvertToolVersion={Application.ProductVersion}");
617+
618+
// 根据前面读取的结果,向simaiFile中最终写入谱面信息相关字段
619+
foreach (var (i, ma2Content) in ma2Contents)
620+
{
621+
var chart = music.Charts[i];
599622
var ma2 = parser.ChartOfToken(ma2Content);
600623
var simai = ma2.Compose(ChartEnum.ChartVersion.SimaiFes);
624+
simaiFile.AppendLine(""); // 为了格式美观,在每个难度之前添加一个空行
601625
simaiFile.AppendLine($"&lv_{i + 2}={chart.Level}.{chart.LevelDecimal}");
602626
simaiFile.AppendLine($"&des_{i + 2}={chart.Designer}");
603627
simaiFile.AppendLine($"&inote_{i + 2}={simai}");

MaiChartManager/Front/src/client/apiGen.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export interface ImportChartCheckResult {
276276
title?: string | null;
277277
/** @format float */
278278
first?: number;
279+
previewTime?: SetAudioPreviewRequest;
279280
}
280281

281282
export interface ImportChartMessage {

MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/index.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default defineComponent({
6969
errors.value.push({ level: MessageLevel.Warning, message: t('chart.import.error.convertPaidFeature'), name: dir.name, isPaid: true });
7070
}
7171

72-
let first = 0, chartPaddings, name = dir.name, isDx = false;
72+
let first = 0, chartPaddings, name = dir.name, isDx = false, previewTime = undefined;
7373
if (maidata) {
7474
const checkRet = (await api.ImportChartCheck({ file: maidata })).data;
7575
reject = reject || !checkRet.accept;
@@ -81,11 +81,12 @@ export default defineComponent({
8181
name = checkRet.title!;
8282
if (checkRet.isDx) id += 1e4;
8383
isDx = checkRet.isDx!;
84+
if (checkRet.previewTime) previewTime = checkRet.previewTime
8485
}
8586

8687
if (!reject) {
8788
meta.value.push({
88-
id, maidata, bg, track, chartPaddings, name, first, movie, isDx,
89+
id, maidata, bg, track, chartPaddings, name, first, movie, isDx, previewTime,
8990
importStep: IMPORT_STEP.start,
9091
})
9192
}
@@ -171,6 +172,14 @@ export default defineComponent({
171172
let audioPadding = chartPadding - music.first;
172173

173174
await api.SetAudio(music.id, selectedADir.value, { file: music.track, padding: audioPadding, ignoreGapless: !!tempOptions.value.ignoreGapless });
175+
if (music.previewTime) {
176+
// 因为&demo_seek和&demo_len都是相对于原始音源的,所以这里的时间必须要扣掉上面SetAudio时应用的padding。
177+
// 根据SetAudioApi的定义,padding为正表示歌曲前面被加了空白,因此原本的预览段在新的裁剪后的音频中的位置也要相应后移;padding为负反之。
178+
// 因此,直接给startTime和endTime都加上audioPadding即可。
179+
music.previewTime.startTime! += audioPadding;
180+
music.previewTime.endTime! += audioPadding
181+
await api.SetAudioPreview(music.id, selectedADir.value, music.previewTime);
182+
}
174183

175184
if (music.movie && !tempOptions.value.disableBga) {
176185
currentMovieProgress.value = 0;

MaiChartManager/Front/src/views/Charts/ImportCreateChartButton/ImportChartButton/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ImportChartCheckResult, ImportChartMessage, ShiftMethod } from "@/client/apiGen";
1+
import { ImportChartCheckResult, ImportChartMessage, SetAudioPreviewRequest, ShiftMethod } from "@/client/apiGen";
22

33
export enum STEP {
44
none,
@@ -30,6 +30,7 @@ export type ImportMeta = {
3030
chartPaddings: ImportChartCheckResult['chartPaddings'],
3131
first: number,
3232
isDx: boolean,
33+
previewTime?: SetAudioPreviewRequest
3334
}
3435

3536
export type FirstPaddingMessage = { first: number, chartPaddings: ImportChartCheckResult['chartPaddings']}

MaiChartManager/Services/MaidataImportService.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,19 @@ public static Dictionary<ShiftMethod, float> CalcChartPadding(List<MaiChart> cha
256256
}
257257

258258
private record AllChartsEntry(string chartText, MaiChart simaiSharpChart);
259+
260+
[GeneratedRegex(@"^(CLK_DEF\t)(\d+\s*)$", RegexOptions.Multiline)]
261+
private static partial Regex ClockDefRegex();
262+
263+
[GeneratedRegex(@"^(MET\t\d+\t\d+\t\d+\t)(\d+\s*)$", RegexOptions.Multiline)]
264+
private static partial Regex FirstMetLineRegex();
265+
266+
private static string ApplyClockCountToFirstMetLine(string ma2Content, int clockCount)
267+
{
268+
ma2Content = ClockDefRegex().Replace(ma2Content, $"${{1}}{96 * clockCount}", 1);
269+
ma2Content = FirstMetLineRegex().Replace(ma2Content, $"${{1}}{clockCount}", 1);
270+
return ma2Content;
271+
}
259272

260273
public ImportChartResult ImportMaidata(
261274
MusicXml music,
@@ -410,6 +423,13 @@ public ImportChartResult ImportMaidata(
410423
errors.Add(new ImportChartMessage(Locale.ChartNotesMissing, MessageLevel.Warning));
411424
logger.LogWarning("BUG! shiftedConverted: {shiftedLen}, originalConverted: {originalLen}", shiftedConverted.Split('\n').Length, originalConverted.Split('\n').Length);
412425
}
426+
427+
// 如果maidata中声明了clock_count,应用之,修改CLOCK_DEF和MET
428+
if (int.TryParse(maiData.GetValueOrDefault("clock_count"), out var clockCount))
429+
{
430+
if (clockCount > 0) shiftedConverted = ApplyClockCountToFirstMetLine(shiftedConverted, clockCount);
431+
else errors.Add(new ImportChartMessage("the \"&clock_count\" value in this maidata is invalid thus ignored.", MessageLevel.Warning));
432+
}
413433

414434
// Just use T_NUM_ALL value in ma2 file
415435
targetChart.MaxNotes = ParseTNumAllFromMa2(shiftedConverted);

MaiChartManager/Utils/CriUtils.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ public static async Task<byte[]> CreateAcbWithPreview(string wavPath, byte[] awb
4343
criTable.Load(File.ReadAllBytes(Path.Combine(StaticSettings.exeDir, "templateV3.acb")));
4444

4545
var cueTable = criTable.Rows[0].GetTable("CueTable");
46-
cueTable.Rows[0]["Length"] = (int)((float)format.SampleCount / format.SampleRate * 1000);
46+
float audioLength = (float)format.SampleCount / format.SampleRate;
47+
cueTable.Rows[0]["Length"] = (int)(audioLength * 1000);
4748
criTable.WriterSettings = CriTableWriterSettings.Adx2Settings;
4849
criTable.Rows[0]["CueTable"] = cueTable.Save();
4950

5051
var trackEventTable = criTable.Rows[0].GetTable("TrackEventTable");
52+
// 对loopEnd予以截断,防止前端传来的loopEnd超过音频结尾的范围
53+
if (loopEnd.TotalSeconds > audioLength) loopEnd = TimeSpan.FromSeconds(audioLength);
5154
trackEventTable.Rows[1]["Command"] = MakeCommandForPreview(loopStart, loopEnd);
5255
criTable.Rows[0]["TrackEventTable"] = trackEventTable.Save();
5356

0 commit comments

Comments
 (0)