Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 52 additions & 5 deletions stationapi/src/domain/ipa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,33 @@ pub fn station_name_to_tts_segments(
name_katakana: &str,
name_roman: Option<&str>,
) -> Vec<TtsNameSegment> {
name_roman
let mut segments = name_roman
.map(str::trim)
.filter(|name| !name.is_empty())
.and_then(romanized_name_to_tts_segments)
.filter(|segments| !segments.is_empty())
.or_else(|| katakana_name_to_tts_segments(name_katakana))
.unwrap_or_default()
.unwrap_or_default();

// tts_segments と、これらを連結した name_roman_ipa は英語音声 (Azure Dragon HD 等)
// で読み上げる「英語読み」トラック。ら行の弾き音 ɾ は英語ではフラップ /t/・/d/
// (water /ˈwɔːɾɚ/ の t) として実現され、「ら行」ではなく「た/だ/ち」に化ける
// (例: ロッポンギ Roppongi → とっぽんぎ)。そのためこのトラックに限り ら行を
// 側面接近音 l に置換する (例: トリデ toɾide→tolide)。英単語の R は別記号 ɹ を
// 使うので影響しない。日本語音声で読む name_ipa (katakana_to_ipa) は honest な ɾ の
// まま残し、将来 ja-JP 音声で読む場合に備える。
for segment in &mut segments {
segment.pronunciation = lateralize_ra_row(&segment.pronunciation);
}

segments
}

/// 英語読みトラックのら行 ɾ を側面接近音 l に置換する。英単語の R (ɹ) は対象外。
/// 英語音声では ɾ が英語のフラップ /t/ として鳴り「ら行」に聞こえないため
/// (例: ロッポンギ → とっぽんぎ)。語頭・語中・語末いずれの ɾ も対象とする。
fn lateralize_ra_row(pronunciation: &str) -> String {
pronunciation.replace('ɾ', "l")
}

fn join_tts_segment_pronunciations(segments: &[TtsNameSegment]) -> Option<String> {
Expand Down Expand Up @@ -1406,22 +1426,24 @@ mod tests {

#[test]
fn test_station_name_ipa_uses_official_english_wording() {
// 英語読みトラックなのでら行は l (Rinkai → liŋka.i)
assert_eq!(
station_name_to_ipa("カサイリンカイコウエン", Some("Kasai-Rinkai Park")),
Some("kasa.i ɾiŋka.i pɑɹk".to_string())
Some("kasa.i liŋka.i pɑɹk".to_string())
);
}

#[test]
fn test_station_name_ipa_supports_english_and_digits() {
assert_eq!(
station_name_to_ipa("ナリタクウコウ", Some("Narita Airport Terminal 1")),
Some("naɾita ɛɚpɔɹt tɚmɪnəl wʌn".to_string())
Some("nalita ɛɚpɔɹt tɚmɪnəl wʌn".to_string())
);
}

#[test]
fn test_station_name_ipa_supports_multi_digit_numbers() {
// 英単語の R (ɹ) は対象外なので zɪɹoʊ の ɹ はそのまま。ら行由来の ɾ のみ l 化。
assert_eq!(
station_name_to_ipa("ハネダクウコウ", Some("Haneda Airport Terminal 10")),
Some("haneda ɛɚpɔɹt tɚmɪnəl wʌnzɪɹoʊ".to_string())
Expand All @@ -1436,6 +1458,30 @@ mod tests {
);
}

#[test]
fn test_romanized_track_lateralizes_ra_row() {
// 英語読みトラック (station_name_to_ipa = name_roman_ipa / tts_segments) は
// 英語音声で読むため、ら行を l に置換する。語頭・語中・語末いずれも対象。
assert_eq!(
station_name_to_ipa("トリデ", Some("Toride")),
Some("tolide".to_string())
);
// 一方 name_ipa 用の katakana_to_ipa は同じ語でも honest な ɾ を維持する。
assert_eq!(katakana_to_ipa("トリデ"), Some("toɾide".to_string()));
}

#[test]
fn test_roppongi_romanized_track_lateralizes_word_initial_r() {
// 語頭のら行 ɾ は英語音声で /t/ 化し「とっぽんぎ」に聞こえるため、英語読み
// トラックでは l に置換する (ロッポンギ ɾoppoŋgi → loppoŋgi)。
assert_eq!(
station_name_to_ipa("ロッポンギ", Some("Roppongi")),
Some("loppoŋgi".to_string())
);
// name_ipa 用 (日本語音声) は honest な ɾ を維持する。
assert_eq!(katakana_to_ipa("ロッポンギ"), Some("ɾoppoŋgi".to_string()));
}

#[test]
fn test_station_name_ipa_supports_mixed_english_facility_words() {
assert_eq!(
Expand Down Expand Up @@ -1470,9 +1516,10 @@ mod tests {

#[test]
fn test_station_name_ipa_splits_other_compound_kaigan_suffix() {
// 英語読みトラックなのでら行は l (omoɾi → omoli)
assert_eq!(
station_name_to_ipa("オオモリカイガン", Some("Omorikaigan")),
Some("omoɾi ka.igaɴ".to_string())
Some("omoli ka.igaɴ".to_string())
);
}

Expand Down
3 changes: 2 additions & 1 deletion stationapi/src/use_case/dto/station.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ mod tests {
assert_eq!(grpc_station.name_tts_segments[0].separator, " ");
assert_eq!(grpc_station.name_tts_segments[1].surface, "Rinkai");
assert_eq!(grpc_station.name_tts_segments[1].fallback_text, "りんかい");
assert_eq!(grpc_station.name_tts_segments[1].pronunciation, "ɾiŋka.i");
// 英語読みトラック (tts_segments) なのでら行は l (Rinkai → liŋka.i)
assert_eq!(grpc_station.name_tts_segments[1].pronunciation, "liŋka.i");
assert_eq!(grpc_station.name_tts_segments[1].separator, " ");
assert_eq!(grpc_station.name_tts_segments[2].surface, "Park");
assert_eq!(grpc_station.name_tts_segments[2].fallback_text, "Park");
Expand Down
Loading