1 module hcspeech.base; 2 3 import std.algorithm; 4 import std.range.primitives; 5 import std.range : cycle, drop, iota, takeExactly, zip; 6 import std.string; 7 import std.random; 8 import std.uni : asLowerCase; 9 10 import hexchat.plugin; 11 import speech.synthesis; 12 13 // Used for all speech synthesis in the plugin. 14 __gshared Synthesizer tts; 15 16 /* 17 * ========================================= 18 * Voice Tracking 19 * ========================================= 20 */ 21 struct ChannelInfo 22 { 23 string name; 24 uint[] voiceDistribution; 25 26 this(string name) 27 { 28 this.name = name; 29 voiceDistribution = new uint[](allVoices.length); 30 } 31 } 32 33 __gshared 34 { 35 ChannelInfo[] ttsChannels; 36 Voice[] allVoices; 37 Voice[string] specifiedUserVoices; 38 Voice[string] userVoices; 39 } 40 41 auto findVoices(const(char)[] voiceSpecifier) 42 { 43 return allVoices.filter!(voice => voice.name.asLowerCase.canFind(voiceSpecifier.asLowerCase)); 44 } 45 46 Voice* voiceByName(in char[] name) 47 { 48 auto search = allVoices.find!((voice, name) => voice.name == name)(name); 49 return search.empty? null : &search.front; 50 } 51 52 Voice getUserVoice(ChannelInfo channel, in char[] nick) 53 { 54 if(auto voice = nick in specifiedUserVoices) 55 return *voice; 56 57 if(auto voice = nick in userVoices) 58 return *voice; 59 60 // Assign least used voice 61 auto offset = uniform(0, allVoices.length); 62 auto min = zip(iota(0, allVoices.length), 63 channel.voiceDistribution.cycle.drop(offset), 64 allVoices.cycle.drop(offset)) 65 .minPos!((a, b) => a[1] < b[1]).front; 66 67 auto minIndex = min[0]; 68 auto voice = min[2]; 69 70 userVoices[nick.idup] = voice; 71 ++channel.voiceDistribution[minIndex]; 72 73 writefln("Assigned voice to %s: %s", nick, voice.name); 74 return voice; 75 } 76