    <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/">
     <channel>
        <title>ACCU  :: Silas's Corner</title>
        <link>https://members.accu.org/index.php/articles/839</link>
        <description>Professionalism in Programming</description>
        <dc:language>en-us</dc:language> 
        <dc:creator>Administrator</dc:creator> 
        <admin:generatorAgent rdf:resource="http://www.xaraya.org" /> 
        <admin:errorReportsTo rdf:resource="mailto:webeditor@accu.org" />
       <sy:updatePeriod>hourly</sy:updatePeriod>
       <sy:updateFrequency>1</sy:updateFrequency>
       <docs>http://backend.userland.com/rss</docs>




<div class="xar-mod-head"><span class="xar-mod-title">CVu Journal Vol 17, #5 - Oct 2005</span></div>

<table border="0" cellpadding="1" cellspacing="0">
    <tbody>
    <tr>
        <td valign="top">
            Browse in :
       </td>
       <td valign="top">

                                            <a href="https://members.accu.org/index.php/articles/">All</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c76/">Journals</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c77/">CVu</a>

                     &gt;                         <a href="https://members.accu.org/index.php/articles/c94/">175</a>
<br />
</td>
   </tr>
   </tbody>
</table>




<div class="xar-error">
   <p>
 <strong>Note:</strong> when you create a new publication type,
the articles module will automatically use the templates
<em>user-display-[publicationtype].xt</em>
and <em>user-summary-[publicationtype].xt</em>.
If those templates do not exist when you try to preview or display a new article,
you'll get this warning :-)  Please place your own templates in themes/<em>yourtheme</em>/modules/articles . The templates will get the extension .xt there. </p>
</div>
<div class="xar-norm xar-standard-box-padding">
   <h1><strong>Title:</strong>&nbsp;Silas's Corner</h1>
