comparison audioio/AudioGenerator.cpp @ 275:6c6950bd7d53

Refactor to combine the two synthetic playback methods, with a single separate method to extract the basic note data from model
author Chris Cannam
date Mon, 15 Apr 2013 17:23:14 +0100
parents eb9a16538173
children e647e880e711 ce71d2d9bdb7
comparison
equal deleted inserted replaced
274:eb9a16538173 275:6c6950bd7d53
388 } 388 }
389 389
390 SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *> 390 SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *>
391 (model); 391 (model);
392 if (sodm) { 392 if (sodm) {
393 return mixSparseOneDimensionalModel(sodm, startFrame, frameCount, 393 return mixSyntheticNoteModel(model, startFrame, frameCount,
394 buffer, gain, pan, fadeIn, fadeOut); 394 buffer, gain, pan, fadeIn, fadeOut);
395 } 395 }
396 396
397 NoteModel *nm = dynamic_cast<NoteModel *>(model); 397 NoteModel *nm = dynamic_cast<NoteModel *>(model);
398 if (nm) { 398 if (nm) {
399 return mixNoteModel(nm, startFrame, frameCount, 399 return mixSyntheticNoteModel(model, startFrame, frameCount,
400 buffer, gain, pan, fadeIn, fadeOut); 400 buffer, gain, pan, fadeIn, fadeOut);
401 } 401 }
402 402
403 return frameCount; 403 return frameCount;
404 } 404 }
405 405
497 497
498 return got; 498 return got;
499 } 499 }
500 500
501 size_t 501 size_t
502 AudioGenerator::mixSparseOneDimensionalModel(SparseOneDimensionalModel *sodm, 502 AudioGenerator::mixSyntheticNoteModel(Model *model,
503 size_t startFrame, size_t frames, 503 size_t startFrame, size_t frames,
504 float **buffer, float gain, float pan, 504 float **buffer, float gain, float pan,
505 size_t /* fadeIn */, 505 size_t /* fadeIn */,
506 size_t /* fadeOut */) 506 size_t /* fadeOut */)
507 { 507 {
508 RealTimePluginInstance *plugin = m_synthMap[sodm]; 508 RealTimePluginInstance *plugin = m_synthMap[model];
509 if (!plugin) return 0; 509 if (!plugin) return 0;
510 510
511 size_t latency = plugin->getLatency(); 511 size_t latency = plugin->getLatency();
512 size_t blocks = frames / m_pluginBlockSize; 512 size_t blocks = frames / m_pluginBlockSize;
513 513
522 //calls to mixModel 522 //calls to mixModel
523 523
524 size_t got = blocks * m_pluginBlockSize; 524 size_t got = blocks * m_pluginBlockSize;
525 525
526 #ifdef DEBUG_AUDIO_GENERATOR 526 #ifdef DEBUG_AUDIO_GENERATOR
527 std::cout << "mixModel [sparse]: frames " << frames 527 std::cout << "mixModel [synthetic note]: frames " << frames
528 << ", blocks " << blocks << std::endl; 528 << ", blocks " << blocks << std::endl;
529 #endif 529 #endif
530 530
531 snd_seq_event_t onEv; 531 snd_seq_event_t onEv;
532 onEv.type = SND_SEQ_EVENT_NOTEON; 532 onEv.type = SND_SEQ_EVENT_NOTEON;
533 onEv.data.note.channel = 0; 533 onEv.data.note.channel = 0;
534 onEv.data.note.note = 64;
535 onEv.data.note.velocity = 100;
536 534
537 snd_seq_event_t offEv; 535 snd_seq_event_t offEv;
538 offEv.type = SND_SEQ_EVENT_NOTEOFF; 536 offEv.type = SND_SEQ_EVENT_NOTEOFF;
539 offEv.data.note.channel = 0; 537 offEv.data.note.channel = 0;
540 offEv.data.note.velocity = 0; 538 offEv.data.note.velocity = 0;
541 539
542 NoteOffSet &noteOffs = m_noteOffs[sodm]; 540 NoteOffSet &noteOffs = m_noteOffs[model];
543 541
544 for (size_t i = 0; i < blocks; ++i) { 542 for (size_t i = 0; i < blocks; ++i) {
545 543
546 size_t reqStart = startFrame + i * m_pluginBlockSize; 544 size_t reqStart = startFrame + i * m_pluginBlockSize;
547 545
548 SparseOneDimensionalModel::PointList points = 546 NoteList notes = getNotes(model,
549 sodm->getPoints(reqStart + latency, 547 reqStart + latency,
550 reqStart + latency + m_pluginBlockSize); 548 reqStart + latency + m_pluginBlockSize);
551 549
552 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime 550 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime
553 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate); 551 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate);
554 552
555 for (SparseOneDimensionalModel::PointList::iterator pli = 553 for (NoteList::const_iterator ni = notes.begin();
556 points.begin(); pli != points.end(); ++pli) { 554 ni != notes.end(); ++ni) {
557 555
558 size_t pliFrame = pli->frame; 556 size_t noteFrame = ni->start;
559 557
560 if (pliFrame >= latency) pliFrame -= latency; 558 if (noteFrame >= latency) noteFrame -= latency;
561 559
562 if (pliFrame < reqStart || 560 if (noteFrame < reqStart ||
563 pliFrame >= reqStart + m_pluginBlockSize) continue; 561 noteFrame >= reqStart + m_pluginBlockSize) continue;
564 562
565 while (noteOffs.begin() != noteOffs.end() && 563 while (noteOffs.begin() != noteOffs.end() &&
566 noteOffs.begin()->frame <= pliFrame) { 564 noteOffs.begin()->frame <= noteFrame) {
567 565
568 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime 566 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
569 (noteOffs.begin()->frame, m_sourceSampleRate); 567 (noteOffs.begin()->frame, m_sourceSampleRate);
570 568
571 offEv.data.note.note = noteOffs.begin()->pitch; 569 offEv.data.note.note = noteOffs.begin()->pitch;
572 570
573 #ifdef DEBUG_AUDIO_GENERATOR 571 #ifdef DEBUG_AUDIO_GENERATOR
574 std::cerr << "mixModel [sparse]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl; 572 std::cerr << "mixModel [synthetic]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl;
575 #endif 573 #endif
576 574
577 plugin->sendEvent(eventTime, &offEv); 575 plugin->sendEvent(eventTime, &offEv);
578 noteOffs.erase(noteOffs.begin()); 576 noteOffs.erase(noteOffs.begin());
579 } 577 }
580 578
581 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime 579 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
582 (pliFrame, m_sourceSampleRate); 580 (noteFrame, m_sourceSampleRate);
583 581
582 if (ni->isMidiPitchQuantized) {
583 onEv.data.note.note = ni->midiPitch;
584 } else {
585 #ifdef DEBUG_AUDIO_GENERATOR
586 std::cerr << "mixModel [synthetic]: non-pitch-quantized notes are not supported [yet], quantizing" << std::endl;
587 #endif
588 onEv.data.note.note = Pitch::getPitchForFrequency(ni->frequency);
589 }
590
591 onEv.data.note.velocity = ni->velocity;
592
584 plugin->sendEvent(eventTime, &onEv); 593 plugin->sendEvent(eventTime, &onEv);
585 594
586 #ifdef DEBUG_AUDIO_GENERATOR 595 #ifdef DEBUG_AUDIO_GENERATOR
587 std::cout << "mixModel [sparse]: point at frame " << pliFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl; 596 std::cout << "mixModel [synthetic]: note at frame " << noteFrame << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl;
588 #endif 597 #endif
589 598
590 size_t duration = 7000; // frames [for now] 599 noteOffs.insert
591 NoteOff noff; 600 (NoteOff(onEv.data.note.note, noteFrame + ni->duration));
592 noff.pitch = onEv.data.note.note;
593 noff.frame = pliFrame + duration;
594 noteOffs.insert(noff);
595 } 601 }
596 602
597 while (noteOffs.begin() != noteOffs.end() && 603 while (noteOffs.begin() != noteOffs.end() &&
598 noteOffs.begin()->frame <= 604 noteOffs.begin()->frame <=
599 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { 605 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) {
602 (noteOffs.begin()->frame, m_sourceSampleRate); 608 (noteOffs.begin()->frame, m_sourceSampleRate);
603 609
604 offEv.data.note.note = noteOffs.begin()->pitch; 610 offEv.data.note.note = noteOffs.begin()->pitch;
605 611
606 #ifdef DEBUG_AUDIO_GENERATOR 612 #ifdef DEBUG_AUDIO_GENERATOR
607 std::cerr << "mixModel [sparse]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl; 613 std::cerr << "mixModel [synthetic]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl;
608 #endif 614 #endif
609 615
610 plugin->sendEvent(eventTime, &offEv); 616 plugin->sendEvent(eventTime, &offEv);
611 noteOffs.erase(noteOffs.begin()); 617 noteOffs.erase(noteOffs.begin());
612 } 618 }
614 plugin->run(blockTime); 620 plugin->run(blockTime);
615 float **outs = plugin->getAudioOutputBuffers(); 621 float **outs = plugin->getAudioOutputBuffers();
616 622
617 for (size_t c = 0; c < m_targetChannelCount; ++c) { 623 for (size_t c = 0; c < m_targetChannelCount; ++c) {
618 #ifdef DEBUG_AUDIO_GENERATOR 624 #ifdef DEBUG_AUDIO_GENERATOR
619 std::cout << "mixModel [sparse]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl; 625 std::cout << "mixModel [synthetic]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
620 #endif 626 #endif
621 627
622 size_t sourceChannel = (c % plugin->getAudioOutputCount()); 628 size_t sourceChannel = (c % plugin->getAudioOutputCount());
623 629
624 float channelGain = gain; 630 float channelGain = gain;
638 } 644 }
639 645
640 return got; 646 return got;
641 } 647 }
642 648
643 649 AudioGenerator::NoteList
644 //!!! mucho duplication with above -- refactor 650 AudioGenerator::getNotes(Model *model,
645 size_t 651 size_t startFrame,
646 AudioGenerator::mixNoteModel(NoteModel *nm, 652 size_t endFrame)
647 size_t startFrame, size_t frames, 653 {
648 float **buffer, float gain, float pan, 654 NoteList notes;
649 size_t /* fadeIn */, 655
650 size_t /* fadeOut */) 656 SparseOneDimensionalModel *sodm =
651 { 657 qobject_cast<SparseOneDimensionalModel *>(model);
652 RealTimePluginInstance *plugin = m_synthMap[nm]; 658
653 if (!plugin) return 0; 659 if (sodm) {
654 660
655 size_t latency = plugin->getLatency(); 661 SparseOneDimensionalModel::PointList points =
656 size_t blocks = frames / m_pluginBlockSize; 662 sodm->getPoints(startFrame, endFrame);
657 663
658 //!!! hang on -- the fact that the audio callback play source's 664 for (SparseOneDimensionalModel::PointList::iterator pli =
659 //buffer is a multiple of the plugin's buffer size doesn't mean 665 points.begin(); pli != points.end(); ++pli) {
660 //that we always get called for a multiple of it here (because it 666
661 //also depends on the JACK block size). how should we ensure that 667 notes.push_back
662 //all models write the same amount in to the mix, and that we 668 (NoteData(pli->frame,
663 //always have a multiple of the plugin buffer size? I guess this 669 m_sourceSampleRate / 6, // arbitrary short duration
664 //class has to be queryable for the plugin buffer size & the 670 64, // default pitch
665 //callback play source has to use that as a multiple for all the 671 100)); // default velocity
666 //calls to mixModel 672 }
667 673
668 size_t got = blocks * m_pluginBlockSize; 674 return notes;
669 675 }
670 #ifdef DEBUG_AUDIO_GENERATOR 676
671 Vamp::RealTime startTime = Vamp::RealTime::frame2RealTime 677 NoteModel *nm = qobject_cast<NoteModel *>(model);
672 (startFrame, m_sourceSampleRate); 678
673 679 if (nm) {
674 std::cout << "mixModel [note]: frames " << frames << " from " << startFrame 680
675 << " (time " << startTime << "), blocks " << blocks << std::endl;
676 #endif
677
678 snd_seq_event_t onEv;
679 onEv.type = SND_SEQ_EVENT_NOTEON;
680 onEv.data.note.channel = 0;
681 onEv.data.note.note = 64;
682 onEv.data.note.velocity = 100;
683
684 snd_seq_event_t offEv;
685 offEv.type = SND_SEQ_EVENT_NOTEOFF;
686 offEv.data.note.channel = 0;
687 offEv.data.note.velocity = 0;
688
689 NoteOffSet &noteOffs = m_noteOffs[nm];
690
691 for (size_t i = 0; i < blocks; ++i) {
692
693 size_t reqStart = startFrame + i * m_pluginBlockSize;
694
695 NoteModel::PointList points = 681 NoteModel::PointList points =
696 nm->getPoints(reqStart + latency, 682 nm->getPoints(startFrame, endFrame);
697 reqStart + latency + m_pluginBlockSize); 683
698 684 for (NoteModel::PointList::iterator pli =
699 Vamp::RealTime blockTime = Vamp::RealTime::frame2RealTime
700 (startFrame + i * m_pluginBlockSize, m_sourceSampleRate);
701
702 for (NoteModel::PointList::iterator pli =
703 points.begin(); pli != points.end(); ++pli) { 685 points.begin(); pli != points.end(); ++pli) {
704 686
705 size_t pliFrame = pli->frame;
706
707 if (pliFrame >= latency) pliFrame -= latency;
708
709 if (pliFrame < reqStart ||
710 pliFrame >= reqStart + m_pluginBlockSize) continue;
711
712 while (noteOffs.begin() != noteOffs.end() &&
713 noteOffs.begin()->frame <= pliFrame) {
714
715 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
716 (noteOffs.begin()->frame, m_sourceSampleRate);
717
718 offEv.data.note.note = noteOffs.begin()->pitch;
719
720 #ifdef DEBUG_AUDIO_GENERATOR
721 std::cerr << "mixModel [note]: sending note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl;
722 #endif
723
724 plugin->sendEvent(eventTime, &offEv);
725 noteOffs.erase(noteOffs.begin());
726 }
727
728 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime
729 (pliFrame, m_sourceSampleRate);
730
731 if (nm->getScaleUnits() == "Hz") {
732 onEv.data.note.note = Pitch::getPitchForFrequency(pli->value);
733 } else {
734 onEv.data.note.note = lrintf(pli->value);
735 }
736
737 if (pli->level > 0.f && pli->level <= 1.f) {
738 onEv.data.note.velocity = lrintf(pli->level * 127);
739 } else {
740 onEv.data.note.velocity = 100;
741 }
742
743 plugin->sendEvent(eventTime, &onEv);
744
745 #ifdef DEBUG_AUDIO_GENERATOR
746 std::cout << "mixModel [note]: point at frame " << pliFrame << ", pitch " << (int)onEv.data.note.note << ", block start " << (startFrame + i * m_pluginBlockSize) << ", resulting time " << eventTime << std::endl;
747 #endif
748
749 size_t duration = pli->duration; 687 size_t duration = pli->duration;
750 if (duration == 0 || duration == 1) { 688 if (duration == 0 || duration == 1) {
751 duration = m_sourceSampleRate / 20; 689 duration = m_sourceSampleRate / 20;
752 } 690 }
753 NoteOff noff; 691
754 noff.pitch = onEv.data.note.note; 692 int pitch = lrintf(pli->value);
755 noff.frame = pliFrame + duration; 693
756 noteOffs.insert(noff); 694 int velocity = 100;
757 695 if (pli->level > 0.f && pli->level <= 1.f) {
758 #ifdef DEBUG_AUDIO_GENERATOR 696 velocity = lrintf(pli->level * 127);
759 std::cout << "mixModel [note]: recording note off at " << noff.frame << std::endl; 697 }
760 #endif 698
761 } 699 NoteData note(pli->frame,
762 700 duration,
763 while (noteOffs.begin() != noteOffs.end() && 701 pitch,
764 noteOffs.begin()->frame <= 702 velocity);
765 startFrame + i * m_pluginBlockSize + m_pluginBlockSize) { 703
766 704 if (nm->getScaleUnits() == "Hz") {
767 Vamp::RealTime eventTime = Vamp::RealTime::frame2RealTime 705 note.frequency = pli->value;
768 (noteOffs.begin()->frame, m_sourceSampleRate); 706 note.isMidiPitchQuantized = false;
769 707 }
770 offEv.data.note.note = noteOffs.begin()->pitch; 708
771 709 notes.push_back(note);
772 #ifdef DEBUG_AUDIO_GENERATOR 710 }
773 std::cerr << "mixModel [note]: sending leftover note-off event at time " << eventTime << " frame " << noteOffs.begin()->frame << " pitch " << noteOffs.begin()->pitch << std::endl; 711
774 #endif 712 return notes;
775 713 }
776 plugin->sendEvent(eventTime, &offEv); 714
777 noteOffs.erase(noteOffs.begin()); 715 return notes;
778 } 716 }
779 717
780 plugin->run(blockTime);
781 float **outs = plugin->getAudioOutputBuffers();
782
783 for (size_t c = 0; c < m_targetChannelCount; ++c) {
784 #ifdef DEBUG_AUDIO_GENERATOR
785 std::cout << "mixModel [note]: adding " << m_pluginBlockSize << " samples from plugin output " << c << std::endl;
786 #endif
787
788 size_t sourceChannel = (c % plugin->getAudioOutputCount());
789
790 float channelGain = gain;
791 if (pan != 0.0) {
792 if (c == 0) {
793 if (pan > 0.0) channelGain *= 1.0 - pan;
794 } else {
795 if (pan < 0.0) channelGain *= pan + 1.0;
796 }
797 }
798
799 for (size_t j = 0; j < m_pluginBlockSize; ++j) {
800 buffer[c][i * m_pluginBlockSize + j] +=
801 channelGain * outs[sourceChannel][j];
802 }
803 }
804 }
805
806 return got;
807 }
808