1 module hcspeech.commands; 2 3 import std.algorithm; 4 import std.array; 5 import std.conv; 6 import std.range; 7 import std.string; 8 9 import hexchat.plugin; 10 11 import speech.synthesis; 12 13 import hcspeech.base; 14 15 /* 16 * ========================================= 17 * Commands 18 * ========================================= 19 */ 20 // TTS context management 21 immutable toggleUsage = "TOGGLE, toggle TTS for the current channel."; 22 immutable addUsage = "ADD <channel>, start TTS in the specified channel."; 23 immutable removeUsage = "REMOVE <channel>, stop TTS in the specified channel."; 24 immutable listUsage = "LIST, list channels with TTS running."; 25 26 void toggleCommand(in char[][] words, in char[][] words_eol) 27 { 28 auto channel = getInfo("channel"); 29 auto position = ttsChannels.countUntil!((channel, name) => channel.name == name)(channel); 30 if(position == -1) 31 { 32 ttsChannels ~= ChannelInfo(channel); 33 writefln("Started TTS in %s.", channel); 34 } 35 else 36 { 37 ttsChannels = ttsChannels.remove!(SwapStrategy.unstable)(position); 38 writefln("Stopped TTS in %s.", channel); 39 } 40 } 41 42 void addCommand(in char[][] words, in char[][] words_eol) 43 { 44 auto channel = words[1]; 45 46 auto search = ttsChannels.find!((channel, name) => channel.name == name)(channel); 47 if(search.empty) 48 { 49 ttsChannels ~= ChannelInfo(channel.idup); 50 writefln("Started TTS in %s.", channel); 51 } 52 else 53 { 54 writefln("TTS is already enabled in %s.", channel); 55 } 56 } 57 58 void removeCommand(in char[][] words, in char[][] words_eol) 59 { 60 auto channel = words[1]; 61 62 auto position = ttsChannels.countUntil!((channel, name) => channel.name == name)(channel); 63 if(position != -1) 64 { 65 ttsChannels = ttsChannels.remove!(SwapStrategy.unstable)(position); 66 writefln("Stopped TTS in %s.", channel); 67 } 68 else 69 { 70 writefln("%s is not in the list of TTS channels.", channel); 71 } 72 } 73 74 void listCommand(in char[][] words, in char[][] words_eol) 75 { 76 if(ttsChannels.empty) 77 writefln("TTS is not enabled for any channels."); 78 else 79 writefln("%(%s%|, %)", ttsChannels.map!(channel => channel.name)); 80 } 81 82 // Voice management 83 immutable voiceListUsage = "VOICELIST, list available TTS voices."; 84 85 immutable assignUsage = "ASSIGN <nick> <voice name>, " ~ 86 "assign a specific voice to a user by voice name."; 87 88 immutable unassignUsage = "UNASSIGN <nick>, unassign any previously " ~ 89 "assigned voice for the specified user."; 90 91 immutable assignedVoicesUsage = "ASSIGNEDVOICES [nick filter], list assigned voices."; 92 93 94 void voiceListCommand(in char[][] words, in char[][] words_eol) 95 { 96 writefln("%-(%s%|, %)", allVoices.map!(v => v.name).enumerate(1).map!(pair => format("#%s %s", pair[0], pair[1]))); 97 } 98 99 void assignCommand(in char[][] words, in char[][] words_eol) 100 { 101 auto nick = words[1]; 102 auto voiceSpecifier = words_eol[2]; 103 104 auto voices = findVoices(voiceSpecifier); 105 if(voices.empty) 106 { 107 writefln(`No voice found for specifier "%s". Use "/TTS VOICELIST" to list available voices.`, voiceSpecifier); 108 return; 109 } 110 111 auto firstVoice = voices.front; 112 voices.popFront(); 113 114 if(voices.empty) // Only one voice found 115 { 116 specifiedUserVoices[nick.idup] = firstVoice; 117 writefln("Assigned voice to %s: %s", nick, firstVoice.name); 118 } 119 else // Ambiguous search 120 { 121 writefln("Specified name matches multiple voices. Which did you mean?"); 122 writefln("%-(%s%|, %)", only(firstVoice).chain(voices).map!(v => v.name)); 123 } 124 } 125 126 void unassignCommand(in char[][] words, in char[][] words_eol) 127 { 128 auto nick = words[1]; 129 if(nick in specifiedUserVoices) 130 { 131 specifiedUserVoices.remove(nick.idup); // Ew, idup :( 132 writefln("Voice cleared for user %s.", nick); 133 } 134 else 135 { 136 writefln("User %s doesn't have a manually assigned voice.", nick); 137 } 138 } 139 140 void assignedVoicesCommand(in char[][] words, in char[][] words_eol) 141 { 142 if(words.length < 2) 143 { 144 if(specifiedUserVoices.length == 0) 145 writefln("There are no assigned voices."); 146 else 147 writefln("%-(%s%|, %)", specifiedUserVoices.byPair.map!(pair => format("%s: %s", pair[0], pair[1].name))); 148 } 149 else 150 { 151 auto matches = specifiedUserVoices.byPair.filter!(pair => pair[0].asLowerCase.canFind(words[1].asLowerCase)); 152 153 if(matches.empty) 154 writefln(`Found no assigned voices with the search "%s".`, words[1]); 155 else 156 writefln("%-(%s%|, %)", matches.map!(pair => format("%s: %s", pair[0], pair[1].name))); 157 } 158 } 159 160 // TTS configuration management 161 immutable volumeUsage = "VOLUME [new volume in the range 0-100], set or display TTS volume."; 162 immutable rateUsage = "RATE [new rate], set or display TTS rate."; 163 164 void volumeCommand(in char[][] words, in char[][] words_eol) 165 { 166 if(words.length > 1) 167 { 168 uint volume; 169 try volume = to!uint(words[1]); 170 catch(ConvException e) 171 { 172 writefln("Volume must be a positive number."); 173 return; 174 } 175 176 if(volume > 100) 177 writefln("Volume level must be in the range 0-100."); 178 else 179 { 180 tts.volume = volume; 181 writefln("TTS volume is now %s/100.", tts.volume); 182 } 183 } 184 else 185 { 186 writefln("TTS volume is %s/100.", tts.volume); 187 } 188 } 189 190 void rateCommand(in char[][] words, in char[][] words_eol) 191 { 192 if(words.length > 1) 193 { 194 int rate; 195 try rate = to!int(words[1]); 196 catch(ConvException e) 197 { 198 writefln("Rate must be a number."); 199 return; 200 } 201 202 tts.rate = rate; 203 writefln("TTS rate is now %s.", tts.rate); 204 } 205 else 206 { 207 writefln("TTS rate is %s.", tts.rate); 208 } 209 } 210