    <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  :: A Python Project</title>
        <link>https://members.accu.org/index.php/journals/1206</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>


        <h2>Journal Articles</h2>


<div class="xar-mod-head"><span class="xar-mod-title">CVu Journal Vol 15, #2 - Apr 2003 + Programming Topics</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/journals/">All</a>

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

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

                     &gt;                         <a href="https://members.accu.org/index.php/journals/c109/">152</a>
                    (9)
<br />

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

                     &gt;                         <a href="https://members.accu.org/index.php/journals/c13/">Topics</a>

                     &gt;                         <a href="https://members.accu.org/index.php/journals/c65/">Programming</a>
                    (877)
<br />

                                            <a href="https://members.accu.org/index.php/journals/c109-65/">Any of these categories</a>

                    -                        <a href="https://members.accu.org/index.php/journals/c109+65/">All of these categories</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;A Python Project</h1>
<p><strong>Author:</strong>&nbsp;</p>
<p>
<strong>Date:</strong> 03 April 2003 13:15:56 +01:00 or Thu, 03 April 2003 13:15:56 +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></h2>
</div>
<p>This article describes the Python code in a small project that
I've written lately. I wrote this article when I heard that C Vu
was short of articles. It is a little rushed. But it might be a
useful &quot;dive in&quot; introduction to Python for programmers experienced
in other languages, as well as being a starting point for
discussion. Some of this code could be written better.</p>
<p>My program was designed to help me increase my vocabulary in a
foreign language, by playing audio samples to me. It is entirely
non-interactive. It plays an English word, then pauses while I'm
supposed to remember how it's said in the other language, then
plays the foreign word so I can check what I've remembered. The
tricky part is I wanted new words to be repeated more often than
old words, in graduated intervals (i.e. intervals that start small
and increase). That meant that each lesson needs some planning.</p>
<p>I decided to represent the lesson as a list of events. Let's
have a class called Schedule that holds a sorted list of
(start,finish) times:</p>
<pre class="programlisting">
class Schedule:
    def __init__(self): self.bookedList = []
    def book(self,start,finish):
        self.bookedList.append((start,finish))
        self.bookedList.sort()
</pre>
<p>We'll leave it at that for now (no functionality to check
overlaps etc yet).</p>
<p>Now, there are two main kinds of events - actual events that
play sound, and events that link other events (I called this
&quot;glue&quot;, from TeX typesetting terminology). For example, two
repetitions separated by an interval of time will be represented by
two (clusters of) events separated by a glue event. The glue event
is invisible to the booking of other events (i.e. other things can
happen in the lesson while the glue is taking place) but it is
still there, ensuring that the delay between the two repetitions is
of an acceptable length. (The glue will tolerate a certain amount
of stretching or shrinking in order to make everything fit.)</p>
<p>Here is a class containing some functionality for both events
and glue. The parameter '<i class=
"parameter"><tt>invisible</tt></i>' is non-zero if it's glue and
other events can take place at the same time; the parameter
'plusMinus' gives the tolerance for stretching/shrinking. Note that
the length is given in the constructor, but the start time is not
given until the methods are called. This is because we won't know
the start time when we're creating the event; we need to find a
time where it will fit in.</p>
<pre class="programlisting">
class GlueOrEvent:
    def __init__(self,length=0,plusMinus=0,invisible=0):
        self.length = length
        self.plusMinus = plusMinus
        self.invisible = invisible
    def bookIn(self,schedule,start):
        if not self.invisible:
            schedule.book(start,start+self.length)
</pre>
<p>The <tt class="methodname">bookIn</tt> method reserves time on
the schedule. This is used later when checking for overlaps, so
invisible events are not booked in.</p>
<pre class="programlisting">
    def addToEvents(self,events,startTime):
        assert not self.invisible
        events.append((startTime,self))
