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