commit 9b70aa22b614f35671816bace11f59cef71f61a1 Author: Tobias Reisinger <tobias@msrg.cc> Date: Thu Mar 5 21:46:08 2020 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dd5c071 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +locale/*/LC_MESSAGES/rhythm.mo +locale/*/LC_MESSAGES/rhythm.po~ diff --git a/audio.php b/audio.php new file mode 100644 index 0000000..9096c6b --- /dev/null +++ b/audio.php @@ -0,0 +1,16 @@ +<?php + +const OUTPUT_FOLDER = "./tmp/"; + +header("Content-Type: audio/ogg"); + +if(isset($_GET["file_name"]) && is_string($_GET["file_name"])) +{ + system("timidity " . OUTPUT_FOLDER . $_GET["file_name"] . ".midi -Ov -o -"); +} +else +{ + http_response_code(400); + echo("Missing Parameters"); +} +?> diff --git a/generate_locale.sh b/generate_locale.sh new file mode 100755 index 0000000..814aeaf --- /dev/null +++ b/generate_locale.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env sh + +xgettext --add-comments templates/*.php -o locale/rhythm.pot + +msgmerge --update locale/de/LC_MESSAGES/rhythm.po locale/rhythm.pot +msgmerge --update locale/en/LC_MESSAGES/rhythm.po locale/rhythm.pot + +msgfmt locale/de/LC_MESSAGES/rhythm.po --output-file=locale/de/LC_MESSAGES/rhythm.mo +msgfmt locale/en/LC_MESSAGES/rhythm.po --output-file=locale/en/LC_MESSAGES/rhythm.mo diff --git a/index.php b/index.php new file mode 100644 index 0000000..8931bb7 --- /dev/null +++ b/index.php @@ -0,0 +1,111 @@ +<?php + +require("./lib//config.php"); +require("./lib//helpers.php"); +require("./lib//validation.php"); + +session_start(); + +setup_locale(); + +validate_get_parameter(); + +$id = $_GET["id"]; +$tempo = $_GET["tempo"]; +$time = $_GET["time"]; +$bars = intval($_GET["bars"]); + +$dynamic_beat = $_GET["dynamic_beat"]; +$dynamic_rhythm = $_GET["dynamic_rhythm"]; + +$notes_final = []; +$notes_modifiers = []; +$bars_count = 0; +$rhythm_length = 0; + +$time_explode = explode("/", $time, 2); +$rhythm_length_max = MAX_FRACTION * (intval($time_explode[0]) / intval($time_explode[1])); + +$id_hashed = md5($id); +do { + $notes_input = []; + for($i = 0; $i < strlen($id_hashed); $i+=2) + { + $notes_input[] = strval(pow(2, (ord(substr($id_hashed, $i)) % intval(log(MAX_FRACTION, 2))) + 1)); + $notes_modifiers[] = ord(substr($id_hashed, $i + 1)); + } + for($i = 0; $i < count($notes_input); $i++) + { + if($bars_count == $bars) + { + continue; + } + $note_val = intval($notes_input[$i]); + if(($note_val == 0) || ($note_val > MAX_FRACTION) || (!is_valid_note($note_val))) + { + continue; + } + if($rhythm_length + (MAX_FRACTION / $note_val) > $rhythm_length_max) + { + continue; + } + $rhythm_length += MAX_FRACTION / $note_val; + + if($notes_modifiers[$i] & 0b0111) + { + $notes_final[] = PERCUSSION_CLAP . $note_val; + } + else + { + $notes_final[] = PERCUSSION_REST . $note_val; + } + + if($rhythm_length == $rhythm_length_max) + { + $notes_final[] = "|"; + $rhythm_length = 0; + $bars_count++; + } + } + $id_hashed = md5($id_hashed); +} while($bars_count < $bars); + +$notes_final = implode(" ", $notes_final); + +$rest_padding = array_fill(0, $time_explode[0], PERCUSSION_REST . $time_explode[1]); +$rest_padding[0] = $rest_padding[0] . "\\$dynamic_rhythm"; +$rest_padding = implode(" ", $rest_padding); + +$beats_one_bar = array_fill(1, $time_explode[0] - 1, PERCUSSION_BEAT . $time_explode[1]); +array_unshift($beats_one_bar, PERCUSSION_BEAT_ACCENT . $time_explode[1] . "\\$dynamic_beat"); +array_push($beats_one_bar, "|"); +$beats = implode(" ", array_fill(0, $bars_count + 2, implode(" ", $beats_one_bar))); + +$file_content = sprintf( + LILYPOND_FORMAT, + $time, + $tempo, + $notes_final, + $rest_padding, + $beats +); +$file_name = md5($file_content); + +file_put_contents( OUTPUT_FOLDER . $file_name . ".ly", $file_content); +exec(sprintf("lilypond --png -o %1\$s %1\$s$file_name.ly && convert %1\$s$file_name.png -trim %1\$s$file_name.png", OUTPUT_FOLDER)); + +echo(render( + "./templates/index.tpl.php", + $locale, + array( + "id" => $id, + "time" => $time, + "tempo" => $tempo, + "bars" => $bars, + "file_name" => $file_name, + "dynamic_beat" => $dynamic_beat, + "dynamic_rhythm" => $dynamic_rhythm, + ) +)); + +?> diff --git a/lib/config.php b/lib/config.php new file mode 100644 index 0000000..2f86f27 --- /dev/null +++ b/lib/config.php @@ -0,0 +1,53 @@ +<?php + +const MAX_FRACTION = 8; +const PERCUSSION_REST = "r"; +const PERCUSSION_CLAP = "hc"; +const PERCUSSION_BEAT = "ss"; +const PERCUSSION_BEAT_ACCENT = "sn"; +const OUTPUT_FOLDER = "./tmp/"; +const LILYPOND_FORMAT = '\\version "2.20.0" +\\header { + tagline = "" +} + +main_source = \\drummode { + \\numericTimeSignature + \\time %1$s + \\tempo %2$s + %3$s +} +main_padded = \\drummode { + %4$s | \\main_source | %4$s +} +main = \\drummode { + \\main_source +} +beat = \\drummode { + \\numericTimeSignature + \\time %1$s + \\tempo %2$s + %5$s +} +\\score { + \\new DrumStaff << + \\new DrumVoice { \\voiceOne \\main_padded } + \\new DrumVoice { \\voiceTwo \\beat } + >> + \\midi { + \\tempo %2$s + } +} +\\score { + \\new DrumStaff \with { + drumStyleTable = #percussion-style + \override StaffSymbol.line-count = #1 + } << + \\new DrumVoice { \\voiceOne \\main } + >> + \\layout { } +} +\\paper { +}'; + +?> diff --git a/lib/helpers.php b/lib/helpers.php new file mode 100644 index 0000000..7c75877 --- /dev/null +++ b/lib/helpers.php @@ -0,0 +1,37 @@ +<?php + +function remove_whitespace($target) +{ + return str_replace(" ", "", $target); +} + +function render($template, $locale, $param){ + ob_start(); + extract($param, EXTR_SKIP); + include($template); + $ret = ob_get_contents(); + ob_end_clean(); + return $ret; +} + +function setup_locale() +{ + $locale = "de_DE.UTF-8"; + + if (isset($_SESSION["locale"])) + { + $locale = $_SESSION["locale"]; + } + + putenv("LC_ALL=$locale"); + setlocale(LC_ALL, $locale); + + $domain = "rhythm"; + + bindtextdomain($domain, "./locale"); + bind_textdomain_codeset($domain, 'UTF-8'); + + textdomain($domain); +} + +?> diff --git a/lib/validation.php b/lib/validation.php new file mode 100644 index 0000000..6f910f6 --- /dev/null +++ b/lib/validation.php @@ -0,0 +1,119 @@ +<?php + +// https://stackoverflow.com/a/600306/9123061 +function is_valid_note($x) +{ + return ($x & ($x - 1)) == 0; +} + +function is_valid_time($check_time) +{ + if(!is_string($check_time)) + { + return false; + } + $check_time = remove_whitespace($check_time); + $check_time_explode = explode("/", $check_time, 2); + $check_time_explode[0] = intval($check_time_explode[0]); + $check_time_explode[1] = intval($check_time_explode[1]); + if(count($check_time_explode) != 2 || $check_time_explode[0] < 1 || $check_time_explode[1] < 1) + { + return false; + } + if(!is_valid_note($check_time_explode[1])) + { + return false; + } + $ratio = $check_time_explode[0] / $check_time_explode[1]; + $new_rhythm_length = intval(MAX_FRACTION * $ratio); + if(!is_int($new_rhythm_length) || $new_rhythm_length == 0) + { + return false; + } + return true; +} + +function is_valid_tempo($check_tempo) +{ + if(!is_string($check_tempo)) + { + return false; + } + $check_tempo = remove_whitespace($check_tempo); + $check_tempo_explode = explode("=", $check_tempo, 2); + $check_tempo_explode[0] = intval($check_tempo_explode[0]); + $check_tempo_explode[1] = intval($check_tempo_explode[1]); + if(count($check_tempo_explode) != 2 || $check_tempo_explode[0] < 1 || $check_tempo_explode[1] < 1) + { + return false; + } + if(!is_valid_note($check_tempo_explode[0])) + { + return false; + } + return true; +} + +function is_valid_bars($check_bars) +{ + if(!intval($check_bars)) + { + return false; + } + $check_bars = intval($check_bars); + if($check_bars < 1 || $check_bars > 64) + { + return false; + } + return true; +} + +function is_valid_dynamic($check_dynamic) +{ + if(!is_string($check_dynamic)) + { + return false; + } + $allowed_dynamics_regex = "/^(p{1,5}|f{1,5}|m(p|f)|s(p{1,2}|f{1,2})|fp|(s|r)fz)$/"; + if(!preg_match($allowed_dynamics_regex, $check_dynamic)) + { + return false; + } + return true; +} + +function validate_get_parameter() +{ + if(!(isset($_GET["time"]) && is_valid_time($_GET["time"]))) + { + $_GET["time"] = "4/4"; + } + $_GET["time"] = remove_whitespace($_GET["time"]); + + if(!(isset($_GET["tempo"]) && is_valid_tempo($_GET["tempo"]))) + { + $_GET["tempo"] = "4=90"; + } + $_GET["tempo"] = remove_whitespace($_GET["tempo"]); + + if(!(isset($_GET["bars"]) && is_valid_bars($_GET["bars"]))) + { + $_GET["bars"] = "2"; + } + + if(!(isset($_GET["dynamic_beat"]) && is_valid_dynamic($_GET["dynamic_beat"]))) + { + $_GET["dynamic_beat"] = "pp"; + } + if(!(isset($_GET["dynamic_rhythm"]) && is_valid_dynamic($_GET["dynamic_rhythm"]))) + { + $_GET["dynamic_rhythm"] = "ff"; + } + + if(!(isset($_GET["id"]) && is_string($_GET["id"]) && strlen($_GET["id"]) > 1)) + { + $_GET["id"] = md5(random_bytes(60)); + } +} + +?> diff --git a/locale/de/LC_MESSAGES/rhythm.po b/locale/de/LC_MESSAGES/rhythm.po new file mode 100644 index 0000000..25451a3 --- /dev/null +++ b/locale/de/LC_MESSAGES/rhythm.po @@ -0,0 +1,68 @@ +# This file is distributed under the same license as the rhythm package. +# Serguzim <serguzim@msrg.cc>, 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1 VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-03-05 21:21+0100\n" +"PO-Revision-Date: 2020-03-05 18:53+0100\n" +"Last-Translator: <serguzim@msrg.cc>\n" +"Language-Team: German <translation-team-de@lists.sourceforge.net>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/index.tpl.php:26 +msgid "Solution" +msgstr "Lösung" + +#: templates/index.tpl.php:37 +msgid "Generate this rhythm" +msgstr "Erzeuge diesen Rhythmus" + +#: templates/index.tpl.php:38 +msgid "Generate new rhythm" +msgstr "Erzeuge neues Rhythmus" + +#: templates/index.tpl.php:43 +msgid "Anything is possible. Used as seed for the rhythm." +msgstr "Alles ist möglich. Wird als Grundlage für den Rhythmus verwendet." + +#: templates/index.tpl.php:47 +msgid "Time Signature" +msgstr "Taktvorzeichen" + +#: templates/index.tpl.php:50 templates/index.tpl.php:58 +msgid "Examples" +msgstr "Beispiele" + +#: templates/index.tpl.php:54 +msgid "Tempo" +msgstr "Tempo" + +#: templates/index.tpl.php:58 +msgid "click" +msgstr "klick" + +#: templates/index.tpl.php:66 +msgid "Bars" +msgstr "Takte" + +#: templates/index.tpl.php:69 +#, php-format +msgid "Anything between %s and %s" +msgstr "Alles zwischen %s und %s" + +#: templates/index.tpl.php:73 +msgid "Dynamic for Beat" +msgstr "Dynamik für den Beat" + +#: templates/index.tpl.php:80 +msgid "Dynamic for Rhythm" +msgstr "Dynamik für den Rhytmus" + +#~ msgid "Submit" +#~ msgstr "Absenden" diff --git a/locale/en/LC_MESSAGES/rhythm.po b/locale/en/LC_MESSAGES/rhythm.po new file mode 100644 index 0000000..f509faf --- /dev/null +++ b/locale/en/LC_MESSAGES/rhythm.po @@ -0,0 +1,68 @@ +# This file is distributed under the same license as the rhythm package. +# Serguzim <serguzim@msrg.cc>, 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: 0.0.1 VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-03-05 21:21+0100\n" +"PO-Revision-Date: 2020-03-05 19:14+0100\n" +"Last-Translator: <serguzim@msrg.cc>\n" +"Language-Team: German <translation-team-de@lists.sourceforge.net>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: templates/index.tpl.php:26 +msgid "Solution" +msgstr "Solution" + +#: templates/index.tpl.php:37 +msgid "Generate this rhythm" +msgstr "Generate this rhythm" + +#: templates/index.tpl.php:38 +msgid "Generate new rhythm" +msgstr "Generate new rhythm" + +#: templates/index.tpl.php:43 +msgid "Anything is possible. Used as seed for the rhythm." +msgstr "Anything is possible. Used as seed for the rhythm." + +#: templates/index.tpl.php:47 +msgid "Time Signature" +msgstr "Time Signature" + +#: templates/index.tpl.php:50 templates/index.tpl.php:58 +msgid "Examples" +msgstr "Examples" + +#: templates/index.tpl.php:54 +msgid "Tempo" +msgstr "Tempo" + +#: templates/index.tpl.php:58 +msgid "click" +msgstr "click" + +#: templates/index.tpl.php:66 +msgid "Bars" +msgstr "Bars" + +#: templates/index.tpl.php:69 +#, php-format +msgid "Anything between %s and %s" +msgstr "Anything between %s and %s" + +#: templates/index.tpl.php:73 +msgid "Dynamic for Beat" +msgstr "Dynamic for Beat" + +#: templates/index.tpl.php:80 +msgid "Dynamic for Rhythm" +msgstr "Dynamic for Rhythm" + +#~ msgid "Submit" +#~ msgstr "Submit" diff --git a/locale/rhythm.pot b/locale/rhythm.pot new file mode 100644 index 0000000..6cfbf2a --- /dev/null +++ b/locale/rhythm.pot @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-03-05 21:22+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/index.tpl.php:26 +msgid "Solution" +msgstr "" + +#: templates/index.tpl.php:37 +msgid "Generate this rhythm" +msgstr "" + +#: templates/index.tpl.php:38 +msgid "Generate new rhythm" +msgstr "" + +#: templates/index.tpl.php:43 +msgid "Anything is possible. Used as seed for the rhythm." +msgstr "" + +#: templates/index.tpl.php:47 +msgid "Time Signature" +msgstr "" + +#: templates/index.tpl.php:50 templates/index.tpl.php:58 +msgid "Examples" +msgstr "" + +#: templates/index.tpl.php:54 +msgid "Tempo" +msgstr "" + +#: templates/index.tpl.php:58 +msgid "click" +msgstr "" + +#: templates/index.tpl.php:66 +msgid "Bars" +msgstr "" + +#: templates/index.tpl.php:69 +#, php-format +msgid "Anything between %s and %s" +msgstr "" + +#: templates/index.tpl.php:73 +msgid "Dynamic for Beat" +msgstr "" + +#: templates/index.tpl.php:80 +msgid "Dynamic for Rhythm" +msgstr "" diff --git a/messages.po b/messages.po new file mode 100644 index 0000000..6a10bae --- /dev/null +++ b/messages.po @@ -0,0 +1,72 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-03-05 21:19+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: templates/index.tpl.php:26 templates/index.tpl.php:67 +msgid "Solution" +msgstr "" + +#: templates/index.tpl.php:37 +msgid "Generate this rhythm" +msgstr "" + +#: templates/index.tpl.php:38 +msgid "Generate new rhythm" +msgstr "" + +#: templates/index.tpl.php:43 templates/index.tpl.php:19 +msgid "Anything is possible. Used as seed for the rhythm." +msgstr "" + +#: templates/index.tpl.php:47 templates/index.tpl.php:23 +msgid "Time Signature" +msgstr "" + +#: templates/index.tpl.php:50 templates/index.tpl.php:58 +#: templates/index.tpl.php:26 templates/index.tpl.php:34 +msgid "Examples" +msgstr "" + +#: templates/index.tpl.php:54 templates/index.tpl.php:30 +msgid "Tempo" +msgstr "" + +#: templates/index.tpl.php:58 templates/index.tpl.php:34 +msgid "click" +msgstr "" + +#: templates/index.tpl.php:66 templates/index.tpl.php:42 +msgid "Bars" +msgstr "" + +#: templates/index.tpl.php:69 templates/index.tpl.php:45 +#, php-format +msgid "Anything between %s and %s" +msgstr "" + +#: templates/index.tpl.php:73 templates/index.tpl.php:49 +msgid "Dynamic for Beat" +msgstr "" + +#: templates/index.tpl.php:80 templates/index.tpl.php:56 +msgid "Dynamic for Rhythm" +msgstr "" + +#: templates/index.tpl.php:63 +msgid "Submit" +msgstr "" diff --git a/static/res/tempo.png b/static/res/tempo.png new file mode 100644 index 0000000..b9d0fa6 Binary files /dev/null and b/static/res/tempo.png differ diff --git a/templates/index.tpl.php b/templates/index.tpl.php new file mode 100644 index 0000000..12d28a4 --- /dev/null +++ b/templates/index.tpl.php @@ -0,0 +1,89 @@ +<html> +<head> + <title>Rhythm Thing</title> + <link rel="stylesheet" href="/static/fontawesome/css/all.min.css"> + <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> + <script src="/static/js/jquery.min.js"></script> + <script src="/static/bootstrap/js/bootstrap.bundle.min.js"></script> +</head> +<body> + <div class="container mt-3"> + <div class="row"> + <h1 class="mx-auto">Quick Rhythm Maker</h1> + </div> + <hr> + <div class="row mb-3"> + <div class="col-12"> + <audio style="width: 100%;" controls> + <source src="audio.php?file_name=<?php echo($file_name) ?>" type="audio/ogg"> + Your browser does not support the audio element. + </audio> + </div> + </div> + <div class="row"> + <div class="col-12"> + <button class="btn btn-warning" type="button" data-toggle="collapse" data-target="#collapse-solution"> + <?php echo(_("Solution")) ?> + </button> + <br> + <div class="collapse" id="collapse-solution"> + <img class="py-3" src="./tmp/<?php echo($file_name) ?>.png"> + </div> + </div> + </div> + <hr> + <div class="row"> + <form class="col-12" action="" method="get"> + <button class="btn btn-primary" type="submit"><?php echo(_("Generate this rhythm")) ?></button> + <button class="btn btn-secondary" onclick="document.getElementById('id').value ='';" type="submit"><?php echo(_("Generate new rhythm")) ?></button> + <div class="form-group"> + ID + <input type="text" class="form-control" name="id" id="id" value="<?php echo($id); ?>" /> + <small> + <?php echo(_("Anything is possible. Used as seed for the rhythm.")) ?> + </small> + </div> + <div class="form-group"> + <?php echo(_("Time Signature")) ?> + <input type="text" class="form-control" name="time" value="<?php echo($time); ?>" /> + <small> + <?php echo(_("Examples")) ?>: 4/4, 3/4, 6/8, 2/2, 13/8, 11/16, 2/4 + </small> + </div> + <div class="form-group"> + <?php echo(_("Tempo")) ?> + <input type="text" class="form-control" name="tempo" value="<?php echo($tempo); ?>" /> + <small> + <a data-toggle="collapse" href="#collapse-tempo" role="button"> + <?php echo(_("Examples")) ?> (<?php echo(_("click")) ?>) + </a> + </small> + <div class="collapse" id="collapse-tempo"> + <img src="./static/res/tempo.png"> + </div> + </div> + <div class="form-group"> + <?php echo(_("Bars")) ?> + <input type="number" class="form-control" min="1" max="64" name="bars" value="<?php echo($bars); ?>" /> + <small> + <?php printf(_("Anything between %s and %s"), 1, 64) ?> + </small> + </div> + <div class="form-group"> + <?php echo(_("Dynamic for Beat")) ?> + <input type="text" class="form-control" name="dynamic_beat" value="<?php echo($dynamic_beat); ?>" /> + <small> + ppppp, pppp, ppp, pp, p, mp, mf, f, ff, fff, ffff, fffff, fp, sf, sff, sp, spp, sfz, rfz + </small> + </div> + <div class="form-group"> + <?php echo(_("Dynamic for Rhythm")) ?> + <input type="text" class="form-control" name="dynamic_rhythm" value="<?php echo($dynamic_rhythm); ?>" /> + <small> + ppppp, pppp, ppp, pp, p, mp, mf, f, ff, fff, ffff, fffff, fp, sf, sff, sp, spp, sfz, rfz + </small> + </div> + </form> + </div> + </div> +</body>