Skip to content

Instantly share code, notes, and snippets.

@sfpgmr
Created March 23, 2023 22:50
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 sfpgmr/b00433b4e0f04fc4ad5284b8414e8472 to your computer and use it in GitHub Desktop.
Save sfpgmr/b00433b4e0f04fc4ad5284b8414e8472 to your computer and use it in GitHub Desktop.
chatgptにおしえてもらったimguiを使用したピアノロールエディタのコード(動きません)
#include "mainWindow.h"
#include <regex>
//using namespace ImGui;
using namespace sf;
/*
#include <imgui.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <vector>
struct NoteData {
int pitch;
float start;
float length;
};
class PianoRollEditor {
public:
PianoRollEditor() : gridDivision(16), gridSubdivision(4), noteBeingEdited(-1) {}
void Render() {
ImGui::Begin("Piano Roll Editor");
DrawGrid();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
// 新しいノートを追加する
ImVec2 mousePos = ImGui::GetMousePos();
int pitch = (int)(127 - (mousePos.y - ImGui::GetCursorScreenPos().y) / gridYSize);
float start = (mousePos.x - ImGui::GetCursorScreenPos().x) / gridXSize;
noteBeingEdited = AddNote(pitch, start, 0.0f);
}
if (noteBeingEdited >= 0) {
// ノートを編集中の場合
bool noteDragged = ImGui::IsMouseDragging(ImGuiMouseButton_Left);
bool noteReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
if (noteDragged) {
// ノートをドラッグ中の場合
ImVec2 mousePos = ImGui::GetMousePos();
float start = (mousePos.x - ImGui::GetCursorScreenPos().x) / gridXSize;
float length = std::max < float>(start - notes[noteBeingEdited].start, 0.0f);
notes[noteBeingEdited].length = length;
}
else if (noteReleased) {
// ノートがリリースされた場合
ImVec2 mousePos = ImGui::GetMousePos();
float start = (mousePos.x - ImGui::GetCursorScreenPos().x) / gridXSize;
float length = std::max<float>(start - notes[noteBeingEdited].start, 0.0f);
notes[noteBeingEdited].length = length;
noteBeingEdited = -1;
}
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) {
// Deleteキーが押された場合
notes.erase(notes.begin() + noteBeingEdited);
noteBeingEdited = -1;
}
}
for (int i = 0; i < notes.size(); i++) {
// ノートを描画する
ImVec2 notePos = ImVec2(notes[i].start * gridXSize, (127 - notes[i].pitch) * gridYSize + ImGui::GetCursorScreenPos().y);
ImVec2 noteSize = ImVec2(notes[i].length * gridXSize, gridYSize);
ImU32 noteColor = (i == noteBeingEdited) ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 255, 255);
ImGui::GetWindowDrawList()->AddRectFilled(notePos, ImVec2(notePos.x + noteSize.x, notePos.y + noteSize.y), noteColor);
}
ImGui::End();
// MIDIファイルをエクスポートする
if (ImGui::Button("Export MIDI")) {
ExportMIDI("output.mid");
}
}
private:
int gridDivision;
int gridSubdivision;
std::vector<NoteData> notes;
int noteBeingEdited;
float gridXSize;
float gridYSize;
void DrawGrid() {
ImDrawList* drawList = ImGui::GetWindowDrawList();
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
ImVec2 windowPos = ImGui::GetWindowPos();
ImVec2 windowSize = ImGui::GetWindowSize();
float totalWidth = windowSize.x - 16.0f;
float totalHeight = windowSize.y - 16.0f;
// グリッドの大きさを計算する
gridXSize = totalWidth / (gridDivision * gridSubdivision);
gridYSize = totalHeight / 128.0f;
// グリッドを描画する
drawList->PushClipRect(ImVec2(windowPos.x + 8, windowPos.y + 8), ImVec2(windowPos.x + 8 + totalWidth, windowPos.y + 8 + totalHeight));
for (int i = 0; i <= gridDivision * gridSubdivision; i++) {
float x = i * gridXSize;
drawList->AddLine(ImVec2(cursorPos.x + x, cursorPos.y), ImVec2(cursorPos.x + x, cursorPos.y + totalHeight), IM_COL32(255, 255, 255, 255));
}
for (int i = 0; i <= 127; i++) {
float y = i * gridYSize;
drawList->AddLine(ImVec2(cursorPos.x, cursorPos.y + y), ImVec2(cursorPos.x + totalWidth, cursorPos.y + y), IM_COL32(255, 255, 255, 255));
}
drawList->PopClipRect();
}
int AddNote(int pitch, float start, float length) {
notes.push_back({ pitch, start, length });
return notes.size() - 1;
}
void ExportMIDI(const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
if (!file.is_open()) {
std::cout << "Failed to open file for writing: " << filename << std::endl;
return;
}
// MIDIファイルヘッダーを書き込む
file.write("MThd", 4);
uint32_t headerLength = 6;
file.write((const char*)&headerLength, 4);
uint16_t format = 1;
file.write((const char*)&format, 2);
uint16_t numTracks = 1;
file.write((const char*)&numTracks, 2);
uint16_t timeDivision = 480;
file.write((const char*)&timeDivision, 2);
// MIDIトラックヘッダーを書き込む
file.write("MTrk", 4);
uint32_t trackLength = 0;
file.write((const char*)&trackLength, 4);
// MIDIイベントを書き込む
for (const NoteData& note : notes) {
// NoteData// 開始イベント
uint8_t statusByte = 0x90; // NoteData On
uint8_t pitch = note.pitch;
uint8_t velocity = 100;
uint8_t deltaTimeBytes[4];
uint32_t deltaTime = (uint32_t)(note.start * timeDivision);
EncodeDeltaTime(deltaTimeBytes, deltaTime);
file.write((const char*)deltaTimeBytes, 4);
file.write((const char*)&statusByte, 1);
file.write((const char*)&pitch, 1);
file.write((const char*)&velocity, 1);
// 終了イベント
statusByte = 0x80; // NoteData Off
deltaTime = (uint32_t)((note.start + note.length) * timeDivision) - deltaTime;
EncodeDeltaTime(deltaTimeBytes, deltaTime);
file.write((const char*)deltaTimeBytes, 4);
file.write((const char*)&statusByte, 1);
file.write((const char*)&pitch, 1);
file.write((const char*)&velocity, 1);
}
// MIDIトラックの長さを書き込む
trackLength = (uint32_t)file.tellp() - 8;
file.seekp(8);
file.write((const char*)&trackLength, 4);
std::cout << "MIDI file exported to " << filename << std::endl;
}
void EncodeDeltaTime(uint8_t* bytes, uint32_t deltaTime) {
int i = 3;
while (deltaTime > 0) {
bytes[i] = deltaTime & 0x7F;
deltaTime >>= 7;
if (i != 3) {
bytes[i] |= 0x80;
}
i--;
}
}
};
int main() {
PianoRollEditor editor;
editor.Run();
return 0;
}
*/
/*
// MIDIノートの情報を格納する構造体
struct NoteInfo {
int pitch; // MIDIノート番号
int startTick; // 開始タイミング
int endTick; // 終了タイミング
};
// ピアノロールの縦方向のキー数
const int NUM_KEYS = 88;
// ピアノロールの横方向のティック数
const int NUM_TICKS = 480;
// ピアノロールのキーの高さを返す関数
float GetKeyHeight() {
return ImGui::GetContentRegionAvail().y / NUM_KEYS;
}
// ピアノロールのティックの幅を返す関数
float GetTickWidth() {
return ImGui::GetContentRegionAvail().x / NUM_TICKS;
}
// ピアノロールのUIを描画する関数
void MainWindow::drawPianoroll(int32_t songIndex) {
// サンプルコードのメイン関数
std::vector<NoteInfo> notes = {
{60, 0, 240},
{64, 120, 360},
{67, 240, 480}
};
// ピアノロールの上部にキー番号を表示
for (int key = NUM_KEYS - 1; key >= 0; key--) {
ImGui::Text("%d", key + 1);
ImGui::SameLine();
}
// ピアノロールの左側にティック数を表示
for (int tick = 0; tick < NUM_TICKS; tick += 24) {
ImGui::Text("%d", tick);
ImGui::SameLine();
}
// ピアノロールの本体を描画
for (int key = NUM_KEYS - 1; key >= 0; key--) {
ImGui::BeginGroup();
// キーの背景を描画
ImGui::PushID(getId());
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.9f, 0.9f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.8f, 0.8f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
ImGui::Button("", ImVec2(ImGui::GetContentRegionAvail().x, GetKeyHeight()));
ImGui::PopStyleColor(3);
ImGui::PopID();
// MIDIノートがある場合はノートの範囲を描画
for (const auto& note : notes) {
if (note.pitch == key) {
const float x1 = note.startTick * GetTickWidth();
const float x2 = note.endTick * GetTickWidth();
ImGui::PushID(getId());
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.7f, 0.9f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.6f, 0.6f, 0.8f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.5f, 0.7f, 1.0f));
ImGui::Button("", ImVec2(x2 - x1, GetKeyHeight()));
ImGui::PopStyleColor(3);
ImGui::PopID();
}
}
ImGui::EndGroup();
}
//auto& io = ImGui::GetIO();
//auto& songs{ audioManager.getSongs() };
//SongPtr song{ songs[songIndex] };
//auto& tracks{ song->getTracks() };
//TrackEditInfos& trackEditInfos{ trackEditInfosList[songIndex] };
}
*/
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
struct NoteInfo {
int pitch; // MIDIノートの音高 (0~127)
int start_tick; // MIDIノートの開始ティック
int end_tick; // MIDIノートの終了ティック
// MIDIノートの長さを返す
int length() const {
return end_tick - start_tick;
}
};
class PianoRoll {
public:
// コンストラクタ
PianoRoll() {
// キーボードの音名
keyboard_names_ = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};
}
// デストラクタ
~PianoRoll() {}
// MIDIノートの情報を設定
void set_notes(const std::vector<NoteInfo>& notes) {
notes_ = notes;
}
// MIDIノートの情報を取得
std::vector<NoteInfo> get_notes() const {
return notes_;
}
// ピアノロールのUIを描画
void draw() {
// ピアノロールのウィンドウを作成
ImGui::Begin("Piano Roll");
// メニューバーを表示
draw_menu_bar();
// キーボードの背景を表示
draw_keyboard_background();
// MIDIノートを表示
draw_notes();
// MIDIノートのドラッグ&ドロップを処理
process_drag_drop();
// ピアノロールのスクロールを処理
process_scroll();
ImGui::End();
}
private:
std::vector<NoteInfo> notes_; // MIDIノートの情報
std::vector<std::string> keyboard_names_; // キーボードの音名
int current_note_index_ = -1; // 現在選択されているMIDIノートのインデックス
bool is_dragging_note_ = false; // MIDIノートをドラッグ中かどうか
bool is_resizing_note_ = false; // MIDIノートをリサイズ中かどうか
bool is_cutting_note_ = false;
// カット操作中かどうか
bool is_copying_ = false; // コピー操作中かどうか
bool is_pasting_ = false; // ペースト操作中かどうか
int copy_note_index_ = -1; // コピーされたMIDIノートのインデックス
int paste_start_tick_ = -1; // ペーストする開始ティック
int paste_end_tick_ = -1; // ペーストする終了ティック
bool is_scroll_active_ = false; // スクロール中かどうか
ImVec2 scroll_ = ImVec2(0.0f, 0.0f); // スクロールのオフセット
float scroll_speed_ = 10.0f; // スクロール速度
float note_width_ = 20.0f; // MIDIノートの横幅
float note_height_ = 10.0f; // MIDIノートの縦幅
float note_min_length_ = 5.0f; // MIDIノートの最小長さ
float note_drag_threshold_ = 5.0f; // MIDIノートをドラッグするための閾値
ImVec2 note_resize_direction_ = ImVec2(0.0f, 0.0f); // MIDIノートのリサイズ方向
ImVec2 cut_start_pos_ = ImVec2(0.0f, 0.0f); // カット操作の開始位置
ImVec2 cut_end_pos_ = ImVec2(0.0f, 0.0f); // カット操作の終了位置
// メニューバーを表示
void draw_menu_bar() {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open MIDI")) {
// MIDIファイルを開く処理を記述
}
if (ImGui::MenuItem("Save MIDI")) {
// MIDIファイルを保存する処理を記述
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
}
// キーボードの背景を表示
void draw_keyboard_background() {
ImGuiStyle& style = ImGui::GetStyle();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 size = ImGui::GetContentRegionAvail();
float white_key_width = size.x / 12.0f;
float black_key_width = white_key_width * 0.6f;
float white_key_height = size.y;
float black_key_height = white_key_height * 0.6f;
for (int i = 0; i < 12; i++) {
if (i % 12 == 0 || i % 12 == 2 || i % 12 == 4 || i % 12 == 5 || i % 12 == 7 || i % 12 == 9 || i % 12 == 11) {
// 白鍵盤の描画
draw_list->AddRectFilled(pos, ImVec2(pos.x + white_key_width, pos
// 黒鍵盤の描画
draw_list->AddRectFilled(ImVec2(pos.x + white_key_width - black_key_width / 2.0f, pos.y), ImVec2(pos.x + white_key_width + black_key_width / 2.0f, pos.y + black_key_height), IM_COL32_BLACK);
}
pos.x += white_key_width;
}
}
// MIDIノートを表示
void draw_notes() {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
for (int i = 0; i < notes_.size(); i++) {
MidiNote& note = notes_[i];
if (note.start_tick + note_length(note) < scroll_start_tick_ || note.start_tick > scroll_end_tick_) {
continue;
}
ImVec2 note_pos = pos + ImVec2((note.start_tick - scroll_start_tick_) * note_width_, (12 - note.note_number) * note_height_);
ImVec2 note_size = ImVec2(note_length(note) * note_width_, note_height_);
ImU32 note_color = note_color(note.note_number);
draw_list->AddRectFilled(note_pos, note_pos + note_size, note_color, 2.0f);
if (ImGui::IsMouseHoveringRect(note_pos, note_pos + note_size)) {
// MIDIノートをドラッグ中の場合
if (ImGui::IsMouseDragging(0) && !is_copying_ && !is_pasting_ && !is_resize_active_) {
if (note_resize_direction_.x != 0.0f) {
// MIDIノートをリサイズする
int new_length = (int)((ImGui::GetMousePos().x - note_pos.x) / note_width_ + 0.5f);
if (new_length < note_min_length_) {
new_length = note_min_length_;
}
note.length = new_length;
}
else {
// MIDIノートをドラッグする
int delta_tick = (int)((ImGui::GetMousePos().x - prev_mouse_pos_.x) / note_width_ + 0.5f);
note.start_tick += delta_tick;
if (note.start_tick < 0) {
note.start_tick = 0;
}
}
}
else {
// MIDIノートのリサイズ方向を決定
ImVec2 mouse_pos = ImGui::GetMousePos();
note_resize_direction_ = ImVec2(mouse_pos.x >= note_pos.x + note_size.x - note_drag_threshold_ ? 1.0f : mouse_pos.x <= note_pos.x + note_drag_threshold_ ? -1.0f : 0.0f, mouse_pos.y >= note_pos.y + note_size.y - note_drag_threshold_ ? 1.0f : mouse_pos.y <= note_pos.y + note_drag_threshold_ ? -1.0f : 0.0f);
if (note_resize_direction_.x != 0.0f || note_resize_direction_.y != 0.0f) {
is_resize_active_ = true;
}
}
}
else if (ImGui::IsMouseClicked(0) && !is_copying_ && !is_pasting_ && !is_resize_active_) {
// MIDIノートを選択する
if (ImGui::IsMouseHoveringRect(note_pos, note_pos + note_size)) {
selected_note_index_
= i;
prev_mouse_pos_ = ImGui::GetMousePos();
note_resize_direction_ = ImVec2(0.0f, 0.0f);
is_resize_active_ = false;
}
}
}
// MIDIノートをドラッグ中の場合
if (selected_note_index_ >= 0 && ImGui::IsMouseDragging(0) && !is_copying_ && !is_pasting_ && !is_resize_active_) {
MidiNote& note = notes_[selected_note_index_];
int delta_tick = (int)((ImGui::GetMousePos().x - prev_mouse_pos_.x) / note_width_ + 0.5f);
note.start_tick += delta_tick;
if (note.start_tick < 0) {
note.start_tick = 0;
}
prev_mouse_pos_ = ImGui::GetMousePos();
}
// MIDIノートのリサイズを終了する
if (ImGui::IsMouseReleased(0) && is_resize_active_) {
note_resize_direction_ = ImVec2(0.0f, 0.0f);
is_resize_active_ = false;
}
}
// MIDIノートを追加
void add_note() {
MidiNote note;
note.note_number = 60;
note.velocity = 127;
note.start_tick = (int)(scroll_start_tick_ + (ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x) / note_width_ + 0.5f);
note.length = note_min_length_;
notes_.push_back(note);
selected_note_index_ = (int)notes_.size() - 1;
is_copying_ = false;
is_pasting_ = false;
}
// MIDIノートを削除
void delete_note() {
if (selected_note_index_ >= 0) {
notes_.erase(notes_.begin() + selected_note_index_);
selected_note_index_ = -1;
is_copying_ = false;
is_pasting_ = false;
}
}
// MIDIノートのコピー
void copy_note() {
if (selected_note_index_ >= 0) {
copy_note_ = notes_[selected_note_index_];
is_copying_ = true;
is_pasting_ = false;
}
}
// MIDIノートの貼り付け
void paste_note() {
if (is_copying_) {
MidiNote note = copy_note_;
note.start_tick = (int)(scroll_start_tick_ + (ImGui::GetMousePos().x - ImGui::GetCursorScreenPos().x) / note_width_ + 0.5f);
notes_.push_back(note);
selected_note_index_ = (int)notes_.size() - 1;
is_copying_ = false;
is_pasting_ = true;
}
}
// MIDIノートの移動
void move_note(int delta_tick) {
if (selected_note_index_ >= 0) {
MidiNote& note = notes_[selected_note_index_];
note.start_tick += delta_tick;
if (note.start_tick < 0) {
note.start_tick = 0;
}
}
}
// MIDIノートの長さを取得
int note_length(const MidiNote& note) {
return note.length == 0 ? note_min_length_ : note.length;
}
// MIDIノート
// MIDIノートを描画
void draw_notes() {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
ImVec2 canvas_pos = ImGui::GetCursorScreenPos() + ImVec2(0.0f, header_height_);
ImVec2 canvas_size = ImGui::GetContentRegionAvail();
canvas_size.x -= vertical_scrollbar_width_;
canvas_size.y -= horizontal_scrollbar_height_;
int num_notes = (int)notes_.size();
int num_octaves = 7;
int num_pitches = num_octaves * 12;
int octave_offset = 4;
int pitch_offset = octave_offset * 12 - num_pitches / 2;
int num_grid_lines = (int)(canvas_size.x / note_width_) + 1;
float note_height = canvas_size.y / num_pitches;
float note_start_y = canvas_pos.y + note_height * (num_pitches - pitch_offset);
float note_end_y = canvas_pos.y + note_height * (num_pitches - pitch_offset - num_pitches);
float grid_start_x = canvas_pos.x + note_width_ - fmod(scroll_start_tick_ * note_width_, note_width_);
float grid_start_tick = (int)(scroll_start_tick_ / note_min_length_) * note_min_length_;
for (int i = 0; i < num_notes; i++) {
const MidiNote& note = notes_[i];
if (note.start_tick + note_length(note) >= scroll_start_tick_ && note.start_tick <= scroll_end_tick_) {
float note_x = canvas_pos.x + note.start_tick * note_width_ - scroll_start_tick_ * note_width_;
float note_y = note_start_y - note_height * (note.note_number - pitch_offset + 1) + note_height * note_resize_direction_.y;
ImVec2 note_pos(note_x, note_y);
ImVec2 note_size(note_length(note) * note_width_, note_height);
bool is_selected = selected_note_index_ == i;
bool is_resizable = ImGui::IsMouseHoveringRect(note_pos, note_pos + note_size) && fabsf(note_pos.y + note_size.y - ImGui::GetMousePos().y) < note_resize_threshold_;
if (is_selected) {
draw_list->AddRectFilled(note_pos - ImVec2(2.0f, 2.0f), note_pos + note_size + ImVec2(2.0f, 2.0f), note_color_selected_, 4.0f);
}
else {
draw_list->AddRectFilled(note_pos, note_pos + note_size, note_color_, 4.0f);
}
if (is_resizable) {
draw_list->AddRectFilled(ImVec2(note_pos.x + note_size.x - note_resize_handle_size_ / 2, note_pos.y + note_size.y - note_resize_handle_size_ / 2),
ImVec2(note_pos.x + note_size.x + note_resize_handle_size_ / 2, note_pos.y + note_size.y + note_resize_handle_size_ / 2),
note_color_resizable_);
}
}
}
// グリッドを描画
for (int i = 0; i < num_grid_lines; i++) {
float grid_x = grid_start_x + i * note_width_;
float grid_tick = grid_start_tick + i * note_min_length_;
if (i % 4 == 0) {
draw_list->AddLine(ImVec2(grid_x, canvas_pos.y), ImVec2(grid_x, canvas_pos.y + canvas_size.y), grid_color_strong_);
}
else {
draw_list->AddLine(ImVec2(grid_x, canvas_pos.y), ImVec2(grid_x, canvas_pos.y + canvas_size.y), grid_color_weak_);
}
char label[32];
sprintf(label, "%.0f", grid_tick);
draw_list->AddText(ImVec2(grid_x + 4.0f, canvas_pos.y), text_color_, label);
}
// ノート名を描画
for (int i = 0; i < num_pitches; i++) {
char note_name[4];
int note_number = i + pitch_offset;
int octave = note_number / 12;
int pitch = note_number % 12;
sprintf(note_name, "%c%d", note_names_[pitch], octave);
float note_name_y = note_start_y - note_height * (i + 1) + note_height / 2.0f - text_size_ / 2.0f;
draw_list->AddText(ImVec2(canvas_pos.x, note_name_y), text_color_, note_name);
}
// ラバーバンドを描画
if (drag_mode_ == DragMode::Selecting) {
draw_list->AddRectFilled(drag_start_, drag_end_, rubber_band_color_, 0.0f);
}
}
// MIDIファイルをロード
void load_midi_file(const char* path) {
// MIDIファイルを解析
midifile_.read(path);
// トラック数をカウント
int num_tracks = 0;
for (int i = 0; i < midifile_.size(); i++) {
if (!midifile_[i].empty()) {
num_tracks++;
}
}
if (num_tracks == 0) {
return;
}
// ノートリストを初期化
notes_.clear();
// トラックを解析
for (int i = 0; i < midifile_.size(); i++) {
if (midifile_[i].empty()) {
continue;
}
for (int j = 0; j < midifile_[i].size(); j++) {
MidiEvent& event = midifile_[i][j];
if (event.isNoteOn()) {
MidiNote note;
note.track = i;
note.channel = event.getChannel();
note.start_tick = event.tick;
note.note_number = event.getNoteNumber();
note.velocity = event.getVelocity();
for (int k = j + 1; k < midifile_[i].size(); k++) {
MidiEvent& event2 = midifile_[i][k];
if (event2.isNoteOff() && event2.getChannel() == note.channel && event2.getNoteNumber() == note.note_number) {
note.end_tick = event2.tick;
break;
}
}
if (note.end_tick == -1) {
note.end_tick = midifile_.getEndTick();
}
notes_.push_back(note);
}
}
}
// ノートリストをソート
std::sort(notes_.begin(), notes_.end(), [](const MidiNote& a, const MidiNote& b) { return a.start_tick < b.start_tick; });
// メタ情報を取得
tempo_map_.clear();
time_sig_map_.clear();
for (int i = 0; i < midifile_[0].size(); i++) {
MidiEvent& event = midifile_[0][i];
if (event.isTempo()) {
float tempo = 60000000.0f / event.getTempoBPM();
tempo_map_[event.tick] = tempo;
}
else if (event.isTimeSignature()) {
TimeSignature time_sig;
time_sig.numerator = event.getTimeSignatureNumerator();
time_sig.denominator = pow(2, event.getTimeSignatureDenominator());
time_sig_map_[event.tick] = time_sig;
}
}
// キーボードをリセット
pitch_offset_ = 0;
// ビューポートをリセット
view_offset_ = 0.0f;
view_scale_ = 1.0f;
}
// MIDIファイルを保存
void save_midi_file(const char* path) {
// MIDIファイルを生成
MidiFile midi_file;
midi_file.setTicksPerQuarter(midifile_.getTicksPerQuarter());
// トラックを生成
std::vector<int> note_index(notes_.size());
std::iota(note_index.begin(), note_index.end(), 0);
std::sort(note_index.begin(), note_index.end(), [&](int a, int b) { return notes_[a].start_tick < notes_[b].start_tick; });
std::vector<int> track_index(note_index.size());
std::iota(track_index.begin(), track_index.end(), 0);
std::sort(track_index.begin(), track_index.end(), [&](int a, int b) { return notes_[note_index[a]].track < notes_[note_index[b]].track; });
std::vector<std::vector<MidiEvent>> tracks;
for (int i = 0; i < num_tracks_; i++) {
tracks.push_back(std::vector<MidiEvent>());
}
for (int i = 0; i < note_index.size(); i++) {
int note_i = note_index[i];
int track_i = track_index[i];
MidiNote& note = notes_[note_i];
int prev_track_i = (i == 0) ? -1 : track_index[i - 1];
if (note.track != prev_track_i) {
// トラックを変更
tracks[note.track].push_back(MidiEvent::makeTrackNameEvent(track_names_[note.track].c_str()));
tracks[note.track].push_back(MidiEvent::makeProgramChangeEvent(note.channel, program_nums_[note.track]));
}
// ノートオンイベントを追加
MidiEvent note_on_event = MidiEvent::makeNoteOnEvent(note.channel, note.note_number, note.velocity, note.start_tick);
tracks[note.track].push_back(note_on_event);
// ノートオフイベントを追加
MidiEvent note_off_event = MidiEvent::makeNoteOffEvent(note.channel, note.note_number, 0, note.end_tick);
tracks[note.track].push_back(note_off_event);
}
// トラックを追加
for (int i = 0; i < num_tracks_; i++) {
if (!tracks[i].empty()) {
midi_file.addTrack(tracks[i]);
}
}
// MIDIファイルを書き出し
midi_file.write(path);
}
private:
// MIDIノート
struct MidiNote {
int track; // トラック番号
int channel; // MIDIチャンネル
int note_number; // MIDIノートナンバー
int velocity; // MIDIベロシティ
int start_tick; // 開始ティック
int end_tick; // 終了ティック
ImColor color; // 色
};
/ 時間情報
struct TimeInfo {
int tick; // ティック
int measure; // 小節数
int beat; // 拍数
int tick_in_beat; // 拍内のティック数
int tick_in_measure;// 小節内のティック数
};
// 拍子記号
struct TimeSignature {
int numerator; // 分子
int denominator; // 分母
};
// キーボード
struct Keyboard {
static const int NUM_WHITE_KEYS = 7;
static const int NUM_BLACK_KEYS = 5;
static const int NUM_KEYS = NUM_WHITE_KEYS + NUM_BLACK_KEYS;
int key_offset = 0; // キーのオフセット
int note_number[NUM_KEYS] = { 0 }; // キーのMIDIノートナンバー
// キーの描画
void draw(ImDrawList* draw_list, ImVec2 pos, ImVec2 size, ImColor color_white_key, ImColor color_black_key) {
ImVec2 white_key_size(size.x / NUM_WHITE_KEYS, size.y);
ImVec2 black_key_size(white_key_size.x * 0.6f, white_key_size.y * 0.6f);
ImVec2 key_pos = pos;
for (int i = 0; i < NUM_WHITE_KEYS; i++) {
if (note_number[i] != -1) {
draw_list->AddRectFilled(key_pos, key_pos + white_key_size, color_white_key);
draw_list->AddRect(key_pos, key_pos + white_key_size, ImColor(0, 0, 0), 0.0f, ImDrawCornerFlags_All, 2.0f);
}
key_pos.x += white_key_size.x;
}
key_pos = pos + ImVec2(white_key_size.x * 0.6f, 0.0f);
for (int i = 0; i < NUM_BLACK_KEYS; i++) {
if (note_number[NUM_WHITE_KEYS + i] != -1) {
draw_list->AddRectFilled(key_pos, key_pos + black_key_size, color_black_key);
draw_list->AddRect(key_pos, key_pos + black_key_size, ImColor(0, 0, 0), 0.0f, ImDrawCornerFlags_All, 2.0f);
}
key_pos.x += white_key_size.x;
if (i == 1) {
key_pos.x += white_key_size.x;
}
}
}
// キーのMIDIノートナンバーを設定
void set_note_number(int note_offset) {
key_offset = note_offset;
note_number[0] = note_offset + 0;
note_number[1] = note_offset + 2;
note_number[2] = note_offset + 4;
note_number[3] = note_offset + 5;
note_number[4] = note_offset + 7;
note_number[5] = note_offset + 9;
note_number[6] = note_offset + 11;
note_number[7] = -1;
note_number[8] = note_offset + 1;
note_number[9] = note_offset + 3;
note_number[10] = -1;
note_number[11] = note_offset + 6;
note_number[12] = note_offset + 8;
note_number[13] = note_offset + 10;
note_number[14] = -1;
}
};
// ピアノロールの設定
struct PianoRollSettings {
bool show_time_signature = true; // 拍子記号を表示するかどうか
bool show_grid = true; // グリッドを表示するかどうか
int grid_division = 16; // グリッドの分割数
int snap = 4; // スナップ値
int piano_key_offset = 36; // ピアノロール上のキーのオフセット
int piano_key_width = 12; // ピアノロール上のキーの幅
int min_note_length = 2; // 最小の音符の長さ
int max_note_length = 16; // 最大の音符の長さ
ImColor grid_color = ImColor(255, 255, 255, 32); // グリッドの色
ImColor time_signature_color = ImColor(255, 255, 255, 128); // 拍子記号の色
ImColor active_note_color = ImColor(255, 255, 255, 128); // アクティブなノートの色
ImColor note_color = ImColor(255, 255, 255, 64); // ノートの色
ImColor black_key_color = ImColor(0, 0, 0); // 黒鍵の色
ImColor white_key_color = ImColor(255, 255, 255); // 白鍵の色
};
// ノートの状態
enum class NoteState {
NONE, // 選択されていない
SELECTED, // 選択された
DRAGGING, // ドラッグ中
RESIZING, // リサイズ中
};
// ノートの矩形情報
struct NoteRect {
int track; // トラック番号
int channel; // MIDIチャンネル
int note_number; // MIDIノートナンバー
int start_tick; // 開始ティック
int end_tick; // 終了ティック
int length; // 長さ
NoteState state; // 状態
ImVec2 pos; // 左上の座標
ImVec2 size; // サイズ
};
// MIDIファイルのロード
void LoadMidiFile(const char* path) {
// MIDIファイルを読み込む
std::unique_ptr<MidiFile> midi_file(new MidiFile());
midi_file->read(path);
// MIDIファイルからトラックを生成する
for (int i = 0; i < midi_file->getTrackCount(); i++) {
tracks.emplace_back();
auto& track = tracks.back();
auto& midi_track = midi_file->getTrack(i);
int current_tick = 0;
for (auto& event : midi_track) {
current_tick += event.tick;
if (event.isNoteOn()) {
// ノートオンイベントの場合は新しいノートを生成する
track.emplace_back();
auto& note = track.back();
note.track = tracks.size() - 1;
note.channel = event.getChannel();
note.note_number = event.getKeyNumber();
note.start_tick = current_tick;
note.end_tick = current_tick;
note.length = 0;
note.state = NoteState::NONE;
}
else if (event.isNoteOff()) {
// ノートオフイベントの場合は対応するノートの終了時刻を更新する
for (auto& note : track) {
if (note.channel == event.getChannel() && note.note_number == event.getKeyNumber() && note.end_tick == current_tick) {
note.end_tick = current_tick;
note.length = std::max(note.end_tick - note.start_tick, piano_roll_settings.min_note_length);
break;
}
}
}
}
}
// グリッドの数を計算する
int max_tick = 0;
for (auto& track : tracks) {
for (auto& note : track) {
max_tick = std::max(max_tick, note.end_tick);
}
}
grid_count = (max_tick + piano_roll_settings.grid_division - 1) / piano_roll_settings.grid_division;
}
// MIDIファイルの保存
void SaveMidiFile(const char* path) {
// MIDIファイルを生成する
std::unique_ptr<MidiFile> midi_file(new MidiFile());
midi_file->setTicksPerQuarter(piano_roll_settings.grid_division);
// MIDIトラックを生成する
std::vector<std::unique_ptr<MidiTrack>> midi_tracks;
for (auto& track : tracks) {
midi_tracks.emplace_back(new MidiTrack());
auto& midi_track = *midi_tracks.back();
int current_tick = 0;
for (auto& note : track) {
if (note.length > 0) {
// ノートオンイベントを生成する
midi_track.addNoteOn(note.channel, current_tick, note.note_number, 127);
// ノートオフイベントを生成する
midi_track.addNoteOff(note.channel, current_tick + note.length, note.note_number, 0);
}
current_tick = note.end_tick;
}
// トラックの末尾にエンドイベントを追加する
midi_track.addEndOfTrack(current_tick);
}
// MIDIトラックをMIDIファイルに設定する
for (auto& midi_track : midi_tracks) {
midi_file->addTrack(*midi_track);
}
// MIDIファイルを保存する
midi_file->write(path);
}
// ノートを描画する
void DrawNote(const PianoRollNote& note, ImDrawList* draw_list, const ImVec2& offset, float key_height, float grid_width) {
// ノートの位置とサイズを計算する
float x = offset.x + note.start_tick / (float)piano_roll_settings.grid_division * grid_width;
float y = offset.y + (127 - note.note_number) * key_height;
float width = note.length / (float)piano_roll_settings.grid_division * grid_width;
float height = key_height;
// ノートの色を決定する
ImU32 color = ImColor(piano_roll_settings.note_colors[note.track % piano_roll_settings.note_colors.size()]);
// ノートを描画する
draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + width, y + height), color);
}
// ピアノロールを描画する
void DrawPianoRoll() {
// ピアノロールの領域を計算する
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 size = ImGui::GetContentRegionAvail();
size.x = std::max(size.x, 100.0f);
size.y = std::max(size.y, 100.0f);
ImVec2 end_pos = pos + size;
// ピアノロールのグリッド幅とキーの高さを計算する
float grid_width = size.x / grid_count;
float key_height = size.y / 128;
// ピアノロールの枠を描画する
ImGui::GetWindowDrawList()->AddRect(pos, end_pos, ImGui::GetColorU32(ImGuiCol_Border), 0.0f, ImDrawCornerFlags_All, 1.0f);
// キーを描画する
for (int i = 0; i < 128; i++) {
// キーの範囲を計算する
float key_top = pos.y + (127 - i) * key_height;
float key_bottom = key_top + key_height;
// キーを描画する
ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x, key_top), ImVec2(end_pos.x, key_bottom), piano_roll_settings.key_colors[i % piano_roll_settings.key_colors.size()]);
ImGui::GetWindowDrawList()->AddLine(ImVec2(pos.x, key_top), ImVec2(end_pos.x, key_top), 0xFFFFFFFF);
}
// グリッドを描画する
for (int i = 0; i <= grid_count; i++) {
float x = pos.x + i * grid_width;
ImGui::GetWindowDrawList()->AddLine(ImVec2(x, pos.y), ImVec2(x, end_pos.y), 0xFFFFFFFF);
}
// ノートを描画する
for (auto& track : tracks) {
for (auto& note : track) {
DrawNote(note, ImGui::GetWindowDrawList(), pos, key_height, grid_width);
}
}
// ピアノロールのスクロールを可能にする
if (ImGui::IsWindowHovered() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
scroll_offset.x -= ImGui::GetIO().MouseDelta.x;
scroll_offset.y -= ImGui::GetIO().MouseDelta.y;
}
}
// MIDIファイルを読み込む
void LoadMidiFile(const std::string& path) {
// MIDIファイルを読み込む
std::unique_ptr<midi::MidiFile> midi_file = std::make_unique<midi::MidiFile>();
midi_file->read(path);
// トラックを初期化する
tracks.clear();
for (int i = 0; i < midi_file->getTrackCount(); i++) {
tracks.emplace_back();
}
// MIDIイベントを処理する
for (int i = 0; i < midi_file->getEventCount(); i++) {
auto event = midi_file->getEvent(i);
int track = event->track;
if (event->isNoteOn()) {
int note_number = event->getNoteNumber();
int velocity = event->getVelocity();
int tick = event->tick;
int length = 0;
for (int j = i + 1; j < midi_file->getEventCount(); j++) {
auto next_event = midi_file->getEvent(j);
if (next_event->isNoteOff() && next_event->track == track && next_event->getNoteNumber() == note_number) {
length = next_event->tick - tick;
break;
}
}
tracks[track].push_back({ note_number, velocity, tick, length });
}
}
}
};
int main(int argc, char** argv) {
// ImGuiを初期化する
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// GLFWを初期化する
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Piano Roll", nullptr, nullptr);
// ImGui GLFW3 バインディングを初期化する
ImGui_ImplGlfw_InitForVulkan(window, true);
// ImGui Vulkan バインディングを初期化する
VkInstance instance = CreateVulkanInstance();
VkPhysicalDevice physical_device = PickPhysicalDevice(instance);
VkDevice device = CreateDevice(physical_device);
VkSurfaceKHR surface = CreateSurface(instance, window);
VkQueue graphics_queue = GetGraphicsQueue(device);
VkCommandPool command_pool = CreateCommandPool(device, graphics_queue);
VkRenderPass render_pass = CreateRenderPass(device);
VkPipelineLayout pipeline_layout = CreatePipelineLayout(device);
VkPipeline pipeline = CreatePipeline(device, pipeline_layout, render_pass);
std::vector<VkFramebuffer> framebuffers = CreateFramebuffers(device, render_pass, surface);
VkCommandBuffer command_buffer = BeginSingleTimeCommands(device, command_pool);
// ピアノロールを初期化する
PianoRoll piano_roll;
// メインループ
while (!glfwWindowShouldClose(window)) {
// イベントを処理する
glfwPollEvents();
// ImGui GLFW3 バインディングを更新する
ImGui_ImplGlfw_NewFrame();
// ImGui フレームを開始する
ImGui::NewFrame();
// メニューバーを表示する
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open MIDI File")) {
// MIDIファイルを開くダイアログを表示する
nfdchar_t* path = nullptr;
nfdresult_t result = NFD_OpenDialog("mid", nullptr, &path);
if (result == NFD_OKAY) {
// MIDIファイルを読み込む
piano_roll.LoadMidiFile(path);
free(path);
}
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
// ピアノロールを更新する
piano_roll.Update();
// ImGui フレームを描画する
ImGui::Render();
// レンダリングを開始する
VkSemaphore image_available_semaphore = CreateSemaphore(device);
VkSemaphore render_finished_semaphore = CreateSemaphore(device);
uint32_t image_index = 0;
VkResult result = vkAcquireNextImageKHR(device, GetSwapchain(device), UINT64_MAX, image_available_semaphore, VK_NULL_HANDLE, &image_index);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
RecreateSwapchain(device, physical_device, surface, render_pass, pipeline_layout, pipeline, framebuffers);
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("Failed to acquire swapchain image.");
}
// コマンドバッファをリセットする
vkResetCommandBuffer(command_buffer, 0);
// レンダーパスを開始する
VkClearValue clear_value = { 0.0f, 0.0f, 0.0f, 1.0f };
VkRenderPassBeginInfo render_pass_begin_info = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
nullptr,
render_pass,
framebuffers[image_index],
{
{ 0, 0 },
GetSwapchainExtent(device)
},
1,
&clear_value
};
vkCmdBeginRenderPass(command_buffer, &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
// ピアノロールを描画する
piano_roll.Draw(command_buffer, pipeline_layout, pipeline);
// レンダーパスを終了する
vkCmdEndRenderPass(command_buffer);
// レンダリングを終了する
EndSingleTimeCommands(device, command_pool, graphics_queue, command_buffer);
PresentImage(device, GetSwapchain(device), graphics_queue, image_index, render_finished_semaphore);
// ImGui Vulkan バインディングを更新する
ImGui_ImplVulkan_NewFrame();
// ImGui Vulkan バインディングで描画する
VkCommandBuffer ImGui_command_buffer = ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), command_buffer);
EndSingleTimeCommands(device, command_pool, graphics_queue, ImGui_command_buffer);
// スワップチェインにイメージをプレゼントする
PresentImage(device, GetSwapchain(device), graphics_queue, image_index, render_finished_semaphore);
// フレームを終了する
vkQueueWaitIdle(graphics_queue);
vkFreeSemaphore(device, image_available_semaphore, nullptr);
vkFreeSemaphore(device, render_finished_semaphore,
}
// ImGui Vulkan バインディングを解放する
ImGui_ImplVulkan_Shutdown();
// ImGui バインディングを解放する
ImGui::DestroyContext();
// ピアノロールを解放する
piano_roll.Unload();
// フレームバッファを解放する
for (VkFramebuffer framebuffer : framebuffers) {
vkDestroyFramebuffer(device, framebuffer, nullptr);
}
// パイプラインを解放する
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipeline_layout, nullptr);
vkDestroyRenderPass(device, render_pass, nullptr);
// デプスステンシルバッファを解放する
vkDestroyImageView(device, depth_stencil_view, nullptr);
vkDestroyImage(device, depth_stencil_image, nullptr);
vkFreeMemory(device, depth_stencil_memory, nullptr);
// スワップチェインを解放する
vkDestroySwapchainKHR(device, swapchain, nullptr);
// インデックスバッファを解放する
vkDestroyBuffer(device, index_buffer, nullptr);
vkFreeMemory(device, index_buffer_memory, nullptr);
// 頂点バッファを解放する
vkDestroyBuffer(device, vertex_buffer, nullptr);
vkFreeMemory(device, vertex_buffer_memory, nullptr);
// コマンドプールを解放する
vkDestroyCommandPool(device, command_pool, nullptr);
// グラフィックスパイプライン用のセマフォを解放する
vkDestroySemaphore(device, graphics_semaphore, nullptr);
vkDestroySemaphore(device, present_semaphore, nullptr);
// デバイスを解放する
vkDestroyDevice(device, nullptr);
// インスタンスを解放する
vkDestroyInstance(instance, nullptr);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment