1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
use haqumei_macros::phonemes;
phonemes! {
UnvoicedA = "A",
UnvoicedE = "E",
UnvoicedI = "I",
UnvoicedO = "O",
UnvoicedU = "U",
Nn = "N", // Moraic nasal (ん)
A = "a",
B = "b",
By = "by",
Ch = "ch",
Cl = "cl",
D = "d",
Dy = "dy",
E = "e",
F = "f",
Fy = "fy",
G = "g",
Gw = "gw",
Gy = "gy",
H = "h",
Hy = "hy",
I = "i",
J = "j",
K = "k",
Kw = "kw",
Ky = "ky",
M = "m",
My = "my",
N = "n", // Standard nasal consonant
Ny = "ny",
O = "o",
P = "p",
Py = "py",
R = "r",
Ry = "ry",
S = "s",
Sh = "sh",
T = "t",
Ts = "ts",
Ty = "ty",
U = "u",
V = "v",
W = "w",
Y = "y",
Z = "z",
Sp = "sp",
Pau = "pau",
Unk = "unk",
}
impl Phoneme {
/// 無声音 (無声化母音、および無声子音) であるか判定します
pub const fn is_unvoiced(&self) -> bool {
self.is_unvoiced_vowel() || self.is_unvoiced_consonant()
}
/// 有声音 (有声母音、および有声子音、撥音) であるか判定します
///
/// なお、ポーズや不明な音は含みません。
pub const fn is_voiced(&self) -> bool {
self.is_voiced_vowel() || self.is_voiced_consonant() || matches!(self, Self::Nn)
}
/// 母音 (有声・無声両方) であるか判定します
pub const fn is_vowel(&self) -> bool {
self.is_voiced_vowel() || self.is_unvoiced_vowel()
}
/// 有声母音であるか判定します
pub const fn is_voiced_vowel(&self) -> bool {
matches!(self, Self::A | Self::E | Self::I | Self::O | Self::U)
}
/// 無声化母音であるか判定します
pub const fn is_unvoiced_vowel(&self) -> bool {
matches!(
self,
Self::UnvoicedA | Self::UnvoicedE | Self::UnvoicedI | Self::UnvoicedO | Self::UnvoicedU
)
}
/// 子音 (有声・無声両方) であるか判定します
pub const fn is_consonant(&self) -> bool {
self.is_unvoiced_consonant() || self.is_voiced_consonant()
}
/// 無声子音であるか判定します (促音 cl を含みません)
pub const fn is_unvoiced_consonant(&self) -> bool {
matches!(
self,
Self::K
| Self::Ky
| Self::S
| Self::Sh
| Self::T
| Self::Ts
| Self::Ty
| Self::Ch
| Self::P
| Self::Py
| Self::F
| Self::Fy
| Self::H
| Self::Hy
| Self::Kw
)
}
/// 有声子音であるか判定します (撥音 Nn は含みません)
pub const fn is_voiced_consonant(&self) -> bool {
matches!(
self,
Self::G
| Self::Gy
| Self::Gw
| Self::Z
| Self::J
| Self::D
| Self::Dy
| Self::B
| Self::By
| Self::M
| Self::My
| Self::N
| Self::Ny
| Self::R
| Self::Ry
| Self::W
| Self::Y
| Self::V
)
}
/// 閉鎖区間 (促音、ポーズ、スペース) であるかを判定します
pub const fn is_silent(&self) -> bool {
matches!(self, Self::Cl | Self::Pau | Self::Sp)
}
/// 無音 (ポーズ、スペース) であるか判定します
pub const fn is_rest(&self) -> bool {
matches!(self, Self::Sp | Self::Pau)
}
/// 特殊記号 (ポーズ、不明な音) であるか判定します
pub const fn is_special(&self) -> bool {
self.is_rest() || matches!(self, Self::Unk)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phoneme_exhaustiveness() {
let all_phonemes = Phoneme::ALL;
for p in all_phonemes {
// Sp, Pau を除くすべての音素は「有声音」「無声音」「閉鎖区間」「無音・特殊記号」のいずれか1つに必ず属するべき
let is_voiced = p.is_voiced();
let is_unvoiced = p.is_unvoiced();
let is_silent = p.is_silent();
let is_special = p.is_special();
let true_count = [is_voiced, is_unvoiced, is_silent, is_special]
.iter()
.filter(|&&x| x)
.count();
if !matches!(p, Phoneme::Sp | Phoneme::Pau) {
assert_eq!(
true_count, 1,
"{:?} の分類が正しくありません (voiced: {}, unvoiced: {}, silent: {}, special: {})",
p, is_voiced, is_unvoiced, is_silent, is_special
);
} else {
assert_eq!(
true_count, 2,
"{:?} の分類が正しくありません (voiced: {}, unvoiced: {}, silent: {}, special: {})",
p, is_voiced, is_unvoiced, is_silent, is_special
);
}
}
}
}