pix_record.cpp 11.6 KB
Newer Older
cclepper's avatar
cclepper committed
1 2 3 4
/*
 *  pix_record.cpp
 *
 */
5
#include "Gem/GemConfig.h"
cclepper's avatar
cclepper committed
6 7
#include "pix_record.h"

8
#include "Gem/State.h"
9
#include "Gem/Exception.h"
10

11 12 13
#include "plugins/PluginFactory.h"

#include <map>
14
#include <algorithm>
15

16
CPPEXTERN_NEW_WITH_GIMME(pix_record);
cclepper's avatar
cclepper committed
17

18 19
class pix_record :: PIMPL
{
zmoelnig's avatar
zmoelnig committed
20
public:
21 22 23 24
  PIMPL(void) {};
  ~PIMPL(void) {};

  struct codechandle {
25 26
    codechandle(gem::plugins::record*h, const std::string&c):handle(h),
      codec(c) {}
27

28
    gem::plugins::record*handle;
zmoelnig's avatar
zmoelnig committed
29
    std::string codec;
30
  };
zmoelnig's avatar
zmoelnig committed
31
  std::map<std::string, std::vector<codechandle> >m_codechandle;
32 33
  std::vector<std::string>m_codecs;

34 35
  void addCodecHandle(gem::plugins::record*handle, const std::string&codec)
  {
36 37 38
#ifdef __GNUC__
# warning better handling of duplicate codecs
#endif
zmoelnig's avatar
zmoelnig committed
39 40
    /* FIXME: we should generate a unique codec-ID, e.g. "<handlename>:<codec>" */
    m_codechandle[codec].push_back(codechandle(handle, codec));
41 42
    m_codecs.push_back(codec);
  }
43 44
  void clearCodecHandle(void)
  {
45 46 47 48
    m_codecs.clear();
    m_codechandle.clear();
  }

49 50
  static gem::any atom2any(t_atom*ap)
  {
51 52 53 54
    gem::any result;
    if(ap) {
      switch(ap->a_type) {
      case A_FLOAT:
55 56
        result=atom_getfloat(ap);
        break;
57
      case A_SYMBOL:
58
        result=std::string(atom_getsymbol(ap)->s_name);
59
        break;
60
      default:
61
        result=ap->a_w.w_gpointer;
62 63 64 65 66 67
      }
    }
    return result;
  }
  static void addProperties(gem::Properties&props, int argc, t_atom*argv)
  {
68 69 70
    if(!argc) {
      return;
    }
71

72 73 74 75 76 77
    if(argv->a_type != A_SYMBOL) {
      ::error("no key given...");
      return;
    }
    std::string key=std::string(atom_getsymbol(argv)->s_name);
    std::vector<gem::any> values;
78 79
    argc--;
    argv++;
80 81 82 83 84 85 86 87 88 89
    while(argc-->0) {
      values.push_back(atom2any(argv++));
    }
    switch(values.size()) {
    default:
      props.set(key, values);
      break;
    case 1:
      props.set(key, values[0]);
      break;
90 91 92 93 94
    case 0: {
      gem::any dummy;
      props.set(key, dummy);
    }
    break;
95 96
    }
  }
97

98 99
};

100
/////////////////////////////////////////////////////////
cclepper's avatar
cclepper committed
101 102 103 104 105 106 107
//
// pix_record
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
zmoelnig's avatar
zmoelnig committed
108
pix_record :: pix_record(int argc, t_atom *argv):
109 110
  m_banged(false), m_automatic(true),
  m_outNumFrames(NULL), m_outInfo(NULL),
zmoelnig's avatar
zmoelnig committed
111
  m_currentFrame(-1),
zmoelnig's avatar
zmoelnig committed
112
  m_maxFrames(0),
113 114 115
  m_recording(false),
  m_handle(NULL),
  m_pimpl(new PIMPL())
cclepper's avatar
cclepper committed
116
{
117
  if (argc != 0) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
118
    error("ignoring arguments");
cclepper's avatar
cclepper committed
119
  }
cclepper's avatar
cclepper committed
120
  m_outNumFrames = outlet_new(this->x_obj, 0);
121
  m_outInfo      = outlet_new(this->x_obj, 0);
cclepper's avatar
cclepper committed
122

123

IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
124
  m_handle=gem::plugins::record::getInstance();
125
  getCodecList();
