Post about your Basic project here

Moderator: Mmiscool

User avatar
By Electroguard
#53307 Voice Announcer

Here are 2 different Voice Announcers...

1. The first offers stand-alone selectable pre-recorded voice messaging capability to your ESP_Basic projects - allowing your projects to speak when you wish them to say something. This is a non-network version developed on V3, but it can easily be converted to be V2 compatable by removing the comma's from the web buttons and slider controls.

2. The second implementation offers a V2 version giving EasyNet udp remote-controlled Voice Announcement system capability - allowing remote (or local) event triggers to cause corresponding local alert messages to be spoken. Multiple Voice Announcer nodes might speak different (or identical) audio messages in different multiple locations if wished, allowing common messages to be broadcast around the house, as well as perhaps more targeted messages to be aimed at specific locations.

Both the stand-alone and network versions use the same JQ6500-28P module that was used in the Music Player published here... http://www.esp8266.com/viewtopic.php?f=40&t=9381

The following link offers a good article about using the JQ6500, including how to control it using IR signals or hardware buttons if wished... http://sparks.gogo.co.nz/jq6500/index.html

The 2 projects offered here are based on sending the JQ6500 appropriate serial control codes for instructing it to play the specified audio file in the specified folder, and no attempt is made to receive any info back from the JQ6500. If you wish to take things further, the following article offers a good source of info regarding the serial codes... http://sparks.gogo.co.nz/jq6500/serial.html

According to the article above:
"Play file by folder and name from SD Card needs to use folders named 01 through 99, and files in those folders named 001.mp3 (or .wave) through 999. Note that arguments are a single byte, so effectively you can only access up to file 255 in any folder"
.
In summary, you appear to be limited to 99 folders each holding up to 255 files... which should be ample for most purposes.

For my purposes I need a few dozen 'events' to announce corresponding event messages, thus 'event 1' would play audio file 001, and 'event 9' would play audio file 009, etc.

I don't envisage ever requiring more than 99 different sensor-triggered events, so I have opted for a convention which uses files 001 to 099 to specify 'primary' event messages, and uses files 101 to 199 for any possible 'opposite' type secondary event messages if they are needed... so for instance event 5 could have an associated primary file 005 'Door Open' message, plus a corresponding associated secondary file 105 'Door Closed' message if wished.
I've reserved file 200 for a main 'Welcome" type message, and file numbers from 201 upwards are reserved for specific system-type messages such as "Volume UP" etc.
This convention allows the entire 'message set' to conveniently fit into one folder, allowing use of different folders for alternative 'custom' message sets, or even alternative language sets if wished. So this convention lends itself well to having a bog-standard development 'message set' in folder 01 which announces events by their 'channel' or zone' number, ie: "channel 5 triggered" or "zone 5 alert" or whatever you may prefer, but it then allows subsequent easy switching to a custom message set in folder 02 for announcing tailored messages to events, such as "Shed door opened" or "Mailbox delivery" etc.
This is the convention adopted for the EasyNet version, which basically just sends a file-number to be announced from the currently selected folder (more later).

Another useful convention (perhaps particularly for the stand-alone version) might be to use folder 01 for all primary 'On' event messages, folder 02 for all secondary 'Off' event messages, and folder 03 for all system type messages.

Don't forget that ESP_Basic is capable of reading IR signals, so an advantage of having a folder just for system type messages could be to have up to 254 voice response messages available for a 'voice announced menu' if you wished, which could effectively offer a multi-level menu of options each containing several sub-menus of multiple options which you might navigate through (perhaps with an IR remote controller) using audio feedback - rather than depending on visual feedback, which obviously requires some form of connected display device.
Choose whatever file and folder convention best suits your own purposes and offers you easiest access and control of your recorded audio files.

The JQ6500-28P module I'm using has on-board mini-USB and micro-SD slot, so it is possible to load files to SD remotely, then plug in the card. Or you could load the files directly through the JQ6500-28P USB port which should show up as a Removable Device on your computer if the device drivers load ok.

The stripped-down numerical filing systems used by cheap asian devices are prone to reading files according to the order they were written to disk irrespective of what the files may be called, and although the JQ6500 actually has a separate 'native FAT order' addressing mode, my experiences have shown that it doesn't take much for the Named addressing to get it's nickers in a twist if given a non-existent file to play, or trying to play file 0 even if it does exist.
So don't take liberties, and only send it filenames to play that actually do exist in folders which exist, cos once it gets it's nickers in a twist it might require a JQ6500 power off/on to straighten things out again.

Be aware that when driving an attached speaker the JQ6500 module is prone to bursting into 'motor-boat' oscillation if the volume is above about a third to a half - this is caused by excessive power consumption of the onboard amp, which keeps causing the module to rapidly reset and reboot over and over. If using an external amplifier instead of a speaker, the modules onboard amp is less loaded so the module volume should be less of an issue, but bear in mind that a breadboard rats-nest acts like a bunch of aerials likely to introduce a lot of 'noise' to the relatively low-level audio signal output.

Record your audio messages however you prefer... you could record someone speaking messages into a mic, or you could use an audio utility like audacity to record text-to-speech messages from your computer (perhaps using Google Translate), or you could use one of the online TTS sites which allow you to type in text then create a resulting audio file using whatever voice you selected.
I am using .wav files, but the voice module should be able to play .mp3 if you prefer (it played MP3s in the Music Player project).

Message Tips - Name your original recordings with something meaningful indicating the message contents, and keep them as an un-touched source to return to if/when needed.
Create a list of file numbers and their corresponding spoken messages to refer to, then rename a copy of the relevent files in another folder according to your list.
First create your folder structure containing the required files on your computer, then copy to SD when done, so that all files and folders get written to SD media in their correct numerical name order.
Create a list of your required message set folders with an appropriate corresponding description, ie:
01 - default english
02 - custom english - Electroguard system
03 - custom english 2 - Joe Bloggs system
04 - default french
05 - custom french
10 - default chinese
99 - klingon


I have no public online storage for posting up accompanying pictures or attachments, so please refer to the previous links for pinouts and connection details.
Essentially you just need to connect up the 3,3v and 0v, an RX serial connection from your designated ESP TX pin, plus either the speaker pair, or left and/or right audio output to an external amp. The JQ6500 can be quite 'juicy' in operation, so ensure it is operating from an adequate 3.3v power supply, and make sure to link the JQ6500 and ESP 0v together.

