add track config menu

This commit is contained in:
Jay Robson 2024-10-04 19:01:15 +10:00
parent f763871106
commit 8c8f1f7a3d
13 changed files with 246 additions and 94 deletions

View File

@ -12,14 +12,13 @@ set(SRCS
src/scheduler.cpp src/scheduler.cpp
src/key.cpp src/key.cpp
src/device.cpp src/device.cpp
src/song_info.cpp
) )
add_subdirectory(midifile) add_subdirectory(midifile)
add_executable(parser src/parser.cpp ${SRCS}) add_executable(parser src/parser.cpp ${SRCS})
add_executable(streamer src/streamer.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(parser PRIVATE midifile PUBLIC stdc++ ncurses)
target_link_libraries(streamer PRIVATE midifile PUBLIC stdc++) target_link_libraries(streamer PRIVATE midifile PUBLIC stdc++ ncurses)
target_link_libraries(header PRIVATE midifile PUBLIC stdc++)

View File

@ -3,56 +3,62 @@
#include "packet.hpp" #include "packet.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "../midifile/include/MidiFile.h" #include "../midifile/include/MidiFile.h"
#include "song_info.hpp"
#include <cstring>
#include <fstream>
#include <iostream> #include <iostream>
#include <ostream> #include <ostream>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector>
unsigned binary::process(std::ostream& dst, const char* path) { unsigned binary::process(std::ostream& dst, const char* path) {
smf::MidiFile midifile; smf::MidiFile midifile;
midifile.read(path); midifile.read(path);
midifile.doTimeAnalysis(); midifile.doTimeAnalysis();
std::vector<packet::ToneType> track_modes(midifile.getNumTracks(), packet::ToneType::tt_sine);
song_info::Info info {.path=path};
info.tracks.resize(midifile.getTrackCount());
midifile.joinTracks(); 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)); Scheduler scheduler(midifile.getTimeInSeconds(1));
auto& track = midifile[0];
unsigned ticks_last = 0; unsigned ticks_last = 0;
unsigned notes_added = 0; unsigned notes_added = 0;
for(int i = 0; i < track.size(); i++) { for(int i = 0; i < track.size(); i++) {
smf::MidiEvent& note = track[i]; smf::MidiEvent& note = track[i];
song_info::Track& track = info.tracks[note.track];
if(note.isTrackName()) { if(!track.enabled || !note.isNote()) {
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()) {
continue; continue;
} }
smf::MidiEvent& note_end = *note.getLinkedEvent(); 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++; 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) { bool binary::process_all(std::string& dst, int start, int argc, char **argv) {
if(argc > start) { if(argc < start + 2) {
std::stringstream ss; return false;
for(int i = start; i < argc; i++) { }
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<char>(file), std::istreambuf_iterator<char>());
return true;
}
if(std::strcmp(argv[start], "-i") == 0) {
for(int i = start+1; i < argc; i++) {
binary::process(ss, argv[i]); binary::process(ss, argv[i]);
} }
binary::finalize(ss); binary::finalize(ss);
@ -71,14 +91,7 @@ bool binary::process_all(std::string& dst, int start, int argc, char **argv) {
return true; return true;
} }
else if(argc == start) { return false;
dst = std::string(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
return true;
}
else {
return false;
}
} }
void binary::finalize(std::ostream &dst) { void binary::finalize(std::ostream &dst) {

View File

@ -1,39 +0,0 @@
#include "binary.hpp"
#include <ios>
#include <iostream>
#include <string>
const int COLS = 32;
static void generate(std::ostream& dst, const std::string& src, const char* var_name) {
dst << "#include <avr/pgmspace.h>\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]);
}

View File

@ -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 { void packet::Clear::finalise(std::ostream& dst) const {
dst.put((Type::clear << 5) | (index & bm(5))); dst.put((Type::clear << 5) | (index & bm(5)));
} }

View File

@ -22,6 +22,8 @@ namespace packet {
tt_triangle = 3, tt_triangle = 3,
}; };
const char* tone_type_get_str(ToneType tt);
struct Stop { struct Stop {
void finalise(std::ostream& dst) const; void finalise(std::ostream& dst) const;
}; };

View File

@ -2,21 +2,24 @@
#include <cmath> #include <cmath>
#include <csignal> #include <csignal>
#include <cstring> #include <cstring>
#include <fstream>
#include <iostream> #include <iostream>
#include "binary.hpp" #include "binary.hpp"
int main(int argc, char** argv) { int main(int argc, char** argv) {
if(argc == 1) { if(argc < 3) {
std::cerr << "Usage: " << argv[0] << " midifile ...\n\n"; std::cerr << "Usage: " << argv[0] << " output [midifile ...]\n\n";
return 1; return 1;
} }
for(int i = 1; i < argc; i++) { std::ofstream output(argv[1], std::ios::binary);
binary::process(std::cout, argv[i]);
for(int i = 2; i < argc; i++) {
binary::process(output, argv[i]);
} }
binary::finalize(std::cout); binary::finalize(output);
return 0; return 0;
} }

View File

@ -11,7 +11,7 @@ Scheduler::Scheduler(double s_per_tick)
: us_per_tick(s_per_tick * 1e6) { : 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); 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--; channels_in_use--;
if(ticks_at >= ticks) { 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 { } 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; ticks_at = ticks;
} }
return true; return true;
@ -61,7 +72,7 @@ bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::To
.index=channel_id, .index=channel_id,
.frequency=key::get_freq(key_id), .frequency=key::get_freq(key_id),
.mode=mode, .mode=mode,
.gain=0, .gain=gain,
}, },
}); });
} else { } else {
@ -72,7 +83,7 @@ bool Scheduler::add_note(unsigned key_id, unsigned ticks, bool state, packet::To
.ticks=ticks - ticks_at, .ticks=ticks - ticks_at,
.frequency=key::get_freq(key_id), .frequency=key::get_freq(key_id),
.mode=mode, .mode=mode,
.gain=0, .gain=gain,
}, },
}); });
ticks_at = ticks; ticks_at = ticks;

View File

@ -20,7 +20,7 @@ struct Scheduler {
Scheduler(double s_per_tick); 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 finalise(std::ostream& os) const;
void display(std::ostream& os) const; void display(std::ostream& os) const;

104
src/song_info.cpp Normal file
View File

@ -0,0 +1,104 @@
#include "song_info.hpp"
#include "packet.hpp"
#include "util.hpp"
#include <cmath>
#include <ncurses.h>
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;
}
}
}
}

24
src/song_info.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <vector>
#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<Track> tracks;
unsigned volume;
std::string path;
};
void collect(Info& info);
};

View File

@ -77,7 +77,9 @@ int main(int argc, char** argv) {
if(!binary::process_all(data, 3, argc, argv)) { if(!binary::process_all(data, 3, argc, argv)) {
error_help: 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; return 1;
} }

0
src/util.cpp Normal file
View File

20
src/util.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <cstddef>
#include <cstdio>
#include <string>
namespace util {
template <typename... Types>
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;
}
};