cclepper's avatar
cclepper committed
126 127 128 129 130 131 132 133
}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_record :: ~pix_record()
{
134 135 136
  if(m_handle) {
    delete m_handle;
  }
137 138
  outlet_free(m_outNumFrames);
  outlet_free(m_outInfo);
139

140 141 142
  if(m_pimpl) {
    delete m_pimpl;
  }
143 144 145 146 147 148 149
}


/////////////////////////////////////////////////////////
// add backends
//
/////////////////////////////////////////////////////////
150 151
bool pix_record :: addHandle( std::vector<std::string>available,
                              std::string ID)
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
{
  unsigned int i=0;
  int count=0;

  std::vector<std::string>id;
  if(!ID.empty()) {
    // if requested 'cid' is in 'available' add it to the list of 'id's
    if(std::find(available.begin(), available.end(), ID)!=available.end()) {
      id.push_back(ID);
    } else {
      // request for an unavailable ID
      verbose(2, "backend '%s' unavailable", ID.c_str());
      return false;
    }
  } else {
    // no 'ID' given: add all available IDs
    id=available;
  }

  for(i=0; i<id.size(); i++) {
    std::string key=id[i];
    verbose(2, "trying to add '%s' as backend", key.c_str());
    if(std::find(m_ids.begin(), m_ids.end(), key)==m_ids.end()) {
      // not yet added, do so now!
176
      gem::plugins::record         *handle=NULL;
zmoelnig's avatar
zmoelnig committed
177
      startpost("backend #%d='%s'\t", m_allhandles.size(), key.c_str());
178
      try {
179
        handle=gem::PluginFactory<gem::plugins::record>::getInstance(key);
180
      } catch (GemException&ex) {
181 182
        startpost("(%s) ", ex.what());
        handle=NULL;
183
      }
184
      if(NULL==handle) {
185 186
        post("<--- DISABLED");
        break;
187 188 189 190
      }
      endpost();

      m_ids.push_back(key);
zmoelnig's avatar
zmoelnig committed
191
      m_allhandles.push_back(handle);
192
      count++;
193
      verbose(2, "added backend#%d '%s' @ %p", m_allhandles.size()-1,
194
              key.c_str(), handle);
195 196 197 198 199 200 201 202 203 204 205
    }
  }

  return (count>0);
}

//
// stops recording into the movie
//
void pix_record :: startRecording()
{
206 207 208
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
209

zmoelnig's avatar
zmoelnig committed
210 211 212 213 214
  if(m_filename.empty()) {
    error("start recording requested with no prior open");
    return;
  }

215
  // find a handle for the current settings (filename, codec, props)
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
216
  /* const std::string codec=m_codec; */
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
217 218
  stopRecording();

zmoelnig's avatar
zmoelnig committed
219
  m_currentFrame = 0;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
220 221
  // do not re-set the codec, if there is no need...
  /* m_handle->setCodec(codec); */
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
222
  if(m_handle->start(m_filename, m_props)) {
223 224
    m_filename=std::string("");
    m_recording=true;
zmoelnig's avatar
zmoelnig committed
225 226
  } else {
    post("unable to open '%s'", m_filename.c_str());
227
  }
cclepper's avatar
cclepper committed
228 229 230
}

//
231
// stops recording into the movie
cclepper's avatar
cclepper committed
232 233 234
//
void pix_record :: stopRecording()
{
235 236 237
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
238

239
  if(m_recording) {
240
    m_handle->stop();
241
    m_currentFrame = 0;
242
    outlet_float(m_outNumFrames,m_currentFrame);
243
    verbose(1, "movie written");
244
  }
cclepper's avatar
cclepper committed
245

246
  m_recording=false;
cclepper's avatar
cclepper committed
247 248 249 250 251 252 253 254 255
}


/////////////////////////////////////////////////////////
// render
//
/////////////////////////////////////////////////////////
void pix_record :: render(GemState *state)
{
256 257 258
  if(!m_handle || !m_recording) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
259

260
  //check if state exists
261 262 263
  if(!state) {
    return;
  }
zmoelnig's avatar
zmoelnig committed
264
  pixBlock*img=NULL;
265
  state->get(GemState::_PIX, img);
zmoelnig's avatar
zmoelnig committed
266

267
  if(!img || !img->image.data) {
zmoelnig's avatar
zmoelnig committed
268 269
    return;
  }
270

271
  if(m_banged||m_automatic) {
272
    //      if(m_maxFrames != 0 && m_currentFrame >= m_maxFrames) m_recordStop = 1;
zmoelnig's avatar
zmoelnig committed
273
    bool success=m_handle->write(&img->image);
274 275 276 277
    m_banged=false;

    if(success) {
      m_currentFrame++;
zmoelnig's avatar
zmoelnig committed
278
      outlet_float(m_outNumFrames,m_currentFrame);
279 280
    } else {
      stopRecording();
zmoelnig's avatar
zmoelnig committed
281 282
    }
  }
cclepper's avatar
cclepper committed
283 284 285
}

/////////////////////////////////////////////////////////
286
// Properties
cclepper's avatar
cclepper committed
287 288
//
/////////////////////////////////////////////////////////
289
void pix_record :: enumPropertiesMess()
cclepper's avatar
cclepper committed
290
{
291 292 293
  if(!m_handle) {
    return;
  }
294

IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
295
  gem::Properties props;
296
  if(!m_handle->enumProperties(props)) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
297
    return;
298
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
299 300 301 302 303 304 305 306 307

  t_atom ap[3];
  std::vector<std::string>keys=props.keys();

  SETFLOAT(ap+0, keys.size());
  outlet_anything(m_outInfo, gensym("numprops"), 1, ap);

  unsigned int i=0;
  for(i=0; i<keys.size(); i++) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
308
    int ac=2;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
309 310 311 312 313 314 315 316 317 318 319
    std::string key=keys[i];
    SETSYMBOL(ap+0, gensym(key.c_str()));
    switch(props.type(key)) {
    case gem::Properties::NONE:
      SETSYMBOL(ap+1, gensym("Bang"));
      break;
    case gem::Properties::DOUBLE: {
      double d=-1;
      SETSYMBOL(ap+1, gensym("Float"));
      /* LATER: get and show ranges */
      if(props.get(key, d)) {
320 321
        ac=3;
        SETFLOAT(ap+2, d);
322
      }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
323
    }
324
    break;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
325 326 327 328
    case gem::Properties::STRING: {
      SETSYMBOL(ap+1, gensym("Symbol"));
      std::string s;
      if(props.get(key, s)) {
329 330
        ac=3;
        SETSYMBOL(ap+2, gensym(s.c_str()));
331
      }
332
    }
333
    break;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
334 335 336 337 338
    default:
      SETSYMBOL(ap+1, gensym("unknown"));
      break;
    }
    outlet_anything(m_outInfo, gensym("property"), ac, ap);
339
  }
cclepper's avatar
cclepper committed
340
}
341
void pix_record :: setPropertiesMess(t_symbol*s, int argc, t_atom*argv)
342 343 344
{
  PIMPL::addProperties(m_props, argc, argv);
}
cclepper's avatar
cclepper committed
345

346
void pix_record :: clearPropertiesMess()
cclepper's avatar
cclepper committed
347
{
348
  m_props.clear();
cclepper's avatar
cclepper committed
349 350
}

351 352


353 354 355 356 357 358
/////////////////////////////////////////////////////////
// dialogMess
//
/////////////////////////////////////////////////////////
void pix_record :: dialogMess()
{
359 360 361
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
362

363
  if(!m_handle->dialog()) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
364
    error("unable to open settings dialog");
365 366
  }
}
367
/////////////////////////////////////////////////////////
368
// recordMess
369 370 371 372 373
//
/////////////////////////////////////////////////////////
void pix_record :: recordMess(bool on)
{
  if (on) {
374
    startRecording();
375
  } else {
376
    stopRecording();
377 378
  }
}
379 380 381 382 383 384 385

/////////////////////////////////////////////////////////
// spits out a list of installed codecs and stores them
//
/////////////////////////////////////////////////////////
void pix_record :: getCodecList()
{
386 387 388
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
389 390 391 392 393 394 395 396 397

  std::vector<std::string>codecs=m_handle->getCodecs();

  unsigned int i;
  for(i=0; i<codecs.size(); i++) {
    const std::string codecname=codecs[i];
    const std::string descr=m_handle->getCodecDescription(codecname);
    t_atom ap[3];

398 399
    verbose(2, "codec%d: '%s': %s", i, codecname.c_str(),
            (descr.empty()?"":descr.c_str()));
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
400 401 402 403
    SETFLOAT (ap+0, static_cast<t_float>(i));
    SETSYMBOL(ap+1, gensym(codecname.c_str()));
    SETSYMBOL(ap+2, gensym(descr.c_str()));
    outlet_anything(m_outInfo, gensym("codec"), 3, ap);
404
  }
405 406 407 408 409 410 411
}


/////////////////////////////////////////////////////////
// deals with the name of a codec
//
/////////////////////////////////////////////////////////
412
void pix_record :: codecMess(t_atom *argv)
413
{
414 415 416
  if(!m_handle) {
    return;
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
417

418
#ifdef __GNUC__
zmoelnig's avatar
zmoelnig committed
419
#warning codecMess is a mess
420
#endif
zmoelnig's avatar
zmoelnig committed
421 422 423 424 425 426 427 428 429 430 431 432 433
  /*
   * allow setting of codec without handle
   */

  /*
   * codecMess should do the following:
   *  find "valid" handles (those that support the given codec)
   *  copy all valid handles from m_allhandles to m_handles
   *  query all valid handles for settable properties
   *
   * if a special codec is given (e.g. none at all), all handles are valid
   */

434 435 436 437
  std::string sid;

  if (A_SYMBOL==argv->a_type) {
    sid=std::string(atom_getsymbol(argv)->s_name);
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
438
  } else if (A_FLOAT==argv->a_type) {
439
    int id=atom_getint(argv);
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
440
    std::vector<std::string>codecs=m_handle->getCodecs();
441
    if(id>0 && ((unsigned int)id)<codecs.size()) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
442
      sid=codecs[id];
443
    } else {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
444 445
      error("invalid codec# %d (0..%d)", id, codecs.size());
      return;
446 447
    }
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
448
  if(m_handle->setCodec(sid)) {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
449
    m_codec=sid;
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
450
    verbose(1, "successfully set codec '%s'", sid.c_str());
451
  } else {
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
452 453
    error("couldn't find a valid backend for codec '%s'", sid.c_str());
    return;
454
  }
IOhannes m zmölnig's avatar
IOhannes m zmölnig committed
455
  enumPropertiesMess();
456 457
}

458
void pix_record :: fileMess(t_symbol*s, int argc, t_atom *argv)
cclepper's avatar
cclepper committed
459
{
zmoelnig's avatar
zmoelnig committed
460
  /* LATER let the record()-handles chose whether they accept an open request
461
   * and then try other handles (if available)
462
   * this would allow us to use this object for streaming, virtual output devices,...
463
   */
464 465
  if(argc) {
    m_filename=std::string(atom_getsymbol(argv)->s_name);
cclepper's avatar
cclepper committed
466 467 468 469 470 471 472 473 474
  }
}

/////////////////////////////////////////////////////////
// static member functions
//
/////////////////////////////////////////////////////////
void pix_record :: obj_setupCallback(t_class *classPtr)
{
475
  CPPEXTERN_MSG (classPtr, "file", fileMess);
cclepper's avatar
cclepper committed
476

477 478
  CPPEXTERN_MSG1(classPtr, "auto", autoMess, bool);
  CPPEXTERN_MSG0(classPtr, "bang", bangMess);
479 480 481
  CPPEXTERN_MSG1(classPtr, "record", recordMess, bool);
  CPPEXTERN_MSG0(classPtr, "dialog", dialogMess);
  CPPEXTERN_MSG0(classPtr, "codeclist", getCodecList);
482 483
  class_addmethod(classPtr,
                  reinterpret_cast<t_method>(&pix_record::codecMessCallback),
484
                  gensym("codec"), A_GIMME, A_NULL);
485

486
  CPPEXTERN_MSG0(classPtr, "proplist", enumPropertiesMess);
487
  CPPEXTERN_MSG0(classPtr, "enumProps", enumPropertiesMess);
488
  CPPEXTERN_MSG (classPtr, "set", setPropertiesMess);
cclepper's avatar
cclepper committed
489

490
  CPPEXTERN_MSG0(classPtr, "clearProps", clearPropertiesMess);
491
  CPPEXTERN_MSG0(classPtr, "clearprops", clearPropertiesMess);
cclepper's avatar
cclepper committed
492 493
}

494
void pix_record :: bangMess(void)
495
{
496
  m_banged=true;
497
}
498
void pix_record :: autoMess(bool on)
499
{
500
  m_automatic=on;
501 502
}

503 504
void pix_record :: codecMessCallback(void *data, t_symbol *s, int argc,
                                     t_atom *argv)
505
{
506
  if(argc) {
507
    GetMyClass(data)->codecMess(argv);
508
  }
509
}