The ESP_Basic script uses serial2 for communicating to the JQ6500, so you can assign alternative serial pin(s) if you wish... the script is not looking for any responses, so a 9600 connection from the assigned ESP TX pin to the JQ6500s RX is all that's required for our purposes.

It is useful to connect an LED with limiting resistor from the JQ6500 'Busy' pin to 3.3v. The normally low 'active high' Busy signal will cause the led to remain lit except during the time it is pulled high when playing an audio file, so it gives a good idea of what's happening and when.

The Busy pin could also be fed back to one of the ESPs digi-inputs to prevent it from sending any new announcements until the previous announcement is finished, but I haven't included any software facility for that yet because it would require some form of FIFO buffer, which will probably involve using arrays, which is something I haven't tried in ESP_Basics yet.


Stand-Alone version - How To Use
To announce (ie: play) the specified 'vfile' from the specified 'vdir' folder, the following command is used...
serial2println chr(vstart) & chr(4) & chr(vfiledir) & chr(vdir) & chr(vfile) & chr(vend)

The script already initialises vdir to the '01' folder, so if that's where your specified audio files are located it is not necessary to keep redefining 'vdir' before each use, therefore its only necessary to change 'vfile' to specify the file number of the required audio file.

Eg: assuming you have a folder on the SD card called 01 which has 001.wav, 002.wave, 003.wav and you wish to play 002.wav, you would use...
Code: Select alllet vfile = 002  '(don't include the file extension)
serial2println chr(vstart) & chr(4) & chr(vfiledir) & chr(vdir) & chr(vfile) & chr(vend) 


So each of your trigger event subroutines might look something like...
Code: Select allif event4triggered then
   let vfile = 004
   serial2println chr(vstart) & chr(4) & chr(vfiledir) & chr(vdir) & chr(vfile) & chr(vend) 
endif


(ESP_Basic will ignore leading zero's from numbers, therefore it doesn't hurt to include them if it helps be consistent when specifying the appropriate 3 digit filename numbers)


StandAlone Code
Code: Select allmemclear
let title = "Voice Announcer - Version JQ6500 - Developed on V3:A40 "
'To use on V2 instead of V3, remove all comma's from 'button' and 'slider' lines.
'Requires JQ6500-28P module, using RX for serial control mode to play selected file. 
 
[VOICEINIT]
serial2begin 9600,4,5  'Only needs JQ6500 RX pin (5)
'Note: to avoid possible confusion, remember that some ESP devices incorrectly depict gpio04 and gpio05 reversed.
let vdir = 1  ' Default audio folder
let vfile = 1  ' Default audio file (rather than risk complications by specifying an empty filename). Change to suit.
let vol = 15  'Default to mid-volume (max=30)
'Obscure JQ6500 serial codes are defined below into more intuitive names for convenience.
let vstart = 126 'Serial command start byte
let vend = 239 'Serial command end byte
let vplay = 13
let vpause = 14
let vnext = 1
let vprev = 2
let volup = 4 
let voldown = 5
let vset = 6
let vreset = hextoint("0C")
let vdirfile = hextoint("12")
let vloopmode = hextoint("11")
let vdchange =  hextoint("0F")
let vdnext =  hextoint("01")
let vdprev =  hextoint("00")
gosub [VOICERESET]

'Paint the screen
print title
print
html "<BR><BR>"
html "Folder "
textbox vdir
html "  File "
textbox vfile
button "Play selected file", [PLAYFILE]
html "<BR><BR>"
button "<", [PREV]
button "Play", [PLAY]
button ">", [NEXT]
html "<BR><BR>"
button "< Folder", [DPREV]
button "Folder >", [DNEXT]
html "<BR><BR>"
button "< Vol", [VOL<]
button "Vol >", [VOL>]
html "<BR><BR>"
slider vol, 0, 30
button "Set Volume", [SETVOL]
html "<BR><BR>"
'drops through to [HOME] page after inititialisation and screen painting.

[HOME]
'Program returns here each time after sending serial instructions to JQ6500
wait


[SETVOL]
'Set absolute volume value
serial2println chr(vstart) & chr(3) & chr(vset) & chr(vol) & chr(vend)
goto [HOME]

[VOL<]
'Relative volume decrease
serial2println chr(vstart) & chr(2) & chr(voldown) & chr(vend)
goto [HOME]

[VOL>]
'Relative volume increase
serial2println chr(vstart) & chr(2) & chr(volup) & chr(vend)
goto [HOME]

[PLAYFILE]
'Play specified 'vfile' audio file from 'vdir' folder
serial2println chr(vstart) & chr(4) & chr(vdirfile) & chr(vdir) & chr(vfile) & chr(vend)
goto [HOME]

[PLAY]
'Play current (last played) file
serial2println chr(vstart) & chr(2) & chr(vplay) & chr(vend)
goto [HOME]

[NEXT]
'Play next file
serial2println chr(vstart) & chr(2) & chr(vnext) & chr(vend)
goto [HOME]

[PREV]
'Play previous file
serial2println chr(vstart) & chr(2) & chr(vprev) & chr(vend)
goto [HOME]

[DNEXT]
'Select next folder
serial2println chr(vstart) & chr(3) & chr(vdchange) & chr(vdnext) & chr(vend)
goto [HOME]

[DPREV]
'Select previous folder
serial2println chr(vstart) & chr(3) & chr(vdchange) & chr(vdprev) & chr(vend)
goto [HOME]

[VOICERESET]
'Reset JQ6500
serial2println chr(vstart) & chr(2) & chr(vreset) & chr(vend)
delay 500
'Set volume
serial2println chr(vstart) & chr(3) & chr(vset) & chr(vol) & chr(vend)
return
 



EasyNet UDP version - How To Use

* Assume your Voice Announce node is called "Voice1" and has a folder on its SD card called 01 which contains files 001.wav, 002.wav, 003.wav.
Let's say you wish a networked nodes triggered event to cause event message 002.wav to be played, the udp command for your event-triggered node to issue would be...
Voice1 ANNOUNCE 002

* Assuming you may have several EasyNet Voice Announcer nodes - all members of the default "ALL" group - you could cause all to play the same message using...
ALL ANNOUNCE 002
(other non-Announcer "ALL" group members would ignore the unrecognised command)

