Mercurial > hg > svapp
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 ¬eOffs = m_noteOffs[sodm]; | 540 NoteOffSet ¬eOffs = 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 ¬eOffs = 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 |