</pre>
<p>The <tt class="methodname">addToEvents</tt> method adds this
event to a list with its start time. This will be used later when
calling the Python schedule library to play the lesson. It will be
overridden by composite events which add each of their constituent
events to the list.</p>
<p>Now for an overlap check. The following method is designed to
return a value indicating how much this event has to move in order
not to overlap with anything else on the schedule. It is passed a
parameter indicating what direction it should move in (+1 or
-1).</p>
<pre class="programlisting">
    def overlaps(self,start,schedule,direction):
        if self.invisible: return 0
        if not schedule.bookedList: return 0
        count = 0
        # Skip over all events that finish before we start
        while schedule.bookedList[count][1] &lt;= start:
            count = count + 1
            if count &gt;= len(schedule.bookedList): return 0
        # Does this event start before we finish?
        if schedule.bookedList[count][0]&lt;start+self.length:
            # We have an overlap
            if direction &lt; 0:
                # Make sure we finish before it starts
                backwards = start+self.length-schedule.bookedList[count][0]
                return self.overlaps(start-backwards,schedule,direction)+backwards
            else:
                # Make sure we start after it finishes
                forwards = schedule.bookedList[count][1] - start
                return self.overlaps(start+forwards,schedule,direction)+forwards
        return 0 # No overlap
</pre>
<p>Finally, a <tt class="methodname">play()</tt> method which will
be overridden later.</p>
<pre class="programlisting">
    def play(self): pass
</pre>
<p>Now we can create some subclasses, such as <tt class=
"classname">Event</tt> and <tt class="classname">Glue</tt>:</p>
<pre class="programlisting">
class Event (GlueOrEvent):
    def __init__(self,length):
        GlueOrEvent.__init__(self,length)
class Glue (GlueOrEvent):
    def __init__(self,length,plusMinus):
        GlueOrEvent.__init__(self,length,plusMinus,1)
</pre>
<p>A <tt class="classname">CompositeEvent</tt> is an event made up
of several others in sequence with nothing intervening (no
glue).</p>
<pre class="programlisting">
class CompositeEvent (Event):
    def __init__(self,eventList):
        len = 0
        for i in eventList: len = len + i.length
        Event.__init__(self,len)
        self.eventList = eventList
    def addToEvents(self,events,startTime):
        for i in self.eventList:
            i.addToEvents(events,startTime)
            startTime = startTime + i.length
</pre>
<p>Now we'll have a class called <tt class=
"classname">GluedEvent</tt> (i.e. an event with some glue before
it), which has functionality to adjust the glue (stretch/shrink it
within its tolerance) in order to get the event to fit properly.
This needs an exception, which can be declared as an empty
class:</p>
<pre class="programlisting">
class StretchedTooFar: pass
</pre>
<p>Here is the <tt class="classname">GluedEvent</tt> class. Note
that there is functionality both for random adjustment (using a
Gaussian distribution) and for adjustment to avoid an overlap. It
is written in such a way that it is possible to call the adjustment
code more than once with different directions.</p>
<pre class="programlisting">
class GluedEvent:
    def __init__(self,glue,event):
        self.glue = glue
        self.event = event
        self.glue.adjustment = self.glue.preAdjustment = 0
    def randomPreAdjustment(self):
        if self.glue.length &lt; randomAdjustmentThreshold: return
        self.glue.preAdjustment = random.gauss(0,self.glue.plusMinus)
        if abs(self.glue.preAdjustment) &gt; self.glue.plusMinus:
            self.glue.preAdjustment = self.glue.plusMinus # err on the +ve side
    def adjustGlue(self,glueStart,schedule,direction):
        needMove = self.event.overlaps(glueStart+self.glue.length+self.glue.preAdjustment,schedule,direction)
        needMove=needMove*direction+self.glue.preAdjustment
        direction=sgn(needMove) ; needMove=abs(needMove)
        if needMove &gt; self.glue.plusMinus \
           or glueStart+needMove*direction &lt; 0 \
           or glueStart+self.glue.length+needMove*direction+self.event.length &gt; maxLenOfLesson:
            raise StretchedTooFar()
        self.glue.adjustment = needMove * direction
    def getAdjustedEnd(self,glueStart):
        return glueStart+self.glue.length+self.glue.adjustment+self.event.length
    def bookIn(self,schedule,glueStart):
        self.event.bookIn(schedule,glueStart+self.glue.length+self.glue.adjustment)
    def getEventStart(self,glueStart):
        return glueStart+self.glue.length+self.glue.adjustment
