Skip to content

Commit

Permalink
Updated RHVoiceSwift to with ability to play text and added sample
Browse files Browse the repository at this point in the history
  • Loading branch information
IhorShevchuk committed Mar 28, 2024
1 parent 15c36f7 commit 608c4c6
Show file tree
Hide file tree
Showing 23 changed files with 779 additions and 74 deletions.
92 changes: 53 additions & 39 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,22 @@ func commonHeaderSearchPath(prefix: String = "") -> [CSetting] {
}
}

func commonCSettings(prefix: String = "") -> [CSetting] {

return [ .define("ANDROID"),
.define("TARGET_OS_IPHONE", .when(platforms: [.iOS])),
.define("TARGET_OS_MAC", .when(platforms: [.macOS]))]
+ boostHeaders(prefix: prefix + "RHVoice/")
+ commonHeaderSearchPath(prefix: prefix)
}

let package = Package(
name: "RHVoice",
platforms: [
.macOS(.v10_13),
.iOS(.v12),
.tvOS(.v12),
.watchOS(.v4)
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v5)
],
products: [
.library(
Expand Down Expand Up @@ -121,66 +130,71 @@ let package = Package(
.define("RHVOICE"),
.define("PACKAGE", to: "\"RHVoice\""),
.define("DATA_PATH", to: "\"\""),
.define("CONFIG_PATH", to: "\"\""),
.define("ANDROID"),
.define("TARGET_OS_IPHONE", .when(platforms: [.iOS])),
.define("TARGET_OS_MAC", .when(platforms: [.macOS]))
.define("CONFIG_PATH", to: "\"\"")
]
+ boostHeaders(prefix: "RHVoice/")
+ commonHeaderSearchPath()
+ commonCSettings()
),
.target(name: "RHVoiceObjC",
dependencies: [
.target(name: "RHVoice")
],
path: "Sources",
exclude: [
"RHVoiceSwift"
],
sources: [
"CoreLib",
"RHVoice",
"Utils"
],
publicHeadersPath: "RHVoiceObjC/PublicHeaders/",
path: "Sources/RHVoiceObjC",
publicHeadersPath: "PublicHeaders/",
cSettings: [
.headerSearchPath("RHVoiceObjC/Logger"),
.headerSearchPath("RHVoiceObjC/PrivateHeaders"),
.headerSearchPath("Logger"),
.headerSearchPath("PrivateHeaders"),
.headerSearchPath("Utils"),
.headerSearchPath("CoreLib"),
.headerSearchPath("Mock"),
.define("ANDROID"),
.define("TARGET_OS_IPHONE", .when(platforms: [.iOS])),
.define("TARGET_OS_MAC", .when(platforms: [.macOS]))
.headerSearchPath("../Mock")
]
+ commonHeaderSearchPath(prefix: "../RHVoice/")
+ commonCSettings(prefix: "../../RHVoice/")
,
linkerSettings: [
.linkedFramework("AVFAudio")
]
),
.target(name: "RHVoiceSwift",
dependencies: [
.target(name: "RHVoice")
],
path: "Sources",
sources: [
"RHVoiceSwift"
.target(name: "RHVoice"),
.target(name: "PlayerLib")
],
path: "Sources/RHVoiceSwift",
cSettings: ([
.headerSearchPath("Mock"),
.define("ANDROID"),
.define("TARGET_OS_IPHONE", .when(platforms: [.iOS])),
.define("TARGET_OS_MAC", .when(platforms: [.macOS]))
.headerSearchPath("../Mock")
]
+ boostHeaders(prefix: "../RHVoice/RHVoice/")
+ commonHeaderSearchPath(prefix: "../RHVoice/")

+ commonCSettings(prefix: "../../RHVoice/")
),
swiftSettings: [
.interoperabilityMode(.Cxx)
]
),
.target(name: "PlayerLib",
dependencies: [
.target(name: "RHVoice")
],
path: "Sources/PlayerLib",
cSettings: ([
.headerSearchPath("../Mock")
]
+ commonCSettings(prefix: "../../RHVoice/")
)
),
.executableTarget(
name: "RHVoiceSwiftSample",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.target(name: "RHVoiceSwift")
],
path: "Sources/RHVoiceSwiftSample",
cSettings: ([
.headerSearchPath("../Mock")
]
+ commonCSettings(prefix: "../../RHVoice/")
),
swiftSettings: [
.interoperabilityMode(.Cxx)
]
),
/// Plugin to copy languages and voices data files
.executableTarget(
name: "PackDataExecutable",
Expand Down
128 changes: 128 additions & 0 deletions Sources/PlayerLib/FilePlaybackStream.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// RHVoiceWrapper.cpp
//
//
// Created by Ihor Shevchuk on 01.02.2023.
//
// Copyright (C) 2022 Olga Yakovleva <olga@rhvoice.org>

#include <stdio.h>

#include <string>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>

#include "core/engine.hpp"
#include "core/document.hpp"
#include "core/client.hpp"
#include "audio.hpp"

#include "FilePlaybackStream.h"

namespace PlayerLib
{
class FilePlaybackStream::Impl
{
public:
Impl(const std::string &path)
{
if (!path.empty())
{
stream.set_backend(RHVoice::audio::backend_file);
stream.set_device(path);
}
}

~Impl() = default;

bool play_speech(const short *samples, std::size_t count)
{
try
{
if (!stream.is_open())
{
stream.open();
}
stream.write(samples, count);
return true;
}
catch (...)
{
stream.close();
return false;
}
}

void finish()
{
if (stream.is_open())
{
stream.drain();
}
}

bool set_sample_rate(int sample_rate)
{
try
{
if (stream.is_open() && (stream.get_sample_rate() != sample_rate))
{
stream.close();
}
stream.set_sample_rate(sample_rate);
return true;
}
catch (...)
{
return false;
}
}
bool set_buffer_size(unsigned int buffer_size)
{
try
{
if (stream.is_open() && (stream.get_buffer_size() != buffer_size))
{
stream.close();
}
stream.set_buffer_size(buffer_size);
return true;
}
catch (...)
{
return false;
}
}

private:
RHVoice::audio::playback_stream stream;
};

FilePlaybackStream::FilePlaybackStream(const char *path) : pimpl(new Impl(path))
{
}

bool FilePlaybackStream::set_sample_rate(int sample_rate)
{
return pimpl->set_sample_rate(sample_rate);
}

bool FilePlaybackStream::set_buffer_size(unsigned int buffer_size)
{
return pimpl->set_buffer_size(buffer_size);
}

bool FilePlaybackStream::play_speech(const short *samples, std::size_t count)
{
return pimpl->play_speech(samples, count);
}

void FilePlaybackStream::finish()
{
pimpl->finish();
}

}
38 changes: 38 additions & 0 deletions Sources/PlayerLib/include/FilePlaybackStream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* Copyright (C) 2012, 2013, 2018 Olga Yakovleva <yakovleva.o.v@gmail.com> */

/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 2 of the License, or */
/* (at your option) any later version. */

/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */

/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <https://www.gnu.org/licenses/>. */


#ifndef FilePlaybackStream_h
#define FilePlaybackStream_h

#include <memory>

namespace PlayerLib
{
class FilePlaybackStream
{
public:
FilePlaybackStream(const char *path);
bool play_speech(const short *samples, std::size_t count);
void finish();
bool set_sample_rate(int sample_rate);
bool set_buffer_size(unsigned int buffer_size);

private:
class Impl;
std::shared_ptr<Impl> pimpl;
};
}
#endif /* FilePlaybackStream_h */
File renamed without changes.
File renamed without changes.
24 changes: 0 additions & 24 deletions Sources/RHVoiceObjC/RHSpeechSynthesisVoice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ @interface RHSpeechSynthesisVoice() {
@property (nonatomic, strong) NSString *dataPath;
@property (nonatomic, strong) RHLanguage *language;
@property (nonatomic, strong) NSString *countryCode;
@property (nonatomic, strong) NSString *voiceLanguageCode;
@property (nonatomic, strong) NSString *identifier;
@property (nonatomic, assign) RHSpeechSynthesisVoiceGender gender;
@end
Expand All @@ -31,28 +30,6 @@ @implementation RHSpeechSynthesisVoice

#pragma mark - Public

- (NSString *)languageCode {

NSString *languageCode = [[self language] code];

const BOOL isLanguageCodeEmpty = languageCode == nil || [languageCode isEqualToString:@""];
const BOOL isCountryCodeEmpty = [self voiceLanguageCode] == nil || [[self voiceLanguageCode] isEqualToString:@""];

if (isLanguageCodeEmpty && isCountryCodeEmpty) {
return @"";
}

if(isLanguageCodeEmpty) {
return [self voiceLanguageCode];
}

if(isCountryCodeEmpty) {
return languageCode;
}

return [NSString stringWithFormat:@"%@-%@", languageCode, [self voiceLanguageCode]];
}

- (RHVersionInfo * __nullable)version {
return [[RHVersionInfo alloc] initWith:[[self dataPath] stringByAppendingPathComponent:@"voice.info"]];
}
Expand Down Expand Up @@ -118,7 +95,6 @@ - (instancetype)initWith:(RHVoice::voice_list::const_iterator)voice_information
self.dataPath = STDStringToNSString(voice_information->get_data_path());
self.name = STDStringToNSString(voice_information->get_name());
self.language = [[RHLanguage alloc] initWith:*voice_information->get_language()];
self.voiceLanguageCode = STDStringToNSString(voice_information->get_alpha2_country_code());
self.identifier = STDStringToNSString(voice_information->get_id());
self.gender = [RHSpeechSynthesisVoice genderFromRHVoiceGender:voice_information->get_gender()];
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
22 changes: 22 additions & 0 deletions Sources/RHVoiceSwift/Extensions/FileManager+TemporaryFolder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// FileManager+TemporaryFolder.swift.swift
//
//
// Created by Ihor Shevchuk on 27.03.2024.
//

import Foundation

extension FileManager {
func createTempFolderIfNeeded(at path: String) throws {
if !fileExists(atPath: path) {
try createDirectory(at: URL(fileURLWithPath: path), withIntermediateDirectories: false)
}
}

func removeTempFolderIfNeeded(at path: String) throws {
if !fileExists(atPath: path) {
try removeItem(atPath: path)
}
}
}
20 changes: 20 additions & 0 deletions Sources/RHVoiceSwift/Extensions/String+TemporaryFiles.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// String+TemporaryFiles.swift
//
//
// Created by Ihor Shevchuk on 27.03.2024.
//

import Foundation

extension String {
private static let tempFolderName = "RHVoice"
static var temporaryFolderPath: String {
return NSTemporaryDirectory().appending("\(tempFolderName)")
}

static func temporaryPath(extesnion: String) -> String {
let uuid = UUID().uuidString
return temporaryFolderPath.appending("/\(uuid).\(extesnion)")
}
}
Loading

0 comments on commit 608c4c6

Please sign in to comment.