add track config menu
This commit is contained in:
parent
f763871106
commit
8c8f1f7a3d
|
@ -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++)
|
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
for(int i = start; i < argc; i++) {
|
|
||||||
|
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,15 +91,8 @@ bool binary::process_all(std::string& dst, int start, int argc, char **argv) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(argc == start) {
|
|
||||||
dst = std::string(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void binary::finalize(std::ostream &dst) {
|
void binary::finalize(std::ostream &dst) {
|
||||||
packet::Generic(packet::Type::stop).finalise(dst);
|
packet::Generic(packet::Type::stop).finalise(dst);
|
||||||
|
|
|
@ -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]);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
||||||
|
|
|
@ -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,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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue