Skip to content

Instantly share code, notes, and snippets.

@plasma-effect
Created June 8, 2019 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save plasma-effect/9e1f5a8e41c31005d49356d20b7ca95d to your computer and use it in GitHub Desktop.
Save plasma-effect/9e1f5a8e41c31005d49356d20b7ca95d to your computer and use it in GitHub Desktop.
音ゲーの譜面っぽいやつを連番pngで出力するやつ
2 256
0 90
128 360
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.IO;
using static System.Linq.Enumerable;
namespace MusicGameMovieMaker
{
class Note : IComparable<Note>
{
public int Time { get; }
public int Line { get; }
public Note(int time, int line)
{
this.Time = time;
this.Line = line;
}
public int CompareTo(Note other)
{
var p = this.Time.CompareTo(other.Time);
if (p != 0)
{
return p;
}
return this.Line.CompareTo(other.Line);
}
}
static class Program
{
const int height = 480;//画面の高さ
const int width = 640;//画面の幅
const int noteSize = 20;//各ノーツのサイズ
const double distanceBase = 7.5;//HiSpeed x1.0での表示距離
const int border = 20;//判定の位置
const int maxDisplay = height - border;//表示される限界の位置
const int horizonDistance = 75;//各列の幅
const double left = width / 2 - horizonDistance * 1.5;//左端の列
//C++のstd::partition_pointに似た挙動をする関数(詳しくはcpprefjpを見よ)
static int PartitionPoint<T>(List<T> list, Func<T, bool> pred)
{
var min = 0;
var max = list.Count;
while (max - min > 1)
{
var mid = (min + max) / 2;
if (pred(list[mid]))
{
min = mid;
}
else
{
max = mid;
}
}
return max;
}
static (int, int) ReadTuple2(TextReader reader)
{
var ar = reader.ReadLine().Split(' ').Select(int.Parse).ToArray();
return (ar[0], ar[1]);
}
//BPMのデータを読み込む
static List<int> ReadBPMList(string path)
{
using (var stream = new StreamReader(path))
{
var (N, maxTime) = ReadTuple2(stream);
var ret = new List<int>();
var impl = new List<(int Time, int BPM)>();
foreach(var i in Range(0, N))
{
impl.Add(ReadTuple2(stream));
}
impl.Add((maxTime, int.MaxValue));
for (var (index, time) = (0, 0); time < maxTime; ++time)
{
if (time == impl[index + 1].Time)
{
++index;
}
ret.Add(impl[index].BPM);
}
return ret;
}
}
//32分刻みでのノーツのデータを読み込む
static List<Note> ReadNoteList(string path)
{
using(var stream = new StreamReader(path))
{
var ret = new List<Note>();
var N = int.Parse(stream.ReadLine());
foreach(var i in Range(0, N))
{
var (time, line) = ReadTuple2(stream);
ret.Add(new Note(time, line));
}
ret.Sort();
return ret;
}
}
static void Main(string[] args)
{
var BPMList = ReadBPMList("bpmdata.txt");
var noteData = ReadNoteList("notedata.txt");
var minIndex = 0;
var bpmIndex = 0;
var hispeed = 1.5;
var line = 0.0;
var (changeTime, realBPMList) = BPMCheck(BPMList);
minIndex = SaveImage(noteData, minIndex, hispeed, line, 0);
// 1.0/60秒ずつ進むことを仮定する(ほぼ誤差の範囲だが厳密には60FPSにはならないことに注意せよ)
const double minTime = 1.0 / 60;
var realTime = 0.0;
for (var frame = 1; minIndex < noteData.Count; ++frame)
{
var time = minTime;
while (time != default)
{
// BPMがxのときy秒で8*x*y/60回だけ32分を刻むことができる
line += Math.Min(changeTime[bpmIndex] - realTime, time) * realBPMList[bpmIndex] * 8 / 60;
time -= Math.Min(changeTime[bpmIndex] - realTime, time);
if (time != default)
{
++bpmIndex;
}
}
realTime += minTime;
minIndex = SaveImage(noteData, minIndex, hispeed, line, frame);
}
}
static Pen Pen = new Pen(Color.Red, noteSize / 4);
private static int SaveImage(List<Note> noteData, int minIndex, double hispeed, double line, int index)
{
// ノーツの中心と判定ラインの距離を返す
double GetPosition(Note note)
{
return (note.Time - line) * hispeed * distanceBase;
}
var pIndex = PartitionPoint(noteData, n => GetPosition(n) < maxDisplay + noteSize);
using (var bitmap = new Bitmap(width, height))
{
using (var g = Graphics.FromImage(bitmap))
{
foreach (var i in Range(0, 4))
{
g.DrawRectangle(Pens.Red, (int)(left + (horizonDistance * i) - noteSize), border - noteSize, 2 * noteSize, 2 * noteSize);
}
for (var i = minIndex; i < pIndex; ++i)
{
var pos = GetPosition(noteData[i]);
if (pos < -noteSize - border)
{
minIndex = i + 1;
}
else
{
var noteLine = noteData[i].Line;
g.FillRectangle(Brushes.Blue, (int)(left + (horizonDistance * noteLine) - noteSize), (int)(pos + border - noteSize), 2 * noteSize, 2 * noteSize);
g.DrawRectangle(Pen, (int)(left + (horizonDistance * noteLine) - noteSize), (int)(pos + border - noteSize), 2 * noteSize, 2 * noteSize);
}
}
}
bitmap.Save($"image/{index + 1}.png", System.Drawing.Imaging.ImageFormat.Png);
return minIndex;
}
}
// BPMが変わる時間とそれに対応するBPMのリストをセットを返す
private static (List<double> Change, List<int> RealBPMList) BPMCheck(List<int> BPMList)
{
var bpmChange = new List<double>();
var realBpmList = new List<int>
{
BPMList[0]
};
var prev = BPMList[0];
var time = 60.0 / (8.0 * prev);
foreach (var bpm in BPMList.Skip(1))
{
if (bpm != prev)
{
bpmChange.Add(time);
realBpmList.Add(bpm);
}
prev = bpm;
time += 60.0 / (8.0 * bpm);
}
bpmChange.Add(double.MaxValue);
return (bpmChange, realBpmList);
}
}
}
75
0 0
0 1
6 0
8 1
12 2
16 3
17 2
18 3
19 2
20 3
22 0
24 3
26 0
30 0
32 1
36 3
38 1
40 0
43 1
43 3
46 1
46 3
48 3
50 1
54 0
56 1
64 0
64 3
68 2
70 1
72 0
74 1
76 2
80 3
81 0
82 3
83 0
84 3
86 0
88 1
90 2
92 2
92 3
96 3
99 2
102 3
105 1
108 1
108 3
112 0
115 2
118 0
121 1
124 0
124 1
128 0
128 3
144 2
152 3
160 1
164 0
168 1
176 2
192 0
200 1
208 2
212 3
216 2
224 0
224 1
232 1
232 2
240 0
240 1
256 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment