annotate host/Processor.cpp @ 17:3cbd40805795 tip

Remove obsolete stuff from README
author Chris Cannam
date Tue, 03 Dec 2013 16:33:08 +0000
parents dbbd2b473eee
children
rev   line source
cannam@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@0 2
cannam@0 3 #include "Processor.h"
cannam@0 4
Chris@16 5 #include <vamp-hostsdk/PluginLoader.h>
cannam@0 6
cannam@4 7 //#define DEBUG_RUN_PROCESSOR 1
cannam@4 8
Chris@16 9 using Vamp::HostExt::PluginLoader;
Chris@16 10
cannam@0 11 using std::cout;
cannam@0 12 using std::cerr;
cannam@0 13 using std::endl;
cannam@0 14
cannam@1 15 void printFeatures(int, int, int, int, Vamp::Plugin::FeatureSet &);
cannam@0 16 void transformInput(float *, size_t);
cannam@0 17 void fft(unsigned int, bool, double *, double *, double *, double *);
cannam@0 18
cannam@0 19 Processor::Processor(BufferingAudioCallbackRecordTarget *audioRecordTarget) :
cannam@0 20 m_exiting(false),
cannam@0 21 m_audioRecordTarget(audioRecordTarget),
cannam@0 22 m_nextNumber(1),
cannam@1 23 m_havePlugins(false),
cannam@1 24 m_minBlockSize(0),
cannam@1 25 m_maxBlockSize(0)
cannam@0 26 {
cannam@5 27 for (int i = 0; i < MAX_DISTINCT_STEP_SIZES; ++i) {
cannam@5 28 m_unusedReaders.insert(i);
cannam@5 29 }
cannam@0 30 }
cannam@0 31
cannam@0 32 Processor::~Processor()
cannam@0 33 {
cannam@0 34 m_exiting = true;
cannam@0 35 wait();
Chris@15 36
Chris@15 37 for (RuleSet::const_iterator i = m_rules.begin(); i != m_rules.end(); ++i) {
Chris@15 38 delete *i;
cannam@7 39 }
cannam@0 40 }
cannam@0 41
cannam@0 42 void
cannam@0 43 Processor::run()
cannam@0 44 {
cannam@0 45 while (!m_exiting) {
cannam@0 46
cannam@1 47 if (!m_havePlugins) {
cannam@0 48 msleep(100);
cannam@0 49 continue;
cannam@0 50 }
cannam@0 51
cannam@1 52 size_t nframes = 0;
cannam@0 53
cannam@1 54 for (StepSizeReaderMap::iterator i = m_stepSizeReaderMap.begin();
cannam@1 55 i != m_stepSizeReaderMap.end(); ++i) {
cannam@1 56 size_t here = m_audioRecordTarget->samplesReady(i->second);
cannam@1 57 if (here > nframes) {
cannam@1 58 nframes = here;
cannam@1 59 }
cannam@1 60 }
cannam@0 61
cannam@1 62 if (nframes > m_minBlockSize) {
cannam@1 63 if (!runPlugins()) {
cannam@1 64 msleep(10);
cannam@1 65 }
cannam@0 66 } else {
cannam@0 67 msleep(50);
cannam@0 68 }
cannam@5 69
cannam@5 70 m_mutex.lock();
cannam@5 71 for (ReaderSet::iterator i = m_unusedReaders.begin();
cannam@5 72 i != m_unusedReaders.end(); ++i) {
cannam@5 73 size_t ch = m_audioRecordTarget->getChannelCount();
cannam@5 74 for (size_t c = 0; c < ch; ++c) {
cannam@5 75 m_audioRecordTarget->skipAllSamples(c, *i);
cannam@5 76 }
cannam@5 77 }
cannam@5 78 m_mutex.unlock();
cannam@0 79 }
cannam@0 80 }
cannam@0 81
cannam@1 82 bool
cannam@0 83 Processor::runPlugins()
cannam@0 84 {
cannam@0 85 QMutexLocker locker(&m_mutex);
cannam@0 86
cannam@1 87 static std::map<int, size_t> frame; // reader -> frame
cannam@1 88 if (frame.empty()) {
cannam@1 89 for (int i = 0; i < MAX_DISTINCT_STEP_SIZES; ++i) {
cannam@1 90 frame[i] = 0;
cannam@1 91 }
cannam@1 92 }
cannam@0 93
cannam@0 94 size_t sr = m_audioRecordTarget->getSourceSampleRate();
cannam@0 95
cannam@1 96 // cerr << "max block size " << m_maxBlockSize << endl;
cannam@1 97
cannam@1 98 //!!!
cannam@0 99 size_t ch = m_audioRecordTarget->getChannelCount();
cannam@0 100 float **buffers = new float *[ch];
cannam@0 101 for (size_t c = 0; c < ch; ++c) {
cannam@1 102 buffers[c] = new float[m_maxBlockSize];
cannam@1 103 }
cannam@1 104
cannam@1 105 bool doneWork = false;
cannam@1 106
cannam@1 107 for (StepSizePluginMap::iterator i = m_processingMap.begin();
cannam@1 108 i != m_processingMap.end(); ++i) {
cannam@1 109
cannam@1 110 size_t step = i->first;
cannam@1 111 if (m_stepSizeReaderMap.find(step) == m_stepSizeReaderMap.end()) {
cannam@1 112 cerr << "ERROR: Unrecorded step size " << step << endl;
cannam@1 113 continue;
cannam@0 114 }
cannam@0 115
cannam@1 116 int reader = m_stepSizeReaderMap[step];
cannam@1 117 size_t maxBlock = 0;
cannam@0 118
cannam@1 119 for (BlockSizePluginMap::iterator j = i->second.begin();
cannam@1 120 j != i->second.end(); ++j) {
cannam@1 121
cannam@1 122 if (j == i->second.begin() || j->first > maxBlock) {
cannam@1 123 maxBlock = j->first;
cannam@0 124 }
cannam@0 125 }
cannam@0 126
cannam@1 127 if (maxBlock == 0) {
cannam@1 128 cerr << "ERROR: maxBlock == 0 for step " << step << endl;
cannam@1 129 continue;
cannam@1 130 }
cannam@0 131
cannam@1 132 // int counter = 0;
cannam@1 133
cannam@1 134 while (m_audioRecordTarget->samplesReady(reader) >= maxBlock) {
cannam@1 135 // && counter < 10) {
cannam@1 136
cannam@1 137 // ++counter;
cannam@1 138
cannam@4 139 #ifdef DEBUG_RUN_PROCESSOR
cannam@4 140 cerr << "enough samples for max block " << maxBlock
cannam@4 141 << " for step " << step << endl;
cannam@4 142 #endif
cannam@1 143
cannam@1 144 for (size_t c = 0; c < ch; ++c) {
cannam@5 145 size_t got = m_audioRecordTarget->peekSamples(c, maxBlock, buffers[c], reader);
cannam@5 146 if (got < maxBlock) {
cannam@5 147 cerr << "ERROR: Requested " << maxBlock << " samples on channel " << c << " reader " << reader << ", only got " << got << endl;
cannam@5 148 }
cannam@5 149 size_t skipped = m_audioRecordTarget->skipSamples(c, step, reader);
cannam@5 150 if (skipped < step) {
cannam@5 151 cerr << "ERROR: Requested skip of " << step << " samples on channel " << c << " reader " << reader << ", only skipped " << skipped << endl;
cannam@5 152 }
cannam@1 153 }
cannam@1 154
cannam@1 155 for (BlockSizePluginMap::iterator j = i->second.begin();
cannam@1 156 j != i->second.end(); ++j) {
cannam@1 157
cannam@1 158 size_t block = j->first;
cannam@1 159 size_t toUse = maxBlock;
cannam@1 160 size_t off = 0;
cannam@1 161
cannam@1 162 while (toUse >= block) {
cannam@1 163
cannam@14 164 bool silentInput = true;
cannam@14 165 for (size_t s = 0; s < block; ++s) {
cannam@14 166 for (size_t c = 0; c < ch; ++c) {
cannam@14 167 if (fabsf(buffers[c][s + off]) > 0.00001) {
cannam@14 168 silentInput = false;
cannam@14 169 break;
cannam@14 170 }
cannam@14 171 }
cannam@14 172 if (!silentInput) break;
cannam@14 173 }
cannam@14 174
cannam@1 175 for (PluginSet::iterator k = j->second.begin();
cannam@1 176 k != j->second.end(); ++k) {
cannam@1 177
cannam@4 178 #ifdef DEBUG_RUN_PROCESSOR
cannam@4 179 cerr << "block inner loop for block " << block
cannam@4 180 << " (toUse = " << toUse << ")" << endl;
cannam@4 181 #endif
cannam@1 182
cannam@1 183 Vamp::Plugin *plugin = *k;
cannam@1 184 Vamp::Plugin::FeatureSet fs;
cannam@1 185 Vamp::RealTime timestamp =
cannam@1 186 Vamp::RealTime::frame2RealTime(frame[reader], sr);
cannam@1 187
cannam@4 188 #ifdef DEBUG_RUN_PROCESSOR
Chris@16 189 cerr << "running " << plugin->getName() << " frame = " << frame[reader] << endl;
cannam@4 190 #endif
Chris@16 191 float *tmp[10];
Chris@16 192 for (size_t c = 0; c < ch; ++c) {
Chris@16 193 tmp[c] = buffers[c] + off;
cannam@5 194 }
Chris@16 195 fs = plugin->process(tmp, timestamp);
cannam@5 196
cannam@5 197 int pluginIndex = m_pluginRMap[plugin];
cannam@5 198
cannam@5 199 if (fs.empty()) {
cannam@5 200 for (OutputStateMap::iterator oi =
cannam@5 201 m_pluginStates[pluginIndex].begin();
cannam@5 202 oi != m_pluginStates[pluginIndex].end(); ++oi) {
cannam@5 203 int output = oi->first;
cannam@5 204 OutputState currentState = oi->second;
cannam@5 205
cannam@5 206 Vamp::RealTime rt =
cannam@5 207 Vamp::RealTime::frame2RealTime(frame[reader], sr);
cannam@5 208
cannam@13 209 // bool changed = currentState.present;
cannam@13 210 bool changed = false;
cannam@14 211 if (currentState.silentInput && !silentInput) {
cannam@14 212 changed = true;
cannam@14 213 }
cannam@5 214 Vamp::RealTime gap = rt - currentState.laststamp;
cannam@5 215 m_pluginStates[pluginIndex][output] =
cannam@14 216 OutputState(false, silentInput, changed, currentState.value, currentState.laststamp, currentState.gap);
cannam@1 217 }
cannam@1 218 }
cannam@5 219
cannam@1 220 for (Vamp::Plugin::FeatureSet::iterator fi = fs.begin();
cannam@1 221 fi != fs.end(); ++fi) {
cannam@4 222
cannam@4 223 int output = fi->first;
cannam@4 224
cannam@4 225 //!!! For each output, we need to step
cannam@4 226 //through the returned features one by one
cannam@4 227 //and update the plugin state for each,
cannam@4 228 //then run the rules each time a timestamp
cannam@4 229 //changes. But it has to do this with the
cannam@4 230 //features in the order of their
cannam@4 231 //timestamps, not in order of
cannam@4 232 //plugin/output enumeration.
cannam@4 233 //
cannam@4 234 // But we don't do that yet, we just use
cannam@4 235 // the first feature in the block (if any)
cannam@4 236
cannam@4 237 Vamp::RealTime rt =
cannam@4 238 Vamp::RealTime::frame2RealTime(frame[reader], sr);
cannam@4 239
cannam@4 240 Vamp::Plugin::FeatureList fl(fi->second);
cannam@4 241
cannam@4 242 OutputState currentState =
cannam@4 243 m_pluginStates[pluginIndex][output];
cannam@5 244
cannam@4 245 if (fl.empty()) {
cannam@13 246 // bool changed = currentState.present;
cannam@13 247 bool changed = false;
cannam@14 248 if (currentState.silentInput && !silentInput) {
cannam@14 249 changed = true;
cannam@14 250 }
cannam@4 251 Vamp::RealTime gap = rt - currentState.laststamp;
cannam@4 252 m_pluginStates[pluginIndex][output] =
cannam@14 253 OutputState(false, silentInput, changed, currentState.value, currentState.laststamp, currentState.gap);
cannam@4 254 } else {
cannam@4 255
cannam@4 256 if (fl[0].hasTimestamp) {
cannam@4 257 rt = fl[0].timestamp;
cannam@4 258 }
cannam@4 259 float value = 0.f;
cannam@4 260 if (!fl[0].values.empty()) {
cannam@4 261 value = fl[0].values[0];
cannam@4 262 }
cannam@5 263 bool changed =
cannam@13 264 // (!currentState.present ||
cannam@5 265 (fabsf(currentState.value - value) >
cannam@13 266 0.000001)
cannam@13 267 // )
cannam@13 268 ;
cannam@14 269 if (currentState.silentInput && !silentInput) {
cannam@14 270 changed = true;
cannam@14 271 }
cannam@13 272 if (changed) {
cannam@13 273 std::cerr << "changed: " <<currentState.value << " -> " << value << std::endl;
cannam@13 274 }
cannam@4 275 Vamp::RealTime gap = rt - currentState.laststamp;
cannam@9 276 // std::cerr << "gap = " << gap << std::endl;
cannam@4 277 m_pluginStates[pluginIndex][output] =
cannam@14 278 OutputState(true, silentInput, changed, value, rt, gap);
cannam@4 279 }
cannam@4 280
Chris@15 281 printFeatures(pluginIndex, frame[reader], sr, output, fs);
cannam@1 282 }
cannam@1 283
cannam@4 284 processRules();
cannam@4 285
cannam@1 286 doneWork = true;
cannam@1 287 }
cannam@1 288
cannam@1 289 toUse -= block;
cannam@1 290 off += block;
cannam@1 291 }
cannam@1 292 }
cannam@1 293
cannam@1 294 frame[reader] += step;
cannam@0 295 }
cannam@1 296
cannam@0 297 }
cannam@0 298
cannam@0 299 for (size_t c = 0; c < ch; ++c) {
cannam@0 300 delete[] buffers[c];
cannam@0 301 }
cannam@0 302 delete[] buffers;
cannam@1 303
cannam@1 304 return doneWork;
cannam@0 305 }
cannam@0 306
cannam@0 307 int
cannam@0 308 Processor::addPlugin(QString pluginId)
cannam@0 309 {
cannam@0 310 QMutexLocker locker(&m_mutex);
cannam@0 311
cannam@0 312 size_t sr = m_audioRecordTarget->getSourceSampleRate();
cannam@0 313
cannam@0 314 if (!sr) {
cannam@0 315 cerr << "ERROR: Processor::addPlugin: Source sample rate is not defined" << endl;
cannam@0 316 return 0;
cannam@0 317 }
cannam@0 318
Chris@16 319 if (pluginId.startsWith("vamp:")) {
Chris@16 320 pluginId = pluginId.right(pluginId.size() - 5);
cannam@0 321 }
cannam@0 322
Chris@16 323 PluginLoader *loader = PluginLoader::getInstance();
Chris@16 324 Vamp::Plugin *plugin = loader->loadPlugin
Chris@16 325 (pluginId.toStdString(), sr, PluginLoader::ADAPT_ALL_SAFE);
cannam@0 326
cannam@0 327 if (!plugin) {
cannam@0 328 cerr << "ERROR: Processor::addPlugin: Failed to instantiate plugin \"" << pluginId.toStdString() << "\"" << endl;
cannam@0 329 return 0;
cannam@0 330 }
cannam@0 331
cannam@1 332 size_t block = plugin->getPreferredBlockSize();
cannam@1 333 if (block == 0) block = m_audioRecordTarget->getSourceBlockSize();
cannam@0 334
cannam@1 335 size_t step = plugin->getPreferredStepSize();
Chris@16 336 if (step == 0) step = block;
cannam@0 337
cannam@1 338 if (m_stepSizeReaderMap.find(step) == m_stepSizeReaderMap.end()) {
cannam@5 339 if (m_unusedReaders.empty()) {
cannam@1 340 cerr << "ERROR: Processor::addPlugin: Run out of distinct step size slots: increase MAX_DISTINCT_STEP_SIZES and recompile" << endl;
cannam@1 341 delete plugin;
cannam@1 342 return 0;
cannam@1 343 }
cannam@5 344 int reader = *m_unusedReaders.begin();
cannam@5 345 m_unusedReaders.erase(reader);
cannam@5 346 m_stepSizeReaderMap[step] = reader;
cannam@1 347 }
cannam@0 348
cannam@1 349 size_t ch = m_audioRecordTarget->getChannelCount();
cannam@0 350
cannam@1 351 if (!plugin->initialise(ch, step, block)) {
cannam@1 352 cerr << "ERROR: Processor::addPlugin: Initialisation failed with step size " << step << ", block size " << block << ", channels " << ch << endl;
cannam@0 353 delete plugin;
cannam@0 354 return 0;
cannam@0 355 }
cannam@0 356
cannam@1 357 if (!m_havePlugins || block < m_minBlockSize) m_minBlockSize = block;
cannam@1 358 if (!m_havePlugins || block > m_maxBlockSize) m_maxBlockSize = block;
cannam@0 359
cannam@5 360 std::cerr << "initialised plugin \"" << pluginId.toStdString()
cannam@5 361 << "\" with step \"" << step << "\" block \"" << block << "\""
cannam@5 362 << std::endl;
cannam@5 363
cannam@0 364 int number = m_nextNumber++;
cannam@1 365 m_plugins[number] = plugin;
cannam@4 366 m_pluginRMap[plugin] = number;
cannam@1 367 m_processingMap[step][block].insert(plugin);
cannam@0 368
cannam@1 369 m_havePlugins = true;
cannam@0 370 return number;
cannam@0 371 }
cannam@0 372
cannam@0 373 void
cannam@0 374 Processor::removePlugin(int number)
cannam@0 375 {
cannam@0 376 QMutexLocker locker(&m_mutex);
cannam@0 377
cannam@0 378 if (m_plugins.find(number) == m_plugins.end()) {
cannam@0 379 cerr << "ERROR: Processor::removePlugin: No such plugin number "
cannam@0 380 << number << endl;
cannam@0 381 return;
cannam@0 382 }
cannam@0 383
cannam@0 384 Vamp::Plugin *plugin = m_plugins[number];
cannam@1 385 bool done = false;
cannam@1 386
cannam@1 387 for (StepSizePluginMap::iterator i = m_processingMap.begin();
cannam@1 388 i != m_processingMap.end(); ++i) {
cannam@1 389
cannam@1 390 BlockSizePluginMap::iterator j;
cannam@1 391
cannam@1 392 for (j = i->second.begin(); j != i->second.end(); ++j) {
cannam@1 393
cannam@1 394 if (j->second.find(plugin) != j->second.end()) {
cannam@1 395
cannam@1 396 j->second.erase(plugin);
cannam@1 397
cannam@1 398 if (j->second.empty()) { // no plugins with this step & block
cannam@1 399
cannam@1 400 i->second.erase(j->first);
cannam@1 401
cannam@1 402 if (i->second.empty()) { // no plugins with this step
cannam@1 403
cannam@1 404 m_processingMap.erase(i->first);
cannam@1 405 m_stepSizeReaderMap.erase(i->first);
cannam@5 406 m_unusedReaders.insert(i->first);
cannam@1 407 }
cannam@1 408 }
cannam@1 409
cannam@1 410 done = true;
cannam@1 411 break;
cannam@1 412 }
cannam@1 413 }
cannam@1 414
cannam@1 415 if (done) break;
cannam@1 416 }
cannam@1 417
cannam@0 418 m_plugins.erase(number);
cannam@4 419 m_pluginRMap.erase(plugin);
cannam@1 420 if (m_plugins.empty()) m_havePlugins = false;
cannam@0 421 delete plugin;
cannam@0 422 }
cannam@0 423
cannam@4 424 void
cannam@7 425 Processor::addRule(Rule *rule)
cannam@4 426 {
Chris@15 427 cerr << "adding rule " << rule << endl;
cannam@7 428 m_rules.insert(rule);
cannam@4 429 }
cannam@4 430
cannam@4 431 bool
cannam@4 432 Processor::processRules()
cannam@4 433 {
cannam@7 434 for (RuleSet::iterator i = m_rules.begin(); i != m_rules.end(); ++i) {
cannam@4 435
cannam@7 436 Rule *rule(*i);
cannam@4 437
cannam@7 438 bool passed = false;
cannam@13 439 bool oneChanged = false;
cannam@4 440
cannam@7 441 for (Rule::ConditionList::const_iterator j = rule->getConditions().begin();
cannam@7 442 j != rule->getConditions().end(); ++j) {
cannam@7 443
cannam@7 444 Condition condition(*j);
cannam@7 445
cannam@7 446 int pluginIndex = condition.getPluginIndex();
cannam@7 447 int outputNumber = condition.getOutputNumber();
cannam@7 448 Condition::Type type = condition.getType();
cannam@7 449 float argument = condition.getArgument();
cannam@7 450
cannam@7 451 OutputState state = m_pluginStates[pluginIndex][outputNumber];
cannam@4 452
cannam@5 453 #ifdef DEBUG_RUN_PROCESSOR
cannam@7 454 std::cerr << "Present = " << state.present << ", changed = " << state.changed << ", value = " << state.value << std::endl;
cannam@5 455 #endif
cannam@5 456
cannam@7 457 passed = false;
cannam@5 458
cannam@14 459 if (state.silentInput) {
cannam@14 460 break;
cannam@14 461 }
cannam@14 462
cannam@13 463 if (state.changed) {
cannam@13 464 // std::cerr << "State changed with present = " << state.present << ", value = " << state.value << std::endl;
cannam@13 465 oneChanged = true;
cannam@13 466 }
cannam@5 467
cannam@7 468 switch (type) {
cannam@5 469
cannam@7 470 case Condition::GreaterThan:
cannam@7 471 passed = (state.value > argument);
cannam@7 472 break;
cannam@7 473
cannam@7 474 case Condition::LessThan:
cannam@7 475 passed = (state.value < argument);
cannam@7 476 break;
cannam@7 477
cannam@7 478 case Condition::EqualTo:
cannam@7 479 passed = fabsf(state.value - argument) < 0.000001;
cannam@7 480 break;
cannam@7 481
cannam@7 482 case Condition::NotEqualTo:
cannam@7 483 passed = fabsf(state.value - argument) > 0.000001;
cannam@7 484 break;
cannam@7 485
cannam@7 486 case Condition::Present:
cannam@7 487 passed = state.present;
cannam@13 488 if (passed) oneChanged = true;
cannam@7 489 break;
cannam@7 490
cannam@7 491 case Condition::Changed:
cannam@13 492 passed = state.changed;
cannam@7 493 break;
cannam@7 494
cannam@7 495 case Condition::GapGreaterThan:
cannam@7 496 passed = (state.gap > Vamp::RealTime::fromSeconds(argument));
cannam@7 497 break;
cannam@7 498
cannam@7 499 case Condition::GapLessThan:
cannam@9 500 std::cout << "GapLessThan: gap is " << state.gap << ", argument is " << argument << std::endl;
cannam@7 501 passed = (state.gap < Vamp::RealTime::fromSeconds(argument));
cannam@7 502 break;
cannam@7 503 }
cannam@5 504
cannam@7 505 if (!passed) break;
cannam@5 506 }
cannam@5 507
cannam@13 508 if (passed && oneChanged) {
cannam@9 509 for (Rule::ActionList::const_iterator ai = rule->getActions().begin();
cannam@9 510 ai != rule->getActions().end(); ++ai) {
cannam@9 511 Action *action = *ai;
cannam@9 512 if (action) {
cannam@9 513 // std::cerr << "FIRING RULE: " << action->getName().toStdString() << "!" << std::endl;
cannam@9 514 action->fire();
cannam@9 515 }
cannam@7 516 }
cannam@4 517 }
cannam@4 518 }
cannam@4 519
cannam@4 520 return true;
cannam@4 521 }
cannam@0 522
cannam@0 523 void
cannam@1 524 printFeatures(int plugno, int frame, int sr, int output, Vamp::Plugin::FeatureSet &features)
cannam@0 525 {
cannam@9 526 return;//!!!
cannam@5 527
cannam@5 528 if (output > 0) return;//!!!
cannam@5 529
cannam@0 530 for (unsigned int i = 0; i < features[output].size(); ++i) {
cannam@0 531 Vamp::RealTime rt = Vamp::RealTime::frame2RealTime(frame, sr);
cannam@0 532 if (features[output][i].hasTimestamp) {
cannam@0 533 rt = features[output][i].timestamp;
cannam@0 534 }
cannam@1 535 cout << plugno << ":" << output << " -" << rt.toString() << ":";
cannam@0 536 for (unsigned int j = 0; j < features[output][i].values.size(); ++j) {
cannam@0 537 cout << " " << features[output][i].values[j];
cannam@0 538 }
cannam@0 539 cout << endl;
cannam@0 540 }
cannam@0 541 }
cannam@0 542