@@ -22,6 +22,29 @@ typedef NS_ENUM(NSInteger, WfloatGenerateResult) {
2222 WfloatGenerateResultFailed = 2 ,
2323};
2424
25+ @interface WfloatDialogueSegment : NSObject
26+ @property (nonatomic , copy ) NSString *text;
27+ @property (nonatomic , assign ) int32_t sid;
28+ @property (nonatomic , copy ) NSString *emotion;
29+ @property (nonatomic , assign ) float intensity;
30+ @property (nonatomic , assign ) float speed;
31+ @property (nonatomic , assign ) float sentenceSilencePaddingSec;
32+ @end
33+
34+ @implementation WfloatDialogueSegment
35+ @end
36+
37+ @interface WfloatPreparedDialogueSegment : NSObject
38+ @property (nonatomic , strong ) NSArray <NSString *> *rawTextChunks;
39+ @property (nonatomic , strong ) NSArray <NSString *> *textCleanChunks;
40+ @property (nonatomic , assign ) int32_t sid;
41+ @property (nonatomic , assign ) float speed;
42+ @property (nonatomic , assign ) float sentenceSilencePaddingSec;
43+ @end
44+
45+ @implementation WfloatPreparedDialogueSegment
46+ @end
47+
2548static NSString *WfloatCacheRootDirectory (void ) {
2649 NSArray <NSString *> *paths =
2750 NSSearchPathForDirectoriesInDomains (NSCachesDirectory, NSUserDomainMask, YES );
@@ -741,6 +764,97 @@ - (WfloatGenerateResult)generateSpeechForSession:(WfloatSpeechSession *)session
741764 return session.isCancelled ? WfloatGenerateResultCancelled : WfloatGenerateResultCompleted;
742765}
743766
767+ - (WfloatGenerateResult)generateDialogueForSession : (WfloatSpeechSession *)session
768+ segments : (NSArray <WfloatDialogueSegment *> *)segments
769+ silenceBetweenSegmentsSec : (float )silenceBetweenSegmentsSec
770+ error : (NSError **)error {
771+ NSMutableArray <WfloatPreparedDialogueSegment *> *preparedSegments =
772+ [NSMutableArray arrayWithCapacity: segments.count];
773+ NSUInteger totalChunkCount = 0 ;
774+
775+ for (WfloatDialogueSegment *segment in segments) {
776+ if (session.isCancelled || ![self isCurrentSpeechSession: session]) {
777+ return WfloatGenerateResultCancelled;
778+ }
779+
780+ NSDictionary *preparedPayload = [self preparedTextPayloadForText: segment.text
781+ emotion: segment.emotion
782+ intensity: segment.intensity
783+ error: error];
784+ if (!preparedPayload) {
785+ return WfloatGenerateResultFailed;
786+ }
787+
788+ WfloatPreparedDialogueSegment *preparedSegment =
789+ [[WfloatPreparedDialogueSegment alloc ] init ];
790+ preparedSegment.rawTextChunks = [self stringArrayFromValue: preparedPayload[@" text" ]];
791+ preparedSegment.textCleanChunks =
792+ [self stringArrayFromValue: preparedPayload[@" text_clean" ]];
793+ preparedSegment.sid = segment.sid ;
794+ preparedSegment.speed = segment.speed ;
795+ preparedSegment.sentenceSilencePaddingSec = segment.sentenceSilencePaddingSec ;
796+ [preparedSegments addObject: preparedSegment];
797+ totalChunkCount += preparedSegment.textCleanChunks .count ;
798+ }
799+
800+ if (totalChunkCount == 0 ) {
801+ [session markGenerationComplete ];
802+ return session.isCancelled ? WfloatGenerateResultCancelled : WfloatGenerateResultCompleted;
803+ }
804+
805+ NSUInteger progressIndex = 0 ;
806+ for (WfloatPreparedDialogueSegment *segment in preparedSegments) {
807+ for (NSUInteger index = 0 ; index < segment.textCleanChunks .count ; index += 1 ) {
808+ if (session.isCancelled || ![self isCurrentSpeechSession: session]) {
809+ return WfloatGenerateResultCancelled;
810+ }
811+
812+ NSString *textClean = segment.textCleanChunks [index];
813+ const SherpaOnnxGeneratedAudio *generatedAudio =
814+ SherpaOnnxOfflineTtsGenerate (self.tts , textClean.UTF8String , segment.sid , segment.speed );
815+ if (!generatedAudio) {
816+ if (error) {
817+ *error = [NSError errorWithDomain: WfloatErrorDomain
818+ code: 113
819+ userInfo: @{
820+ NSLocalizedDescriptionKey :
821+ @" Failed to generate speech audio from the prepared text." ,
822+ }];
823+ }
824+ return WfloatGenerateResultFailed;
825+ }
826+
827+ progressIndex += 1 ;
828+ NSString *rawChunkText = index < segment.rawTextChunks .count ? segment.rawTextChunks [index] : @" " ;
829+ float silencePaddingSec = segment.sentenceSilencePaddingSec ;
830+ if (index + 1 == segment.textCleanChunks .count ) {
831+ silencePaddingSec = silenceBetweenSegmentsSec;
832+ }
833+
834+ NSError *scheduleError = nil ;
835+ BOOL didSchedule = [session scheduleAudioSamples: generatedAudio->samples
836+ frameCount: generatedAudio->n
837+ progress: (double )progressIndex / (double )totalChunkCount
838+ text: rawChunkText
839+ highlightStart: 0
840+ highlightEnd: 1
841+ silencePaddingSec: silencePaddingSec
842+ error: &scheduleError];
843+ SherpaOnnxDestroyOfflineTtsGeneratedAudio (generatedAudio);
844+
845+ if (!didSchedule) {
846+ if (error) {
847+ *error = scheduleError;
848+ }
849+ return session.isCancelled ? WfloatGenerateResultCancelled : WfloatGenerateResultFailed;
850+ }
851+ }
852+ }
853+
854+ [session markGenerationComplete ];
855+ return session.isCancelled ? WfloatGenerateResultCancelled : WfloatGenerateResultCompleted;
856+ }
857+
744858- (void )cleanupStaleFilesInDirectory : (NSString *)directoryPath
745859 activeFileNames : (NSSet <NSString *> *)activeFileNames {
746860 NSError *contentsError = nil ;
@@ -1017,6 +1131,147 @@ - (void)generate:(JS::NativeWfloat::GenerateNativeOptions &)options
10171131 });
10181132}
10191133
1134+ - (void )generateDialogue : (JS ::NativeWfloat::GenerateDialogueNativeOptions &)options
1135+ resolve : (RCTPromiseResolveBlock)resolve
1136+ reject : (RCTPromiseRejectBlock)reject {
1137+ if (!self.tts ) {
1138+ reject (@" not_loaded" ,
1139+ @" SpeechClient is not created. Call SpeechClient.loadModel(...) first." ,
1140+ nil );
1141+ return ;
1142+ }
1143+
1144+ double requestIdValue = options.requestId ();
1145+ auto nativeSegments = options.segments ();
1146+ double silenceBetweenSegmentsSecValue = options.silenceBetweenSegmentsSec ();
1147+
1148+ if (!isfinite (requestIdValue) || requestIdValue < 0 || floor (requestIdValue) != requestIdValue) {
1149+ reject (@" invalid_arguments" , @" requestId must be a non-negative integer." , nil );
1150+ return ;
1151+ }
1152+
1153+ if (nativeSegments.empty ()) {
1154+ reject (@" invalid_arguments" , @" segments is required." , nil );
1155+ return ;
1156+ }
1157+
1158+ if (!isfinite (silenceBetweenSegmentsSecValue) || silenceBetweenSegmentsSecValue < 0 ) {
1159+ silenceBetweenSegmentsSecValue = 0.2 ;
1160+ }
1161+
1162+ NSMutableArray <WfloatDialogueSegment *> *segments =
1163+ [NSMutableArray arrayWithCapacity: (NSUInteger )nativeSegments.size ()];
1164+ for (facebook::react::LazyVector<JS ::NativeWfloat::GenerateDialogueNativeSegment>::size_type
1165+ index = 0 ;
1166+ index < nativeSegments.size ();
1167+ index += 1 ) {
1168+ JS ::NativeWfloat::GenerateDialogueNativeSegment nativeSegment = nativeSegments[index];
1169+ NSString *text = nativeSegment.text ();
1170+ double sidValue = nativeSegment.sid ();
1171+ NSString *emotion = nativeSegment.emotion ();
1172+ double intensityValue = nativeSegment.intensity ();
1173+ double speedValue = nativeSegment.speed ();
1174+ double sentenceSilencePaddingSecValue = nativeSegment.sentenceSilencePaddingSec ();
1175+
1176+ if (text.length == 0 ) {
1177+ reject (@" invalid_arguments" ,
1178+ [NSString stringWithFormat: @" segments[%d ].text is required." , index],
1179+ nil );
1180+ return ;
1181+ }
1182+
1183+ if (!isfinite (sidValue) || sidValue < 0 || floor (sidValue) != sidValue) {
1184+ reject (@" invalid_arguments" , @" sid must be a non-negative integer." , nil );
1185+ return ;
1186+ }
1187+
1188+ if (!isfinite (intensityValue)) {
1189+ intensityValue = 0.5 ;
1190+ }
1191+
1192+ if (!isfinite (speedValue) || speedValue <= 0 ) {
1193+ speedValue = 1.0 ;
1194+ }
1195+
1196+ if (!isfinite (sentenceSilencePaddingSecValue) ||
1197+ sentenceSilencePaddingSecValue < 0 ) {
1198+ sentenceSilencePaddingSecValue = 0.1 ;
1199+ }
1200+
1201+ WfloatDialogueSegment *segment = [[WfloatDialogueSegment alloc ] init ];
1202+ segment.text = text;
1203+ segment.sid = (int32_t )sidValue;
1204+ segment.emotion = emotion.length > 0 ? emotion : @" neutral" ;
1205+ segment.intensity = (float )MAX (0.0 , MIN (intensityValue, 1.0 ));
1206+ segment.speed = (float )speedValue;
1207+ segment.sentenceSilencePaddingSec = (float )sentenceSilencePaddingSecValue;
1208+ [segments addObject: segment];
1209+ }
1210+
1211+ NSInteger requestId = (NSInteger )requestIdValue;
1212+ float silenceBetweenSegmentsSec = (float )silenceBetweenSegmentsSecValue;
1213+ int32_t sampleRate = SherpaOnnxOfflineTtsSampleRate (self.tts );
1214+
1215+ __weak Wfloat *weakSelf = self;
1216+ WfloatSpeechSession *session = [[WfloatSpeechSession alloc ]
1217+ initWithRequestId: requestId
1218+ sampleRate: sampleRate
1219+ progressHandler: ^(NSInteger progressRequestId,
1220+ double progress,
1221+ BOOL isPlaying,
1222+ NSInteger textHighlightStart,
1223+ NSInteger textHighlightEnd,
1224+ NSString *chunkText) {
1225+ [weakSelf emitSpeechProgressWithRequestId: progressRequestId
1226+ progress: progress
1227+ isPlaying: isPlaying
1228+ textHighlightStart: textHighlightStart
1229+ textHighlightEnd: textHighlightEnd
1230+ text: chunkText];
1231+ }
1232+ playbackFinishedHandler: ^(NSInteger finishedRequestId) {
1233+ if (!weakSelf) {
1234+ return ;
1235+ }
1236+
1237+ if (weakSelf.speechSession .requestId == finishedRequestId) {
1238+ weakSelf.speechSession = nil ;
1239+ }
1240+
1241+ [weakSelf emitSpeechPlaybackFinishedWithRequestId: finishedRequestId];
1242+ }];
1243+
1244+ WfloatSpeechSession *previousSession = self.speechSession ;
1245+ self.speechSession = session;
1246+ [previousSession cancel ];
1247+
1248+ dispatch_async (self.workQueue , ^{
1249+ NSError *generationError = nil ;
1250+ WfloatGenerateResult result =
1251+ [self generateDialogueForSession: session
1252+ segments: segments
1253+ silenceBetweenSegmentsSec: silenceBetweenSegmentsSec
1254+ error: &generationError];
1255+
1256+ dispatch_async (dispatch_get_main_queue (), ^{
1257+ if (result == WfloatGenerateResultFailed) {
1258+ if ([self isCurrentSpeechSession: session]) {
1259+ [self cancelCurrentSpeechSession ];
1260+ } else {
1261+ [session cancel ];
1262+ }
1263+
1264+ reject (@" generate_failed" ,
1265+ generationError.localizedDescription ?: @" Failed to generate speech audio." ,
1266+ generationError);
1267+ return ;
1268+ }
1269+
1270+ resolve (nil );
1271+ });
1272+ });
1273+ }
1274+
10201275- (void )play : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject {
10211276 (void )reject;
10221277 [self .speechSession play ];
0 commit comments