voicevox.rb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+
4
+ class Voicevox
5
+ #
6
+ # テキストからAudioQueryを生成します。
7
+ #
8
+ # @param [String] text 生成するAudioQueryのテキスト。
9
+ # @param [Voicevox::CharacterInfo, Voicevox::StyleInfo, Integer] speaker 話者、または話者のID。
10
+ # @param [Boolean] kana textをAquesTalkライクな記法として解釈するかどうか。デフォルトはfalse。
11
+ #
12
+ # @return [Voicevox::AudioQuery] 生成されたAudioQuery。
13
+ #
14
+ # @see Voicevox#synthesis
15
+ #
16
+ def audio_query(text, speaker, kana: false)
17
+ options = Voicevox::Core.voicevox_make_default_audio_query_options
18
+ options[:kana] = kana
19
+ speaker_id = speaker.is_a?(Integer) ? speaker : speaker.id
20
+ load_model speaker_id
21
+ return_ptr = FFI::MemoryPointer.new(:pointer)
22
+ Voicevox.process_result Voicevox::Core.voicevox_audio_query(
23
+ text,
24
+ speaker_id,
25
+ options,
26
+ return_ptr
27
+ )
28
+ return_str_ptr = return_ptr.read_pointer
29
+ json = return_str_ptr.read_string
30
+ Voicevox::Core.voicevox_audio_query_json_free return_str_ptr
31
+
32
+ AudioQuery.new JSON.parse(json, symbolize_names: true)
33
+ end
34
+
35
+ #
36
+ # AudioQueryから音声を生成します。
37
+ #
38
+ # @param [AudioQuery] query AudioQuery。
39
+ # @param [Voicevox::CharacterInfo, Voicevox::StyleInfo, Integer] speaker 話者、または話者のID。
40
+ # @param [Boolran] enable_interrogative_upspeak 疑問文の調整を有効にするかどうか。デフォルトはtrue。
41
+ #
42
+ # @return [String] 生成された音声のwavデータ。
43
+ #
44
+ def synthesis(query, speaker, enable_interrogative_upspeak: true)
45
+ size_ptr = FFI::MemoryPointer.new(:int)
46
+ return_ptr = FFI::MemoryPointer.new(:pointer)
47
+ id = speaker.is_a?(Integer) ? speaker : speaker.id
48
+ load_model id
49
+ options = Voicevox::Core::VoicevoxSynthesisOptions.new
50
+ options[:enable_interrogative_upspeak] = enable_interrogative_upspeak
51
+ Voicevox.process_result(
52
+ Voicevox::Core.voicevox_synthesis(
53
+ query.to_json,
54
+ id,
55
+ options,
56
+ size_ptr,
57
+ return_ptr
58
+ )
59
+ )
60
+ data_ptr = return_ptr.read_pointer
61
+ size_ptr.free
62
+ data = data_ptr.read_string(size_ptr.read_int)
63
+ Voicevox::Core.voicevox_wav_free(data_ptr)
64
+ data
65
+ end
66
+
67
+ #
68
+ # 音声合成用のクエリ。
69
+ #
70
+ class AudioQuery
71
+ # @return [Array<AccentPhrase>] アクセント句のリスト。
72
+ attr_accessor :accent_phrases
73
+ # @return [Float] 全体の話速。
74
+ attr_accessor :speed_scale
75
+ # @return [Float] 全体の音高。
76
+ attr_accessor :pitch_scale
77
+ # @return [Float] 全体の抑揚。
78
+ attr_accessor :intonation_scale
79
+ # @return [Float] 全体の音量。
80
+ attr_accessor :volume_scale
81
+ # @return [Float] 音声の前の無音時間。
82
+ attr_accessor :pre_phoneme_length
83
+ # @return [Float] 音声の後の無音時間。
84
+ attr_accessor :post_phoneme_length
85
+ # @return [Integer] 音声データの出力サンプリングレート。
86
+ attr_accessor :output_sampling_rate
87
+ # @return [Boolean] 音声データをステレオ出力するか否か。
88
+ attr_accessor :output_stereo
89
+ # @return [String] AquesTalkライクな読み仮名。
90
+ attr_reader :kana
91
+
92
+ def initialize(query)
93
+ @accent_phrases = query[:accent_phrases].map { |ap| AccentPhrase.new ap }
94
+ @speed_scale = query[:speed_scale]
95
+ @pitch_scale = query[:pitch_scale]
96
+ @intonation_scale = query[:intonation_scale]
97
+ @volume_scale = query[:volume_scale]
98
+ @pre_phoneme_length = query[:pre_phoneme_length]
99
+ @post_phoneme_length = query[:post_phoneme_length]
100
+ @output_sampling_rate = query[:output_sampling_rate]
101
+ @output_stereo = query[:output_stereo]
102
+ @kana = query[:kana]
103
+ end
104
+
105
+ #
106
+ # AudioQueryをHashにします。
107
+ #
108
+ # @return [Hash]
109
+ #
110
+ def to_hash
111
+ {
112
+ accent_phrases: @accent_phrases.map(&:to_hash),
113
+ pitch_scale: @pitch_scale,
114
+ speed_scale: @speed_scale,
115
+ intonation_scale: @intonation_scale,
116
+ volume_scale: @volume_scale,
117
+ pre_phoneme_length: @pre_phoneme_length,
118
+ post_phoneme_length: @post_phoneme_length,
119
+ output_sampling_rate: @output_sampling_rate,
120
+ output_stereo: @output_stereo,
121
+ kana: @kana
122
+ }
123
+ end
124
+
125
+ #
126
+ # AudioQueryをjsonにします。
127
+ #
128
+ # @return [String]
129
+ #
130
+ def to_json(...)
131
+ to_hash.to_json(...)
132
+ end
133
+ end
134
+
135
+ #
136
+ # アクセント句ごとの情報。
137
+ #
138
+ class AccentPhrase
139
+ # @return [Array<Mora>] モーラのリスト。
140
+ attr_reader :moras
141
+ # @return [Integer] アクセント箇所。
142
+ attr_reader :accent
143
+ # @return [Mora, nil] 後ろに無音を付けるかどうか。
144
+ attr_reader :pause_mora
145
+ # @return [Boolean] 疑問系かどうか。
146
+ attr_reader :is_interrogative
147
+ alias interrogative? is_interrogative
148
+
149
+ def initialize(query)
150
+ @moras = query[:moras].map { |ap| Mora.new ap }
151
+ @accent = query[:accent]
152
+ @pause_mora = query[:pause_mora] && Mora.new(query[:pause_mora])
153
+ @is_interrogative = query[:is_interrogative]
154
+ end
155
+
156
+ #
157
+ # AccentPhraseをHashにします。
158
+ #
159
+ # @return [Hash]
160
+ #
161
+ def to_hash
162
+ {
163
+ moras: @moras.map(&:to_hash),
164
+ accent: @accent,
165
+ pause_mora: @pause_mora&.to_hash,
166
+ is_interrogative: @is_interrogative
167
+ }
168
+ end
169
+
170
+ #
171
+ # モーラ(子音+母音)ごとの情報。
172
+ #
173
+ class Mora
174
+ # @return [String] 文字。
175
+ attr_reader :text
176
+ # @return [String] 子音の音素。
177
+ attr_reader :consonant
178
+ # @return [Float] 子音の音長。
179
+ attr_reader :consonant_length
180
+ # @return [String] 母音の音素。
181
+ attr_reader :vowel
182
+ # @return [Float] 母音の音長。
183
+ attr_reader :vowel_length
184
+ # @return [Float] 音高。
185
+ attr_reader :pitch
186
+
187
+ def initialize(query)
188
+ @text = query[:text]
189
+ @consonant = query[:consonant]
190
+ @consonant_length = query[:consonant_length]
191
+ @vowel = query[:vowel]
192
+ @vowel_length = query[:vowel_length]
193
+ @pitch = query[:pitch]
194
+ end
195
+
196
+ #
197
+ # MoraをHashにします。
198
+ #
199
+ # @return [Hash]
200
+ #
201
+ def to_hash
202
+ {
203
+ text: @text,
204
+ consonant: @consonant,
205
+ consonant_length: @consonant_length,
206
+ vowel: @vowel,
207
+ vowel_length: @vowel_length,
208
+ pitch: @pitch
209
+ }
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ class Voicevox
6
+ # サポートされているデバイスを表すStruct。
7
+ SupportedDevices = Struct.new(:cpu, :cuda, :dml, keyword_init: true)
8
+
9
+ # キャラクターの情報を表すStruct。
10
+ CharacterInfo =
11
+ Struct.new(:name, :styles, :speaker_uuid, :version, keyword_init: true) do
12
+ #
13
+ # キャラクターの最初のスタイルのIDを返します。
14
+ # @note ほとんどの場合はノーマルになります。
15
+ #
16
+ # @return [Integer] スタイルのID。
17
+ #
18
+ def id
19
+ styles[0].id
20
+ end
21
+
22
+ #
23
+ # キャラクターのスタイルが全てロードされているかを返します。
24
+ #
25
+ # @return [Boolean] 全てロードされている場合はtrue、そうでない場合はfalse。
26
+ #
27
+ def loaded?
28
+ styles.map(&:loaded?).all?
29
+ end
30
+
31
+ #
32
+ # キャラクターのスタイルを全てロードします。
33
+ #
34
+ # @return [void]
35
+ #
36
+ def load
37
+ Voicevox.initialize_required
38
+ styles.map(&:load)
39
+ end
40
+ end
41
+ StyleInfo =
42
+ Struct.new(:name, :id, keyword_init: true) do
43
+ #
44
+ # スタイルがロードされているかを返します。
45
+ #
46
+ # @return [Boolean] ロードされている場合はtrue、そうでない場合はfalse。
47
+ #
48
+ def loaded?
49
+ Voicevox::Core.is_model_loaded(id)
50
+ end
51
+
52
+ #
53
+ # スタイルをロードします。
54
+ #
55
+ # @return [void]
56
+ #
57
+ def load
58
+ Voicevox.initialize_required
59
+ Voicevox.process_result Voicevox::Core.voicevox_load_model(id)
60
+ end
61
+ end
62
+
63
+ class << self
64
+ #
65
+ # サポートしているデバイスを取得します。
66
+ #
67
+ # @return [Voicevox::SupportedDevices] サポートしているデバイス。
68
+ #
69
+ def supported_devices
70
+ SupportedDevices.new(
71
+ **JSON.parse(Voicevox::Core.voicevox_get_supported_devices_json)
72
+ )
73
+ end
74
+
75
+ #
76
+ # キャラクターの一覧を取得します。
77
+ #
78
+ # @return [Array<CharacterInfo>] キャラクターの一覧。
79
+ #
80
+ def characters
81
+ JSON
82
+ .parse(Voicevox::Core.voicevox_get_metas_json)
83
+ .map do |meta|
84
+ CharacterInfo.new(
85
+ **{
86
+ **meta,
87
+ "styles" => meta["styles"].map { |style| StyleInfo.new(**style) }
88
+ }
89
+ )
90
+ end
91
+ end
92
+
93
+ #
94
+ # GPUをサポートしているかを返します。
95
+ #
96
+ # @note CUDA、またはDirectMLが使える場合にtrueを返します。
97
+ #
98
+ # @return [Boolean] GPUをサポートしているかどうか。
99
+ #
100
+ def gpu_supported?
101
+ Voicevox.supported_devices.cuda || Voicevox.supported_devices.dml
102
+ end
103
+
104
+ #
105
+ # コアのバージョンを取得します。
106
+ #
107
+ # @return [String] コアのバージョン。
108
+ #
109
+ def core_version
110
+ Voicevox::Core.voicevox_get_version
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+ require "objspace"
5
+
6
+ class Voicevox
7
+ @initialized = false
8
+ # @return [:cpu, :gpu] ハードウェアアクセラレーションモード。
9
+ attr_reader :acceleration_mode
10
+ # @return [Integer] スレッド数。
11
+ attr_reader :cpu_num_threads
12
+ # @return [Boolean] 起動時に全てのモデルを読み込むかどうか。
13
+ attr_reader :load_all_models
14
+
15
+ #
16
+ # GPUモードで動作しているかどうか。
17
+ #
18
+ # @return [Boolean] GPUモードで動作している場合はtrue、そうでない場合はfalse。
19
+ #
20
+ def gpu?
21
+ @acceleration_mode == :gpu
22
+ end
23
+
24
+ #
25
+ # CPUモードで動作しているかどうか。
26
+ #
27
+ # @return [Boolean] CPUモードで動作している場合はtrue、そうでない場合はfalse。
28
+ #
29
+ def cpu?
30
+ @acceleration_mode == :cpu
31
+ end
32
+
33
+ #
34
+ # Voicevoxのコアを初期化します。
35
+ #
36
+ # @param [String] openjtalk_dict_path OpenJTalkの辞書へのパス。
37
+ # @param [:cpu, :gpu, :auto] acceleration_mode ハードウェアアクセラレーションモード。:autoを指定するとコア側で自動的に決定されます。
38
+ # @param [Integer] cpu_num_threads スレッド数。省略する、または0を渡すとコア側で自動的に決定されます。
39
+ # @param [Boolean] load_all_models 全てのモデルを読み込むかどうか。省略するとfalseになります。
40
+ #
41
+ def initialize(
42
+ openjtalk_dict_path,
43
+ acceleration_mode: :auto,
44
+ cpu_num_threads: nil,
45
+ load_all_models: false
46
+ )
47
+ acceleration_mode_enum =
48
+ {
49
+ auto: :voicevox_acceleration_mode_auto,
50
+ gpu: :voicevox_acceleration_mode_gpu,
51
+ cpu: :voicevox_acceleration_mode_cpu
52
+ }.fetch(acceleration_mode) do
53
+ raise ArgumentError, "無効なacceleration_mode: #{acceleration_mode}"
54
+ end
55
+ @cpu_num_threads = cpu_num_threads || 0
56
+ @load_all_models = load_all_models
57
+ @openjtalk_dict_path = openjtalk_dict_path
58
+ options = Voicevox::Core.voicevox_make_default_initialize_options
59
+ options[:acceleration_mode] = acceleration_mode_enum
60
+ options[:cpu_num_threads] = @cpu_num_threads
61
+ options[:load_all_models] = @load_all_models
62
+ options[:openjtalk_dict_path] = FFI::MemoryPointer.from_string(
63
+ openjtalk_dict_path
64
+ )
65
+
66
+ Voicevox.process_result Voicevox::Core.voicevox_initialize(options)
67
+ @acceleration_mode = Voicevox::Core.voicevox_is_gpu_mode ? :gpu : :cpu
68
+ at_exit { Voicevox::Core.voicevox_finalize } unless self.class.initialized
69
+ self.class.initialized = true
70
+ end
71
+
72
+ #
73
+ # Voicevoxのコアをファイナライズします。
74
+ #
75
+ def finalize
76
+ Voicevox::Core.voicevox_finalize
77
+ self.class.initialized = false
78
+ end
79
+
80
+ #
81
+ # 話者のモデルを読み込みます。
82
+ #
83
+ # @param [Voicevox::CharacterInfo, Voicevox::StyleInfo, Integer] speaker 話者、または話者のID。
84
+ #
85
+ def load_model(speaker)
86
+ id = speaker.is_a?(Integer) ? speaker : speaker.id
87
+
88
+ Voicevox.process_result Voicevox::Core.voicevox_load_model(id)
89
+ end
90
+
91
+ #
92
+ # モデルが読み込まれているかどうかを返します。
93
+ #
94
+ # @param [Voicevox::CharacterInfo, Voicevox::StyleInfo, Integer] speaker 話者、または話者のID。
95
+ #
96
+ # @return [Boolean] 読み込まれているかどうか。
97
+ #
98
+ def model_loaded?(speaker)
99
+ id = speaker.is_a?(Integer) ? speaker : speaker.id
100
+
101
+ Voicevox::Core.voicevox_is_model_loaded(id)
102
+ end
103
+
104
+ #
105
+ # voicevox_ttsを使って音声を生成します。
106
+ #
107
+ # @param [String] text 生成する音声のテキスト。
108
+ # @param [Voicevox::CharacterInfo, Voicevox::StyleInfo, Integer] speaker 話者、または話者のID。
109
+ # @param [Boolean] kana textをAquesTalkライクな記法として解釈するかどうか。デフォルトはfalse。
110
+ # @param [Boolran] enable_interrogative_upspeak 疑問文の調整を有効にするかどうか。デフォルトはtrue。
111
+ #
112
+ # @return [String] 生成された音声のwavデータ。
113
+ #
114
+ def tts(text, speaker, kana: false, enable_interrogative_upspeak: true)
115
+ size_ptr = FFI::MemoryPointer.new(:int)
116
+ return_ptr = FFI::MemoryPointer.new(:pointer)
117
+ id = speaker.is_a?(Integer) ? speaker : speaker.id
118
+ load_model id
119
+ options = Voicevox::Core.voicevox_make_default_tts_options
120
+ options[:kana] = kana
121
+ options[:enable_interrogative_upspeak] = enable_interrogative_upspeak
122
+ Voicevox.process_result(
123
+ Voicevox::Core.voicevox_tts(text, id, options, size_ptr, return_ptr)
124
+ )
125
+ data_ptr = return_ptr.read_pointer
126
+ data = data_ptr.read_string(size_ptr.read_int)
127
+ size_ptr.free
128
+ Voicevox::Core.voicevox_wav_free(data_ptr)
129
+ data
130
+ end
131
+
132
+ class << self
133
+ attr_accessor :initialized
134
+
135
+ alias initialized? initialized
136
+ end
137
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+
5
+ class Voicevox
6
+ class << self
7
+ #
8
+ # Voicevoxが初期化されていなかったらエラーを出す。
9
+ #
10
+ def initialize_required
11
+ raise Voicevox::Error, "Voicevoxが初期化されていません" unless Voicevox.initialized?
12
+ end
13
+
14
+ #
15
+ # voicevox_result_codeに対応するエラーをraiseします。
16
+ #
17
+ # @param [Symbol] result voicevox_result_code。
18
+ #
19
+ def process_result(result)
20
+ return if result == :voicevox_result_succeed
21
+ raise "#{result}はSymbolではありません" unless result.is_a?(Symbol)
22
+
23
+ raise Voicevox::CoreError.from_code(result)
24
+ end
25
+
26
+ #
27
+ # 製品版Voicevoxのパスを返します。
28
+ #
29
+ # @return [String] Voicevoxへの絶対パス。
30
+ # @return [nil] Voicevoxが見付からなかった場合。zip版やLinux版ではnilを返します。
31
+ #
32
+ def voicevox_path
33
+ paths =
34
+ if Gem.win_platform?
35
+ [File.join(ENV.fetch("LOCALAPPDATA", ""), "Programs", "VOICEVOX")]
36
+ else
37
+ [
38
+ "/Applications/VOICEVOX",
39
+ "/Users/#{Etc.getlogin}/Library/Application Support/VOICEVOX"
40
+ ]
41
+ end
42
+ paths.find { |path| Dir.exist?(path) }
43
+ end
44
+ end
45
+ end
data/lib/voicevox.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "voicevox/core"
4
+ require_relative "voicevox/version"
5
+ require_relative "voicevox/error"
6
+ require_relative "voicevox/wrapper/utils"
7
+ require_relative "voicevox/wrapper/info"
8
+ require_relative "voicevox/wrapper/manager"
9
+ require_relative "voicevox/wrapper/audio_query"
10
+
11
+ #
12
+ # voicevox_coreのラッパー。
13
+ #
14
+ class Voicevox # rubocop:disable Lint/EmptyClass
15
+ end
@@ -0,0 +1,100 @@
1
+ ---
2
+ sources:
3
+ - name: ruby/gem_rbs_collection
4
+ remote: https://github.com/ruby/gem_rbs_collection.git
5
+ revision: main
6
+ repo_dir: gems
7
+ path: ".gem_rbs_collection"
8
+ gems:
9
+ - name: activesupport
10
+ version: '6.0'
11
+ source:
12
+ type: git
13
+ name: ruby/gem_rbs_collection
14
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
15
+ remote: https://github.com/ruby/gem_rbs_collection.git
16
+ repo_dir: gems
17
+ - name: ast
18
+ version: '2.4'
19
+ source:
20
+ type: git
21
+ name: ruby/gem_rbs_collection
22
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
23
+ remote: https://github.com/ruby/gem_rbs_collection.git
24
+ repo_dir: gems
25
+ - name: i18n
26
+ version: '1.10'
27
+ source:
28
+ type: git
29
+ name: ruby/gem_rbs_collection
30
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
31
+ remote: https://github.com/ruby/gem_rbs_collection.git
32
+ repo_dir: gems
33
+ - name: io-console
34
+ version: '0'
35
+ source:
36
+ type: stdlib
37
+ - name: json
38
+ version: '0'
39
+ source:
40
+ type: stdlib
41
+ - name: listen
42
+ version: '3.2'
43
+ source:
44
+ type: git
45
+ name: ruby/gem_rbs_collection
46
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
47
+ remote: https://github.com/ruby/gem_rbs_collection.git
48
+ repo_dir: gems
49
+ - name: minitest
50
+ version: '0'
51
+ source:
52
+ type: stdlib
53
+ - name: parallel
54
+ version: '1.20'
55
+ source:
56
+ type: git
57
+ name: ruby/gem_rbs_collection
58
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
59
+ remote: https://github.com/ruby/gem_rbs_collection.git
60
+ repo_dir: gems
61
+ - name: rainbow
62
+ version: '3.0'
63
+ source:
64
+ type: git
65
+ name: ruby/gem_rbs_collection
66
+ revision: e920cbaa517738b75f3b1e70223a0e51da80d5aa
67
+ remote: https://github.com/ruby/gem_rbs_collection.git
68
+ repo_dir: gems
69
+ - name: steep
70
+ version: 1.1.1
71
+ source:
72
+ type: rubygems
73
+ - name: monitor
74
+ version: '0'
75
+ source:
76
+ type: stdlib
77
+ - name: date
78
+ version: '0'
79
+ source:
80
+ type: stdlib
81
+ - name: singleton
82
+ version: '0'
83
+ source:
84
+ type: stdlib
85
+ - name: logger
86
+ version: '0'
87
+ source:
88
+ type: stdlib
89
+ - name: mutex_m
90
+ version: '0'
91
+ source:
92
+ type: stdlib
93
+ - name: time
94
+ version: '0'
95
+ source:
96
+ type: stdlib
97
+ - name: pathname
98
+ version: '0'
99
+ source:
100
+ type: stdlib
@@ -0,0 +1,15 @@
1
+ # Download sources
2
+ sources:
3
+ - name: ruby/gem_rbs_collection
4
+ remote: https://github.com/ruby/gem_rbs_collection.git
5
+ revision: main
6
+ repo_dir: gems
7
+
8
+ # A directory to install the downloaded RBSs
9
+ path: .gem_rbs_collection
10
+
11
+ gems:
12
+ # Skip loading rbs gem's RBS.
13
+ # It's unnecessary if you don't use rbs as a library.
14
+ - name: rbs
15
+ ignore: true
data/sig/ffi.rbs ADDED
@@ -0,0 +1,16 @@
1
+ module FFI
2
+ module Library
3
+ def ffi_lib: (Array[String] | String) -> void
4
+
5
+ def enum: (Symbol, Array[Symbol | Integer]) -> void
6
+
7
+ def attach_function: (Symbol, Array[Symbol], Symbol) -> void
8
+ end
9
+
10
+ class Pointer
11
+ end
12
+
13
+ class MemoryPointer < Pointer
14
+ def initialize: (Symbol `type`) -> void
15
+ end
16
+ end