<p><strong>Author:</strong>&nbsp;</p>
<p>
<strong>Date:</strong> 06 October 2005 05:00:00 +01:00 or Thu, 06 October 2005 05:00:00 +01:00</p>
<p><strong>Summary:</strong>&nbsp;</p>
<p><strong>Body:</strong>&nbsp;<div class="section" lang="en">
<div class="titlepage">
<h2><a name="d0e22" id="d0e22"></a>Recycling
throwaway hardware</h2>
</div>
<p>I was recently given the challenge of setting up a system
suitable for an individual to use for selecting and playing
educational sound recordings. What made this particularly
interesting was that the individual was an adult with very little
schooling (so no complicated user interfaces allowed) and no
knowledge of the English language.  His accommodation was too small
to set up a full PC with monitor, and there was absolutely no
budget.</p>
<p>I was able to obtain an old PC that was being given away for
free. It had 16M of RAM, a 2G hard disk, a slow CD-ROM drive and a
soundcard.  The system unit was not exactly small, but I planned to
set it up in such a way that nothing else would be needed apart
from the system unit and keyboard (and a pair of headphones).  Of
course the Windows 98 on it ran extremely slowly, was full of
viruses and was probably unlicensed, not to mention being
unsuitable for &quot;blind&quot; use, so I decided to erase it and install
Linux.</p>
<p>First I used Knoppix (<a href="http://www.knoppix.org" target=
"_top">www.knoppix.org</a>) to detect the sound hardware. Knoppix
isn't very good at dealing with a low amount of RAM, so I had to go
into &quot;expert&quot; mode and override some of the bootup behaviour. It
helped to repartition the hard disk as soon as possible and create
a swap partition. I typed lsmod and noted which kernelmodules
Knoppix had loaded; this told me which modules would be needed for
the soundcard.</p>
<p>The Linux installation to the hard disk had to be as smallas
possible so as to make room for the sound recordings and also given
the limited amount of RAM. Graphics was out ofthe question (but not
needed for this application anyway). Knoppix was too big: I didn't
want to do the drudgery required to trim it down, and anyway its
installation script is broken in recent releases, and older
versions of Knoppix didn't detect the soundcard at all; the unusual
type of soundcard installed was supported only by the latest (2.6)
version of the Linux kernel. So I needed a distribution that is
up-to-date, that can give you a near-minimal installation without
too much hassle, and that contains players for MP3, OGG and SPX
sound files.</p>
<p>The obvious answer was Debian. Its most recent release (3.1 or
&quot;sarge&quot;) has a much improved installer; it probably wouldn't detect
the old soundcard, but thanks to Knoppix Inow knew which module to
load. I burnt myself a copy of the first CD of Debian (you don't
need all the CDs) and installed a minimal system. Then I installed
the 2.6 kernel, and installed the sound packages by copying the
necessary package files onto a floppy disk (they weren't popular
enough to be on the first CD) and finally trimmed out any remaining
unnecessary packages (such as the old 2.4 kernel). Then I
customised the startup scripts into /etc/init.d so as to load the
correct module with modprobe, set the volume with aumix, clean out
old logfiles (the disk would be very nearly full once the
recordings were on it) and run my program. I also commented out
potentially time-consuming startup scripts such as the filesystem
check (since nowadays it uses a journalling filesystem, the check
is less necessary and it doesn't matter if the user prematurely
switches off the power, especially given that this application does
not require saving anything).</p>
<p>The main part of the program was written in Python. Basically I
wanted a system that would read out (in the user's native language)
a list of recordings and invite him to press a number to choose one
to play. Of course, I couldn't have the complication of asking him
to press Enter after the number, so I used the Python curses
library which interfaces with the terminal at a lower level:</p>
<pre class="programlisting">
import curses
curses.initscr()
curses.cbreak()
</pre>
<p><tt class="function">initscr()</tt> is required to initialise
the curses library, and cbreak() tells it to accept one character
at a time (but still allow sequences such as control-break to
interrupt the program, which may be useful for debugging). Then to
read a character, one would do something like:</p>
<pre class="programlisting">
import sys
response = sys.stdin.read(1)
</pre>
<p>That wasn't too difficult to find in the Python library
documentation, and didn't require any extra libraries to be set up
(it's all included with Python). We can build it up into a function
that repeatedly plays a prompt while waiting for an answer like
this:</p>
<pre class="programlisting">
def get_selection(play_prompt_command):
  pid = os.spawnlp(os.P_NOWAIT,&quot;/bin/bash&quot;,
        &quot;/bin/bash&quot;,&quot;-c&quot;,
        &quot;while true; do %s; done&quot;
        % play_prompt_command)
        curses.flushinp()
        response = sys.stdin.read(1)
    os.kill(pid,signal.SIGKILL)
    os.system(&quot;killall -9 ogg123;
    killall -9 mpg123;killall -9 speexdec&quot;)
    return response
</pre>
<p>The killall command is hacky but the alternative would be to
write a function that goes through all the processes and checks
each one to see if it is in the same process group as the current
process but is not the same as the current process (we cannot
include the current process in an uncatchable signal, and if any
other signal is used then some sound-playing commands do not stop
immediately).  Also we use curses.flushinp() to flush the keyboard
buffer (typeahead would probably be too confusing in this
application).</p>
<p>As for what to do when something was selected (say, option 2), I
decided to check for <tt class="filename">2.mp3</tt>, <tt class=
"filename">2.ogg</tt>, <tt class="filename">2.spx</tt> or a
directory called <tt class="filename">2</tt> (and option <tt class=
"filename">0</tt> always goes up one level).</p>
<p>Here's the rest of the script:</p>
<pre class="programlisting">
def get_play_command(filename):
  for extension,format in [
    (&quot;.mp3&quot;, &quot;mpg123 %s.mp3&quot;),
    (&quot;.mp2&quot;, &quot;mpg123 %s.mp2&quot;),
    (&quot;.ogg&quot;, &quot;ogg123 %s.ogg&quot;),
    (&quot;.spx&quot;, &quot;speexdec %s.spx&quot;)]:
   try:
    if open(filename+extension):
       return format % filename
   except IOError: pass
  # by default returns None

def menu():
  play_prompt_command = get_play_command(&quot;index&quot;)
  if play_prompt_command:
    while True:
      response = get_selection(play_prompt_command)
      if response==&quot;0&quot;: return
      play_cmd = get_play_command(response)
      if play_cmd: os.system(play_cmd)
      else:
        # it might be a directory
        try:
          os.chdir(response)
        except OSError: continue
        menu()
        os.chdir(&quot;..&quot;)
  else:
    print &quot;\a Warning: index not found!&quot;
    # but continue running (up one level)

def main():
  try:
    while True: menu()
  except KeyboardInterrupt: curses.endwin()if __name__ == &quot;__main__&quot;: main()
</pre>
<p>I threw in a feature that checks for an executable script on any
CD-ROM that is inserted (for future expansion) and then delivered
the box.  It's a shame that throwaway PCs don'tcome any
smaller.</p>
</div>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
