maxl0rd / standingwave3

Flash ActionScript3 dynamic audio library
http://www.noteflight.com
160 stars 25 forks source link

QueuePerformance #22

Closed Jobbies closed 4 years ago

Jobbies commented 12 years ago

So I see that this wasn't ported over because there were zero instances of it being used. I'm trying to implement it because it would be perfect for a looping drum machine. Do you have any tips?

Thanks for all your hard work on this lib.

Jobbies commented 12 years ago

This is what I have so far. Seems to be working but there is a pause at the end of the loop.

////////////////////////////////////////////////////////////////////////////////
//
//  NOTEFLIGHT LLC
//  Copyright 2009 Noteflight LLC
// 
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package com.noteflight.standingwave3.performance
{
    import __AS3__.vec.Vector;

    import com.noteflight.standingwave3.elements.*;

  /**
     * A QueuePerformance works forward through a queue of audio sources.  The final element
     * of the queue is repeated indefinitely.  When more elements are added to a QueuePerformance,
     * the new elements are played immediately following the last complete repeat of any currently
     * playing element.
     */
    public class QueuePerformance implements IPerformance
    {

        private var _elements:Vector.<PerformableAudioSource> = new Vector.<PerformableAudioSource>;

        private var _dirty:Boolean = false;

        private var _lastIndex:Number = 0;

        private var _nextStart:Number;

        private var _started:Boolean;

        public function QueuePerformance() {
               _nextStart = 0;
               _started = false;
        }

        /**
         * Add a Performance Element to this Performance. 
         */
        public function addElement(element:PerformableAudioSource):void
        {
            if ((! _dirty)
                && _elements.length > 0
                && element.start < _elements[_elements.length - 1].start)
            {
                // If we add an element which is out of order, note that we will have
                // to re-sort the performance later.
                //
                _dirty = true;
            }
            _elements.push(element);

        }

        /**
         * Add an IAudioSource to this performance, to start at a particular start time.
         */
        public function addSourceAt(startTime:Number, source:IAudioSource, gain:Number=0, pan:Number=0):void
        {
            addElement(new PerformableAudioSource(startTime, source, gain, pan));
        }

        /**
         * The list of PerformableAudioSources within this Performance, sorted by onset. 
         */        
        public function get elements():Vector.<PerformableAudioSource>
        {
            ensureSorted();
            return _elements;
        }

        /**
         * The start of the last performance element in the Performance. 
         */
        public function get lastStart():Number
        {
            var el:Vector.<PerformableAudioSource> = elements;
            return (el.length == 0) ? 0 : el[el.length-1].start;
        }

        /**
         * The frame count of the entire Performance. 
         */
        public function get frameCount():Number
        {
            return int.MAX_VALUE;
        }

        //
        // IPerformance interface implementation
        //

        /**
         * @inheritDoc 
         */        
        public function getElementsInRange(start:Number, end:Number):Vector.<PerformableAudioSource>
        {
            // This makes use of _lastIndex as a memory of what was last queried to optimize
            // the search for the first matching element, since queries will in general run
            // in forward order.
            //
            var el:Vector.<PerformableAudioSource> = elements;
            var result:Vector.<PerformableAudioSource> = new Vector.<PerformableAudioSource>();             
            _lastIndex = Math.max(0, Math.min(_lastIndex, el.length - 1));

            if (_nextStart >= start && _nextStart < end) {
                if (_started && el.length > 1)
                {
                    el.shift();
                }
                var source:IAudioSource = el[0].source;
                result.push(new PerformableAudioSource(_nextStart / source.descriptor.rate, source.clone()));
                _nextStart += source.frameCount;
                _started = true;
            }

            return result;
        }

        public function clone():IPerformance
        {
            var p:ListPerformance = new ListPerformance();
            for each (var element:PerformableAudioSource in elements)
            {
                p.addElement(new PerformableAudioSource(element.startTime, element.source.clone()));
            }
            return p;
        }

        private function ensureSorted():void
        {
            if (_dirty)
            {
                _elements.sort(sortByStart);
                _dirty = false;
            }
        }

        private static function sortByStart(a:PerformableAudioSource, b:PerformableAudioSource):Number
        {
            var aStart:Number = a.start;
            var bStart:Number = b.start;
            if(aStart > bStart)
            {
                return 1;
            }
            else if(aStart < bStart)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }
    }
}
maxl0rd commented 12 years ago

Hi

I'm not sure that QueuePerformance is your best bet for a looping drum machine. I have worked on something like this before with sw3, and took a different approach.

I created an object that understood the map of the beats, and managed a normal ListPerformance. When you hit play a single ListPerformance begins playing. This manager object then continually inserts sources into the playing performance just in time as needed. It is what is responsible for knowing that it's measure 24, beat 3 or whatever.

Then there is a collection of source "prototypes" for each sound, and you clone them every time you place them into a performance. Every source in SW3 is only meant to be consumed once. So you think of it as the same sound playing again, but sw3 thinks of it as just a long performance with repeating elements.

Hope this helps. It's pretty easy once you get your head around it.

Jobbies commented 12 years ago

Cool Sounds like a smart way to do it i'll give it a go.

BrianHubble commented 12 years ago

Hey, it's been a decent amount of time but if anyone is still looking at the project I'd appreciate some feedback.

I'm trying to do something similar to what was discussed above but found that the playback would stop. Like Max said earlier I don't think looping is the way to go, rather I'm trying to place the samples in time, but when there's is a (scheduled) silence then the player thinks it is completed and playback stops.

I was under the impression that a performance will continue until explicitly stopped and would accept more and more scheduled events.

Anyway, if anyone has any insight into this I would appreciate it.

Jobbies commented 12 years ago

In the end I decided that the SiON soft synth library did what I needed and switched over to that. https://sites.google.com/site/sioncenter/

BrianHubble commented 12 years ago

SiON's not really what I want to be doing.

Anyway, I realized that I was simply handling my scheduling incorrectly. Everything works like a dream.