Queuing message and timing. (text to voice stuff)

(Sod's law says if I don't ask I won't think of the answer ITMT)

I'm playing with voice messages being played.

Usually they are short messages and I have a rate limit node set to limit them to 1 message per 10 5 seconds - which should be enough.

BUT - and there's usually one of those:

I'm messing around with some other messages that are longer than 10 seconds and the queuing doesn't work. (They overlap) :frowning:

So how am I going to store any message/s until these longer ones are complete?

First thoughts are I get the signal from the player node and use that to clock the next message through.

But that gets complicated if there are more than one messages queued.

The rate limiter node also queues up other messages that arrive and sends them on with the 10 5 second delay between them.

But this problem transcends (?) this because a message may take more than 10 5 seconds.

Thoughts?

(
A longer message is 13.39 seconds. It is not ....... going to be nice in the bigger picture if I limit the message to 1 every 14/15 seconds.
That is like 3 times the delay in getting messages out.
)

Is it possible to calculate the length of each message up front? I mean knowing the time it will be spoken so to say? If so you could, for each message that you send to the delay node (rate limiting) set the msg.rate accordingly. As example, the first message is expected to take 4.7 sec to be spoken, add 0.5 second and set the msg.rate to 5.2. The second message is a longer, like 17.3 sec, you set msg.rate to 17.8. In this way I don't think you will get any overlaps. Primary issue is to calculate the expected message length (EDIT: but it might be one way by counting the numbers of chars in the message and from that make an estimate)

1 Like

Alas no.

I was thinking that, but as the actual play time is unknown, it kinda makes it difficult.

It is inconvenient but alas I got trapped while testing and messages over ran one another.

I'm not sure it is really worth worrying about but trying to be a perfectionist. :wink:

Again: I'll say it will be Sod's law that if I don't worry about it I will get conflicts/overlaps.
And if I do put in the effort, they probably won't.

Just looking for easy-ish options how to mitigate the problem/possibility.

But you have already some message samples I guess, and you know or have measured the play time for them. That means you just have to count the number of chars in each and you have a pretty good rule for "time to speak", also taking aussie english voice variant into account :wink:
If you then run each text message through a function node (or change node if you prefer) before you buffer them you can easily get the length of the message string and then set msg.rate

Yes, and I gave a nominal length.

But that message is not a fixed length message.
(It is a weather report)

So depending on how complex the weather is, depends on how long the message could be.

I haven't fully tested ALL the messages - mostly notifications of fixed events - so should be short enough to fit in 5 seconds.

Yeah, I may have to stretch it out a bit, but don't really want to go as far as >15 seconds.

Yeah, I don't know how to make google talk Aussie. I shall have to check the accents. (Just there are a LOT of them from which to choose)

I could put it through a function node and count the words and give (say) 1.1 seconds per word.
Then create a msg.delay to include in the message.

That goes through the rate limit node and sets the value.

Ok, expanding on that:

I'd have to block any message with smaller values getting through - while it is running.

Then when all messages are sent (no more in the queue) a signal is sent back to the ~~rate limit ~~ sorry, the node that checks the msg.delay value and only lets bigger ones through, to then accept the next one regardless of the value.

Thoughts?

Maybe my node can help?

It’s a semaphore node, with some advanced queue management.

If you prefer to use built in nodes, I think there is one from @Colin, but I’m on my phone, so don’t have the screen space to find it currently.

1 Like

Does the speech node output anything when the speech is finished?
If so you could send msg.flush = 1 back to delay node to release the next message. Then you can set a high rate limit say 30 seconds.

1 Like

Thanks, but reading that stuff it is very much above my skill set.

It looks like a good idea, but I am not sure how to apply it to what I am doing.

Yes.

It is an exec node.

That is interesting.
So I have the rate limit (to call it that) set to 30 second.

When the message is played and the exec node gives it's output, I send the flush = 1 back to that node and that will send the next message.

Is that right?

Yes send msg.flush set to 1 with no other properties in the message.

(I should read MORE of the readme in the node's notes.)

Yeah, I scrolled down and there it is written.

That may be what I need.

(ALL)

Hang on folks while I test it.
:slight_smile:

SO CLOSE!

Ok, so there is a problem when the queue is emptied, it still is getting a flush = 1 message.

So I guess I will have to put a node to detect an undefined message just after the rate limit node.

That's what I understand.

Any other ideas?

This is the code:

Sorry it is spread around.
Hard to reposition nodes in/on an active flow.

Input on the left.
On the right is the signal when the TTS is done and the signal is sent back to the rate limit node.

[{"id":"fc43ebd3458bb418","type":"function","z":"235f16ee6e459f2c","name":"next message","func":"let msg1 = {\"flush\":1};\nreturn msg1;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2855,"y":1040,"wires":[["d0e9fa97e2ccdf1d","b158f920aa60f2e6"]],"l":false},{"id":"d0e9fa97e2ccdf1d","type":"link out","z":"235f16ee6e459f2c","name":"next message","mode":"link","links":["70681aaf5f80084c"],"x":2915,"y":1040,"wires":[]},{"id":"70681aaf5f80084c","type":"link in","z":"235f16ee6e459f2c","name":"next message","links":["d0e9fa97e2ccdf1d"],"x":1485,"y":690,"wires":[["65cb9ca1817a2880","ec0e4a9740c2a5fe"]]},{"id":"65cb9ca1817a2880","type":"delay","z":"235f16ee6e459f2c","name":"Rate limit messagse","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":true,"outputs":1,"x":1690,"y":640,"wires":[["1fb4394a9fd4a897"]]},{"id":"1fb4394a9fd4a897","type":"switch","z":"235f16ee6e459f2c","name":"block blank messages","property":"payload","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":1835,"y":640,"wires":[["32d77837106b0a01"]],"l":false}]

Seems to work.

Thanks @E1cid

This is what it looks like - picture.
(See selected nodes)

Here is a simple flow using a Delay node to do what you want, I think. It was originally inspired by @dceejay. It has been posted on the forum multiple times. I aught to add it to the cookbook I suppose.

Replace the 'Some functions to be performed' and 'Some more stuff to do' nodes with your audio out stuff. Set the delay time in the Delay (Queue) node to a value larger than the longest you expect your output to take. That stops the loop locking up if the speech output hangs for some reason.

Edit. Forgot the flow:

[{"id":"b6630ded2db7d680","type":"inject","z":"bdd7be38.d3b55","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":480,"wires":[["ed63ee4225312b40"]]},{"id":"ed63ee4225312b40","type":"delay","z":"bdd7be38.d3b55","name":"Queue","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":310,"y":480,"wires":[["d4d479e614e82a49","7eb760e019b512dc"]]},{"id":"a82c03c3d34f683c","type":"delay","z":"bdd7be38.d3b55","name":"Some more stuff to do","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":800,"y":480,"wires":[["7c6253e5d34769ac","b23cea1074943d4d"]]},{"id":"2128a855234c1016","type":"link in","z":"bdd7be38.d3b55","name":"link in 1","links":["7c6253e5d34769ac"],"x":95,"y":560,"wires":[["3a9faf0a95b4a9bb"]]},{"id":"7c6253e5d34769ac","type":"link out","z":"bdd7be38.d3b55","name":"link out 1","mode":"link","links":["2128a855234c1016"],"x":665,"y":560,"wires":[]},{"id":"b23cea1074943d4d","type":"debug","z":"bdd7be38.d3b55","name":"OUT","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":400,"wires":[]},{"id":"d4d479e614e82a49","type":"debug","z":"bdd7be38.d3b55","name":"IN","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":470,"y":400,"wires":[]},{"id":"3a9faf0a95b4a9bb","type":"function","z":"bdd7be38.d3b55","name":"Flush","func":"return {flush: 1}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":190,"y":560,"wires":[["ed63ee4225312b40"]]},{"id":"7eb760e019b512dc","type":"function","z":"bdd7be38.d3b55","name":"Some functions to be performed","func":"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":550,"y":480,"wires":[["a82c03c3d34f683c"]]},{"id":"e35f37deeae94860","type":"comment","z":"bdd7be38.d3b55","name":"Set the queue timeout to larger than you ever expect the process to take","info":"OK, here is a simple flow which allows a sequence of nodes to be protected so that only one message is allowed in at a time. It uses a Delay node in Rate Limit mode to queue them, but releases them, using the Flush mechanism, as soon as the previous one is complete. Set the timeout in the delay node to a value greater than the maximum time you expect it ever to take. If for some reason the flow locks up (a message fails to indicate completion) then the next message will be released after that time.\nMake sure that you trap any errors and feed back to the Flush node when you have handled the error. Also make sure only one message is fed back for each one in, even in the case of errors.","x":270,"y":360,"wires":[]}]

Did you forget something?

Oops! Apparently so, thanks. I have added it to the post.

That is what I did, but there is a problem when the queue was empty, empty messages are sent out.

I had to put a switch node to only allow messages with valid/non-blank/empty payloads.

Are you using the latest version of node-red? I think there was a bug in the Delay node to do with flush. It was some time ago though.

So mixing things together, this is also a working example, at least it works for me. Each message of any length is spoken clearly and when the exec node finishes, the next message is released from the queue
UPDATE: Now with the possibility to set volume per message

[
    {
        "id": "dc9c804d93b8604c",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "This works perfect for me",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 440,
        "wires": [
            [
                "98f0f0a0fb7fa91a",
                "fb65b55a15f91ce0"
            ]
        ]
    },
    {
        "id": "98f0f0a0fb7fa91a",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Shouting out loud",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 490,
        "wires": [
            [
                "065487eaf8d72eb8",
                "00dad86f5d1dc82e"
            ]
        ]
    },
    {
        "id": "3045e161477d9c6c",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Whispering something",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 590,
        "wires": [
            [
                "be37ccad33cd4349",
                "b249543429ae890f"
            ]
        ]
    },
    {
        "id": "be37ccad33cd4349",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Hi",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 640,
        "wires": [
            [
                "e6326c8168bb0b49"
            ]
        ]
    },
    {
        "id": "3992d0b1c0f6c3ae",
        "type": "inject",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 130,
        "y": 390,
        "wires": [
            [
                "6e7c5489faed9f97"
            ]
        ]
    },
    {
        "id": "6e7c5489faed9f97",
        "type": "delay",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "5",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 300,
        "y": 390,
        "wires": [
            [
                "dc9c804d93b8604c"
            ]
        ]
    },
    {
        "id": "a1f7aa1d2c570191",
        "type": "delay",
        "z": "bf1b20db733dbc1c",
        "name": "Queue",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "hour",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 820,
        "y": 440,
        "wires": [
            [
                "fcebfe9a28e170b8"
            ]
        ]
    },
    {
        "id": "5b88c1d8e8a57e94",
        "type": "function",
        "z": "bf1b20db733dbc1c",
        "name": "Flush",
        "func": "return {flush: 1}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 820,
        "y": 510,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "50dedb7ad0098434",
        "type": "switch",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "property": "filesArray",
        "propertyType": "msg",
        "rules": [
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 1130,
        "y": 440,
        "wires": [
            [
                "35e354a71a23bf33"
            ]
        ]
    },
    {
        "id": "35e354a71a23bf33",
        "type": "function",
        "z": "bf1b20db733dbc1c",
        "name": "function 10",
        "func": "let mp = msg.filesArray[0].file;\nlet vol = msg.passThroughMessage.volume;\nmsg.payload = '-af ' + vol + ' ' + mp;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1130,
        "y": 510,
        "wires": [
            [
                "4974b215e624956d"
            ]
        ]
    },
    {
        "id": "4974b215e624956d",
        "type": "exec",
        "z": "bf1b20db733dbc1c",
        "command": "ffplay -nodisp -autoexit",
        "addpay": "payload",
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "winHide": false,
        "oldrc": false,
        "name": "",
        "x": 1140,
        "y": 590,
        "wires": [
            [],
            [],
            [
                "5b88c1d8e8a57e94"
            ]
        ]
    },
    {
        "id": "fcebfe9a28e170b8",
        "type": "ttsultimate",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "voice": "en-AU",
        "ssml": false,
        "sonosipaddress": "192.168.1.109",
        "sonosvolume": "30",
        "sonoshailing": "0",
        "config": "557d8082.eb5a8",
        "property": "payload",
        "propertyType": {},
        "rules": [],
        "playertype": "noplayer",
        "speakingrate": "",
        "speakingpitch": "",
        "unmuteIfMuted": false,
        "elevenlabsStability": "",
        "elevenlabsSimilarity_boost": "",
        "x": 970,
        "y": 440,
        "wires": [
            [
                "50dedb7ad0098434"
            ],
            []
        ]
    },
    {
        "id": "065487eaf8d72eb8",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "A long message about nothing really, just demonstrating that I can talk about anything",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 540,
        "wires": [
            [
                "3045e161477d9c6c",
                "a6610b8ddb62b773"
            ]
        ]
    },
    {
        "id": "fb65b55a15f91ce0",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 440,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "00dad86f5d1dc82e",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.7",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 490,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "a6610b8ddb62b773",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 540,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "b249543429ae890f",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.1",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 590,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "e6326c8168bb0b49",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 640,
        "wires": [
            [
                "a1f7aa1d2c570191"
            ]
        ]
    },
    {
        "id": "557d8082.eb5a8",
        "type": "ttsultimate-config",
        "name": "googletranslate",
        "noderedipaddress": "127.0.0.1",
        "noderedport": "1980",
        "purgediratrestart": "leave",
        "ttsservice": "googletranslate",
        "TTSRootFolderPath": ""
    }
]

Just for the completness, a variant that uses ffmpeg and aplay instead of ffplay. Both variants works fine on my RPi4 with a JBL BT speaker as well as on my Lenovo laptop running Debian using internal speakers

[
    {
        "id": "d4aabbddc6e7ae52",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "This works perfect for me",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 790,
        "wires": [
            [
                "cd3b318c9a91def8",
                "2e5cee9598bda8ef"
            ]
        ]
    },
    {
        "id": "cd3b318c9a91def8",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Shouting out loud",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 840,
        "wires": [
            [
                "9aa2d23f4c44d37c",
                "5aaacff5bd23d1c6"
            ]
        ]
    },
    {
        "id": "b0ec47d1af194872",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Whispering something",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 940,
        "wires": [
            [
                "a5d7ad1337e6fe0f",
                "8794c1417ac1ce63"
            ]
        ]
    },
    {
        "id": "a5d7ad1337e6fe0f",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "Yeah, I don't know how to make google talk Aussie. I shall have to check the accents. (Just there are a lot of them from which to choose)",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 990,
        "wires": [
            [
                "874794483c6bb6f2",
                "57f41d5db45625a0"
            ]
        ]
    },
    {
        "id": "382ccf406bbba79d",
        "type": "inject",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "true",
        "payloadType": "bool",
        "x": 130,
        "y": 740,
        "wires": [
            [
                "7148105b7475350d"
            ]
        ]
    },
    {
        "id": "7148105b7475350d",
        "type": "delay",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "5",
        "rateUnits": "second",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 300,
        "y": 740,
        "wires": [
            [
                "d4aabbddc6e7ae52"
            ]
        ]
    },
    {
        "id": "8b4370ce030eb42c",
        "type": "delay",
        "z": "bf1b20db733dbc1c",
        "name": "Queue",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "1",
        "rateUnits": "hour",
        "randomFirst": "1",
        "randomLast": "5",
        "randomUnits": "seconds",
        "drop": false,
        "allowrate": false,
        "outputs": 1,
        "x": 820,
        "y": 790,
        "wires": [
            [
                "b5b66dde6664f9d7"
            ]
        ]
    },
    {
        "id": "52f3de084e06e73d",
        "type": "function",
        "z": "bf1b20db733dbc1c",
        "name": "Flush",
        "func": "return {flush: 1}",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 820,
        "y": 860,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "e9f6d6e02176127b",
        "type": "switch",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "property": "filesArray",
        "propertyType": "msg",
        "rules": [
            {
                "t": "nnull"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 1,
        "x": 1130,
        "y": 790,
        "wires": [
            [
                "7204f0eec38cc9dc"
            ]
        ]
    },
    {
        "id": "7204f0eec38cc9dc",
        "type": "function",
        "z": "bf1b20db733dbc1c",
        "name": "function 11",
        "func": "let mp = msg.filesArray[0].file;\nlet vol = msg.passThroughMessage.volume;\nmsg.payload = 'ffmpeg -i ' + mp + ' -hide_banner ' + '-af ' + vol + ' -f wav - | aplay';\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1130,
        "y": 860,
        "wires": [
            [
                "2156ba89eef3c7c5"
            ]
        ]
    },
    {
        "id": "2156ba89eef3c7c5",
        "type": "exec",
        "z": "bf1b20db733dbc1c",
        "command": "",
        "addpay": "payload",
        "append": "",
        "useSpawn": "false",
        "timer": "",
        "winHide": false,
        "oldrc": false,
        "name": "",
        "x": 1130,
        "y": 930,
        "wires": [
            [],
            [],
            [
                "52f3de084e06e73d"
            ]
        ]
    },
    {
        "id": "b5b66dde6664f9d7",
        "type": "ttsultimate",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "voice": "en-AU",
        "ssml": false,
        "sonosipaddress": "192.168.1.109",
        "sonosvolume": "30",
        "sonoshailing": "0",
        "config": "557d8082.eb5a8",
        "property": "payload",
        "propertyType": {},
        "rules": [],
        "playertype": "noplayer",
        "speakingrate": "",
        "speakingpitch": "",
        "unmuteIfMuted": false,
        "elevenlabsStability": "",
        "elevenlabsSimilarity_boost": "",
        "x": 970,
        "y": 790,
        "wires": [
            [
                "e9f6d6e02176127b"
            ],
            []
        ]
    },
    {
        "id": "9aa2d23f4c44d37c",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "A long message about nothing really, just demonstrating that I can talk about anything",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 890,
        "wires": [
            [
                "b0ec47d1af194872",
                "c12e41d9d44b71fc"
            ]
        ]
    },
    {
        "id": "2e5cee9598bda8ef",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 790,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "5aaacff5bd23d1c6",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.7",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 840,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "c12e41d9d44b71fc",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 890,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "8794c1417ac1ce63",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.1",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 940,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "874794483c6bb6f2",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 990,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "57f41d5db45625a0",
        "type": "trigger",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "op1": "I haven't fully tested ALL the messages - mostly notifications of fixed events - so should be short enough to fit in 5 seconds.",
        "op2": "",
        "op1type": "str",
        "op2type": "nul",
        "duration": "250",
        "extend": false,
        "overrideDelay": false,
        "units": "ms",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 300,
        "y": 1040,
        "wires": [
            [
                "3aa592ae6d028079"
            ]
        ]
    },
    {
        "id": "3aa592ae6d028079",
        "type": "change",
        "z": "bf1b20db733dbc1c",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "volume",
                "pt": "msg",
                "to": "volume=0.3",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 540,
        "y": 1040,
        "wires": [
            [
                "8b4370ce030eb42c"
            ]
        ]
    },
    {
        "id": "557d8082.eb5a8",
        "type": "ttsultimate-config",
        "name": "googletranslate",
        "noderedipaddress": "127.0.0.1",
        "noderedport": "1980",
        "purgediratrestart": "leave",
        "ttsservice": "googletranslate",
        "TTSRootFolderPath": ""
    }
]