diff options
-rw-r--r-- | MIDIPlayer.cpp | 55 | ||||
-rw-r--r-- | MIDIPlayer.h | 13 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | html/index.html | 140 | ||||
-rw-r--r-- | midiplay.cpp | 138 |
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; }; @@ -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; } |