* If you wish to test the node locally (from the Broadcast message window) you could use... "LOCAL ANNOUNCE 002", similarly for any of the nodes recognised udp commands, such as "LOCAL SETVOL 20".

* If you wish to use a different message set in a different folder you can effectively specify the required folder by sending a "VDIR (number)" udp command - eg:
"Voice1 VDIR 02" which would set Node1's message folder variable 'vdir' to "02" for all subsequent ANNOUNCE messages. The event message numbers still need to correspond of course - so event 005 would still play a relevent message 005 - but it might now be a customised message, or perhaps even in a different language if wished.

* Alternatively you could change folder by using the "DIR>" and "DIR<" network commands, but be aware it's an easy way to cause problems by pointing to non-existing folders and files.

* If you do get things wrong you can try sending the "VOICERESET" command - eg: sending "Voice1 VOICERESET" would cause Voice1 node to reset it's file, folder, and volume variables back to defaults (vdir=1, vfile=1, vol=15).

* You can remotely change the volume by issuing the "VOL>" and "VOL<" commands, or alternatively use "SETVOL (number)", where number is between 0 and 30 (midvol=15).

* For completeness, I've created a 'VFILE' udp command (to accompany the VDIR command) for allowing remote setting of the 'vfile' filename variable - but I doubt it'll prove useful because the required vfile parameter would invariable be passed along with the 'ANNOUNCE (vfile)' command anyway.

All such new 'Voice Announce' commands mentioned above have been added into [LOCAL_COMMANDS] along with an appropriate subroutine [BRANCH] for the command to jump to when recognised, so adding/removing/modifying any recognised udp commands is a simple matter for others to do likewise if wished.

And finally - the Voice Announcer functionality does not impede the Relay functionality. Therefore I have included a 'goto [CYCLEON]' relay command in the [ANNOUNCE] subroutine to cause announcements to CycleOn the Relay for a few seconds then Off again (according to the CycleOn defaults). This offers a visual indicator of announced messages if wished, but is easily commented out if not required and you prefer to retain independent Relay control.

Network version code
Code: Select all'EasyNet Framework 1d - Configured for JQ6500 Audio module plus SonOff Relay Module
memclear
let debugmode = "normal" 'debugmode options are: silent, normal, verbose
let voicemode = "On"  'Set to "Off" if only Relay node required without Voice Announcer node.
let buttonmode = "MultiMode"
let dhtsensor = 0  '0 for no dht sensor, 11 for dht11, 22 for dht22
let ledpin = 13
let relaypin = 12
let ledlogic = 1    'Default led pin off state (allows configuring led pin for active high or active low operation)
let relaylogic = 0  'Default relay pin off state (allows configuring relay pin for active high or active low operation)
let buttonpin = 0 'Using onboard GOIO00 flashing button by default, change to suit (needs pullup resistor).
let buttonlogic = 1 'Default button off state (allows configuring a paralled PIR output for active high or active low)
interrupt buttonpin, [PRESSED]
if dht <> 0 then
 let dhtpin = 14
 let settemp = 24
 let sethumid = 50
 let frequency = 5000  'sensor read update frequency
 let statmode = "TooHot"  'change to different mode to suit
endif
let nowait = 0 '
let localIP = ip()
gosub [IPSPLIT]
let shoutmsg = "All Blinks" ' The default msg shown in the broadcast msg window
gosub [READVARS]
if (title == "" or title == " ") then let title = "EasyNet Voice Announcer  "
if (localname == "" or localname == " ") then let localname = "Node" & nodeIP
if globalname == "" then let globalname = "ALL"
if groupname == "" then let groupname = "Voice"
if udpport == "" then let udpport = "5001"
if (description == "" or description == " ") then let description = "Network Voice Announcement plus Network Relay. "
if debugmode == "" then let debugmode = "normal"
if buttonmode == "" then let buttonmode = "MultiMode"
if voicemode == "" then let voicemode = "Off"
if voicemode == "On" then gosub [VOICEINIT]
if userpage == "" then let userpage = "Normal"
if startmode == "" then let startmode = "Off"
if startmode == "On" then
 if relaylogic == 0 then io(po,relaypin,1) else io(po,relaypin,0)
 if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
else
 if relaylogic == 0 then io(po,relaypin,0) else io(po,relaypin,1)
 if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
endif

[DHTINIT]
if startmode = "Stat" then
 dht.setup(dhtsensor, dhtpin)
 timer frequency, [HOME]
else
 timer 0
endif

[SENSORINIT]
 'This expects a sensor output (eg: PIR or magnetic reed switch) to be connected across (in parallel with) the gpio00 button
 let buttonlogic = 1 'Default button off state (allows configuring a paralled sensor output for active high or active low)
 let settleTime = 1000  'Adjust for whatever time is needed for your sensor to stabilise and stop false-triggering
 delay settleTime  'Simple crude delay to prevent sending false udp trigger alerts while the sensor stabilises

udpbegin udpport
udpbranch [UDPMAIN]

[HOME]
cls
print " "
button " ? " [HELP]
wprint " "
wprint title
wprint " "
wprint "- Developed on V2 Alpha 24"
print " "
if startmode == "UserToggle" then goto [USERTOGGLE]
html "<BR>"
html "NODE DETAILS  "
html "<BR>" & "<BR>"
html "Local name:  " & localname
html "<BR>"
html "Global name:  " & globalname
html "<BR>"
html "Group name:  " & groupname
html "<BR>"
html "Local IP address:  " & localIP
html "<BR>" & "<BR>"
if description <>"" then html "Description:  " & description
wprint "<BR><BR><BR>"
gosub [SENDMSG]
button "ON" [RELAYON]
wprint " "
button "Toggle Relay" [TOGGLE]
wprint " "
button "OFF" [RELAYOFF]
wprint "<BR><BR>"
button "Cycle ON [OFF]" [CYCLEON]
wprint "  "
wprint "On delay: " & ondelay
wprint "  "
wprint "On duration: " & onduration
wprint "<BR><BR>"
button "Cycle OFF [ON]" [CYCLEOFF]
wprint "  "
wprint "Off delay: " & offdelay
wprint "  "
wprint "Off duration: " & offduration
wprint "<BR><BR>"
wprint "Local button mode = " & buttonmode
wprint "<BR><BR>"
wprint " "
if voicemode == "On" then
 wprint "Voice Announcer On "
 wprint "&nbsp;&nbsp;&nbsp;"
 gosub [VOICEINIT]
 wprint " "
 button "Vol >" [VOL>]
 wprint " "
