From d7c956f46819d2ec6aa093fdae9d92c9aa76940a Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Sun, 14 Aug 2011 23:21:21 +0200
Subject: [PATCH] Almost finished. Preview works, score hierarchy, etc.

Missing:
-) Page layout (margins/spacing/stretching)
-) footer annotations
-) Space for header
---
 .gitignore                                    |   2 +
 empty_sheet.php                               | 206 +++++++++++++++---
 empty_sheet_form.html                         | 131 +++++++----
 loading.gif                                   | Bin 0 -> 3208 bytes
 templates/Ch2.tpl.ly                          |   4 +
 templates/Ch4.tpl.ly                          |   6 +
 templates/Group.tpl.ly                        |   9 +
 templates/O.tpl.ly                            |   4 +
 templates/P.tpl.ly                            |   4 +
 templates/Rh.tpl.ly                           |   1 +
 templates/S.tpl.ly                            |   1 +
 templates/S0.tpl.ly                           |   1 +
 templates/S8.tpl.ly                           |   1 +
 templates/Sa.tpl.ly                           |   1 +
 templates/Sb.tpl.ly                           |   1 +
 templates/St.tpl.ly                           |   1 +
 templates/Tab.tpl.ly                          |   1 +
 .../empty_sheet.tpl.ly                        |  32 +--
 18 files changed, 318 insertions(+), 88 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 loading.gif
 create mode 100644 templates/Ch2.tpl.ly
 create mode 100644 templates/Ch4.tpl.ly
 create mode 100644 templates/Group.tpl.ly
 create mode 100644 templates/O.tpl.ly
 create mode 100644 templates/P.tpl.ly
 create mode 100644 templates/Rh.tpl.ly
 create mode 100644 templates/S.tpl.ly
 create mode 100644 templates/S0.tpl.ly
 create mode 100644 templates/S8.tpl.ly
 create mode 100644 templates/Sa.tpl.ly
 create mode 100644 templates/Sb.tpl.ly
 create mode 100644 templates/St.tpl.ly
 create mode 100644 templates/Tab.tpl.ly
 rename empty_sheet.tpl.ly => templates/empty_sheet.tpl.ly (58%)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f879246
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+smarty/templates_c/*
+*~
\ No newline at end of file
diff --git a/empty_sheet.php b/empty_sheet.php
index b7688ed..4a997c6 100644
--- a/empty_sheet.php
+++ b/empty_sheet.php
@@ -1,6 +1,6 @@
 <?php
 
-umask (0644)
+umask (0001);
 $basedir=dirname(__FILE__);
 
 $user = "lilyjail";
@@ -8,14 +8,15 @@ $lilymnt = "/home/lilyjail/lilymnt";
 $lilyhome = "/lilyhome";
 $lilyworkdir = "$lilyhome/empty_sheet";
 
-$ly_cmd = "/usr/bin/sudo /opt/lily2.14/bin/lilypond -j$user,$user,$lilymnt,$lilyhome ";
+$ly_cmd = "/usr/bin/sudo /opt/lily2.14/bin/lilypond -j$user,$user,$lilymnt,$lilyhome --png --pdf -dpixmap-format=pngalpha -dresolution=20 ";
 // print "<p>basedir: $basedir</p>";
 // print "<p>ly_cmd: $ly_cmd</p>";
 
 include('smarty3/Smarty.class.php');
 $smarty = new Smarty();
 
-$smarty->setTemplateDir($basedir.'/smarty/templates');
+// $smarty->setTemplateDir($basedir.'/smarty/templates');
+// $smarty->setTemplateDir($basedir.'/smarty/templates');
 $smarty->setCompileDir($basedir.'/smarty/templates_c');
 $smarty->setCacheDir($basedir.'/smarty/cache');
 $smarty->setConfigDir($basedir.'/smarty/configs');
@@ -23,51 +24,181 @@ $smarty->setConfigDir($basedir.'/smarty/configs');
 $smarty->left_delimiter = '{{';
 $smarty->right_delimiter = '}}';
 
+function robust_request_string ($parm, $default) {
+  if (isset ($_REQUEST[$parm]) && is_string ($_REQUEST[$parm])) {
+    // remove all problematic characters (\ and "):
+    $val = $_REQUEST[$parm];
+    $val = str_replace ('"', '', $val);
+    $val = str_replace ('\\', '', $val);
+    return $val;
+  } else {
+    return $default;
+  }
+}
+function robust_request_number ($parm, $default, $min, $max) {
+  if (isset ($_REQUEST[$parm]) && is_numeric ($_REQUEST[$parm])) {
+    $val = $_REQUEST[$parm];
+    // Ensure the value is inside the given range:
+    return min (max ($val, $min), $max);
+  } else {
+    return $default;
+  }
+}
 
-//////// Sanity checks for the URL parameters:
-// TODO: Escape all values (i.e. make sure they are within the range, don't contain ", etc.
-$paper_size = $_REQUEST["paper_size"];
-$staff_size = $_REQUEST["staff_size"];
-$systems = $_REQUEST["systems"];
-$pages = $_REQUEST["pages"];
-$title = $_REQUEST["title"];
-$subtitle = $_REQUEST["subtitle"];
-$composer = $_REQUEST["composer"];
-$instrument = $_REQUEST["instrument"];
-$metainfo = $title . $subtitle . $composer . $instrument;
-$metahash = md5( $metainfo );
 
+// Score type: 
+// Syntax: new Staff/Group indicated by capital letter
+// S0, S, Sa, St, S8, Sb ... Staff w/o clef, w/ treble, alto, tenor, treble_8, bass clef
+// Ch2, Ch4 ... Choir staff with 2/4 staves
+// P, O ... Piano / Organ staff
+// Rh ... Rhythmic staff
+// Tab ... Tab staff
+// [...] ... StaffGroup
+// {...} ... GrandStaff
+// (...) ... <<..>>
+
+$known_staff_types = array ("S0", "S", "S8", "Sa", "St", "Sb", "O", "P", "Ch2", "Ch4", "Rh", "Tab");
+$known_group_types = array ("{" => "GrandStaff", "(" => "", "[" => "StaffGroup");
+$known_group_endings = array ("{" => "}", "(" => ")", "[" => "]");
+
+// Get the next entry (single staff or group), will be called recursively.
+// Return (next-elm, remaining), where next-elm is either a string 
+// describing the next staff or an array describing a group (keys "type" and
+// "contents").
+function get_next_group_staff ($remaining)
+{
+  global $known_staff_types;
+  global $known_group_types;
+  global $known_group_endings;
+
+  if (empty($remaining)) {
+    return NULL;
+  }
+  $l = array_shift($remaining);
+  if (in_array ($l, $known_staff_types)) {
+    return array ($l, $remaining);
+  } elseif (isset ($known_group_types[$l])) {
+    $ending = $known_group_endings[$l];
+    $grp["type"] = $known_group_types[$l];
+    $contents = array ();
+    while ($remaining && reset($remaining) != $ending) {
+      list ($elt, $remaining) = get_next_group_staff ($remaining);
+      if ($elt)
+        $contents[] = $elt;
+    }
+    if (reset($remaining) == $ending)
+      array_shift ($remaining);
+    $grp["contents"] = $contents;
+    return array ($grp, $remaining);
+  } elseif (in_array ($l, $known_group_endings)) {
+    // Somehow we missed a closing bracket, or the staff type definition is messed up.
+    return array (NULL, $remaining);
+  } else {
+    die ("Unknown type `$l' in Staff type definition.");
+  }
+  
+}
+
+function parse_score_type ($type) {
+  // First, split the type into its components, i.e. strings of the form 
+  // "Xxxx" or any bracket.
+  $elements = preg_split ("/([{}\[\]\(\)]|[A-Z][a-z0-9]*)/", $type, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+  $result = array();
+  while ($elements) {
+    list ($thiselt, $elements) = get_next_group_staff ($elements);
+    if ($thiselt)
+      $result[] = $thiselt;
+  }
+  return $result;
+}
 
-$score_type=$_REQUEST["score_type"];
 
+//////// Sanity checks for the URL parameters:
+// Escape all values, i.e. make sure they are within the range, don't contain ", etc.
+// $type = strtr (robust_request_string ("score_type", NULL), "{}[]()", "E3LJ");
+$type = robust_request_string ("score_type", NULL);
+
+$psz = explode (" ", robust_request_string ("paper_size", "a4"), 2);
+$paper_size = array_shift ($psz);
+if ($psz) {
+  $orientation = array_shift($psz);
+}
+
+$staff_size = robust_request_number ("staff_size", 20, 3, 100);
+$systems = robust_request_number ("systems", 10, 1, 30);
+$pages = robust_request_number ("pages", 2, 1, 20);
+$title = robust_request_string ("title", NULL);
+$subtitle = robust_request_string ("subtitle", NULL);
+$composer = robust_request_string ("composer", NULL);
+$instrument = robust_request_string ("instrument", NULL);
+$metainfo = $title . $subtitle . $composer . $instrument;
+$metahash = md5( $metainfo );
 
 //////// Use cache file name composed from the arguments
-$out_file_basename = "Score_${score_type}_${paper_size}_${staff_size}pt_${systems}Systems_${pages}Pages";
+$out_file_basename = "Score_${type}_${paper_size}${orientation}_${staff_size}pt_${systems}Systems_${pages}Pages";
 if ($metainfo != "")
   $out_file_basename .= "_${metahash}";
+  
+// Create file names
+$base_file = $lilyworkdir . "/" . $out_file_basename;
+$ly_file = $base_file . ".ly";
+$pdf_file = $base_file . ".pdf";
+if ($pages > 1) {
+  $png_file = $base_file . "-page1.png";
+} else {
+  $png_file = $base_file . ".png";
+}
+
+// If the file exists, return it now.
+if (isset ($_REQUEST["preview"])) {
+  if (file_exists ($lilymnt.$png_file)) {
+    header('Content-type: image/png');
+    header('Content-Description: Empty Music Score Preview');
+    header('Content-Length: ' . filesize($lilymnt.$png_file));
+    readfile($lilymnt.$png_file);
+    exit (0);
+  }
+} elseif (file_exists ($lilymnt.$pdf_file)) {
+  header('Content-type: application/pdf');
+  header('Content-Description: Empty Music Score Sheets - provided by Edition Kainhofer');
+  header('Content-Length: ' . filesize($lilymnt.$pdf_file));
+  header('Content-Disposition: inline; filename=' . basename($lilymnt.$pdf_file));
+  readfile($lilymnt.$pdf_file);
+  exit (0);
+}
+
+
+
+// Parse the score type:
+if (isset ($_REQUEST["score_type"])) {
+  $score_type = parse_score_type ($_REQUEST["score_type"]);
+} else {
+  $score_type = array ('S0');
+}
+
+
+
 
 /////// Output variables for the template
 $smarty->assign('paper_size', $paper_size);
+$smarty->assign('orientation', $orientation);
 $smarty->assign('staff_size', $staff_size);
 $smarty->assign('systems', $systems);
 $smarty->assign('pages', $pages);
 
-if ($_REQUEST["title_check"])
-  $smarty->assign('title', $title);
-if ($_REQUEST["subtitle_check"])
-  $smarty->assign('subtitle', $subtitle) ;
-if ($_REQUEST["composer_check"]) 
-  $smarty->assign('composer', $composer);
-if ($_REQUEST["instrument_check"])
-  $smarty->assign('instrument', $instrument);
+$smarty->assign('title', $title);
+$smarty->assign('subtitle', $subtitle) ;
+$smarty->assign('composer', $composer);
+$smarty->assign('instrument', $instrument);
 
+$smarty->assign('indent', '');
+$smarty->assign('contents', $score_type);
+
+$smarty->allow_php_tag = true;
 
 //////// Check for the pdf file, create it if needed and return it:
 $lycode = $smarty->fetch('empty_sheet.tpl.ly');
 
-$base_file = $lilyworkdir . "/" . $out_file_basename;
-$ly_file = $base_file . ".ly";
-$pdf_file = $base_file . ".pdf";
 
 if (!file_exists ($pdf_file)) {
   // Create the lilypond file
@@ -79,19 +210,30 @@ if (!file_exists ($pdf_file)) {
 
   // run it through lilypond to create the pdf
   // As we are running inside the jail, we don't include $lilymnt in the pathes!
-  $cmd = "$ly_cmd -o $base_file $ly_file";
+  $cmd = "$ly_cmd -o $base_file $ly_file 2>&1";
   $lily_result = exec($cmd, $output, $retval);
 }
 
-if (file_exists ($lilymnt.$pdf_file)) {
+if (isset ($_REQUEST["preview"])) {
+  if (file_exists ($lilymnt.$png_file)) {
+    header('Content-type: image/png');
+    header('Content-Description: Empty Music Score Preview');
+    header('Content-Length: ' . filesize($lilymnt.$png_file));
+//     header('Content-Disposition: inline; filename=' . basename($lilymnt.$png_file));
+    readfile($lilymnt.$png_file);
+  }
+} elseif (file_exists ($lilymnt.$pdf_file)) {
   header('Content-type: application/pdf');
   header('Content-Description: Empty Music Score Sheets - provided by Edition Kainhofer');
-  header('Content-Type: application/pdf');
   header('Content-Length: ' . filesize($lilymnt.$pdf_file));
   header('Content-Disposition: inline; filename=' . basename($lilymnt.$pdf_file));
   readfile($lilymnt.$pdf_file);
 } else {
-  die ("Unable to create the score file. Wrong input or server error encountered.");
+  print ("Unable to create the score file. Wrong input or server error encountered.");
+  print ("The output from running lilypond is:\n<pre>");
+  print_r ($output);
+  print ("</pre>\n");
+//   die ("Unable to create the score file. Wrong input or server error encountered.");
 }
 
 ?>
diff --git a/empty_sheet_form.html b/empty_sheet_form.html
index a989465..e3b766c 100644
--- a/empty_sheet_form.html
+++ b/empty_sheet_form.html
@@ -1,33 +1,77 @@
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html>
-  <head>
-<style>
-.vsep {
-  background: darkgray;
-}
+<head>
+  <style>
+    #preview { border: 1px solid black; }
+    .vsep { background: darkgray; }
+    #previewdiv {
+      position: relative;
+    }
+    #loadingOverlay {
+      background-image: url('loading.gif');
+      position: absolute;
+      z-index: 1;
+      top: 40%;
+      left: 35%;
+      width: 32px;
+      height: 32px;
+    }
 
-</style>
-  </head>
-<body>
-  <FORM action="empty_sheet.php" name="score_form">
+  </style>
+  <script type="text/javascript">
+  <!--
+    function loadPreview () {
+      // Show loading overlay, will be hidden in onLoad handler of the image!
+      document.getElementById("loadingOverlay").style.visibility = "visible";
+
+      // Add all non-button form elements:      
+      var src = "empty_sheet.php?preview=True";
+      var elt = document.score_form.elements;
+      for(var i=0; i < elt.length; i++)
+      {
+        if (elt[i].type != 'submit' && elt[i].type != 'reset') {
+          src += "&" + encodeURIComponent (elt[i].name) + "=" + 
+                 encodeURIComponent (elt[i].value);
+        }
+      }
+      document.getElementById("preview").src = src;
+    }
+  //-->
+  </script>
+</head>
+<body onLoad="javascript:loadPreview();">
+  <FORM action="empty_sheet.php" name="score_form" onreset="javascript:loadPreview();">
   <table>
   <TR>
     <td><label for="paper_size">Paper size:</label></td>
-    <td><select name="paper_size" id="paper_size">
+    <td><select name="paper_size" id="paper_size" onchange="javascript:loadPreview();">
         <option value="a4" selected="selected">A4</option>
         <option value="a5">A5</option>
         <option value="a6">A6</option>
         <option value="letter">US letter</option>
         <option value="legal">Legal</option>
         <option value="11x17">Ledger</option>
+        <option value="a4 landscape">A4 (landscape)</option>
+        <option value="a5 landscape">A5 (landscape)</option>
+        <option value="a6 landscape">A6 (landscape)</option>
+        <option value="letter landscape">US letter (landscape)</option>
+        <option value="legal landscape">Legal (landscape)</option>
+        <option value="11x17 landscape">Ledger (landscape)</option>
       </select></td>
-    <td rowspan=4 class="vsep"></td>
-    <td><INPUT type="checkbox" name="title_check" size="15" accesskey="t" onchange="document.score_form.title.disabled = !this.checked" id="title_check"><label for="title_check" >Title: </label></td>
-    <td><INPUT type="text" checked name="title" size="25" maxlength="100" id="title" disabled="true"></td>
+    <td rowspan=5 class="vsep"></td>
+    <td rowspan=5 colspan=2 align=center>
+      <div id=previewdiv class="foo">
+        <div id="loadingOverlay"></div>
+        <img id=preview onLoad="javascript:document.getElementById('loadingOverlay').style.visibility = 'hidden';"/>
+      </div>
+    </td>
+
+<!--     <td><INPUT type="checkbox" name="title_check" size="15" accesskey="t" onchange="document.score_form.title.disabled = !this.checked" id="title_check"><label for="title_check" >Title: </label></td> -->
+<!--     <td><INPUT type="text" checked name="title" size="25" maxlength="100" id="title" disabled="true"></td> -->
   </tr>
   <tr>
     <td><label for="staff_size">Staff size:</label></td>
-    <td><select name="staff_size" id="staff_size">
+    <td><select name="staff_size" id="staff_size" onchange="javascript:loadPreview();">
         <option value="11">11pt (Pocket scores)</option>
         <option value="12.5">12.5pt</option>
         <option value="14">14pt</option>
@@ -40,12 +84,12 @@
         <option value="22">22</option>
         <option></option>
       </select></td>
-    <td><INPUT type="checkbox" name="subtitle_check" size="15" accesskey="c" onchange="document.score_form.subtitle.disabled = !this.checked" id="subtitle_check"><label for="subtitle_check" >Subtitle: </label></td>
-    <td><INPUT type="text" checked name="subtitle" size="25" maxlength="100" id="subtitle" disabled="true"></td>
+<!--     <td><INPUT type="checkbox" name="subtitle_check" size="15" accesskey="c" onchange="document.score_form.subtitle.disabled = !this.checked" id="subtitle_check"><label for="subtitle_check" >Subtitle: </label></td> -->
+<!--     <td><INPUT type="text" checked name="subtitle" size="25" maxlength="100" id="subtitle" disabled="true"></td> -->
   </tr>
   <tr>
     <td><label for="systems">Systems/page:</label></td>
-    <td><select name="systems" id="systems">
+    <td><select name="systems" id="systems" onchange="javascript:loadPreview();">
         <option value="1">1</option>
         <option value="2">2</option>
         <option value="3">3</option>
@@ -62,12 +106,12 @@
         <option value="14">14</option>
         <option value="15">15</option>
       </select></td>
-    <td><INPUT type="checkbox" name="composer_check" size="15" accesskey="c" onchange="document.score_form.composer.disabled = !this.checked" id="composer_check"><label for="composer_check" >Composer: </label></td>
-    <td><INPUT type="text" checked name="composer" size="25" maxlength="100" id="composer" disabled="true"></td>
+<!--     <td><INPUT type="checkbox" name="composer_check" size="15" accesskey="c" onchange="document.score_form.composer.disabled = !this.checked" id="composer_check"><label for="composer_check" >Composer: </label></td> -->
+<!--     <td><INPUT type="text" checked name="composer" size="25" maxlength="100" id="composer" disabled="true"></td> -->
   </tr>
   <tr>
     <td><label for="pages">Pages:</label></td>
-    <td><select name="pages" id="pages">
+    <td><select name="pages" id="pages" onchange="javascript:loadPreview();">
         <option value="1">1</option>
         <option value="2" selected="selected">2</option>
         <option value="3">3</option>
@@ -84,8 +128,13 @@
         <option value="14">14</option>
         <option value="15">15</option>
       </select></td>
-    <td><INPUT type="checkbox" name="instrument_check" size="15" accesskey="i" onchange="document.score_form.instrument.disabled = !this.checked" id="instrument_check"><label for="instrument_check" >Instrument: </label></td>
-    <td><INPUT type="text" checked name="instrument" size="25" maxlength="100" id="instrument" disabled="true"></td>
+<!--     <td><INPUT type="checkbox" name="instrument_check" size="15" accesskey="i" onchange="document.score_form.instrument.disabled = !this.checked" id="instrument_check"><label for="instrument_check" >Instrument: </label></td> -->
+<!--     <td><INPUT type="text" checked name="instrument" size="25" maxlength="100" id="instrument" disabled="true"></td> -->
+  </tr>
+  <tr>
+    <td colspan=2>
+      <INPUT type="checkbox" name="header_check" id="header_check"><label for="header_check" >Reserve space for title</label>
+    </td>
   </tr>
 
   <tr><TD colspan=5><hr></TD></tr>
@@ -93,31 +142,31 @@
   <tr>
     <td valign=top><label for="score_type">Score type:</label></td>
     <td colspan=4>
-      <SELECT name="score_type" id="score_type" style='width: 100%' size="20">
-        <option value="1" selected="selected">Single staff, no clef</option>
-        <option value="2">Single staff, treble clef</option>
-        <option value="3">Single staff, bass clef</option>
-        <option value="4">Single staff, tenor clef</option>
-        <option value="5">Single staff, alto clef</option>
+      <SELECT name="score_type" id="score_type" style='width: 100%' size="20" onchange="javascript:loadPreview();">
+        <option value="S0" selected="selected">Single staff, no clef</option>
+        <option value="S">Single staff, treble clef</option>
+        <option value="Sb">Single staff, bass clef</option>
+        <option value="St">Single staff, tenor clef</option>
+        <option value="Sa">Single staff, alto clef</option>
         <option disabled="disabled">---------</option>
-        <option value="6">Piano staff</option>
-        <option value="7">Organ staff</option>
+        <option value="P">Piano staff</option>
+        <option value="O">Organ staff</option>
         <option disabled="disabled">---------</option>
-        <option value="8">Choir, 2 staves (SA + TB)</option>
-        <option value="9">Choir, 4 staves (S, A, T, B)</option>
+        <option value="Ch2">Choir, 2 staves (SA + TB)</option>
+        <option value="Ch4">Choir, 4 staves (S, A, T, B)</option>
         <option disabled="disabled">---------</option>
-        <option value="10">Piano reduction for choir</option>
-        <option value="11">Piano reduction for single soloist</option>
+        <option value="Ch4P">Piano reduction for choir</option>
+        <option value="S0P">Piano reduction for single soloist</option>
         <option disabled="disabled">---------</option>
-        <option value="12">String Trio</option>
-        <option value="13">String Quartett</option>
-        <option value="14">Piano Trio</option>
-        <option value="15">Piano Quartett</option>
+        <option value="[StSaSb]">String Trio</option>
+        <option value="[StStSaSb]">String Quartett</option>
+        <option value="[StSbP]">Piano Trio</option>
+        <option value="[StSaSbP]">Piano Quartett</option>
         <option disabled="disabled">---------</option>
-        <option value="16">Full Score, Chamber Orchestra</option>
-        <option value="17">Full Score, Symphonic Orchestra</option>
+        <option value="">Full Score, Chamber Orchestra</option>
+        <option value="[SSb][SS][Sb][{SS}SaSb]">Full Score, Symphonic Orchestra</option>
         <option value="18">Full Score, Chamber Orchestra + Choir</option>
-        <option value="19">Full Score, Symphonic Orchestra + Choir</option>
+        <option value="[SSb][SS][Sb][{SS}Sa]Ch4[Sb]">Full Score, Symphonic Orchestra + Choir</option>
       </SELECT>
     </td>
   </tr>
diff --git a/loading.gif b/loading.gif
new file mode 100644
index 0000000000000000000000000000000000000000..b15321949c8f49673463a3cca3d15c1bd5988439
GIT binary patch
literal 3208
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

literal 0
HcmV?d00001

diff --git a/templates/Ch2.tpl.ly b/templates/Ch2.tpl.ly
new file mode 100644
index 0000000..85a009c
--- /dev/null
+++ b/templates/Ch2.tpl.ly
@@ -0,0 +1,4 @@
+{{$indent}}\new ChoirStaff <<
+{{$indent}}  \new Staff \new Voice { \clef "treble" \emptymusic }
+{{$indent}}  \new Staff \new Voice { \clef "bass" \emptymusic }
+{{$indent}}>>
\ No newline at end of file
diff --git a/templates/Ch4.tpl.ly b/templates/Ch4.tpl.ly
new file mode 100644
index 0000000..aac6589
--- /dev/null
+++ b/templates/Ch4.tpl.ly
@@ -0,0 +1,6 @@
+{{$indent}}\new ChoirStaff <<
+{{$indent}}  \new Staff \new Voice { \clef "treble" \emptymusic }
+{{$indent}}  \new Staff \new Voice { \clef "treble" \emptymusic }
+{{$indent}}  \new Staff \new Voice { \clef "treble_8" \emptymusic }
+{{$indent}}  \new Staff \new Voice { \clef "bass" \emptymusic }
+{{$indent}}>>
\ No newline at end of file
diff --git a/templates/Group.tpl.ly b/templates/Group.tpl.ly
new file mode 100644
index 0000000..cda9599
--- /dev/null
+++ b/templates/Group.tpl.ly
@@ -0,0 +1,9 @@
+{{$indent}}{{if $type}}\new {{$type}} {{/if}}<<
+{{foreach $contents as $c}}
+{{if is_array($c)}}
+{{include file='Group.tpl.ly' indent="$indent  " type=$c.type contents=$c.contents}}
+{{else}}
+{{include file="$c.tpl.ly" indent="$indent  "}}
+{{/if}}
+{{/foreach}}
+{{$indent}}>>
diff --git a/templates/O.tpl.ly b/templates/O.tpl.ly
new file mode 100644
index 0000000..20026c6
--- /dev/null
+++ b/templates/O.tpl.ly
@@ -0,0 +1,4 @@
+{{$indent}}<<
+{{include file='P.tpl.ly' indent="$indent  "}}
+{{$indent}}  \new Staff \new Voice { \clef "bass" \emptymusic }
+{{$indent}}>>
\ No newline at end of file
diff --git a/templates/P.tpl.ly b/templates/P.tpl.ly
new file mode 100644
index 0000000..df3fc7b
--- /dev/null
+++ b/templates/P.tpl.ly
@@ -0,0 +1,4 @@
+{{$indent}}\new PianoStaff <<
+{{$indent}}  \new Staff \new Voice { \clef "treble" \emptymusic }
+{{$indent}}  \new Staff \new Voice { \clef "bass" \emptymusic }
+{{$indent}}>>
\ No newline at end of file
diff --git a/templates/Rh.tpl.ly b/templates/Rh.tpl.ly
new file mode 100644
index 0000000..dba2ffc
--- /dev/null
+++ b/templates/Rh.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new RhythmicStaff \new Voice { \emptymusic }
\ No newline at end of file
diff --git a/templates/S.tpl.ly b/templates/S.tpl.ly
new file mode 100644
index 0000000..0974458
--- /dev/null
+++ b/templates/S.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \new Voice { \clef "treble" \emptymusic }
\ No newline at end of file
diff --git a/templates/S0.tpl.ly b/templates/S0.tpl.ly
new file mode 100644
index 0000000..a17e9e5
--- /dev/null
+++ b/templates/S0.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \with {\remove "Clef_engraver"} \new Voice { \emptymusic }
\ No newline at end of file
diff --git a/templates/S8.tpl.ly b/templates/S8.tpl.ly
new file mode 100644
index 0000000..c7c8b3f
--- /dev/null
+++ b/templates/S8.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \new Voice { \clef "treble_8" \emptymusic }
\ No newline at end of file
diff --git a/templates/Sa.tpl.ly b/templates/Sa.tpl.ly
new file mode 100644
index 0000000..add01bd
--- /dev/null
+++ b/templates/Sa.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \new Voice { \clef "alto" \emptymusic }
\ No newline at end of file
diff --git a/templates/Sb.tpl.ly b/templates/Sb.tpl.ly
new file mode 100644
index 0000000..e812297
--- /dev/null
+++ b/templates/Sb.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \new Voice { \clef "bass" \emptymusic }
\ No newline at end of file
diff --git a/templates/St.tpl.ly b/templates/St.tpl.ly
new file mode 100644
index 0000000..e44f56d
--- /dev/null
+++ b/templates/St.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new Staff \new Voice { \clef "tenor" \emptymusic }
\ No newline at end of file
diff --git a/templates/Tab.tpl.ly b/templates/Tab.tpl.ly
new file mode 100644
index 0000000..9a0e7eb
--- /dev/null
+++ b/templates/Tab.tpl.ly
@@ -0,0 +1 @@
+{{$indent}}\new TabStaff \new TabVoice { \emptymusic }
\ No newline at end of file
diff --git a/empty_sheet.tpl.ly b/templates/empty_sheet.tpl.ly
similarity index 58%
rename from empty_sheet.tpl.ly
rename to templates/empty_sheet.tpl.ly
index 2b0c8de..1c36414 100644
--- a/empty_sheet.tpl.ly
+++ b/templates/empty_sheet.tpl.ly
@@ -1,7 +1,7 @@
 \version "2.12.3"
 
 #(set-global-staff-size {{$staff_size}})
-#(set-default-paper-size "{{$paper_size}}")
+#(set-default-paper-size "{{$paper_size}}"{{if $orientation}} '{{$orientation}}{{/if}})
 \header { 
 {{if $title}}  title="{{$title}}"
 {{/if}}
@@ -20,27 +20,29 @@
   ragged-last-bottom=##f
   ragged-last=##f
   ragged-right=##f
+  #(set-paper-size "{{$paper_size}}"{{if $orientation}} '{{$orientation}}{{/if}})
+}
+
+\layout {
+  \context { \StaffGroup
+    \override SystemStartBracket #'collapse-height = #1
+  }
+%   \context { \Score
+%     \override SystemStartBar #'collapse-height = #1
+%   }
+  \context { \PianoStaff 
+    \override SystemStartBrace #'collapse-height = #1
+  }
 }
 
 emptymusic = {
   \repeat unfold {{$systems*$pages}} { s1\break }
 }
 
+
 \new Score \with {
   \override TimeSignature #'transparent = ##t
   defaultBarType = #""
   \remove Bar_number_engraver
-} <<
-  \context ChoirStaff <<
-    \context Staff = women <<
-      \context Voice = sopranos { \emptymusic }
-      \context Voice = altos { \emptymusic }
-    >>
-    
-    \context Staff = men <<
-      \clef bass
-      \context Voice = tenors { \voiceOne <<\emptymusic >> }
-      \context Voice = basses { \voiceTwo <<\emptymusic >> }
-    >>
-  >>
->>
+}
+{{include file='Group.tpl.ly' indent='' contents=$contents}}
-- 
GitLab