Add speech synthesis to your games with Speako8, a speech synthesis library for PICO-8 in under a thousand tokens! It's loosely based on a Klatt synthesizer and will remind some folks of Software Automatic Mouth (S.A.M.)
To add Speako8 to your games, copy and paste the library below:
Speako8's voice must be configured prior to first use:
function _init() spk8_pitch,spk8_rate,spk8_volume,spk8_quality,spk8_intonation,spk8_if0,spk8_shift,spk8_bandwidth,spk8_whisper= 140,1,1,.5,10,10,1,1,1 end
Warning: This application may generate loud and harsh sounds. Protect your hearing! Do not test with headphones on and turn down the volume.
|spk8_pitch||60-230||Fundamental pitch of voice (F0) in hertz|
|spk8_rate||.1-2||Standard rate of speech divisor— below 1 is slower; above 1 is faster.|
|spk8_volume||.1-2||Standard volume factor— below 1 is quieter; above 1 is louder.|
|spk8_quality||.1-2||Glottis open period— below .5 is creakier voice; .5 is modal voice; 1 is breathy voice; above 1 is weaker voice.|
|spk8_intonation||0-20||Degree of pitch prosody in hertz— set to zero for robotic monotone.|
|spk8_if0||0-20||Degree to which the inherent pitch (F0) of vowels varies (in hertz)— set to zero for robotic monotone.|
|spk8_shift||.8-1.2||Factor by which to shift formant frequencies (F1, F2, F3)— above raises formant frequencies; below one lowers|
|spk8_bandwidth||.5-5||Factor by which to alter bandwidth of formant frequencies (F1, F2, F3)— below 1 narrows the bandwidth of the formants; above 1 widens. When formants are shifted upward, it is recommended to widen bandwidths as well.|
|spk8_whisper||1 or 2||Speaking mode— Normal voice is 1; whispering is 2.|
To enable speech synthesis, you must call
speako8 in the
_update function. Then call
say with a speech string:
function _update() speako8() -- must appear once unconditionally in _update. if btnp(5) then --❎ button -- use speaking if you want to check if anything is currently being said. -- if not speaking() then say("_/hh/-1.57/eh/-1.07/l/-1.33/3/ow/-1.03/-3/w/1.27/-3/er/1.65/-3/l/-1.64/-3/_/d") -- end elseif btnp(4) then -- 🅾️ button mute() --flushes the sound queue and immediately stops whatever is currently being said. end end
Speech strings represent text phonetically and include prosody markup. The easiest way to create and test them is with the Declare web app. You can also try out different voice options with it.
If you are interested in learning more about speech synthesis, try googling these key words: Klatt synthesizer, formant, acoustic phonetics. It's a fascinating topic. An unminimized version of the library is included in the demo cart.
Special thanks to the gang on the Discord server for lending me their ears and keeping me motivated, especially @packbat, who did it for science. Thanks also to @IMLXH for finding the Klatt prosody rules.
@dw817, I think it's possible to write phonetic and prosody rules to convert English text to speech strings and fit them into a cart, but I doubt very much that there would be many tokens left over for anything else.
@zep, yes, really looking forward to see what folks do with this. Glad you liked the name.
To everyone, thank you for your appreciations. They made my day.
@idk_how_to_code, that's because it kinda is! Hawking spoke via a Klatt synthesizer and Speako8 is loosely based on that synthesizer. Much of the formant data and prosody rules I used came from Klatt's writings and are based on his own speech samples. So, yeah Speako8 sounds like Hawking sounds like Klatt.
@augusto99999, it could be done. You'd have to write rules for phoneticizing. They wouldn't be as accurate as the pronouncing dictionary that Declare uses, but they might be okay enough. You'd also have to convert the prosody rules from declare.js. I'm afraid, though, that once those tasks were done, there wouldn't be many tokens left for an actual game.
I've added this to my exploring game and finding the CPU/mem usage spikes crazy high when the talking happens, slowing everything else in the game down.
I guess it doesn't surprise me given the magic it's doing, but is there anything I can/should do to try and minimize the hit?
I'm just using the say() with a trigger like documented. It's an incredibly tight and well-thought out component - super thanks for that - but that puts the magic way above my head to try and tweak it :)
[Please log in to post a comment]