' wprint "&nbsp;&nbsp;"
 button "Vol <" [VOL<]
 wprint "&nbsp;&nbsp;"
 'button "Next Dir" [DIR>]
 wprint " "
' button "Previous Dir" [DIR<]
else
 wprint "Voice Announcer Off "
endif
wprint " "
wprint "<BR><BR>"
wprint " "
print "<BR>"
button "Configuration" [CONFIG]
wait


[VOICEINIT]
'udpwrite netIP & "255", val(udpport), "Voice Announce initialised"
serial2begin 9600,4,5
delay 500
let vstart = 126 'Serial command start byte
let vol = 15
let vup = 4
let vdown = 5
let vend = 239 'Serial command end byte
let vplay = 13
let vreset = 12
let vpause = 14
let vfiledir = hextoint("12")
let vfile = 1  'Select file 001
let vdir = 1  'Select folder 01
let vdchange = hextoint("0F")
serial2println chr(vstart) & chr(2) & chr(vreset) & chr(vend)
delay 500
serial2println chr(vstart) & chr(3) & chr(vset) & chr(vol) & chr(vend)
return

[VOICERESET]
if debugmode <> "silent" then udpreply localname & " VoiceReset" & " acknowledged"
serial2println chr(vstart) & chr(2) & chr(vreset) & chr(vend)
delay 500
let vol = 15
serial2println chr(vstart) & chr(3) & chr(vset) & chr(vol) & chr(vend)
let vfile = 1  'Select file 001
let vdir = 1  'Select folder 01
goto [HOME]

[ANNOUNCE]
if debugmode <> "silent" then udpreply localname & " Announce " & params & " acknowledged"
if len(params) > 0 then let vfile = val(params)
let params = ""
serial2println chr(vstart) & chr(4) & chr(vfiledir) & chr(vdir) & chr(vfile) & chr(vend)
goto [CYCLEON]
goto [HOME]

[VFILE]
if debugmode <> "silent" then udpreply localname & " VFile " & params & " acknowledged"
if len(params) > 0 then let vfile = val(params)
let params = ""
goto [HOME]

[VDIR]
if debugmode <> "silent" then udpreply localname & " VDir " & params & " acknowledged"
if len(params) > 0 then let vdir = val(params)
let params = ""
goto [HOME]

[SETVOL]
if debugmode <> "silent" then udpreply localname & " SetVol " & params & " acknowledged"
serial2println chr(vstart) & chr(3) & chr(vset) & chr(vol) & chr(vend)
goto [HOME]

[VOL>]
if debugmode <> "silent" then udpreply localname & " Vol > " & " acknowledged"
serial2println chr(vstart) & chr(2) & chr(volup) & chr(vend)
goto [HOME]

[VOL<]
if debugmode <> "silent" then udpreply localname & " Vol < " & " acknowledged"
serial2println chr(vstart) & chr(2) & chr(voldown) & chr(vend)
goto [HOME]

[DIR>]
if debugmode <> "silent" then udpreply localname & " DIR > " & " acknowledged"
serial2println chr(vstart) & chr(3) & chr(vdchange) & chr(vdnext) & chr(vend)
goto [HOME]

[DIR<]
if debugmode <> "silent" then udpreply localname & " Vol < " & " acknowledged"
serial2println chr(vstart) & chr(3) & chr(vdchange) & chr(vdprev) & chr(vend)
goto [HOME]


[SENSORALERT]
let MsgTarget = "ALL"  'Change to the required receiver node, else alerts will be sent to ALL group of nodes
let SensorAlertMsg = localname & " " & "Sensor alert triggered"  'Change for whatever message you wish to be transmitted
udpwrite netIP & "255", val(udpport), MsgTarget & " " & SensorAlertMsg
let oldstate = io(laststat,ledpin)
io(po,ledpin,ledlogic)
if relaylogic == 0 then io(po,relaypin,1) else io(po,relaypin,0)  'Comment out if you don't want to use the local relay
delay 200
if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
let Sensortimeout = 5000  'Time to wait before sending retriggered alert
delay Sensortimeout - 250
if relaylogic == 0  then io(po,relaypin,0) else io(po,relaypin,1) 'Comment out if you don't want to use the local relay
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
delay 200
io(po,ledpin,oldstate)
goto [HOME]


[TOGGLE]
if debugmode <> "silent" then udpreply localname & " Toggle acknowledged"
if io(laststat,relaypin) = 0 then io(po,relaypin,1) else io(po,relaypin,0)
if io(laststat,ledpin) = 0 then io(po,ledpin,1) else io(po,ledpin,0)
goto [HOME]

[CYCLEON]
if debugmode <> "silent" then udpreply localname & " CycleOn acknowledged"
if len(params) > 0 then gosub [GETCYCLEPARS]
if ondelay <> 0 then delay ondelay
if relaylogic == 0 then io(po,relaypin,1) else io(po,relaypin,0)
if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
if onduration == 0 then goto [HOME]
delay onduration
goto [RELAYOFF]

[CYCLEOFF]
if debugmode <> "silent" then udpreply localname &  " CycleOff acknowledged"
if offdelay <> 0 then delay offdelay
if relaylogic == 0 then io(po,relaypin,0) else io(po,relaypin,1)
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
if offduration == 0 then goto [HOME]
delay offduration
goto [RELAYON]

[RELAYON]
if debugmode <> "silent" then udpreply localname & " On acknowledged"
if io(laststat,relaypin) <> relaylogic then goto [HOME]
if relaylogic == 0 then io(po,relaypin,1) else io(po,relaypin,0)
if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
goto [HOME]

[RELAYOFF]
if debugmode <> "silent" then udpreply localname & " Off acknowledged"
if io(laststat,relaypin) == relaylogic then goto [HOME]
if relaylogic == 0  then io(po,relaypin,0) else io(po,relaypin,1)
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
goto [HOME]

[STATMODE]
wprint "<BR><BR>"
if statmode == "TooHot" then goto [TOOHOT]
if statmode == "TooCold" then goto [TOOCOLD]
if statmode == "TooWet" then goto [TOOWET]
if statmode == "TooDry" then goto [TOODRY]

[TOODRY]
wprint " Irrigation switch"
wprint "<BR><BR>"
wprint "Humidity=" & dht.hum()
wprint "<BR><BR>"
button "<" [HUMID<]
wprint " "
wprint sethumid
wprint " "
button ">" [HUMID>]
if dht.hum() < sethumid and io(laststat,relaypin) == relaylogic then goto [RELAYON]
if dht.hum() > sethumid and io(laststat,relaypin) <> relaylogic then goto [RELAYOFF]
wprint "<BR><BR><BR>"
button "Configuration" [CONFIG]
wait

[TOOWET]
wprint " Ventilation switch"
wprint "<BR><BR>"
wprint "Humidity=" & dht.hum()
wprint "<BR><BR>"
button "<" [HUMID<]
wprint " "
wprint sethumid
wprint " "
button ">" [HUMID>]
if dht.hum() > sethumid and io(laststat,relaypin) == relaylogic then goto [RELAYON]
if dht.hum() < sethumid and io(laststat,relaypin) <> relaylogic then goto [RELAYOFF]
wprint "<BR><BR><BR>"
button "Configuration" [CONFIG]
wait

[TOOCOLD]
wprint " Heating switch"
wprint "<BR><BR>"
wprint "temp="
wprint dht.temp()
wprint "<BR><BR>"
button "<" [TEMP<]
wprint " "
wprint settemp
wprint " "
button ">" [TEMP>]
if dht.temp() < settemp and io(laststat,relaypin) == relaylogic then goto [RELAYON]
if dht.temp() > settemp and io(laststat,relaypin) <> relaylogic then goto [RELAYOFF]
wprint "<BR><BR><BR>"
button "Configuration" [CONFIG]
wait

[TOOHOT]
wprint " Cooling Fan switch"
wprint "<BR><BR>"
wprint "temp="
wprint dht.temp()
wprint "<BR><BR>"
button "<" [TEMP<]
wprint " "
wprint settemp
wprint " "
button ">" [TEMP>]
if dht.temp() > settemp and io(laststat,relaypin) == relaylogic then goto [RELAYON]
if dht.temp() < settemp and io(laststat,relaypin) <> relaylogic then goto [RELAYOFF]
wprint "<BR><BR><BR>"
button "Configuration" [CONFIG]
wait

[HUMID>]
let sethumid = sethumid + 1
goto [HOME]

[HUMID<]
let sethumid = sethumid - 1
goto [HOME]

[TEMP>]
let settemp = settemp + 1
goto [HOME]

[TEMP<]
let settemp = settemp - 1
goto [HOME]

[USERONOFF]
wprint |<body>|
wprint |<div style="text-align: center;">|
wprint |<table style="text-align: left; width: 100%;" border="0"|
wprint | cellpadding="2" cellspacing="2">|
wprint |  <tbody>|
wprint |    <tr>|
wprint |      <td style="text-align: right;">|
button " ? " [HELP]
wprint |</td>|
wprint |      <td style="text-align: center;color: rgb(102, 51, 255);"><big><big>|
wprint localname
wprint |    </big></big></td>|
wprint |      <td style="text-align: left;">|
button " ! " [CONFIG]
wprint |</td>|
wprint |    </tr>|
wprint |  </tbody>|
wprint |</table>|
print " "
wprint |<br>|
wprint "<BR><BR><BR>"
wprint "Status="
if io(laststat,relaypin) == relaylogic then
wprint |<big style="color: rgb(0, 153, 0);">OFF|
wprint |</big><br> |
else
wprint |<big style="color: rgb(204, 0, 0);">ON|
wprint |</big><br> |
endif
wprint "<BR><BR>"
wprint |<big><big>|
button "ON" [RELAYON]
wprint "    "
button "OFF" [RELAYOFF]
wprint | </big></big><br>|
wprint |</div>|
wprint |</body>|
wprint "<BR><BR>"
wait

[USERTOGGLE]
wprint "<BR><BR><BR><BR><BR><BR>"
wprint |<!DOCTYPE html>|
wprint |<html>|
wprint |<head>|
wprint |<style>|
wprint | .bigbutton, input[type="button"], input[type="submit"] { |
wprint |  display: inline-block;|
wprint |  padding: 15px 25px;|
wprint |  font-size: 24px;|
wprint |  cursor: pointer;|
wprint |  text-align: center;|
wprint |  color: #fff;|
if io(laststat,relaypin)=0 then
 wprint |background-color: #4CAF50; /* Green */|
else
 wprint |background-color: #f44336; /* Red */|
endif
wprint |  border: 2px solid black;|
wprint |  border-radius: 15px;|
'wprint |  box-shadow: 0 9px #999;|
wprint |}|
wprint | .box, input[type="button"], input[type="submit"] { |
wprint |}|
wprint |</style>|
wprint |</head>|
wprint |<body>|
wprint |<div style="font-size: 32px; text-align: center;">|
if io(laststat,relaypin) = 0 then button "ON" [TOGGLE] else button "OFF" [TOGGLE]
wprint |</div>|
wprint |</body>|
wprint |</html>|
wait

[READVARS]
read Title title
read Name localname
read Global globalname
read Group groupname
read Description description
read Userpage userpage
read Startmode startmode
read Debugmode tmp
if tmp <> "" then let debugmode = tmp
read Buttonmode tmp
if tmp <> "" then let buttonmode = tmp
read Voicemode tmp
if tmp <> "" then let voicemode = tmp
read UDPport udpport
read Ondelay tmp
if tmp == "" then let ondelay = 0 else let ondelay = val(tmp)
let ondelay = val(tmp)
read Offdelay tmp
if tmp == "" then let offdelay = 2000 else let offdelay = val(tmp)
read Onduration tmp
if tmp == "" then let onduration = 5000 else let onduration = val(tmp)
read Offduration tmp
if tmp == "" then let offduration = 9000 else let offduration = val(tmp)
read Blinks tmp
if tmp == "" then let numblinks = 5 else let numblinks = val(tmp)
return

[SAVE]
if debugmode <> "silent" then udpreply localname & " Save to Flash acknowledged"
write Title title
write Name localname
write Global globalname
write Group groupname
write Description description
write UDPport udpport
write Debugmode debugmode
write Buttonmode buttonmode
write Voicemode voicemode
write Ondelay ondelay
write Offdelay offdelay
write Onduration onduration
write Offduration offduration
write Blinks numblinks
write Startmode startmode
write Userpage userpage
goto [DHTINIT]
return

[BLINKS]
if debugmode <> "silent" then udpreply localname & " Blinks acknowledged. Params=" & params
let oldstate = io(laststat,ledpin)
io(po,ledpin,ledlogic)
if len(params) > 0 then let numblinks = val(params)
let params = ""
delay 200
for count = 1 to numblinks
if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
delay 600
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
delay 400
next count
delay 2000
io(po,ledpin,oldstate)
goto [HOME]

[BROADCAST]
if instr(upper(shoutmsg),"LOCAL") == 1 then goto [LOCAL]
udpwrite netIP & "255", val(udpport), shoutmsg
wait

[LOCAL]
let msg = shoutmsg
'udpreply msg
goto [MAIN]

[SENDMSG]
html " Broadcast message  "
textbox shoutmsg
button "Send" [BROADCAST] ' broadcast the message
wprint " "
wprint "    (use name LOCAL to respond to commands sent from the same local node, eg: LOCAL ANNOUNCE 13)."
html "<BR>" & "<BR>"
return

[UDPMAIN]
let msg = udpread()
let lastmsg = msg
[MAIN]
if debugmode == "verbose" then udpreply localname & " msg received: " & msg
let name = upper(word(msg, 1))
let command = ""
let params = ""
let pos = len(name)
if len(msg) > pos then command = upper(word(msg, 2))
let pos = instr(upper(msg),command) + len(command)
if len(msg) > pos then params = mid(msg,(pos)) else params = ""
if debugmode = "verbose" then udpreply localname & " Name=" & name
if debugmode = "verbose" then udpreply localname & " Command=" & command
if debugmode = "verbose" then udpreply localname & " Params=" & params
if instr(upper(localname),name) > 0 then name = upper(localname) ' complete partial name to full name
if name == "LOCAL" then goto [LOCAL_COMMANDS]                    ' handle locally-sent commands
if name == upper(localname) then goto [LOCAL_COMMANDS]           ' handle localname commands
if name == upper(groupname) then goto [GROUP_COMMANDS]           ' handle any group commands
if name == upper(globalname) then goto [GLOBAL_COMMANDS]         ' handle any global commands
if debugmode <> "silent" then udpreply localname & " Error: NAME not recognised in " & msg
wait

[LOCAL_COMMANDS]
'let lastmsg = msg
' Add any new unique local commands here (change the test udreply to gosub to your appropriate subroutine handler
if command == upper("ON") then goto [RELAYON]                                             
if command == upper("OFF") then goto [RELAYOFF]
if command == upper("CYCLEON") then goto [CYCLEON]
if command == upper("CYCLEOFF") then goto [CYCLEOFF]
if command == upper("ONDELAY") then goto [ONDELAY]
if command == upper("OFFDELAY") then goto [OFFDELAY]
if command == upper("ONDURATION") then goto [ONDURATION]
if command == upper("OFFDURATION") then goto [OFFDURATION]
if command == upper("BUTTONMODE") then goto [BUTTONMODE]
if command == upper("TOGGLE") then goto [TOGGLE]
if command == upper("ANNOUNCE") then goto [ANNOUNCE]
if command == upper("VOL>") then goto [VOL>]
if command == upper("VOL<") then goto [VOL<]
if command == upper("SETVOL") then goto [SETVOL]
if command == upper("DIR>") then goto [DIR>]
if command == upper("DIR<") then goto [DIR<]
if command == upper("VFILE") then goto [VFILE]
if command == upper("VDIR") then goto [VDIR]
if command == upper("VOICERESET") then goto [VOICERESET]
if command == upper("SAVE") then goto [SAVE]
gosub [COMMON_COMMANDS]
if debugmode <> "silent" then udpreply localname & " Error: COMMAND " & command & " not recognised in " & msg
return

[COMMON_COMMANDS]
' Commands recognised by all nodes. Add any new common commands here
if command == upper("Report") then goto [REPORT]
if command == "?" then goto [DECLARE]
if command == upper("Blinks") then goto [BLINKS]
if command == upper("BlinkIP") then goto [BLINKSHORTIP]
if command == upper("BlinkShortIP") then goto [BLINKSHORTIP]
if command == upper("BlinkFullIP") then goto [BLINKFULLIP]
if command == upper("Exit") then goto [EXIT]       
if command == upper("Reset") then goto [RESET]       
if debugmode <> "silent" then udpreply localname & " Error: common COMMAND " & command & " not recognised in " & msg
return

[GROUP_COMMANDS]
' Sent prefixed by groupname.
' Add any new group-only commands here, eg: "Groupname  NewGroupCommand"
goto [LOCAL_COMMANDS]

[GLOBAL_COMMANDS]
' Sent prefixed by globalname, eg: "Globalname NewGlobalCommand"
' Add any new global-only commands here,
goto [LOCAL_COMMANDS] ' branch to look for local_commands

[DECLARE]
if debugmode <> "silent" then udpreply localname & " Declare IP acknowledged "
udpreply localname & "   " & localIP
goto [HOME]
return

[REPORT]
if debugmode <> "silent" then udpreply localname & " Report request acknowledged "
udpreply "Sending data"  ' change to include whatever info needs to be sent back
return

[ONDELAY]
if debugmode <> "silent" then udpreply localname &  " OnDelay " & params & " acknowledged"
let ondelay = val(params)
params = ""
goto [HOME]

[OFFDELAY]
if debugmode <> "silent" then udpreply localname & " OffDelay " & params & " acknowledged"
let offdelay = val(params)
params = ""
goto [HOME]

[ONDURATION]
if debugmode <> "silent" then udpreply localname &  " OnDuration " & params & " acknowledged"
let onduration = val(params)
params = ""
goto  [HOME]

[OFFDURATION]
if debugmode <> "silent" then udpreply localname & " OffDuration " & params & " acknowledged"
let offduration = val(params)
params = ""
goto  [HOME]

