All Downloads are FREE. Search and download functionalities are using the official Maven repository.

animatedledstrip.leds.AnimatedLEDStrip.kt Maven / Gradle / Ivy

There is a newer version: 0.3
Show newest version
package animatedledstrip.leds

/*
 *  Copyright (c) 2018 AnimatedLEDStrip
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  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.
 */


import animatedledstrip.ccpresets.*
import com.diozero.ws281xj.PixelAnimations.delay
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.pmw.tinylog.Logger
import java.lang.Math.random

/**
 * A subclass of [LEDStrip] adding animations
 *
 * @param numLEDs Number of leds in the strip
 * @param pin GPIO pin connected for signal
 * @param emulated Is this strip real or emulated?
 */
open class AnimatedLEDStrip(
    numLEDs: Int,
    pin: Int,
    emulated: Boolean = false,
    constantRender: Boolean = true,
    imageDebugging: Boolean = false
) :
    LEDStrip(numLEDs, pin, emulated, constantRender, imageDebugging) {

    /**
     * Array used for shuffle animation
     */
    private var shuffleArray = mutableListOf()

    /**
     * Map containing Mutex instances for locking access to each led while it is
     * being used
     */
    private val locks = mutableMapOf()

    /**
     * Mutex that tracks if a thread is using the shuffleArray
     */
    private val shuffleLock = Mutex()


    /**
     * A pool of threads to be used for animations that spawn new sub-threads
     * (with the exception of sparkle-type animations, those use the
     * [sparkleThreadPool]).
     */
    private val animationThreadPool = newFixedThreadPoolContext(2 * numLEDs, "Animation Pool")

    /**
     * A pool of threads to be used for sparkle-type animations due to the
     * number of threads a concurrent sparkle animation uses. This prevents
     * memory leaks caused by the overhead associated with creating new threads.
     */
    private val sparkleThreadPool = newFixedThreadPoolContext(numLEDs + 1, "Sparkle Pool")


    private val fadeMap = mutableMapOf()

    inner class FadePixel(val pixel: Int) {
        var owner = ""
        fun fade(destinationColor: ColorContainer, amountOfOverlay: Int = 25, delay: Int = 30) {
            val myName = Thread.currentThread().name
            owner = myName
            var i = 0
            while (getPixelColor(pixel).hex != destinationColor.hex && i <= 40) {
                if (owner != myName) break
                setPixelColor(pixel, blend(getPixelColor(pixel), destinationColor, amountOfOverlay))
                delay(delay)
                i++
            }
        }
    }

    fun fadePixel(pixel: Int, destinationColor: ColorContainer, amountOfOverlay: Int = 25, delay: Int = 30) {
        Logger.trace("Fading pixel $pixel to ${destinationColor.hexString}")
        fadeMap[pixel]?.fade(destinationColor, amountOfOverlay, delay)
        Logger.trace("Fade of pixel $pixel complete")
    }

    init {

        for (i in 0 until numLEDs) {
            locks += Pair(i, Mutex())        // Initialize locks map
            fadeMap += Pair(i, FadePixel(i))
        }
        runBlocking {
            shuffleLock.withLock {
                for (i in 0 until numLEDs) shuffleArray.add(i)      // Initialize shuffleArray
            }
        }
    }


    companion object SectionCreator {
        fun newSection(startPixel: Int, endPixel: Int) = 0
    }

    /**
     * Function to run an Alternate animation.
     *
     * Strip alternates between two colors at the specified rate (delay between changes).
     *
     * @param colorValues1 First color to be displayed
     * @param colorValues2 Second color to be displayed
     * @param delay Delay in milliseconds before color changes from first to
     * second color and between second color and returning
     * @param delayMod Multiplier for delay
     */
    fun alternate(
        colorValues1: ColorContainer,
        colorValues2: ColorContainer,
        delay: Int = Alternate.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        setSectionColor(startPixel, endPixel, colorValues1)
        delay((delay * delayMod).toInt())
        setSectionColor(startPixel, endPixel, colorValues2)
        delay((delay * delayMod).toInt())
    }


    /**
     * TODO (Will)
     * @param point1
     * @param point2
     * @param color1
     * @param color2
     * @param delay
     * @param delayMod
     */
    fun multiAlternate(
        point1: Int,
        point2: Int,
        color1: ColorContainer,
        color2: ColorContainer,
        delay: Int = 1000,
        delayMod: Double = 1.0
    ) {
        val endptA = 0
        val endptB: Int
        val endptC: Int
        val endptD = numLEDs - 1

        if (point1 <= point2 && point1 > endptA && point2 < endptD) {
            endptB = point1
            endptC = point2
        } else if (point2 > endptA && point1 < endptD) {
            endptB = point2
            endptC = point1
        } else {
            endptB = numLEDs / 3
            endptC = (numLEDs * 2 / 3) - 1
        }

        // TODO: Change to use threads from animationThreadPool
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-1")) {
            setSectionColor(endptA, endptB, color1)
            delay((delay * delayMod).toInt())
            setSectionColor(endptA, endptB, color2)
            delay((delay * delayMod).toInt())
        }
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-2")) {
            setSectionColor(endptC, endptD, color1)
            delay((delay * delayMod).toInt())
            setSectionColor(endptC, endptD, color2)
            delay((delay * delayMod).toInt())
        }
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-3")) {
            setSectionColor(endptB, endptC, color2)
            delay((delay * delayMod).toInt())
            setSectionColor(endptB, endptC, color1)
            delay((delay * delayMod).toInt())
        }
    }


    /**
     * Function to run a Multi Pixel Run animation.
     *
     * Similar to Pixel Run but with multiple leds at a specified spacing.
     *
     * @param spacing Spacing between lit leds (for example, if spacing is 3
     * and led 0 is lit, led 3 will also be lit)
     * @param chaseDirection [Direction] of animation
     * @param colorValues1 Color of moving pixels
     * @param colorValues2 Color of background pixels
     * @param delay Delay between moves
     * @param delayMod Multiplier for delay
     */
    fun multiPixelRun(
        spacing: Int,
        chaseDirection: Direction,
        colorValues1: ColorContainer,
        colorValues2: ColorContainer = MultiPixelRun.color2Default,
        delay: Int = MultiPixelRun.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        if (chaseDirection == Direction.BACKWARD) {
            for (q in 0 until spacing) {
                setStripColor(colorValues2)
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    colorValues1
                )
                show()
                delay((delay * delayMod).toInt())
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    colorValues2
                )
            }
        } else if (chaseDirection == Direction.FORWARD) {
            for (q in spacing - 1 downTo 0) {
                setStripColor(colorValues2)
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    colorValues1
                )
                show()
                delay((delay * delayMod).toInt())
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    colorValues2
                )
            }
        }
    }


    /**
     * A non-repetitive function to run a Multi Pixel Run To Color animation.
     *
     * @param spacing Spacing between lit leds (for example, if spacing is 3
     * and led 0 is lit, led 3 will also be lit)
     * @param chaseDirection [Direction] of animation
     * @param destinationColor Color of moving pixels and color strip will
     * be at end of animation
     * @param delay Delay between moves
     * @param delayMod Multiplier for delay
     */
    fun multiPixelRunToColor(
        spacing: Int,
        chaseDirection: Direction,
        destinationColor: ColorContainer,
        delay: Int = MultiPixelRunToColor.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        if (chaseDirection == Direction.BACKWARD) {
            for (q in 0 until spacing) {
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    destinationColor
                )
                show()
                delay((delay * delayMod).toInt())
            }
        } else if (chaseDirection == Direction.FORWARD) {
            for (q in spacing - 1 downTo 0) {
                for (i in startPixel..endPixel step spacing) setPixelColor(
                    i + (-(q - (spacing - 1))),
                    destinationColor
                )
                show()
                delay((delay * delayMod).toInt())
            }
        }
    }


    /**
     * TODO(Katie)
     * @param pixelColor1
     * @param pixelColor2
     * @param pixelColor3
     * @param pixelColor4
     * @param pixelColor5
     * @param delay
     */
    fun pixelMarathon(
        pixelColor1: ColorContainer,
        pixelColor2: ColorContainer,
        pixelColor3: ColorContainer,
        pixelColor4: ColorContainer,
        pixelColor5: ColorContainer,
        delay: Int = PixelMarathon.delayDefault
    ) {
        // TODO: Change to use threads from animationThreadPool

        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-1")) {
            pixelRun(Direction.FORWARD, pixelColor5, delay = delay)
        }
        delay(100)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-2")) {
            pixelRun(Direction.BACKWARD, pixelColor4, delay = delay)
        }
        delay(200)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-1")) {
            pixelRun(Direction.BACKWARD, pixelColor2, delay = delay)
        }
        delay(300)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-2")) {
            pixelRun(Direction.FORWARD, pixelColor3, delay = delay)
        }
        delay(400)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-3")) {
            pixelRun(Direction.FORWARD, pixelColor1, delay = delay)
        }
        delay(500)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-4")) {
            pixelRun(Direction.FORWARD, pixelColor2, delay = delay)
        }
        delay(100)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-5")) {
            pixelRun(Direction.BACKWARD, pixelColor5, delay = delay)
        }
        delay(200)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-6")) {
            pixelRun(Direction.BACKWARD, pixelColor3, delay = delay)
        }
        delay(300)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-7")) {
            pixelRun(Direction.FORWARD, pixelColor4, delay = delay)
        }
        delay(200)
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-8")) {
            pixelRun(Direction.BACKWARD, pixelColor1, delay = delay)
        }
    }


    /**
     * Function to run a Pixel Run animation.
     *
     * The strip is set to colorValues2, then a pixel 'runs' along the strip.
     * Similar to Multi Pixel Run but with only one pixel.
     *
     * @param movementDirection [Direction] of animation
     * @param colorValues1 Color of 'running' pixel
     * @param colorValues2 Background color
     * @param delay Delay between moves
     * @param delayMod Multiplier for delay
     */
    fun pixelRun(
        movementDirection: Direction,
        colorValues1: ColorContainer,
        colorValues2: ColorContainer = PixelRun.color2Default,
        delay: Int = PixelRun.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        setStripColor(colorValues2)
        if (movementDirection == Direction.FORWARD) {
            for (q in 0 until ledStrip.numPixels) {
                runBlocking {
                    locks[q]!!.tryWithLock {
                        setPixelColor(q, colorValues1)
                        show()
                        delay((delay * delayMod).toInt())
                        setPixelColor(q, colorValues2)
                    }
                }
            }
        } else if (movementDirection == Direction.BACKWARD) {
            for (q in ledStrip.numPixels - 1 downTo 0) {
                runBlocking {
                    locks[q]!!.tryWithLock {
                        setPixelColor(q, colorValues1)
                        show()
                        delay((delay * delayMod).toInt())
                        setPixelColor(q, colorValues2)
                    }
                }
            }
        }
    }


    /**
     * Function to run a Pixel Run With Trail animation.
     *
     * Like a Pixel Run animation, but the 'running' pixel has a trail behind it
     * where the pixels fade from colorValues1 to colorValues2 over ~20 iterations.
     *
     * @param movementDirection [Direction] of animation
     * @param colorValues1 Color of 'running' pixel
     * @param colorValues2 Background color
     * @param delay Time between moves
     * @param delayMod Multiplier for delay
     */
    fun pixelRunWithTrail(
        movementDirection: Direction,
        colorValues1: ColorContainer,
        colorValues2: ColorContainer = PixelRunWithTrail.color2Default,
        delay: Int = PixelRunWithTrail.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        if (movementDirection == Direction.FORWARD) {
            for (q in startPixel..endPixel) {
                setPixelColor(q, colorValues1)
                show()
                GlobalScope.launch(animationThreadPool) {
                    fadePixel(q, colorValues2, 60, 25)
                }
                delay((delay * delayMod).toInt())
            }
        } else if (movementDirection == Direction.BACKWARD) {
            for (q in endPixel downTo startPixel) {
                setPixelColor(q, colorValues1)
                show()
                GlobalScope.launch(animationThreadPool) {
                    fadePixel(q, colorValues2, 60, 25)
                }
                delay((delay * delayMod).toInt())
            }
        }
    }


    /**
     * Function to run a Smooth Chase animation.
     *
     * The [colorsFromPalette] function is used to create a collection of colors
     * for the strip:
     * *The palette colors are spread out along the strip at approximately equal
     * intervals. All pixels between these 'pure' pixels are a blend between the
     * colors of the two nearest pure pixels. The blend ratio is determined by the
     * location of the pixel relative to the nearest pure pixels.*
     *
     * The collection created, palette2, is a map of integers to ColorContainers
     * where each integer is a pixel index. Each pixel is set to palette2[i],
     * where i is the pixel index. Then, if the direction is [Direction].FORWARD,
     * each pixel is set to palette2[i + 1], then palette[i + 2], etc.
     * to create the illusion that the animation is 'moving'. If the direction is
     * [Direction].BACKWARD, the same happens but with indices i, i-1, i-2, etc.
     * The index is found with (i + a) mod s, where i is the pixel index, a is the
     * offset for this iteration and s is the number of pixels in the strip.
     *
     * @param colorList A list of [ColorContainer]s to be used as pure colors when
     * creating the palette
     * @param movementDirection [Direction] of the animation
     * @param delay Time between moves
     * @param delayMod Multiplier for delay
     */
    fun smoothChase(
        colorList: List,
        movementDirection: Direction,
        delay: Int = SmoothChase.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        val palette = colorsFromPalette(colorList, numLEDs)

        if (movementDirection == Direction.FORWARD)
            for (m in startPixel..endPixel) {
                setStripColorWithPalette(palette, m)
                show()
                delay((delay * delayMod).toInt())
            }
        else
            for (m in endPixel downTo startPixel) {
                setStripColorWithPalette(palette, m)
                show()
                delay((delay * delayMod).toInt())
            }
    }


    /**
     * Function to run a Sparkle animation.
     *
     * Each LED is changed to sparkleColor for delay milliseconds before reverting
     * to its original color.
     *
     * If concurrent = true (default), a separate thread will be created for each
     * pixel. Each thread saves its pixel's original color, then waits for 0-5
     * seconds before sparkling its pixel.
     *
     * If concurrent = false, shuffleArray is shuffled and used to determine the
     * order in which the LEDs are sparkled. Unlike the concurrent version, only
     * one pixel will sparkle at any given time.
     *
     * @param sparkleColor The color the pixels will sparkle with
     * @param delay Duration of each sparkle
     * @param delayMod Multiplier for delay
     * @param concurrent Use concurrent sparkle algorithm?
     */
    fun sparkle(
        sparkleColor: ColorContainer,
        delay: Int = Sparkle.delayDefault,
        delayMod: Double = 1.0,
        concurrent: Boolean = true,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {

        if (concurrent) {
            val deferred = (startPixel..endPixel).map { n ->
                GlobalScope.async(sparkleThreadPool) {
                    val originalColor: ColorContainer = getPixelColor(n)
                    delay((random() * 5000).toInt() % 4950)
                    setPixelColor(n, sparkleColor)
                    show()
                    delay((delay * delayMod).toInt())
                    setPixelColor(n, originalColor)
                }
            }
            runBlocking {
                deferred.awaitAll()
            }
        } else {
            val myShuffleArray = runBlocking {
                shuffleLock.withLock { shuffleArray.shuffle() }
                return@runBlocking shuffleArray
            }
            myShuffleArray.shuffle()
            var originalColor: ColorContainer
            for (i in startPixel..endPixel) {
                originalColor = getPixelColor(myShuffleArray[i])
                setPixelColor(myShuffleArray[i], sparkleColor)
                show()
                delay((delay * delayMod).toInt())
                setPixelColor(myShuffleArray[i], originalColor)
            }

        }
    }


    /**
     * A non-repetitive function to run a Sparkle To Color animation.
     *
     * Very similar to the Sparkle animation, but the LEDs are not reverted to their
     * original color after the sparkle.
     *
     * If concurrent = true (default), a separate thread will be created for each
     * pixel. Each thread waits for 0-5 seconds before sparkling its pixel.
     *
     * If concurrent = false, shuffleArray is shuffled and used to determine the
     * order in which the LEDs are sparkled. Unlike the concurrent version, only
     * one pixel will sparkle at any given time.
     *
     * @param destinationColor The color the pixels will sparkle with
     * @param delay Duration of each sparkle
     * @param delayMod Multiplier for delay
     * @param concurrent Use concurrent sparkle algorithm?
     */
    fun sparkleToColor(
        destinationColor: ColorContainer,
        delay: Int = SparkleToColor.delayDefault,
        delayMod: Double = 1.0,
        concurrent: Boolean = true,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {

        if (concurrent) {
            val deferred = (startPixel..endPixel).map { n ->
                GlobalScope.async(sparkleThreadPool) {
                    delay((random() * 5000).toInt() % 4950)
                    setPixelColor(n, destinationColor)
                    show()
                    delay((delay * delayMod).toInt())
                }
            }
            runBlocking {
                deferred.awaitAll()
            }
        } else {
            val myShuffleArray = runBlocking {
                shuffleLock.withLock {
                    shuffleArray.shuffle()
                    return@runBlocking shuffleArray
                }

            }
            myShuffleArray.shuffle()
            for (i in startPixel..endPixel) {
                setPixelColor(myShuffleArray[i], destinationColor)
                show()
                delay((delay * delayMod).toInt())
            }
        }
    }


    /**
     * TODO(Katie)
     * @param stackDirection
     * @param colorValues1
     * @param delay
     * @param delayMod
     */
    fun stack(
        stackDirection: Direction,
        colorValues1: ColorContainer,
        delay: Int = Stack.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        if (stackDirection == Direction.FORWARD) {
            for (q in endPixel downTo startPixel) {
                var originalColor: ColorContainer
                for (i in startPixel until q) {
                    runBlocking {
                        locks[i]!!.tryWithLock {
                            originalColor = getPixelColor(i)
                            setPixelColor(i, colorValues1)
                            show()
                            delay((delay * delayMod).toInt())
                            setPixelColor(i, originalColor)
                        }
                    }
                }
                setPixelColor(q, colorValues1)
                show()
            }
        } else if (stackDirection == Direction.BACKWARD) {
            for (q in startPixel..endPixel) {
                var originalColor: ColorContainer
                for (i in endPixel downTo q) {
                    runBlocking {
                        locks[i]!!.tryWithLock {
                            originalColor = getPixelColor(i)
                            setPixelColor(i, colorValues1)
                            show()
                            delay((delay * delayMod).toInt())
                            setPixelColor(i, originalColor)
                        }
                    }
                }
                setPixelColor(q, colorValues1)
                show()
            }
        }
    }


    /**
     * TODO(Katie)
     * @param stackColor1
     * @param stackColor2
     */
    fun stackOverflow(stackColor1: ColorContainer, stackColor2: ColorContainer) {
        // TODO: Change to use threads from animationThreadPool
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-1")) {
            stack(Direction.FORWARD, stackColor1, delay = 2)
        }
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-2")) {
            stack(Direction.BACKWARD, stackColor2, delay = 2)
        }
    }


    /**
     * A non-repetitive function to run a Wipe animation.
     *
     * Similar to a Pixel Run animation, but the pixels do not revert to their
     * original color.
     *
     * @param colorValues Color of moving pixel and color strip will be at end
     * of animation
     * @param wipeDirection [Direction] of animation
     * @param delay Delay between moves
     * @param delayMod Multiplier for delay
     */
    fun wipe(
        colorValues: ColorContainer,
        wipeDirection: Direction,
        delay: Int = Wipe.delayDefault,
        delayMod: Double = 1.0,
        startPixel: Int = 0,
        endPixel: Int = numLEDs - 1
    ) {
        if (wipeDirection == Direction.BACKWARD) {
            for (i in endPixel downTo startPixel) {
                setPixelColor(i, colorValues)
                show()
                delay((delay * delayMod).toInt())
            }
        } else if (wipeDirection == Direction.FORWARD) {
            for (i in startPixel..endPixel) {
                setPixelColor(i, colorValues)
                show()
                delay((delay * delayMod).toInt())
            }
        }
    }


    /* Experimental animations */

    @Experimental
    fun animation004(colorValues1: ColorContainer) {    // "bounce to color"
        for (i in 0 until ledStrip.numPixels / 2) {
            for (j in i until ledStrip.numPixels - i) {
                setPixelColor(j, colorValues1)
                show()
                delay(5)
                setPixelColor(j, CCBlack)
            }
            setPixelColor(ledStrip.numPixels - i - 1, colorValues1)
            for (j in ledStrip.numPixels - i - 2 downTo i) {
                setPixelColor(j, colorValues1)
                show()
                delay(5)
                setPixelColor(j, CCBlack)
            }
            setPixelColor(i, colorValues1)
            show()
        }
        if (ledStrip.numPixels % 2 == 1) {
            setPixelColor(ledStrip.numPixels / 2, colorValues1)
            show()
        }
    }


    @Experimental
    fun animation004_2(colorValues1: ColorContainer) {    // "bounce"
        for (i in 0 until ledStrip.numPixels / 2) {
            for (j in i until ledStrip.numPixels - i) {
                val originalColor: ColorContainer = getPixelColor(j)
                setPixelColor(j, colorValues1)
                show()
                delay(10)
                setPixelColor(j, originalColor)
            }
            setPixelColor(ledStrip.numPixels - i - 1, colorValues1)
            GlobalScope.launch(animationThreadPool) {
                val p = ledStrip.numPixels - i - 1
                fadePixel(p, CCBlack, 25, 50)
            }
            for (j in ledStrip.numPixels - i - 2 downTo i) {
                val originalColor: ColorContainer = getPixelColor(j)
                setPixelColor(j, colorValues1)
                show()
                delay(10)
                setPixelColor(j, originalColor)
            }
            setPixelColor(i, colorValues1)
            show()
            GlobalScope.launch(animationThreadPool) {
                fadePixel(i, CCBlack, 25, 50)
            }
        }
        if (ledStrip.numPixels % 2 == 1) {
            setPixelColor(ledStrip.numPixels / 2, colorValues1)
            show()
            GlobalScope.launch(animationThreadPool) {
                val p = ledStrip.numPixels / 2
                fadeMap[p]?.fade(CCBlack)
            }
        }
    }


    @Experimental
    fun animation005(   // "sparkle fade"
        sparkleColor: ColorContainer,
        delay: Int = 50,
        delayMod: Double = 1.0,
        concurrent: Boolean = true
    ) {
        if (concurrent) {
            var complete = false
            val deferred = (0 until ledStrip.numPixels).map { n ->
                GlobalScope.async(sparkleThreadPool) {
                    delay((random() * 5000).toInt())
                    setPixelColor(n, sparkleColor)
                    GlobalScope.launch(sparkleThreadPool) {
                        fadePixel(n, CCBlack, 25)
                    }
                    delay((delay * delayMod).toInt())
                }
            }
            runBlocking {
                deferred.awaitAll()
                complete = true
            }
        } else {
            val myShuffleArray = runBlocking {
                shuffleLock.withLock { shuffleArray.shuffle() }
                return@runBlocking shuffleArray
            }
            myShuffleArray.shuffle()
            for (i in 0 until ledStrip.numPixels) {
                for (j in 0 until ledStrip.numPixels) {
                    setPixelColor(myShuffleArray[j], blend(getPixelColor(myShuffleArray[j]), CCBlack, 10))
                }
                setPixelColor(myShuffleArray[i], sparkleColor)
                show()
                delay((delay * delayMod).toInt())
            }
        }
    }


    @Experimental
    fun multiSparkleToColor(
        point1: Int,
        point2: Int,
        color1: ColorContainer,
        color2: ColorContainer,
        delay: Int = 1000,
        delayMod: Double = 1.0
    ) {
        val endptA = 0
        val endptB: Int
        val endptC: Int
        val endptD = numLEDs - 1

        if (point1 <= point2 && point1 > endptA && point2 < endptD) {
            endptB = point1
            endptC = point2
        } else if (point2 > endptA && point1 < endptD) {
            endptB = point2
            endptC = point1
        } else {
            endptB = numLEDs / 3
            endptC = (numLEDs * 2 / 3) - 1
        }

        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-1")) {
            setSectionColor(endptA, endptB, color1)
            delay((delay * delayMod).toInt())
            setSectionColor(endptA, endptB, color2)
            delay((delay * delayMod).toInt())
        }
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-2")) {
            setSectionColor(endptC, endptD, color1)
            delay((delay * delayMod).toInt())
            setSectionColor(endptC, endptD, color2)
            delay((delay * delayMod).toInt())
        }
        GlobalScope.launch(newSingleThreadContext("Thread ${Thread.currentThread().name}-3")) {
            setSectionColor(endptB, endptC, color2)
            delay((delay * delayMod).toInt())
            setSectionColor(endptB, endptC, color1)
            delay((delay * delayMod).toInt())
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy