implemented loveletter module
Stefan Schuermans

Stefan Schuermans commited on 2011-12-23 00:38:54
Showing 11 changed files, with 723 additions and 0 deletions.

... ...
@@ -99,6 +99,7 @@
99 99
       <ul>
100 100
         <li><a href="canvas.html">Canvas</a></li>
101 101
         <li><a href="flexipix.html">FlexiPix</a></li>
102
+        <li><a href="loveletter.html">Loveletter</a></li>
102 103
         <li><a href="opprinter.html">Operator Connection Printer</a></li>
103 104
         <li><a href="output.html">Output</a></li>
104 105
         <li><a href="player.html">Player</a></li>
... ...
@@ -0,0 +1,48 @@
1
+<html>
2
+  <head>
3
+    <title>Blinker - Loveletter</title>
4
+  </head>
5
+  <body>
6
+    <h1>Blinker - Loveletter</h1>
7
+    <p>
8
+      The loveletter module plays Blinken movies on demand.
9
+      An operator connection is used to submit a movie number
10
+      (prefixed with a star and followed by ahash, e.g. <code>*123#</code>).
11
+      The movie with this number will then be played until another
12
+      number is requested or the connection is closed.
13
+    </p>
14
+    <h2>Configuration</h2>
15
+    <p>
16
+      The configuration of the loveletter module with name <code>NAME</code>
17
+      is located in the <code>loveletters/NAME</code> subdirectory.
18
+    </p>
19
+    <h3>Movies</h3>
20
+    <p>
21
+      The movies available for on-demand play are located in the subdirectory
22
+      <code>movies</code>.
23
+      The movie number is derived from the filename by leaving out all
24
+      non-digit characters.
25
+      Please make sure that there are not two different movies with the
26
+      same derived number (e.g. <code>123.bml</code> and <code>123.bbm</code>.
27
+    </p>
28
+    <h3>Output Stream</h3>
29
+    <p>
30
+      The file <code>outstream</code> contains the name of the stream to
31
+      send the frames to.
32
+    </p>
33
+    <h3>Halt Stream</h3>
34
+    <p>
35
+      The playing movie can be halted whenever another stream is active.
36
+      Therefore, the name of the other stream is written to the file
37
+      <code>haltstream</code>.
38
+      When the halt stream becoms active, the current frame is frozen,
39
+      i.e. the frame is kept.
40
+      When the halt stream ends, the movie resumes with the remaining
41
+      time for the current frame and continues as usual afterwards.
42
+      <br>
43
+      If this feature is not needed, the file <code>haltstream</code>
44
+      should not exist.
45
+    </p>
46
+  </body>
47
+</html>
48
+
... ...
@@ -0,0 +1,334 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <list>
7
+#include <string>
8
+
9
+#include <BlinkenLib/BlinkenFrame.h>
10
+#include <BlinkenLib/BlinkenMovie.h>
11
+
12
+#include "Directory.h"
13
+#include "File.h"
14
+#include "InStreamFile.h"
15
+#include "ListTracker.h"
16
+#include "ListTracker_impl.h"
17
+#include "Loveletter.h"
18
+#include "LoveletterMovie.h"
19
+#include "Mgrs.h"
20
+#include "Module.h"
21
+#include "OutStreamFile.h"
22
+#include "StreamRecv.h"
23
+#include "Time.h"
24
+#include "TimeCallee.h"
25
+
26
+namespace Blinker {
27
+
28
+/**
29
+ * @brief constructor
30
+ * @param[in] name module name
31
+ * @param[in] mgrs managers
32
+ * @param[in] dirBase base directory
33
+ */
34
+Loveletter::Loveletter(const std::string &name, Mgrs &mgrs,
35
+               const Directory &dirBase):
36
+  Module(name, mgrs, dirBase),
37
+  m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr),
38
+  m_fileHaltStream(dirBase.getFile("haltstream"), mgrs.m_streamMgr),
39
+  m_movieTracker(*this, dirBase.getSubdir("movies")),
40
+  m_curValid(false),
41
+  m_pCurMovie(NULL),
42
+  m_curFrame(0),
43
+  m_curChange(false),
44
+  m_halted(false),
45
+  m_pOpConn(NULL)
46
+{
47
+  // load movies
48
+  m_fileHaltStream.setStreamRecv(this);
49
+  m_movieTracker.init();
50
+  checkCurChanged();
51
+
52
+  // open operator connection interface
53
+  m_mgrs.m_opMgr.open(m_name, this);
54
+}
55
+
56
+/// virtual destructor
57
+Loveletter::~Loveletter()
58
+{
59
+  // close operator connection interface
60
+  m_mgrs.m_opMgr.close(m_name);
61
+  closeOpConn();
62
+
63
+  // cancel time callback request
64
+  m_mgrs.m_callMgr.cancelTimeCall(this);
65
+
66
+  // free all movies
67
+  m_movieTracker.clear();
68
+  m_fileHaltStream.setStreamRecv(NULL);
69
+}
70
+
71
+/// check for update of configuration
72
+void Loveletter::updateConfig()
73
+{
74
+  // output stream name file was modified -> re-get output stream
75
+  if (m_fileOutStream.checkModified()) {
76
+    m_fileOutStream.update();
77
+    sendFrame();
78
+  }
79
+
80
+  // halt stream name file was modified -> re-get halt stream
81
+  if (m_fileHaltStream.checkModified())
82
+    m_fileHaltStream.update();
83
+
84
+  // movie update
85
+  m_movieTracker.updateConfig();
86
+  checkCurChanged();
87
+}
88
+
89
+/**
90
+ * @brief set current frame
91
+ * @param[in] stream stream name
92
+ * @param[in] pFrame current frame (NULL for none)
93
+ */
94
+void Loveletter::setFrame(const std::string &stream, stBlinkenFrame *pFrame)
95
+{
96
+  // this is coming from the halt stream, which will halt playing
97
+  // whenever a frame is available on this halt stream
98
+
99
+  // halt stream came to life -> halt playing
100
+  if (pFrame && !m_halted) {
101
+    m_halted = true;
102
+    if (m_curValid) {
103
+      // store remaining frame time
104
+      m_remainTime = m_nextTime - Time::now();
105
+      if (m_remainTime < Time::zero)
106
+        m_remainTime = Time::zero;
107
+    }
108
+    // cancel time call
109
+    m_mgrs.m_callMgr.cancelTimeCall(this);
110
+  }
111
+
112
+  // halt stream ended -> continue playing
113
+  else if (!pFrame && m_halted) {
114
+    m_halted = false;
115
+    if (m_curValid) {
116
+      // determine time for next frame
117
+      m_nextTime = Time::now() + m_remainTime;
118
+      // schedule time call
119
+      m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
120
+    }
121
+  }
122
+
123
+  (void)stream; // unused
124
+}
125
+
126
+/// callback when requested time reached
127
+void Loveletter::timeCall()
128
+{
129
+  // leave if halted
130
+  if (m_halted)
131
+    return;
132
+
133
+  // leave if time is not yet ready to next frame
134
+  if (Time::now() < m_nextTime) {
135
+    // request call at time for next frame
136
+    m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
137
+    return;
138
+  }
139
+
140
+  // go to next frame
141
+  ++m_curFrame;
142
+
143
+  // process new current frame
144
+  procFrame();
145
+}
146
+
147
+/**
148
+ * @brief check if accepting new operator connction is possible
149
+ * @param[in] name operator interface name
150
+ * @return if accepting new connection is possible
151
+ */
152
+bool Loveletter::acceptNewOpConn(const std::string &name)
153
+{
154
+  // accept new connection if none active
155
+  return !m_pOpConn;
156
+  (void)name; // unused
157
+}
158
+
159
+/**
160
+ * @brief new operator connection
161
+ * @param[in] name operator interface name
162
+ * @param[in] pConn operator connection object
163
+ */
164
+void Loveletter::newOpConn(const std::string &name, OpConn *pConn)
165
+{
166
+  closeOpConn(); // close old connection (to be on the safe side)
167
+  m_pOpConn = pConn; // remember new connection
168
+  (void)name; // unused
169
+}
170
+
171
+/**
172
+ * @brief key command received on operator connection
173
+ * @param[in] pConn operator connection object
174
+ * @param[in] key key that was pressed
175
+ */
176
+void Loveletter::opConnRecvKey(OpConn *pConn, char key)
177
+{
178
+  switch (key) {
179
+    // begin new movie number
180
+    case '*':
181
+      m_movieNumber.clear();
182
+      break;
183
+    // add digit to novie number
184
+    case '0':
185
+    case '1':
186
+    case '2':
187
+    case '3':
188
+    case '4':
189
+    case '5':
190
+    case '6':
191
+    case '7':
192
+    case '8':
193
+    case '9':
194
+      m_movieNumber += key;
195
+      break;
196
+    // play movie
197
+    case '#':
198
+      startPlaying();
199
+      break;
200
+  }
201
+  (void)pConn; // unused
202
+}
203
+
204
+/**
205
+ * @brief play command received on operator connection
206
+ * @param[in] pConn operator connection object
207
+ * @param[in] sound name of sound to play
208
+ */
209
+void Loveletter::opConnRecvPlay(OpConn *pConn, const std::string &sound)
210
+{
211
+  // this events does not make sense in this direction, ignore it
212
+  (void)pConn; // unused
213
+  (void)sound; // unused
214
+}
215
+
216
+/**
217
+ * @brief operator connection is closed
218
+ * @param[in] pConn operator connection object
219
+ */
220
+void Loveletter::opConnClose(OpConn *pConn)
221
+{
222
+  m_pOpConn = NULL;
223
+  stopPlaying();
224
+  (void)pConn; // unused
225
+}
226
+
227
+/// check if current movie changed and react
228
+void Loveletter::checkCurChanged()
229
+{
230
+  // current movie changed
231
+  if (m_curChange) {
232
+    m_curChange = false;
233
+
234
+    // go to begin of new current movie and start playing now
235
+    m_curFrame = 0;
236
+    m_remainTime = Time::zero;
237
+    m_nextTime = Time::now();
238
+    procFrame();
239
+
240
+  } // if (m_curChange)
241
+}
242
+
243
+/// process current frame
244
+void Loveletter::procFrame()
245
+{
246
+  // a movie is selected and this is loaded
247
+  if (m_pCurMovie && m_pCurMovie->m_pMovie) {
248
+    int frameCnt = BlinkenMovieGetFrameCnt(m_pCurMovie->m_pMovie);
249
+    // movie not empty
250
+    if (frameCnt > 0) {
251
+      // movie finsihed -> restart
252
+      if (m_curFrame >= frameCnt)
253
+        m_curFrame = 0;
254
+      // valid
255
+      m_curValid = true;
256
+    }
257
+    // movie empty -> not valid
258
+    else
259
+      m_curValid = false;
260
+  }
261
+  // no movie selected or selected one not loaded -> not valid
262
+  else
263
+    m_curValid = false;
264
+
265
+  // send new frame to stream
266
+  sendFrame();
267
+
268
+  // if a frame is there
269
+  if (m_curValid) {
270
+    // get frame time and calculate absolute time for next frame
271
+    stBlinkenFrame *pFrame =
272
+      BlinkenMovieGetFrame(m_pCurMovie->m_pMovie, m_curFrame);
273
+    m_remainTime.fromMs(BlinkenFrameGetDuration(pFrame));
274
+    m_nextTime += m_remainTime;
275
+    // request call at time for next frame
276
+    if (!m_halted)
277
+      m_mgrs.m_callMgr.requestTimeCall(this, m_nextTime);
278
+  }
279
+}
280
+
281
+/// send current frame to output stream
282
+void Loveletter::sendFrame()
283
+{
284
+  // frame available -> send it
285
+  if (m_curValid) {
286
+    stBlinkenFrame *pFrame =
287
+      BlinkenMovieGetFrame(m_pCurMovie->m_pMovie, m_curFrame);
288
+    m_fileOutStream.setFrame(pFrame);
289
+  }
290
+  // no frame available -> send this information
291
+  else
292
+    m_fileOutStream.setFrame(NULL);
293
+}
294
+
295
+/// close current operator connection
296
+void Loveletter::closeOpConn()
297
+{
298
+  if (m_pOpConn) {
299
+    m_pOpConn->close();
300
+    m_pOpConn = NULL;
301
+  }
302
+
303
+  stopPlaying();
304
+};
305
+
306
+/// start playing movie (indictaed by m_movieNumber)
307
+void Loveletter::startPlaying()
308
+{
309
+  // find movie and make it current
310
+  MovieMap::const_iterator itMovie = m_movieMap.find(m_movieNumber);
311
+  if (itMovie == m_movieMap.end())
312
+    m_pCurMovie = NULL; // not found -> no movie
313
+  else
314
+    m_pCurMovie = itMovie->second;
315
+
316
+  // start playing movie
317
+  m_curChange = true;
318
+  checkCurChanged();
319
+}
320
+
321
+/// stop playing movie
322
+void Loveletter::stopPlaying()
323
+{
324
+  // forget movie number
325
+  m_movieNumber.clear();
326
+
327
+  // stop playing movie
328
+  m_pCurMovie = NULL;
329
+  m_curChange = true;
330
+  checkCurChanged();
331
+}
332
+
333
+} // namespace Blinker
334
+
... ...
@@ -0,0 +1,156 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_LOVELETTER_H
7
+#define BLINKER_LOVELETTER_H
8
+
9
+#include <list>
10
+#include <map>
11
+#include <string>
12
+
13
+#include <BlinkenLib/BlinkenMovie.h>
14
+
15
+#include "Directory.h"
16
+#include "File.h"
17
+#include "InStreamFile.h"
18
+#include "ListTracker.h"
19
+#include "Mgrs.h"
20
+#include "Module.h"
21
+#include "OpConn.h"
22
+#include "OpConnIf.h"
23
+#include "OpReqIf.h"
24
+#include "OutStreamFile.h"
25
+#include "StreamRecv.h"
26
+#include "Time.h"
27
+#include "TimeCallee.h"
28
+
29
+namespace Blinker {
30
+
31
+/// on demand movie player
32
+class Loveletter: public Module, public OpReqIf, public StreamRecv,
33
+                  public TimeCallee
34
+{
35
+protected:
36
+  /// movie
37
+  class Movie;
38
+
39
+  /// movie tracker
40
+  typedef ListTracker<Loveletter, Movie, File> MovieTracker;
41
+
42
+  /// movie iterator
43
+  typedef MovieTracker::ListIt MovieIt;
44
+
45
+  /// map from movie number to movie
46
+  typedef std::map<std::string, Movie *> MovieMap;
47
+
48
+public:
49
+  /**
50
+   * @brief constructor
51
+   * @param[in] name module name
52
+   * @param[in] mgrs managers
53
+   * @param[in] dirBase base directory
54
+   */
55
+  Loveletter(const std::string &name, Mgrs &mgrs, const Directory &dirBase);
56
+
57
+  /// virtual destructor
58
+  virtual ~Loveletter();
59
+
60
+private:
61
+  /// copy constructor disabled
62
+  Loveletter(const Loveletter &that);
63
+
64
+  /// assignment operator disabled
65
+  const Loveletter & operator=(const Loveletter &that);
66
+
67
+public:
68
+  /// check for update of configuration
69
+  virtual void updateConfig();
70
+
71
+  /**
72
+   * @brief set current frame
73
+   * @param[in] stream stream name
74
+   * @param[in] pFrame current frame (NULL for none)
75
+   */
76
+  virtual void setFrame(const std::string &stream, stBlinkenFrame *pFrame);
77
+
78
+  /// callback when requested time reached
79
+  virtual void timeCall();
80
+
81
+  /**
82
+   * @brief check if accepting new operator connction is possible
83
+   * @param[in] name operator interface name
84
+   * @return if accepting new connection is possible
85
+   */
86
+  virtual bool acceptNewOpConn(const std::string &name);
87
+
88
+  /**
89
+   * @brief new operator connection
90
+   * @param[in] name operator interface name
91
+   * @param[in] pConn operator connection object
92
+   */
93
+  virtual void newOpConn(const std::string &name, OpConn *pConn);
94
+
95
+  /**
96
+   * @brief key command received on operator connection
97
+   * @param[in] pConn operator connection object
98
+   * @param[in] key key that was pressed
99
+   */
100
+  virtual void opConnRecvKey(OpConn *pConn, char key);
101
+
102
+  /**
103
+   * @brief play command received on operator connection
104
+   * @param[in] pConn operator connection object
105
+   * @param[in] sound name of sound to play
106
+   */
107
+  virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound);
108
+
109
+  /**
110
+   * @brief operator connection is closed
111
+   * @param[in] pConn operator connection object
112
+   */
113
+  virtual void opConnClose(OpConn *pConn);
114
+
115
+protected:
116
+  /// check if current movie changed and react
117
+  void checkCurChanged();
118
+
119
+  /// process current frame
120
+  void procFrame();
121
+
122
+  /// send current frame to output stream
123
+  void sendFrame();
124
+
125
+  /// close current operator connection
126
+  void closeOpConn();
127
+
128
+  /// start playing movie (indictaed by m_movieNumber)
129
+  void startPlaying();
130
+
131
+  /// stop playing movie
132
+  void stopPlaying();
133
+
134
+protected:
135
+  OutStreamFile m_fileOutStream;  ///< output stream name file
136
+  InStreamFile  m_fileHaltStream; /**< halt stream name file
137
+                                       (playing halts if stream active) */
138
+  MovieTracker  m_movieTracker;   ///< current movies
139
+  MovieMap      m_movieMap;       ///< map from movie number to movie
140
+  bool          m_curValid;       ///< if there is a current frame
141
+  Movie         *m_pCurMovie;     ///< current movie or NULL
142
+  int           m_curFrame;       ///< current frame in movie
143
+  bool          m_curChange;      ///< current movie changed
144
+  bool          m_halted;         ///< if playing is halted
145
+  Time          m_remainTime;     /**< remaining time of current frame
146
+                                       (valid if m_curValid && m_halted) */
147
+  Time          m_nextTime;       /**< when to show next frame
148
+                                       (valid if m_curValid && !m_halted) */
149
+  OpConn        *m_pOpConn;       ///< current operator connection or NULL
150
+  std::string   m_movieNumber;    ///< number of movie to play
151
+}; // class Loveletter
152
+
153
+} // namespace Blinker
154
+
155
+#endif // #ifndef BLINKER_LOVELETTER_H
156
+
... ...
@@ -0,0 +1,106 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <string>
7
+
8
+#include <BlinkenLib/BlinkenFrame.h>
9
+#include <BlinkenLib/BlinkenMovie.h>
10
+
11
+#include "File.h"
12
+#include "Loveletter.h"
13
+#include "LoveletterMovie.h"
14
+
15
+namespace Blinker {
16
+
17
+/**
18
+ * @brief constructor
19
+ * @param[in] loveletter owning loveletter module
20
+ * @param[in] name name of movie
21
+ * @param[in] file movie file
22
+ */
23
+Loveletter::Movie::Movie(Loveletter &loveletter, const std::string &name,
24
+                         const File &file):
25
+  m_loveletter(loveletter),
26
+  m_name(name),
27
+  m_file(file),
28
+  m_addedToMap(false),
29
+  m_pMovie(NULL)
30
+{
31
+  // add movie to map
32
+  name2number(m_name, m_number);
33
+  if (m_loveletter.m_movieMap.find(m_number) ==
34
+      m_loveletter.m_movieMap.end()) {
35
+    m_loveletter.m_movieMap[m_number] = this;
36
+    m_addedToMap = true;
37
+  }
38
+
39
+  // load movie
40
+  load();
41
+}
42
+
43
+/// destructor
44
+Loveletter::Movie::~Movie()
45
+{
46
+  // check if this is the current movie
47
+  if (m_loveletter.m_pCurMovie == this) {
48
+    // select no movie
49
+    m_loveletter.m_pCurMovie = NULL;
50
+    m_loveletter.m_curChange = true;
51
+  }
52
+
53
+  // free movie
54
+  free();
55
+
56
+  // remove movie from map
57
+  if (m_addedToMap)
58
+    m_loveletter.m_movieMap.erase(m_number);
59
+}
60
+
61
+/// check for update of configuration
62
+void Loveletter::Movie::updateConfig()
63
+{
64
+  // movie file was modified
65
+  if (m_file.checkModified()) {
66
+    // load new movie
67
+    load();
68
+    // if this is the current movie, remember that it changed
69
+    if (m_loveletter.m_pCurMovie == this)
70
+      m_loveletter.m_curChange = true;
71
+  }
72
+}
73
+
74
+/// load movie from current file
75
+void Loveletter::Movie::load()
76
+{
77
+  free();
78
+  m_pMovie = BlinkenMovieLoad(m_file.getPath().c_str());
79
+}
80
+
81
+/// free current movie
82
+void Loveletter::Movie::free()
83
+{
84
+  if (m_pMovie) {
85
+    BlinkenMovieFree(m_pMovie);
86
+    m_pMovie = NULL;
87
+  }
88
+}
89
+
90
+/**
91
+ * @brief convert movie name to movie number
92
+ * @param[in] name movie name
93
+ * @param[out] number movie number (name without all non-digits)
94
+ */
95
+void Loveletter::Movie::name2number(const std::string &name,
96
+                                    std::string &number)
97
+{
98
+  number.clear();
99
+  for (std::string::const_iterator pos = name.begin();
100
+       pos != name.end(); ++pos)
101
+    if (*pos >= '0' && *pos <= '9')
102
+      number += *pos;
103
+}
104
+
105
+} // namespace Blinker
106
+
... ...
@@ -0,0 +1,74 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_LOVELETTERMOVIE_H
7
+#define BLINKER_LOVELETTERMOVIE_H
8
+
9
+#include <string>
10
+
11
+#include <BlinkenLib/BlinkenFrame.h>
12
+#include <BlinkenLib/BlinkenMovie.h>
13
+
14
+#include "File.h"
15
+#include "Loveletter.h"
16
+
17
+namespace Blinker {
18
+
19
+/// movie in loveletters
20
+class Loveletter::Movie
21
+{
22
+public:
23
+  /**
24
+   * @brief constructor
25
+   * @param[in] loveletter owning loveletter module
26
+   * @param[in] name name of movie
27
+   * @param[in] file movie file
28
+   */
29
+  Movie(Loveletter &loveletter, const std::string &name,
30
+        const File &file);
31
+
32
+  /// destructor
33
+  ~Movie();
34
+
35
+private:
36
+  /// copy constructor disabled
37
+  Movie(const Movie &that);
38
+
39
+  /// assignment operator disabled
40
+  const Movie & operator=(const Movie &that);
41
+
42
+public:
43
+  /// check for update of configuration
44
+  void updateConfig();
45
+
46
+protected:
47
+  /// load movie from current file
48
+  void load();
49
+
50
+  /// free current movie
51
+  void free();
52
+
53
+  /**
54
+   * @brief convert movie name to movie number
55
+   * @param[in] name movie name
56
+   * @param[out] number movie number (name without all non-digits)
57
+   */
58
+  static void name2number(const std::string &name, std::string &number);
59
+
60
+protected:
61
+  Loveletter  &m_loveletter; ///< owning loveletter module
62
+  std::string m_name;        ///< name of movie
63
+  File        m_file;        ///< movie file
64
+  std::string m_number;      ///< number of movie
65
+  bool        m_addedToMap;  ///< if movie number could be added to movie map
66
+
67
+public:
68
+  stBlinkenMovie *m_pMovie; ///< loaded movie object
69
+}; // class Loveletter::Movie
70
+
71
+} // namespace Blinker
72
+
73
+#endif // #ifndef BLINKER_LOVELETTERMOVIE_H
74
+
... ...
@@ -9,6 +9,7 @@
9 9
 #include "Canvas.h"
10 10
 #include "Directory.h"
11 11
 #include "FlexiPix.h"
12
+#include "Loveletter.h"
12 13
 #include "Mgrs.h"
13 14
 #include "ModuleMgr.h"
14 15
 #include "ModuleMgr_impl.h"
... ...
@@ -36,6 +37,7 @@ void run(const std::string &dirConfig)
36 37
 
37 38
   MODULEMGR(Canvas,       canvases);
38 39
   MODULEMGR(FlexiPix,     flexipixes);
40
+  MODULEMGR(Loveletter,   loveletters);
39 41
   MODULEMGR(OpPrinter,    opprinters);
40 42
   MODULEMGR(Output,       outputs);
41 43
   MODULEMGR(Player,       players);
42 44