[BLINKFULLIP]
if debugmode <> "silent" then udpreply localname &  " BlinkIP acknowledged "
let oldstate = io(laststat,ledpin)
let blinkon = 200
let blinkoff = 400
let blinkpause = 1000
let blinkgap = 1400
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1) ' turn led off
delay blinkpause
for pos = 1 to len(localIP)
  let digitchr = mid(localIP,pos,1)
  if digitchr == "." then
    delay blinkgap
  else
    if digitchr = "0" then let digit = val("10") else digit = val(digitchr)
    for count = 1 to digit
      if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
      delay blinkon
      if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
      delay blinkoff
    next count
    delay blinkpause
  end if
next pos
delay blinkgap
io(po,ledpin,oldstate)
if nowait == 0 then wait else nowait = 0
return

[BLINKSHORTIP]
if debugmode <> "silent" then udpreply localname &  " BlinkIP acknowledged "
let oldstate = io(laststat,ledpin)
let blinkon = 200
let blinkoff = 400
let blinkpause = 1000
let blinkgap = 1400
let ip = val(nodeIP)
if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1) ' turn led off
delay blinkpause
let ipstr = str(ip)
let iplength = len(ipstr)
for pos = 1 to iplength
  let digit = mid(ipstr,pos,1)
  for temp = 1 to digit
    if ledlogic == 0 then io(po,ledpin,1) else io(po,ledpin,0)
    delay blinkon
    if ledlogic == 0 then io(po,ledpin,0) else io(po,ledpin,1)
    delay blinkoff
  next temp
  delay blinkpause
next pos
delay blinkpause
io(po,ledpin,oldstate)
if nowait == 0 then wait else nowait = 0
return

[IPSPLIT]
pos = instrrev(localIP,".")
netIP = left(localIP,pos)
nodeIP = mid(localIP,pos+1)
return

[HELP]
cls
print " "
button "X"  [HOME]
wprint "    "
wprint title & " - HELP"
print ""
wprint "Syntax:      NODENAME   COMMAND   [OPTIONAL PARAMETERS]  "
wprint " (name and commands are NOT case-sensitive, parameters ARE case-sensitive)"
wprint "<BR>"
wprint "Example:   LOCAL   ANNOUNCE  13  (would instruct the local Announcer node to to play recorded message file 013"
wprint "<BR><BR>"
wprint "Valid Voice commands: ANNOUNCE (file_number), SETVOL (vol)"
wprint ", VOL>, VOL<, VDIR (folder_number), VFILE (file_number), DIR>, DIR<, VOICERESET"
wprint "<BR><BR>"
wprint "Other valid commands:"
wprint "<BR>"
wprint "ON - turns relay instantly On and it stays On."
wprint "<BR>"
wprint "OFF - turns relay instantly Off and it stays Off."
wprint "<BR>"
wprint "TOGGLE - instantly changes relay state."
wprint "<BR>"
wprint "CycleON - turns relay On after optional OnDelay, then optionally turns back Off after optional OnDuration."
wprint "  (useful for automatic lights etc)"
wprint "<BR>"
wprint "OnDelay - 0 for no On delay."
wprint "<BR>"
wprint "OnDuration - 0 to stay On."
wprint "<BR>"
wprint "CycleOFF - turns relay Off after optional OffDelay, then optionally turns back On after optional OffDuration."
wprint "  (useful for power-down reboots of routers etc) "
wprint "<BR>"
wprint "OffDelay - 0 for no Off delay."
wprint "<BR>"
wprint "OffDuration - 0 to stay Off. "
wprint "<BR>"
wprint "Blinks [numblinks] - blinks the local led to aid location. Optional parameter over-rides default of 5 blinks."
wprint "<BR>"
wprint "BlinkFullIP - locally blinks the full IP byte digits (10 blinks for 0)."
wprint "<BR>"
wprint "BlinkShortIP - locally blinks the last IP byte digits (the wifi subnet address will already be known)."
wprint "<BR>"
wprint "Report - returns the nodes IP address via UDP. "
wprint "<BR>"
wprint "Save - saves variables to non-volatile flash memory."
wprint "<BR>"
wprint "Reset - restarts the node. "
wprint "<BR>"
wprint "Exit - shuts down the node. "
wprint "<BR>"
wprint "ButtonMode - allows different behaviour for local button. "
wprint " FlipSwitch allows standard flip-type light switch to be used instead of momentary button."
wprint " MultiMode allows quick blip to Toggle relay, or press and quck "
wprint " hold (less than 1 sec) for BlinkShortIP, or press and long hold (greater than 1.5 sec) for BlinkFullIP."
wprint "<BR><BR>"
wprint "This script is configured for the Sonoff module, but is adaptable for alternative "
wprint "switched relay modules."
wprint "<BR>"
wprint "Sonoff pins - button=0, led=12, relay=13. Ledlogic=0 (active low), relaylogic=1 (Sonoff relay is active high)."
wprint "<BR><BR>"
button "Home" [HOME]
wait

[CONFIG]
cls
print " "
button "X" [DHTINIT]
wprint " "
wprint title & " - CONFIG"
print " "
html "<BR>"
html "Title: "
textbox title
html "<BR><BR>"
html "Local name:  "
textbox localname
wprint " "
html "Group name:  "
textbox groupname
html " "
html "Global name:  "
textbox globalname
html "<BR><BR>"
html "UDP port    "
textbox udpport
html "<BR><BR>"
html "Description:  "
textbox description
html "<BR><BR>"
wprint "Local button mode   "
dropdown "MultiMode,BlinkFullIP,BlinkShortIP,ToggleButton,FlipSwitch,Sensor" buttonmode
wprint " "
wprint " selects different behaviour for local button. MultiMode is button-speed dependent (see Help ?)."
wprint "<BR><BR>"
wprint "Start mode"
dropdown "On,Off,Stat,UserToggle,UserOnOff" startmode
wprint " "
wprint " Allows selecting a pre-defined startup mode."
wprint "<BR><BR>"
wprint "DHT sensor"
dropdown "None, 11, 21, 22" dhtsensor
wprint " "
wprint " Stat mode "
wprint " "
dropdown "TooHot, TooCold, TooWet, TooDry" statmode
wprint " "
wprint " Temperature setting="
textbox settemp
wprint " Humidity setting="
textbox sethumid
wprint "<BR><BR>"
wprint " On delay: "
textbox ondelay
wprint " "
wprint " "
wprint " On duration: "
textbox onduration
wprint "<BR><BR>"
wprint " Off delay: "
textbox offdelay
wprint " "
wprint " "
wprint " Off duration: "
textbox offduration
'wprint " <BR><BR><BR>"
wprint "<BR><BR>"
wprint "Number of Blinks: "
textbox numblinks
wprint "<BR><BR>"
wprint "Debug mode   "
dropdown "silent,normal,verbose" debugmode
wprint " "
wprint " UDP messages - silent=none, normal=acknowledgements, verbose=debug info."
wprint "<BR><BR><BR>"
button "Home" [HOME] 'don't save edits
wprint " "
wprint " "
button "Save" [SAVE] ' save details to persistent memory
wprint " "
wprint " "
wprint "HOME loses changes at power-off, SAVE holds changes in non-volatile flash memory. "
wait

