From 8c8f1f7a3d61fa7f31ba0902ca3ea15e0fd49e87 Mon Sep 17 00:00:00 2001 From: Jay Robson Date: Fri, 4 Oct 2024 19:01:15 +1000 Subject: [PATCH] add track config menu --- CMakeLists.txt | 7 ++-- src/binary.cpp | 91 +++++++++++++++++++++++----------------- src/header.cpp | 39 ----------------- src/packet.cpp | 13 ++++++ src/packet.hpp | 2 + src/parser.cpp | 13 +++--- src/scheduler.cpp | 21 +++++++--- src/scheduler.hpp | 2 +- src/song_info.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++++ src/song_info.hpp | 24 +++++++++++ src/streamer.cpp | 4 +- src/util.cpp | 0 src/util.hpp | 20 +++++++++ 13 files changed, 246 insertions(+), 94 deletions(-) delete mode 100644 src/header.cpp create mode 100644 src/song_info.cpp create mode 100644 src/song_info.hpp create mode 100644 src/util.cpp create mode 100644 src/util.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a624c9e..49db93a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,14 +12,13 @@ set(SRCS src/scheduler.cpp src/key.cpp src/device.cpp + src/song_info.cpp ) add_subdirectory(midifile) add_executable(parser src/parser.cpp ${SRCS}) add_executable(streamer src/streamer.cpp ${SRCS}) -add_executable(header src/header.cpp ${SRCS}) -target_link_libraries(parser PRIVATE midifile PUBLIC stdc++) -target_link_libraries(streamer PRIVATE midifile PUBLIC stdc++) -target_link_libraries(header PRIVATE midifile PUBLIC stdc++) +target_link_libraries(parser PRIVATE midifile PUBLIC stdc++ ncurses) +target_link_libraries(streamer PRIVATE midifile PUBLIC stdc++ ncurses) diff --git a/src/binary.cpp b/src/binary.cpp index 4ca18bf..51d1c28 100644 --- a/src/binary.cpp +++ b/src/binary.cpp @@ -3,56 +3,62 @@ #include "packet.hpp" #include "scheduler.hpp" #include "../midifile/include/MidiFile.h" +#include "song_info.hpp" +#include +#include #include #include #include #include -#include unsigned binary::process(std::ostream& dst, const char* path) { smf::MidiFile midifile; midifile.read(path); midifile.doTimeAnalysis(); - std::vector track_modes(midifile.getNumTracks(), packet::ToneType::tt_sine); + + song_info::Info info {.path=path}; + info.tracks.resize(midifile.getTrackCount()); + midifile.joinTracks(); + auto& track = midifile[0]; + int simultaneous_notes = 0; + int max_simultaneous_notes = 0; + + for(int i = 0; i < track.size(); i++) { + smf::MidiEvent& note = track[i]; + song_info::Track& track = info.tracks[note.track]; + + if(note.isTrackName()) { + track.name = note.getMetaContent(); + } + + if(note.isNote()) { + if(note.isNoteOn()) { + simultaneous_notes += 1; + } else { + simultaneous_notes -= 1; + } + max_simultaneous_notes = std::max(max_simultaneous_notes, simultaneous_notes); + } + } + + info.volume = 256 / std::max(max_simultaneous_notes, 1); + song_info::collect(info); Scheduler scheduler(midifile.getTimeInSeconds(1)); - auto& track = midifile[0]; unsigned ticks_last = 0; unsigned notes_added = 0; for(int i = 0; i < track.size(); i++) { smf::MidiEvent& note = track[i]; + song_info::Track& track = info.tracks[note.track]; - if(note.isTrackName()) { - std::string name = note.getMetaContent(); - - for(int i = 0; i < name.length(); i++) { - char c = name[i]; - if(c >= 'A' && c <= 'Z') { - name[i] = c + 32; - } - } - - if(name.find("saw") != std::string::npos) { - track_modes[note.track] = packet::ToneType::tt_saw; - } - - else if(name.find("square") != std::string::npos || name.find("distortion") != std::string::npos || name.find("overdrive") != std::string::npos) { - track_modes[note.track] = packet::ToneType::tt_square; - } - - else if(name.find("triangle") != std::string::npos) { - track_modes[note.track] = packet::ToneType::tt_triangle; - } - } - - if(!note.isNote()) { + if(!track.enabled || !note.isNote()) { continue; } smf::MidiEvent& note_end = *note.getLinkedEvent(); - scheduler.add_note(note.getKeyNumber(), note.tick, note.isNoteOn(), track_modes[note.track]); + scheduler.add_note(note.getKeyNumber(), note.tick, note.isNoteOn(), track.type, track.gain); notes_added++; } @@ -61,9 +67,23 @@ unsigned binary::process(std::ostream& dst, const char* path) { } bool binary::process_all(std::string& dst, int start, int argc, char **argv) { - if(argc > start) { - std::stringstream ss; - for(int i = start; i < argc; i++) { + if(argc < start + 2) { + return false; + } + + std::stringstream ss; + + if(std::strcmp(argv[start], "-b") == 0) { + if(argc != start + 2) { + return false; + } + std::ifstream file(argv[start + 1], std::ios::binary); + dst = std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); + return true; + } + + if(std::strcmp(argv[start], "-i") == 0) { + for(int i = start+1; i < argc; i++) { binary::process(ss, argv[i]); } binary::finalize(ss); @@ -71,14 +91,7 @@ bool binary::process_all(std::string& dst, int start, int argc, char **argv) { return true; } - else if(argc == start) { - dst = std::string(std::istreambuf_iterator(std::cin), std::istreambuf_iterator()); - return true; - } - - else { - return false; - } + return false; } void binary::finalize(std::ostream &dst) { diff --git a/src/header.cpp b/src/header.cpp deleted file mode 100644 index ef2db9b..0000000 --- a/src/header.cpp +++ /dev/null @@ -1,39 +0,0 @@ - -#include "binary.hpp" -#include -#include -#include - -const int COLS = 32; - -static void generate(std::ostream& dst, const std::string& src, const char* var_name) { - - dst << "#include \n"; - dst << "inline const PROGMEM unsigned char " << var_name << "[] = {\n"; - - for(int i = 0; i < src.length(); i += COLS) { - - dst << '\t'; - - for(int j = i; j < i + COLS && j < src.size(); j++) { - dst << std::dec << (src[j] & 0xff) << ','; - } - - dst << '\n'; - } - - dst << "};\n" << std::dec; -} - -int main(int argc, char** argv) { - - std::string data; - - if(!binary::process_all(data, 2, argc, argv)) { - std::cerr << "Usage: " << argv[0] << " varname [midifile ...]?\n\n"; - return 1; - } - - generate(std::cout, data, argv[1]); -} - diff --git a/src/packet.cpp b/src/packet.cpp index f92d5b5..6ed5468 100644 --- a/src/packet.cpp +++ b/src/packet.cpp @@ -12,6 +12,19 @@ static void output(std::ostream& dst, uint64_t v, int size) { } } +const char* packet::tone_type_get_str(ToneType tt) { + switch(tt) { + case tt_sine: + return "sine"; + case tt_saw: + return "sawtooth"; + case tt_square: + return "square"; + case tt_triangle: + return "triangle"; + } +} + void packet::Clear::finalise(std::ostream& dst) const { dst.put((Type::clear << 5) | (index & bm(5))); } diff --git a/src/packet.hpp b/src/packet.hpp index 178f9e0..49dc614 100644 --- a/src/packet.hpp +++ b/src/packet.hpp @@ -22,6 +22,8 @@ namespace packet { tt_triangle = 3, }; + const char* tone_type_get_str(ToneType tt); + struct Stop { void finalise(std::ostream& dst) const; }; diff --git a/src/parser.cpp b/src/parser.cpp index 20a5648..037181d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2,21 +2,24 @@ #include #include #include +#include #include #include "binary.hpp" int main(int argc, char** argv) { - if(argc == 1) { - std::cerr << "Usage: " << argv[0] << " midifile ...\n\n"; + if(argc < 3) { + std::cerr << "Usage: " << argv[0] << " output [midifile ...]\n\n"; return 1; } - for(int i = 1; i < argc; i++) { - binary::process(std::cout, argv[i]); + std::ofstream output(argv[1], std::ios::binary); + + for(int i = 2; i < argc; i++) { + binary::process(output, argv[i]); } - binary::finalize(std::cout); + binary::finalize(output); return 0; } diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 8da7b73..4d44424 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -11,7 +11,7 @@ Scheduler::Scheduler(double s_per_tick) : us_per_tick(s_per_tick * 1e6) { } -bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::ToneType mode) { +bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::ToneType mode, unsigned gain) { unsigned id = key_id * (mode + 1); @@ -29,9 +29,20 @@ bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::To channels_in_use--; if(ticks_at >= ticks) { - packets.push_back({.type=packet::Type::clear, .clear={.index=channel}}); + packets.push_back({ + .type=packet::Type::clear, + .clear={ + .index=channel, + }, + }); } else { - packets.push_back({.type=packet::Type::clear_ts, .clear_ts={.index=channel, .ticks=ticks - ticks_at}}); + packets.push_back({ + .type=packet::Type::clear_ts, + .clear_ts={ + .index=channel, + .ticks=ticks - ticks_at, + }, + }); ticks_at = ticks; } return true; @@ -61,7 +72,7 @@ bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::To .index=channel_id, .frequency=key::get_freq(key_id), .mode=mode, - .gain=0, + .gain=gain, }, }); } else { @@ -72,7 +83,7 @@ bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::To .ticks=ticks - ticks_at, .frequency=key::get_freq(key_id), .mode=mode, - .gain=0, + .gain=gain, }, }); ticks_at = ticks; diff --git a/src/scheduler.hpp b/src/scheduler.hpp index 49954f0..2f1a877 100644 --- a/src/scheduler.hpp +++ b/src/scheduler.hpp @@ -20,7 +20,7 @@ struct Scheduler { Scheduler(double s_per_tick); - bool add_note(unsigned key, unsigned ticks, bool active, packet::ToneType mode); + bool add_note(unsigned key, unsigned ticks, bool active, packet::ToneType mode, unsigned gain); void finalise(std::ostream& os) const; void display(std::ostream& os) const; diff --git a/src/song_info.cpp b/src/song_info.cpp new file mode 100644 index 0000000..cead09d --- /dev/null +++ b/src/song_info.cpp @@ -0,0 +1,104 @@ + +#include "song_info.hpp" +#include "packet.hpp" +#include "util.hpp" +#include +#include + +struct ScreenGuard { + ScreenGuard() { + initscr(); + keypad(stdscr, true); + nonl(); + cbreak(); + noecho(); + } + + ~ScreenGuard() { + endwin(); + } +}; + +void song_info::collect(Info& info) { + ScreenGuard guard; + bool running = true; + int x = 0; + int y = 0; + + while(running) { + + // render info screen + clear(); + mvaddstr(1, 0, "Configuring "); + addstr(info.path.c_str()); + + mvaddstr(3, 0, "Volume"); + mvaddstr(3, 32, util::sprintf("[ %d / 256 ]", info.volume).c_str()); + + mvaddstr(6, 0, "Track Name"); + mvaddstr(6, 32, "Enable"); + mvaddstr(6, 48, "Gain"); + mvaddstr(6, 64, "Instrument"); + + for(unsigned i = 0; i < info.tracks.size(); i++) { + Track& t = info.tracks[i]; + mvaddstr(8+i, 0, t.name.c_str()); + mvaddstr(8+i, 32, util::sprintf("[ %s ]", t.enabled ? "*" : " ").c_str()); + if(t.enabled) { + mvaddstr(8+i, 48, util::sprintf("[ %d% ]", (t.gain + 1) * 100).c_str()); + mvaddstr(8+i, 64, util::sprintf("[ %s ]", packet::tone_type_get_str(t.type)).c_str()); + } + } + + if(y == 0) { + move(3, 32); + } else { + move(8+y-1, 34+x*16); + } + + int c = getch(); + int dir = 1; + + switch(c) { + case KEY_UP: + y = std::max(0, y - 1); + break; + case KEY_DOWN: + y = std::min((int)info.tracks.size(), y + 1); + break; + case KEY_LEFT: + x = ((x - 1) % 3 + 3) % 3; + break; + case KEY_RIGHT: + x = (x + 1) % 3; + break; + case 27: + running = false; + break; + case 'z': case 'Z': + dir = -1; + case 'x': case 'X': case 13: case ' ': case KEY_ENTER: { + if(y == 0) { + info.volume += dir; + info.volume &= 255; + break; + } + Track& t = info.tracks[y - 1]; + switch(x) { + case 0: + t.enabled ^= 1; + break; + case 1: + t.gain += dir; + t.gain &= 3; + break; + case 2: + t.type = (packet::ToneType)((t.type + dir) & 3); + break; + } + break; + } + } + } +} + diff --git a/src/song_info.hpp b/src/song_info.hpp new file mode 100644 index 0000000..d31ed86 --- /dev/null +++ b/src/song_info.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include +#include +#include "packet.hpp" + +namespace song_info { + + struct Track { + std::string name = ""; + packet::ToneType type = packet::ToneType::tt_sine; + unsigned gain = 0; + bool enabled = 1; + }; + struct Info { + std::vector tracks; + unsigned volume; + std::string path; + }; + + void collect(Info& info); +}; + diff --git a/src/streamer.cpp b/src/streamer.cpp index 1993d3d..e1fb44a 100644 --- a/src/streamer.cpp +++ b/src/streamer.cpp @@ -77,7 +77,9 @@ int main(int argc, char** argv) { if(!binary::process_all(data, 3, argc, argv)) { error_help: - std::cerr << "Usage: " << argv[0] << " -[s|f] device [midifile ...]?\n\n"; + std::cerr << "Usage: " << argv[0] << " -[s|f] device ...\n"; + std::cerr << " -i [midifile ...]\n"; + std::cerr << " -b binary\n\n"; return 1; } diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..afc9544 --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include +#include + +namespace util { + + template + constexpr std::string sprintf(const char* fmt, Types... args) { + std::size_t len = std::snprintf(nullptr, 0, fmt, args...); + std::string str; + str.resize(len + 1); + len = std::snprintf(str.data(), len + 1, fmt, args...); + str[len] = '\0'; + return str; + } +}; +