</pre>
<p>Now for a function called <tt class="function">setGlue</tt>
which &quot;sets&quot; (adjusts) the glue for all the events in a list. This
function needs to backtrack when a <tt class=
"exceptionname">StretchedTooFar</tt> exception is raised, and try a
different way of setting the glue. (Don't forget that we can always
try another direction.) If there is no solution at all then the
<tt class="exceptionname">StretchedTooFar</tt> exception will be
raised by the function itself.</p>
<pre class="programlisting">
def setGlue(gluedEventList, schedule, glueStart = 0):
    if not gluedEventList: return
    try:
        gluedEventList[0].randomPreAdjustment()
        gluedEventList[0].adjustGlue(glueStart,schedule,1)
        setGlue(gluedEventList[1:],schedule,gluedEventList[0].getAdjustedEnd(glueStart))
    except StretchedTooFar:
        gluedEventList[0].adjustGlue(glueStart,schedule,-1)
        setGlue(gluedEventList[1:],schedule,gluedEventList[0].getAdjustedEnd(glueStart))
</pre>
<p>There is a problem with the above algorithm. It fits the first
event into the first place it will fit, and then it will try to fit
the rest of the sequence based on the position of that first event.
If it fails to fit the rest of the sequence, it will fail
completely; it doesn't ever try to fit the first event in a
different place (later). I hacked around this with a wrapper
function; it's a bit rushed but it usually works (hey this is
Python):</p>
<pre class="programlisting">
def setGlue_wrapper(gluedEventList, schedule):
    sillyOffset = 0 # seconds
    worked = 0
    while (not worked) and sillyOffset &lt; maxLenOfLesson:
        try:
            setGlue(gluedEventList, schedule)
            worked = 1
        except StretchedTooFar:
            # try harder
            sillyOffset = sillyOffset + 10 # *** needs to be a constant
            gluedEventList[0].glue.length = sillyOffset
    if not worked: raise StretchedTooFar()
</pre>
<p>So now we can write a function that will book a list of
<tt class="classname">GluedEvent</tt>s into a lesson, adjusting the
glue as necessary:</p>
<pre class="programlisting">
def bookIn(gluedEventList,schedule):
    setGlue_wrapper(gluedEventList,schedule)
    glueStart = 0
    for i in gluedEventList:
        i.bookIn(schedule,glueStart)
        glueStart = i.getAdjustedEnd(glueStart)
</pre>
<p>And now the lesson itself, including the function to play the
events (I've cut this down a bit - taken out all the diagnostic
messages and the functionality to write the lesson to a sound file
as well as play it through the speakers - I don't know how big this
article is getting)</p>
<pre class="programlisting">
class Lesson:
    def __init__(self):
        self.schedule = Schedule()
        self.events = [] # list of (time,event)
        self.newWords = self.oldWords = 0
    def addSequence(self,gluedEventList):
        bookIn(gluedEventList,self.schedule)
        glueStart = 0
        for i in gluedEventList:
            startTime = i.getEventStart(glueStart)
            i.event.addToEvents(self.events,startTime)
            glueStart = i.getAdjustedEnd(glueStart)
    def play(self):
        self.events.sort()
        runner = sched.scheduler(time.time,time.sleep)
        for (t,event) in self.events:
            runner.enter(t,1,play,(event,))
        runner.run()
def play(event):
    event.play()
</pre>
<p>Now we need a type of <tt class="classname">Event</tt> that will
actually play something:</p>
<pre class="programlisting">
class WavEvent(Event):
    def __init__(self,file):
        self.file = file
        header = sndhdr.what(file)
        if not header: raise IOError(&quot;Problem opening wave file&quot;)
        (wtype,wrate,wchannels,wframes,wbits) = header
        divisor = wrate*wchannels*(wbits/8)
        o = open(file,'rb')
        fileLen = len(o.read())
        o.close()
        seconds = math.ceil((fileLen + 0.0) / divisor) # approx
        Event.__init__(self,seconds)
    def play(self):
        if winsound: winsound.PlaySound(self.file,winsound.SND_FILENAME)
        else: os.system(&quot;play %s&quot; % (self.file,))
</pre>
<p>And now all we have to do is make those <tt class=
"classname">GluedEventList</tt>s in a sensible manner. I'll leave
that to the next issue.</p>
</div>
</p>
<p><strong>Notes:</strong>&nbsp;</p>
<p><em>More fields may be available via dynamicdata ..</em></p>
</div>
</channel>
</rss>
