Mercurial > hg > sonic-visualiser
comparison audioio/AudioCallbackPlaySource.cpp @ 16:3715efc38f95
* substantial enhancements to time stretcher:
-- use putInput/getOutput methods to ensure the audio source always feeds
it enough input, avoiding underruns due to rounding error
-- add a percussion detector and an optional "Sharpen" toggle to the main
window, which invokes a very basic variable speed timestretcher
author | Chris Cannam |
---|---|
date | Wed, 13 Sep 2006 17:17:42 +0000 |
parents | cc566264c935 |
children | 80126455d169 |
comparison
equal
deleted
inserted
replaced
15:cc566264c935 | 16:3715efc38f95 |
---|---|
49 m_playing(false), | 49 m_playing(false), |
50 m_exiting(false), | 50 m_exiting(false), |
51 m_lastModelEndFrame(0), | 51 m_lastModelEndFrame(0), |
52 m_outputLeft(0.0), | 52 m_outputLeft(0.0), |
53 m_outputRight(0.0), | 53 m_outputRight(0.0), |
54 m_slowdownCounter(0), | |
55 m_timeStretcher(0), | 54 m_timeStretcher(0), |
56 m_fillThread(0), | 55 m_fillThread(0), |
57 m_converter(0) | 56 m_converter(0) |
58 { | 57 { |
59 m_viewManager->setAudioPlaySource(this); | 58 m_viewManager->setAudioPlaySource(this); |
425 readSpace = size_t(readSpace * ratio + 0.1); | 424 readSpace = size_t(readSpace * ratio + 0.1); |
426 } | 425 } |
427 | 426 |
428 size_t latency = m_playLatency; | 427 size_t latency = m_playLatency; |
429 if (resample) latency = size_t(m_playLatency * ratio + 0.1); | 428 if (resample) latency = size_t(m_playLatency * ratio + 0.1); |
430 | 429 |
431 TimeStretcherData *timeStretcher = m_timeStretcher; | 430 PhaseVocoderTimeStretcher *timeStretcher = m_timeStretcher; |
432 if (timeStretcher) { | 431 if (timeStretcher) { |
433 latency += timeStretcher->getStretcher(0)->getProcessingLatency(); | 432 latency += timeStretcher->getProcessingLatency(); |
434 } | 433 } |
435 | 434 |
436 latency += readSpace; | 435 latency += readSpace; |
437 size_t bufferedFrame = m_readBufferFill; | 436 size_t bufferedFrame = m_readBufferFill; |
438 | 437 |
586 AudioCallbackPlaySource::getSourceSampleRate() const | 585 AudioCallbackPlaySource::getSourceSampleRate() const |
587 { | 586 { |
588 return m_sourceSampleRate; | 587 return m_sourceSampleRate; |
589 } | 588 } |
590 | 589 |
591 AudioCallbackPlaySource::TimeStretcherData::TimeStretcherData(size_t channels, | 590 void |
592 float factor, | 591 AudioCallbackPlaySource::setSlowdownFactor(float factor, bool sharpen) |
593 size_t blockSize) : | |
594 m_factor(factor), | |
595 m_blockSize(blockSize) | |
596 { | |
597 // std::cerr << "TimeStretcherData::TimeStretcherData(" << channels << ", " << factor << ", " << blockSize << ")" << std::endl; | |
598 | |
599 for (size_t ch = 0; ch < channels; ++ch) { | |
600 | |
601 m_stretcher[ch] = new PhaseVocoderTimeStretcher(factor, blockSize); | |
602 // 128), | |
603 // (blockSize/2) / factor), | |
604 // new float[lrintf(blockSize * factor)]); | |
605 } | |
606 } | |
607 | |
608 AudioCallbackPlaySource::TimeStretcherData::~TimeStretcherData() | |
609 { | |
610 // std::cerr << "TimeStretcherData::~TimeStretcherData" << std::endl; | |
611 | |
612 while (!m_stretcher.empty()) { | |
613 delete m_stretcher.begin()->second; | |
614 // delete[] m_stretcher.begin()->second.second; | |
615 m_stretcher.erase(m_stretcher.begin()); | |
616 } | |
617 // delete m_stretchInputBuffer; | |
618 } | |
619 | |
620 PhaseVocoderTimeStretcher * | |
621 AudioCallbackPlaySource::TimeStretcherData::getStretcher(size_t channel) | |
622 { | |
623 return m_stretcher[channel]; | |
624 } | |
625 /* | |
626 float * | |
627 AudioCallbackPlaySource::TimeStretcherData::getOutputBuffer(size_t channel) | |
628 { | |
629 return m_stretcher[channel].second; | |
630 } | |
631 | |
632 float * | |
633 AudioCallbackPlaySource::TimeStretcherData::getInputBuffer() | |
634 { | |
635 return m_stretchInputBuffer; | |
636 } | |
637 | |
638 void | |
639 AudioCallbackPlaySource::TimeStretcherData::run(size_t channel) | |
640 { | |
641 getStretcher(channel)->process(getInputBuffer(), | |
642 getOutputBuffer(channel), | |
643 m_blockSize); | |
644 } | |
645 */ | |
646 void | |
647 AudioCallbackPlaySource::setSlowdownFactor(float factor) | |
648 { | 592 { |
649 // Avoid locks -- create, assign, mark old one for scavenging | 593 // Avoid locks -- create, assign, mark old one for scavenging |
650 // later (as a call to getSourceSamples may still be using it) | 594 // later (as a call to getSourceSamples may still be using it) |
651 | 595 |
652 TimeStretcherData *existingStretcher = m_timeStretcher; | 596 PhaseVocoderTimeStretcher *existingStretcher = m_timeStretcher; |
653 | 597 |
654 if (existingStretcher && existingStretcher->getFactor() == factor) { | 598 if (existingStretcher && |
599 existingStretcher->getRatio() == factor && | |
600 existingStretcher->getSharpening() == sharpen) { | |
655 return; | 601 return; |
656 } | 602 } |
657 | 603 |
658 if (factor != 1) { | 604 if (factor != 1) { |
659 TimeStretcherData *newStretcher = new TimeStretcherData | 605 PhaseVocoderTimeStretcher *newStretcher = new PhaseVocoderTimeStretcher |
660 (getTargetChannelCount(), factor, | 606 (getTargetChannelCount(), |
661 // factor > 1 ? getTargetBlockSize() : getTargetBlockSize() / factor); | 607 factor, |
662 //!!! doesn't work if the block size > getTargetBlockSize(), but it | 608 sharpen, |
663 // should be made to | |
664 // getTargetBlockSize()); | |
665 lrintf(getTargetBlockSize() / factor)); | 609 lrintf(getTargetBlockSize() / factor)); |
666 m_slowdownCounter = 0; | |
667 m_timeStretcher = newStretcher; | 610 m_timeStretcher = newStretcher; |
668 } else { | 611 } else { |
669 m_timeStretcher = 0; | 612 m_timeStretcher = 0; |
670 } | 613 } |
671 | 614 |
684 } | 627 } |
685 } | 628 } |
686 return 0; | 629 return 0; |
687 } | 630 } |
688 | 631 |
689 TimeStretcherData *timeStretcher = m_timeStretcher; | 632 PhaseVocoderTimeStretcher *ts = m_timeStretcher; |
690 | 633 |
691 if (!timeStretcher || timeStretcher->getFactor() == 1) { | 634 if (!ts || ts->getRatio() == 1) { |
692 | 635 |
693 size_t got = 0; | 636 size_t got = 0; |
694 | 637 |
695 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { | 638 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { |
696 | 639 |
719 | 662 |
720 m_condition.wakeAll(); | 663 m_condition.wakeAll(); |
721 return got; | 664 return got; |
722 } | 665 } |
723 | 666 |
667 float ratio = ts->getRatio(); | |
668 | |
669 // std::cout << "ratio = " << ratio << std::endl; | |
670 | |
671 size_t available; | |
672 | |
673 while ((available = ts->getAvailableOutputSamples()) < count) { | |
674 | |
675 size_t reqd = lrintf((count - available) / ratio); | |
676 reqd = std::max(reqd, ts->getRequiredInputSamples()); | |
677 if (reqd == 0) reqd = 1; | |
678 | |
679 size_t channels = getTargetChannelCount(); | |
680 | |
681 float *ib[channels]; | |
682 | |
683 size_t got = reqd; | |
684 | |
685 for (size_t c = 0; c < channels; ++c) { | |
686 ib[c] = new float[reqd]; //!!! fix -- this is a rt function | |
687 RingBuffer<float> *rb = getReadRingBuffer(c); | |
688 if (rb) { | |
689 size_t gotHere = rb->read(ib[c], got); | |
690 if (gotHere < got) got = gotHere; | |
691 } | |
692 } | |
693 | |
694 if (got < reqd) { | |
695 std::cerr << "WARNING: Read underrun in playback (" | |
696 << got << " < " << reqd << ")" << std::endl; | |
697 } | |
698 | |
699 ts->putInput(ib, got); | |
700 | |
701 for (size_t c = 0; c < channels; ++c) { | |
702 delete[] ib[c]; | |
703 } | |
704 | |
705 if (got == 0) break; | |
706 | |
707 if (ts->getAvailableOutputSamples() == available) { | |
708 std::cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples" << std::endl; | |
709 break; | |
710 } | |
711 } | |
712 | |
713 ts->getOutput(buffer, count); | |
714 | |
715 | |
724 /*!!! | 716 /*!!! |
725 if (m_slowdownCounter == 0) { | 717 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { |
726 | 718 |
727 size_t got = 0; | 719 RingBuffer<float> *rb = getReadRingBuffer(ch); |
728 float *ib = timeStretcher->getInputBuffer(); | 720 |
729 | 721 if (rb) { |
730 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { | 722 |
731 | 723 float ratio = ts->getRatio(); |
732 RingBuffer<float> *rb = getReadRingBuffer(ch); | 724 |
733 | 725 // std::cout << "ratio = " << ratio << std::endl; |
734 if (rb) { | 726 |
735 | 727 size_t available; |
736 size_t request = count; | 728 |
737 if (ch > 0) request = got; // see above | 729 while ((available = ts->getAvailableOutputSamples()) < count) { |
738 got = rb->read(buffer[ch], request); | 730 |
731 size_t reqd = lrintf((count - available) / ratio); | |
732 reqd = std::max(reqd, ts->getRequiredInputSamples()); | |
733 if (reqd == 0) reqd = 1; | |
734 | |
735 float ib[reqd]; | |
736 size_t got = rb->read(ib, reqd); | |
739 | 737 |
740 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING | 738 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING |
741 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << ", running time stretcher" << std::endl; | 739 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << " (reqd=" << reqd << ", count=" << count << ", ratio=" << ratio << ", got*ratio=" << got * ratio << "), running time stretcher" << std::endl; |
742 #endif | 740 #endif |
743 | 741 |
744 for (size_t i = 0; i < count; ++i) { | 742 if (got < reqd) { |
745 ib[i] = buffer[ch][i]; | 743 std::cerr << "WARNING: Read underrun in playback (" |
746 } | 744 << got << " < " << reqd << ")" << std::endl; |
747 | 745 } |
748 timeStretcher->run(ch); | 746 |
749 } | 747 ts->putInput(ib, got); |
750 } | 748 |
751 | 749 if (got == 0) break; |
752 } else if (m_slowdownCounter >= timeStretcher->getFactor()) { | 750 |
753 // reset this in case the factor has changed leaving the | 751 if (ts->getAvailableOutputSamples() == available) { |
754 // counter out of range | 752 std::cerr << "WARNING: AudioCallbackPlaySource::getSamples: Added " << got << " samples to time stretcher, created no new available output samples" << std::endl; |
755 m_slowdownCounter = 0; | 753 break; |
756 } | 754 } |
757 | 755 } |
758 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { | 756 |
759 | 757 ts->getOutput(buffer[ch], count); |
760 float *ob = timeStretcher->getOutputBuffer(ch); | 758 } |
761 | |
762 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING | |
763 std::cerr << "AudioCallbackPlaySource::getSamples: Copying from (" << (m_slowdownCounter * count) << "," << count << ") to buffer" << std::endl; | |
764 #endif | |
765 | |
766 for (size_t i = 0; i < count; ++i) { | |
767 buffer[ch][i] = ob[m_slowdownCounter * count + i]; | |
768 } | |
769 } | 759 } |
770 */ | 760 */ |
771 | 761 m_condition.wakeAll(); |
772 for (size_t ch = 0; ch < getTargetChannelCount(); ++ch) { | 762 |
773 | |
774 RingBuffer<float> *rb = getReadRingBuffer(ch); | |
775 | |
776 if (rb) { | |
777 | |
778 float ratio = timeStretcher->getStretcher(ch)->getRatio(); | |
779 size_t request = lrintf(count / ratio); | |
780 // if (ch > 0) request = got; // see above | |
781 | |
782 float *ib = new float[request]; //!!! | |
783 | |
784 size_t got = rb->read(ib, request); | |
785 | |
786 #ifdef DEBUG_AUDIO_PLAY_SOURCE_PLAYING | |
787 std::cout << "AudioCallbackPlaySource::getSamples: got " << got << " samples on channel " << ch << " (count=" << count << ", ratio=" << timeStretcher->getStretcher(ch)->getRatio() << ", got*ratio=" << got * ratio << "), running time stretcher" << std::endl; | |
788 #endif | |
789 | |
790 timeStretcher->getStretcher(ch)->process(ib, buffer[ch], request); | |
791 | |
792 delete[] ib; | |
793 | |
794 // for (size_t i = 0; i < count; ++i) { | |
795 // ib[i] = buffer[ch][i]; | |
796 // } | |
797 | |
798 // timeStretcher->run(ch); | |
799 | |
800 | |
801 | |
802 } | |
803 } | |
804 | |
805 | |
806 | |
807 //!!! if (m_slowdownCounter == 0) m_condition.wakeAll(); | |
808 // m_slowdownCounter = (m_slowdownCounter + 1) % timeStretcher->getFactor(); | |
809 return count; | 763 return count; |
810 } | 764 } |
811 | 765 |
812 // Called from fill thread, m_playing true, mutex held | 766 // Called from fill thread, m_playing true, mutex held |
813 bool | 767 bool |