UNPKG

kshoot-tools

Version:

A collection of tools related to KSH and KSON chart files of K-Shoot Mania

159 lines (158 loc) 8.1 kB
import * as kshoot from 'kshoot'; var NoteColumn; (function (NoteColumn) { NoteColumn[NoteColumn["EMPTY"] = 0] = "EMPTY"; NoteColumn[NoteColumn["CHIP"] = 1] = "CHIP"; NoteColumn[NoteColumn["HOLD"] = 2] = "HOLD"; NoteColumn[NoteColumn["MAX"] = 3] = "MAX"; })(NoteColumn || (NoteColumn = {})); var LaserColumn; (function (LaserColumn) { LaserColumn[LaserColumn["EMPTY"] = 0] = "EMPTY"; LaserColumn[LaserColumn["STATIONARY"] = 1] = "STATIONARY"; LaserColumn[LaserColumn["LEFT"] = 2] = "LEFT"; LaserColumn[LaserColumn["RIGHT"] = 3] = "RIGHT"; LaserColumn[LaserColumn["X_VALUE"] = 4] = "X_VALUE"; LaserColumn[LaserColumn["MAX"] = 5] = "MAX"; })(LaserColumn || (LaserColumn = {})); const NUMPY_HEADER = Buffer.from([0x93, 0x4E, 0x55, 0x4D, 0x50, 0x59, 0x01, 0x00]); const LASER_BASE_IND = NoteColumn.MAX * 6; const COLUMN_SIZE = LASER_BASE_IND + LaserColumn.MAX * 2; export class NumpyChart { offset = 0; // Note: always should be non-negative resolution = 60; timing = new kshoot.Timing(kshoot.kson.schema.BeatInfo.parse({})); data = new Float32Array(); _length = 0; get length() { return this._length; } _note_bookkeep = [0, 0, 0, 0, 0, 0]; setNoteInterval(ind, sub_ind, start_ms, end_ms) { const start_find = (start_ms * this.resolution) / 1000; let start_ind = Math.round(start_find); const end_find = (end_ms * this.resolution) / 1000; let end_ind = Math.round(end_find); if (start_ind < this._note_bookkeep[ind]) { start_ind = this._note_bookkeep[ind]; } if (end_ind <= start_ind) { end_ind = start_ind + 1; } const empty_lane = ind * 3; const fill_lane = empty_lane + sub_ind; for (let i = start_ind; i < end_ind; ++i) { this.data[COLUMN_SIZE * i + fill_lane] = 1; this.data[COLUMN_SIZE * i + empty_lane] = 0; } this._note_bookkeep[ind] = end_ind; } getTimeByPulse(pulse) { return this.offset + this.timing.getTimeByPulse(pulse); } setChart(chart) { const MS_PER_ROW = 1000 / this.resolution; this.timing = chart.getTiming(); const last_note_ms = this.getTimeByPulse(chart.getLastNotePulse()); this._length = Math.floor((last_note_ms * this.resolution) / 1000) + 2; this.data = new Float32Array(this._length * COLUMN_SIZE); this._note_bookkeep = [0, 0, 0, 0, 0, 0, 0, 0]; for (let i = 0; i < this._length; ++i) { for (let j = 0; j < 6; ++j) { this.data[COLUMN_SIZE * i + NoteColumn.MAX * j] = 1.0; } for (let j = 0; j < 2; ++j) { this.data[COLUMN_SIZE * i + LASER_BASE_IND + LaserColumn.MAX * j] = 1.0; } } // Export notes for (const [pulse, notes] of chart.buttonNotes()) { const note_start_ms = this.getTimeByPulse(pulse); for (const note of notes) { if (note.length === 0n) { this.setNoteInterval(note.lane, NoteColumn.CHIP, note_start_ms, note_start_ms); } else { let note_end_ms = this.getTimeByPulse(pulse + note.length); if (note_end_ms < note_start_ms + MS_PER_ROW) note_end_ms = note_start_ms + MS_PER_ROW; this.setNoteInterval(note.lane, NoteColumn.HOLD, note_start_ms, note_end_ms); } } } // Export lasers for (let lane = 0; lane < 2; ++lane) { const column_empty = LASER_BASE_IND + lane * LaserColumn.MAX + LaserColumn.EMPTY; const column_stationary = LASER_BASE_IND + lane * LaserColumn.MAX + LaserColumn.STATIONARY; const column_left = LASER_BASE_IND + lane * LaserColumn.MAX + LaserColumn.LEFT; const column_right = LASER_BASE_IND + lane * LaserColumn.MAX + LaserColumn.RIGHT; const column_x_value = LASER_BASE_IND + lane * LaserColumn.MAX + LaserColumn.X_VALUE; let bookkeep = 0; for (const [section_pulse, section, section_width] of chart.note.laser[lane]) { if (section.size === 0) continue; const section_start_ms = this.getTimeByPulse(section_pulse); const section_start_ind = Math.round((section_start_ms * this.resolution) / 1000); const section_points = [...section]; if (section_points.length === 0) continue; const posToFloat = (pos) => section_width * (pos - 0.5); if (bookkeep < section_start_ind) { bookkeep = section_start_ind; } let prev_inter_column = column_stationary; for (let i = 0; i < section_points.length; ++i) { const curr_point = section_points[i]; const next_point = i + 1 === section_points.length ? null : section_points[i + 1]; const inter_column = next_point ? curr_point[1][1] > next_point[1][0] ? column_left : curr_point[1][1] < next_point[1][0] ? column_right : column_stationary : column_stationary; if (curr_point[1][0] !== curr_point[1][1]) { this.data[COLUMN_SIZE * bookkeep + (curr_point[1][0] > curr_point[1][1] ? column_left : column_right)] = 1; this.data[COLUMN_SIZE * bookkeep + column_empty] = 0; this.data[COLUMN_SIZE * bookkeep + column_x_value] = posToFloat(curr_point[1][0]); this.data[COLUMN_SIZE * (bookkeep + 1) + inter_column] = 1; this.data[COLUMN_SIZE * (bookkeep + 1) + column_empty] = 0; this.data[COLUMN_SIZE * bookkeep + column_x_value] = posToFloat(curr_point[1][1]); bookkeep += 2; } if (!next_point) { if (curr_point[1][0] === curr_point[1][1]) { this.data[COLUMN_SIZE * bookkeep + prev_inter_column] = 1; this.data[COLUMN_SIZE * bookkeep + column_empty] = 1; this.data[COLUMN_SIZE * bookkeep + column_x_value] = posToFloat(curr_point[1][0]); } break; } const next_ms = this.getTimeByPulse(section_pulse + next_point[0]); let next_ind = Math.round((next_ms * this.resolution) / 1000); if (next_ind <= bookkeep) next_ind = bookkeep + 1; const pos_from = posToFloat(curr_point[1][1]); const pos_to = posToFloat(next_point[1][1]); for (let t = bookkeep; t < next_ind; ++t) { const lerp = t / (next_ind - bookkeep); this.data[COLUMN_SIZE * t + inter_column] = 1; this.data[COLUMN_SIZE * t + column_empty] = 0; this.data[COLUMN_SIZE * bookkeep + column_x_value] = (1 - lerp) * pos_from + lerp * pos_to; } bookkeep = next_ind; prev_inter_column = inter_column; } } } } export() { const header_str = `{'descr':'<f4','fortran_order':False,'shape':(${this.length},${COLUMN_SIZE})}`; const header_buffer = Buffer.alloc(NUMPY_HEADER.length + 2 + header_str.length); NUMPY_HEADER.copy(header_buffer); header_buffer.writeUInt16LE(header_str.length, NUMPY_HEADER.length); header_buffer.write(header_str, NUMPY_HEADER.length + 2, 'ascii'); return Buffer.concat([header_buffer, new Uint8Array(this.data.buffer)]); } } export function fromChart(chart) { const seq = new NumpyChart(); seq.setChart(chart); return seq; }