2023-08-09 03:30:26 +02:00
/ *
TODO :
2023-08-10 15:59:00 +02:00
- Allow to upload RVC model to extras server ?
- Settings per characters ?
2023-08-09 03:30:26 +02:00
* /
import { saveSettingsDebounced } from "../../../script.js" ;
2023-08-10 15:59:00 +02:00
import { getContext , getApiUrl , extension _settings , doExtrasFetch } from "../../extensions.js" ;
2023-08-09 03:30:26 +02:00
export { MODULE _NAME , rvcVoiceConversion } ;
const MODULE _NAME = 'RVC' ;
const DEBUG _PREFIX = "<RVC module> "
// Send an audio file to RVC to convert voice
async function rvcVoiceConversion ( response , character ) {
let apiResult
// Check voice map
if ( extension _settings . rvc . voiceMap [ character ] === undefined ) {
2023-08-11 14:50:04 +02:00
toastr . error ( "No model is assigned to character '" + character + "', check RVC voice map in the extension menu." , DEBUG _PREFIX + 'RVC Voice map error' , { timeOut : 10000 , extendedTimeOut : 20000 , preventDuplicates : true } ) ;
2023-08-10 17:12:37 +02:00
console . error ( "No RVC model assign in voice map for current character " + character ) ;
return response ;
2023-08-09 03:30:26 +02:00
}
// Load model if different from currently loaded
//if (currentModel === null | currentModel != extension_settings.rvc.voiceMap[character])
// await rvcLoadModel(extension_settings.rvc.voiceMap[character]);
const audioData = await response . blob ( )
if ( ! audioData . type in [ 'audio/mpeg' , 'audio/wav' , 'audio/x-wav' , 'audio/wave' , 'audio/webm' ] ) {
throw ` TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${ audioData . type } `
}
2023-08-10 02:47:53 +02:00
console . log ( "Audio type received:" , audioData . type )
2023-08-09 03:30:26 +02:00
console . log ( "Sending tts audio data to RVC on extras server" )
var requestData = new FormData ( ) ;
2023-08-10 02:47:53 +02:00
requestData . append ( 'AudioFile' , audioData , 'record' ) ;
2023-08-09 03:30:26 +02:00
requestData . append ( "json" , JSON . stringify ( {
"modelName" : extension _settings . rvc . voiceMap [ character ] ,
"pitchOffset" : extension _settings . rvc . pitchOffset ,
"pitchExtraction" : extension _settings . rvc . pitchExtraction ,
"indexRate" : extension _settings . rvc . indexRate ,
"filterRadius" : extension _settings . rvc . filterRadius ,
//"rmsMixRate": extension_settings.rvc.rmsMixRate,
"protect" : extension _settings . rvc . protect
} ) ) ;
const url = new URL ( getApiUrl ( ) ) ;
url . pathname = '/api/voice-conversion/rvc/process-audio' ;
apiResult = await doExtrasFetch ( url , {
method : 'POST' ,
body : requestData ,
} ) ;
if ( ! apiResult . ok ) {
2023-08-11 14:50:04 +02:00
toastr . error ( apiResult . statusText , DEBUG _PREFIX + ' RVC Voice Conversion Failed' , { timeOut : 10000 , extendedTimeOut : 20000 , preventDuplicates : true } ) ;
2023-08-09 03:30:26 +02:00
throw new Error ( ` HTTP ${ apiResult . status } : ${ await apiResult . text ( ) } ` ) ;
}
return apiResult ;
}
//#############################//
// Extension UI and Settings //
//#############################//
const defaultSettings = {
enabled : false ,
model : "" ,
pitchOffset : 0 ,
pitchExtraction : "dio" ,
indexRate : 0.88 ,
filterRadius : 3 ,
//rmsMixRate:1,
protect : 0.33 ,
voicMapText : "" ,
voiceMap : { }
}
function loadSettings ( ) {
if ( Object . keys ( extension _settings . rvc ) . length === 0 ) {
Object . assign ( extension _settings . rvc , defaultSettings )
}
$ ( '#rvc_enabled' ) . prop ( 'checked' , extension _settings . rvc . enabled ) ;
$ ( '#rvc_model' ) . val ( extension _settings . rvc . model ) ;
$ ( '#rvc_pitch_offset' ) . val ( extension _settings . rvc . pitchOffset ) ;
$ ( '#rvc_pitch_offset_value' ) . text ( extension _settings . rvc . pitchOffset ) ;
$ ( '#rvc_pitch_extraction' ) . val ( extension _settings . rvc . pitchExtraction ) ;
$ ( '#rvc_pitch_extractiont_value' ) . text ( extension _settings . rvc . pitchExtraction ) ;
$ ( '#rvc_index_rate' ) . val ( extension _settings . rvc . indexRate ) ;
$ ( '#rvc_index_rate_value' ) . text ( extension _settings . rvc . indexRate ) ;
$ ( '#rvc_filter_radius' ) . val ( extension _settings . rvc . filterRadius ) ;
$ ( "#rvc_filter_radius_value" ) . text ( extension _settings . rvc . filterRadius ) ;
//$('#rvc_mix_rate').val(extension_settings.rvc.rmsMixRate);
$ ( '#rvc_protect' ) . val ( extension _settings . rvc . protect ) ;
$ ( "#rvc_protect_value" ) . text ( extension _settings . rvc . protect ) ;
$ ( '#rvc_voice_map' ) . val ( extension _settings . rvc . voiceMapText ) ;
}
async function onApplyClick ( ) {
let error = false ;
let array = $ ( '#rvc_voice_map' ) . val ( ) . split ( "," ) ;
array = array . map ( element => { return element . trim ( ) ; } ) ;
array = array . filter ( ( str ) => str !== '' ) ;
extension _settings . rvc . voiceMap = { } ;
for ( const text of array ) {
2023-08-11 14:50:04 +02:00
if ( text . includes ( ":" ) ) {
const pair = text . split ( ":" )
2023-08-09 03:30:26 +02:00
extension _settings . rvc . voiceMap [ pair [ 0 ] . trim ( ) ] = pair [ 1 ] . trim ( )
console . debug ( DEBUG _PREFIX + "Added mapping" , pair [ 0 ] , "=>" , extension _settings . rvc . voiceMap [ pair [ 0 ] ] ) ;
}
else {
$ ( "#rvc_status" ) . text ( "Voice map is invalid, check console for errors" ) ;
$ ( "#rvc_status" ) . css ( "color" , "red" ) ;
2023-08-11 14:50:04 +02:00
console . error ( DEBUG _PREFIX , "Wrong syntax for message mapping, no ':' found in:" , text ) ;
toastr . error ( "no ':' found in: '" + text + "'" , DEBUG _PREFIX + ' RVC Voice map error' , { timeOut : 10000 , extendedTimeOut : 20000 , preventDuplicates : true } ) ;
2023-08-09 03:30:26 +02:00
error = true ;
}
}
if ( ! error ) {
$ ( "#rvc_status" ) . text ( "Successfully applied settings" ) ;
$ ( "#rvc_status" ) . css ( "color" , "green" ) ;
console . debug ( DEBUG _PREFIX + "Updated message mapping" , extension _settings . rvc . voiceMap ) ;
2023-08-11 14:50:04 +02:00
toastr . info ( "New map:\n" + JSON . stringify ( extension _settings . rvc . voiceMap ) . substring ( 0 , 200 ) + "..." , DEBUG _PREFIX + "Updated message mapping" , { timeOut : 10000 , extendedTimeOut : 20000 , preventDuplicates : true } ) ;
2023-08-09 03:30:26 +02:00
extension _settings . rvc . voiceMapText = $ ( '#rvc_voice_map' ) . val ( ) ;
saveSettingsDebounced ( ) ;
}
}
async function onEnabledClick ( ) {
extension _settings . rvc . enabled = $ ( '#rvc_enabled' ) . is ( ':checked' ) ;
saveSettingsDebounced ( )
}
async function onPitchExtractionChange ( ) {
extension _settings . rvc . pitchExtraction = $ ( '#rvc_pitch_extraction' ) . val ( ) ;
saveSettingsDebounced ( )
}
async function onIndexRateChange ( ) {
extension _settings . rvc . indexRate = Number ( $ ( '#rvc_index_rate' ) . val ( ) ) ;
$ ( "#rvc_index_rate_value" ) . text ( extension _settings . rvc . indexRate )
saveSettingsDebounced ( )
}
async function onFilterRadiusChange ( ) {
extension _settings . rvc . filterRadius = Number ( $ ( '#rvc_filter_radius' ) . val ( ) ) ;
$ ( "#rvc_filter_radius_value" ) . text ( extension _settings . rvc . filterRadius )
saveSettingsDebounced ( )
}
async function onPitchOffsetChange ( ) {
extension _settings . rvc . pitchOffset = Number ( $ ( '#rvc_pitch_offset' ) . val ( ) ) ;
$ ( "#rvc_pitch_offset_value" ) . text ( extension _settings . rvc . pitchOffset )
saveSettingsDebounced ( )
}
async function onProtectChange ( ) {
extension _settings . rvc . protect = Number ( $ ( '#rvc_protect' ) . val ( ) ) ;
$ ( "#rvc_protect_value" ) . text ( extension _settings . rvc . protect )
saveSettingsDebounced ( )
}
$ ( document ) . ready ( function ( ) {
function addExtensionControls ( ) {
const settingsHtml = `
< div id = "rvc_settings" >
< div class = "inline-drawer" >
< div class = "inline-drawer-toggle inline-drawer-header" >
< b > RVC < / b >
< div class = "inline-drawer-icon fa-solid fa-circle-chevron-down down" > < / d i v >
< / d i v >
< div class = "inline-drawer-content" >
< div >
< label class = "checkbox_label" for = "rvc_enabled" >
< input type = "checkbox" id = "rvc_enabled" name = "rvc_enabled" >
< small > Enabled < / s m a l l >
< / l a b e l >
< / d i v >
< div >
< span > Select Pitch Extraction < / s p a n > < / b r >
< select id = "rvc_pitch_extraction" >
< option value = "dio" > dio < / o p t i o n >
< option value = "pm" > pm < / o p t i o n >
< option value = "harvest" > harvest < / o p t i o n >
< option value = "torchcrepe" > torchcrepe < / o p t i o n >
< option value = "rmvpe" > rmvpe < / o p t i o n >
< / s e l e c t >
< / d i v >
< div >
< label for = "rvc_index_rate" >
Index rate for feature retrieval ( < span id = "rvc_index_rate_value" > < / s p a n > )
< / l a b e l >
< input id = "rvc_index_rate" type = "range" min = "0" max = "1" step = "0.01" value = "0.5" / >
< label for = "rvc_filter_radius" > Filter radius ( < span id = "rvc_filter_radius_value" > < / s p a n > ) < / l a b e l >
< input id = "rvc_filter_radius" type = "range" min = "0" max = "7" step = "1" value = "3" / >
< label for = "rvc_pitch_offset" > Pitch offset ( < span id = "rvc_pitch_offset_value" > < / s p a n > ) < / l a b e l >
< input id = "rvc_pitch_offset" type = "range" min = "-100" max = "100" step = "1" value = "0" / >
< label for = "rvc_protect" > Protect amount ( < span id = "rvc_protect_value" > < / s p a n > ) < / l a b e l >
< input id = "rvc_protect" type = "range" min = "0" max = "1" step = "0.01" value = "0.33" / >
< label > Voice Map < / l a b e l >
< textarea id = "rvc_voice_map" type = "text" class = "text_pole textarea_compact" rows = "4"
placeholder = "Enter comma separated map of charName:rvcModel. Example: \nAqua:Bella,\nYou:Josh," > < / t e x t a r e a >
< div id = "rvc_status" >
< / d i v >
< div class = "rvc_buttons" >
< input id = "rvc_apply" class = "menu_button" type = "submit" value = "Apply" / >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
< / d i v >
` ;
$ ( '#extensions_settings' ) . append ( settingsHtml ) ;
$ ( "#rvc_enabled" ) . on ( "click" , onEnabledClick ) ;
$ ( '#rvc_pitch_extraction' ) . on ( 'change' , onPitchExtractionChange ) ;
$ ( '#rvc_index_rate' ) . on ( 'input' , onIndexRateChange ) ;
$ ( '#rvc_filter_radius' ) . on ( 'input' , onFilterRadiusChange ) ;
$ ( '#rvc_pitch_offset' ) . on ( 'input' , onPitchOffsetChange ) ;
$ ( '#rvc_protect' ) . on ( 'input' , onProtectChange ) ;
$ ( "#rvc_apply" ) . on ( "click" , onApplyClick ) ;
}
addExtensionControls ( ) ; // No init dependencies
loadSettings ( ) ; // Depends on Extension Controls
} )