[BUTTONMODE]
if debugmode <> "silent" then udpreply localname &  " ChangeButtonMode acknowledged"
let buttonmode = params
let params = ""
goto [HOME]
return
 
[PRESSED]
if debugmode <> "silent" then udpwrite netIP & "255", val(udpport), localname & " Local button pressed"
if buttonmode == "Sensor" then
 if io(laststat,buttonpin) <> buttonlogic then goto [SENSORALERT]
endif
if buttonmode == "FlipSwitch" then
 if io(laststat,buttonpin) = 0 then goto [RELAYON] else goto [RELAYOFF]
endif
if buttonmode == "Toggle" then
 if io(laststat,buttonpin) = 0 then goto [TOGGLE]
endif
if buttonmode == "BlinkShortIP" then
 if io(laststat,buttonpin) = 0 then goto [BLINKSHORTIP]
endif
if buttonmode == "BlinkFullIP" then
 if io(laststat,buttonpin) = 0 then goto [BLINKFULLIP]
endif
if io(laststat,buttonpin) = 0 then let start = millis() else let stop = millis()
if stop > start then
  if stop-start < 500 then goto [TOGGLE]
  if stop-start < 1000 then goto [BLINKSHORTIP]
  if stop-start > 1500 then goto [BLINKFULLIP]
endif
goto [HOME]

[EXIT]
print "<BR>" & "Node " & upper(localname) & " with IP address " & localIP & " has been shut down."
udpwrite netIP & "255", val(udpport), "Node " & upper(localname) & " has been shut down." & "<BR>"
for count = 1 to 5
po ledpin 0
delay 70
po ledpin 1
delay 200
next count
end

[RESET]
reboot
User avatar
By forlotto
#53409 Interesting project a lot of careful thought put into this.

It would be excellent to experience this audio visually as some may not have the same minds eye not to mention it would be the same as putting a name to a face. To see a video of this rolled out would be most excellent.

To sum it up the Voice Announcer is based off of events that happen to trigger specific audio files to be played to voice announce what has or is about to happen depending on the users integration and positioning of the said audio trigger vs the triggered event if I am understanding this correctly.

Some things that come to mind as possibilities:

- Bluetooth Module Interfacing
Interconnecting this with a bluetooth audio module that would allow play to other bluetooth based devices such as speakers or phones a local solution.

- FM transmitter Interfacing
Simply hook up a whole home FM transmitter to the output and use cheap small simple FM radios to play what is happening locally to limit the number of wires and difficult setup of whole home audio wiring.

- Internet Broadcasting
Audio output of the MP3 player to audio input of a small low power computer such as a pi based computer and the ability to host a radio station. From this what is happening could be heard via audio on the broadcasted network from wherever you are via any internet connected device. This could be both local and remote for those looking for a remote solution.

Other thoughts couldn't there be a handler to solve some of issues or a newly developed board? For instance are we sure there is not a board made with an amplifier that is integrated for the hardware issue of level of sound and could the sound not be limited by software control? Finally seeing as how the sounds are triggered by software would it not be safe to say unless we make a mistake in selection of audio file we really should not be selecting something that does not exist but for good measure could not one create an array of accepted requests manually seeing as how there is no rx capabilities before sending the request to ensure no miscommunications occur causing the unit to have a need of power on/off?
User avatar
By Electroguard
#53476 Yeah, you grasp the idea ok, forlotto, and thanks for showing an interest.

It could be used wherever you might like audio notifications - whether for home automation, a security system, verbal compass or weather alerts etc, or perhaps just a personalised speaking clock, or an unusual talking gizmo.

As you said, the user could additionally interface to it however they wish, which is entirely up to them. Or they might just announce local status... perhaps the changing of a roomstat setpoint temperature, or warn of an overheating situation.

The possibilities are limited only by imagination - I'm converting an ex-ambulance into a camper, and much of the internal lighting and other systems will be controlled by a gaggle of ESP-Basic devices, including some outside PIRs which will cause internal verbal alerts if anyone approaches the vehicle too close for too long (we were gassed and robbed in our sleep in Spain a few years ago and I don't intend to let that happen again).

I included the udp version because I'm working towards creating a distributed ESP_Basic automation system over wifi to replace my existing security/automation system - which currently uses half a dozen mixed PIRs beam-breaks and magnetic door contacts to trigger appropriate alert announcements in the places where we are likely to be (lounge, kitchen, bedroom, office, workshop, garden). So we hear announcements of approaching visitors or trespassers or other notifications wherever we are.

That's why I've been developing the 'EasyNet' common framework configuration for UDP networking, it's over-kill for 2 node interaction, but it will be very useful for a dynamic fault-tolerant multi-node interactive system. It may not be of interest to anyone else at the moment, but I keep publishing things anyway on the off-chance that maybe some day someone on the same wavelength might be glad of not having to invent the same wheel for themselves.

As I said, I don't have any publicly hosted storage for uploading to, cos I'm very old-school and have nothing cloud-based for hackers to target, nor any social-media pipelines tapped into my jugular. But if someone with online storage was to PM me their email address I'd be happy to email some sample audio files and pics (and perhaps even a video) to link to from the article.

The JQ6500 is actually one of the better audio modules I've tried, and like anything with lots of options it is basically a matter of if it is used properly, so any mentioned issues pretty much come down to a GIGO situation (if you put garbage in you can expect garbage out). But it's very good when driven properly, which is quite easy to do when given the benefit of someone elses hindsight pointing out a few tips and gotcha's.
User avatar
By forlotto
#53492 I'll tell you what upload a zip with all the information without registration here http://www.filedropper.com/ and share with me the link. I will see to it that you get the links to post to your project page. Upload audio, video, and pictures and I will post them in the appropriate spots and give you the live links.