summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--MIDIPlayer.cpp55
-rw-r--r--MIDIPlayer.h13
-rw-r--r--Makefile2
-rw-r--r--html/index.html140
-rw-r--r--midiplay.cpp138
5 files changed, 275 insertions, 73 deletions
diff --git a/MIDIPlayer.cpp b/MIDIPlayer.cpp
index 41f852e..c7945d6 100644
--- a/MIDIPlayer.cpp
+++ b/MIDIPlayer.cpp
@@ -1,27 +1,76 @@
#include "MIDIPlayer.h"
-MIDIPlayer::MIDIPlayer()
+#include <signal.h>
+
+#include <iostream>
+
+namespace bp = boost::process;
+namespace fs = std::filesystem;
+
+MIDIPlayer::MIDIPlayer(const std::filesystem::path& path):
+ m_child{},
+ m_dir{path},
+ m_file{}
{
-}
+ std::vector<std::string> list = get_filelist();
+ if (list.size() > 0) {
+ m_file = list[0];
+ }
+}
void MIDIPlayer::start()
{
- "aplaymidi -p24 locked_out_of_heaven.midi"
+ if (m_child.valid() && m_child.running()) {
+ stop();
+ } else {
+ m_child = bp::child("aplaymidi -p24 locked_out_of_heaven.midi");//, bp::std_out > bp::null);
+ }
}
void MIDIPlayer::stop()
{
+ // note:: m_child.terminate() would kill via SIGKILL, preventing note offs
+
+ if (m_child.valid()) {
+ int result = kill(m_child.native_handle(), SIGTERM);
+ if (result < 0) {
+ std::cerr << "Error in MIDIPlayer::stop(): kill() unsuccessful\n";
+ }
+ }
}
bool MIDIPlayer::is_playing()
{
+ if (!m_child.valid()) {
+ return false;
+ }
+ return m_child.running();
}
void MIDIPlayer::set_file(const std::string& filename)
{
+ m_file = filename;
+}
+
+std::string MIDIPlayer::get_file()
+{
+ return m_file;
}
std::vector<std::string> MIDIPlayer::get_filelist()
{
+ std::vector<std::string> result;
+ for (auto const& dir_entry: fs::directory_iterator{m_dir}) {
+ fs::path entry{dir_entry.path()};
+ fs::path extension = entry.extension();
+ if (extension == ".midi" || extension == ".mid") {
+ result.push_back(entry.filename());
+ }
+ if (result.size() == 99) {
+ break;
+ }
+ }
+ return result;
}
+
diff --git a/MIDIPlayer.h b/MIDIPlayer.h
index e859bed..e104321 100644
--- a/MIDIPlayer.h
+++ b/MIDIPlayer.h
@@ -3,10 +3,14 @@
#include <string>
#include <vector>
+#include <boost/process.hpp>
+
+#include <filesystem>
+
class MIDIPlayer
{
public:
- MIDIPlayer();
+ MIDIPlayer(const std::filesystem::path& path = ".");
void start();
@@ -16,6 +20,13 @@ public:
void set_file(const std::string& filename);
+ std::string get_file();
+
std::vector<std::string> get_filelist();
+
+private:
+ boost::process::child m_child;
+ std::filesystem::path m_dir;
+ std::string m_file;
};
diff --git a/Makefile b/Makefile
index 7ceb738..7cb4c9d 100644
--- a/Makefile
+++ b/Makefile
@@ -25,4 +25,4 @@ $(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^ $(CXXLIBS)
clean:
- -rm -rf $(TARGET)
+ -rm -rf $(OBJS) $(TARGET)
diff --git a/html/index.html b/html/index.html
index a27b654..ac85aa1 100644
--- a/html/index.html
+++ b/html/index.html
@@ -9,42 +9,108 @@
height: 150px;
font-size: 20px;
}
+
+.selected{
+ color: #FF8080;
+}
+
+.normal{
+ color: #000000;
+}
</style>
<script type="text/javascript">
- function playbutton_clicked()
- {
- var element = document.getElementById("playbutton");
- if (element.innerHTML == "Play") {
- element.innerHTML = "Stop";
- } else {
- element.innerHTML = "Play";
- }
-
- var xhr = new XMLHttpRequest();
-
- xhr.onreadystatechange = function() {
- if (this.readyState != 4) {
- return;
- }
- if (this.status != 200) {
- document.getElementById("status").innerHTML = "HTTP error";
- } else {
- var xml = xhr.responseXML;
- var value = xml.getElementsByTagName("value1")[0].childNodes[0].nodeValue;
- document.getElementById("songlist").innerHTML = value;
- }
- }
-
- xhr.open("POST", "midiplay.fcgi" + "?command=list", true);
- xhr.setRequestHeader("Content-type", "text/xml");
- xhr.send("<data><value1>3</value1></data>");
- }
-
- function startup() {
- document.getElementById("playbutton").onclick = playbutton_clicked;
-
- document.getElementById("songlist").innerHTML = "01 Locked Out Of Heaven";
- }
+ function get_filelist(){
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4) {
+ return;
+ }
+ if (this.status != 200) {
+ document.getElementById("status").innerHTML = "HTTP error";
+ } else {
+ var xml = xhr.responseXML;
+ var selected_file = xml.getElementsByTagName("selected")[0].childNodes[0].nodeValue;
+ var list = xml.getElementsByTagName("list")[0].getElementsByTagName("filename");
+ var n = list.length;
+ var value = "";
+ for (var i = 0; i < n; ++i) {
+ var filename = list[i].childNodes[0].nodeValue;
+ value += "<span class=\"" + (filename == selected_file ? "selected" : "normal") + "\">" + filename + "</span><br/>"
+ }
+
+ document.getElementById("songlist").innerHTML = value;
+ }
+ }
+
+ xhr.open("POST", "midiplay.fcgi" + "?command=getlist", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send("");
+ }
+
+ function playbutton_clicked()
+ {
+ var action = "start";
+ var element = document.getElementById("playbutton");
+ if (element.innerHTML == "Play") {
+ element.innerHTML = "Stop";
+ } else {
+ element.innerHTML = "Play";
+ action = "stop";
+ }
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4) {
+ return;
+ }
+ if (this.status != 200) {
+ document.getElementById("status").innerHTML = "HTTP error";
+ } else {
+ var xml = xhr.responseXML;
+ var message = xml.getElementsByTagName("message")[0].childNodes[0].nodeValue;
+
+ document.getElementById("status").innerHTML = message;
+ }
+ }
+
+ xhr.open("POST", "midiplay.fcgi" + "?command=" + action, true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send("");
+ }
+
+ function songlist_clicked()
+ {
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4) {
+ return;
+ }
+ if (this.status != 200) {
+ document.getElementById("status").innerHTML = "HTTP error";
+ } else {
+ var xml = xhr.responseXML;
+ var message = xml.getElementsByTagName("message")[0].childNodes[0].nodeValue;
+
+ document.getElementById("status").innerHTML = message;
+
+ get_filelist(); // trigger reload
+ }
+ }
+
+ xhr.open("POST", "midiplay.fcgi" + "?command=setfile", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send("<data><value>magic.midi</value></data>");
+ }
+
+ function startup() {
+ document.getElementById("playbutton").onclick = playbutton_clicked;
+ document.getElementById("songlist").onclick = songlist_clicked;
+
+ get_filelist();
+ }
</script>
</head>
@@ -54,10 +120,16 @@ MIDIPLAY
<br/>
<br/>
<div id="songlist">
+(Loading Songlist...)
</div>
<br/>
<br/>
<button id="playbutton" class="button">Play</button>
+<br>
+<div>
+ Status: <span id="status">ok</span>
+</div>
+
</body>
</html>
diff --git a/midiplay.cpp b/midiplay.cpp
index d5c0ef0..34879ee 100644
--- a/midiplay.cpp
+++ b/midiplay.cpp
@@ -1,63 +1,133 @@
#include "MIDIPlayer.h"
+#include <stdexcept>
#include <string>
#include <fcgiapp.h>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
#include <fmt/format.h>
-std::string getPostData(FCGX_Request& request)
+namespace pt = boost::property_tree;
+
+using namespace std::string_literals;
+
+namespace {
+
+class PostData
{
- std::string result;
- std::string contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
- int contentLength = std::stoul(contentLengthString);
+public:
+ PostData(FCGX_Request& request) {
+ std::string result;
+ std::string contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
+ int contentLength = std::stoul(contentLengthString);
- if (contentLength < 1) {
- return "Bad content length";
- } else {
result.resize(contentLength);
unsigned int status = FCGX_GetStr(result.data(), result.size(), request.in);
if (status != result.size()) {
- return fmt::format("Read error: {}/{}", status, result.size());
+ throw std::runtime_error(fmt::format("Read error: {}/{}", status, result.size()));
}
- return result;
+ m_data = result;
+ }
+
+ std::string getData()
+ {
+ return m_data;
+ }
+
+ // path: xml path, e.g. data.value
+ std::string getXMLElement(const std::string& path)
+ {
+ pt::ptree tree{};
+ std::istringstream iss{m_data};
+ pt::read_xml(iss, tree, pt::xml_parser::trim_whitespace);
+
+ return tree.get<std::string>(path);
+ }
+
+private:
+ std::string m_data;
+};
+
+std::string getCommand(FCGX_Request& request)
+{
+ std::string query = FCGX_GetParam("QUERY_STRING", request.envp);
+ size_t pos = query.find("command=");
+ if (pos != query.npos) {
+ return query.substr(pos + 8);
+ } else {
+ return {};
}
}
-int main(int argc, char* argv[]) {
+std::string to_xml(const std::vector<std::string>& filelist, const std::string& selected)
+{
+ std::string result{"<data><status>ok</status><list>"};
+
+ for (const auto& i: filelist) {
+ result += "<filename>" + i + "</filename>";
+ }
+
+ result += "</list>";
+ result += "<selected>" + selected + "</selected>";
+ return result + "</data>";
+}
- int result = FCGX_Init();
- if (result != 0) {
- return 1; // error on init
- }
+} // namespace
- FCGX_Request request;
+int main(int argc, char* argv[]) {
+ MIDIPlayer player;
- if (FCGX_InitRequest(&request, 0, 0) != 0) {
- return 1; // error on init
- }
+ std::string ok_data{"<data><status>ok</status><message>OK</message></data>"};
+ std::string error_data{"<data><status>error</status><message>General Error</message></data>"};
- while (FCGX_Accept_r(&request) == 0) {
- std::string query = FCGX_GetParam("QUERY_STRING", request.envp);
-
- std::string method = FCGX_GetParam("REQUEST_METHOD", request.envp);
+ int result = FCGX_Init();
+ if (result != 0) {
+ return 1; // error on init
+ }
- if (method == "POST") {
- FCGX_PutS("Content-Type: text/xml\r\n\r\n", request.out);
+ FCGX_Request request;
- std::string data = getPostData(request);
- if (data == "<data><command>3</command></data>") {
- FCGX_PutS("<data><value1>4</value1></data>", request.out);
- }
- } else {
- FCGX_PutS("Content-Type: text/text\r\n\r\n", request.out);
- FCGX_PutS("Bad request method: POST expected", request.out);
- }
+ if (FCGX_InitRequest(&request, 0, 0) != 0) {
+ return 1; // error on init
+ }
- }
+ while (FCGX_Accept_r(&request) == 0) {
+ std::string method = FCGX_GetParam("REQUEST_METHOD", request.envp);
+
+ try {
+ if (method == "POST") {
+ FCGX_PutS("Content-Type: text/xml\r\n\r\n", request.out);
+
+ PostData data{request};
+ std::string command {getCommand(request)};
+ if (command == "start") {
+ player.start();
+ FCGX_PutS(ok_data.c_str(), request.out);
+ } else if (command == "stop") {
+ player.stop();
+ FCGX_PutS(ok_data.c_str(), request.out);
+ } else if (command == "getlist") {
+ FCGX_PutS(to_xml(player.get_filelist(), player.get_file()).c_str(), request.out);
+ } else if (command == "setfile") {
+ std::string filename = data.getXMLElement("data.value");
+ player.set_file(filename);
+ FCGX_PutS(ok_data.c_str(), request.out);
+ } else {
+ FCGX_PutS(error_data.c_str(), request.out);
+ }
+ } else {
+ throw std::runtime_error(fmt::format("Bad request method: POST expected, got {}", method).c_str());
+ }
+ } catch (const std::exception& ex) {
+ FCGX_PutS("Content-Type: text/xml\r\n\r\n", request.out);
+ FCGX_PutS(("<data><status>error</status><message>"s + ex.what() + "</message></data>").c_str(), request.out);
+ }
+ }
- return 0;
+ return 0;
}