convert method

  1. @override
G2pResult convert(
  1. String text
)
override

Converts text with the injected frontend.

As in the pinned pyopenjtalk-style mode, G2pResult.phonemes contains the rendered phoneme text followed immediately by its equal-scalar-length pitch trace.

Implementation

@override
G2pResult convert(String text) {
  final words = _analyze(text);
  final tokens = <_WorkingJapaneseToken>[];
  var lastAccentState = 0;
  int? phraseAccent;
  var phraseMoraCount = 0;

  for (var wordIndex = 0; wordIndex < words.length; wordIndex++) {
    final word = words[wordIndex];
    if (word.moraSize < 0) {
      throw _invalidWord(
        wordIndex,
        'moraSize must be non-negative, got ${word.moraSize}.',
      );
    }
    if (word.accent < 0) {
      throw _invalidWord(
        wordIndex,
        'accent must be non-negative, got ${word.accent}.',
      );
    }

    var moras = const <String>[];
    if (word.moraSize > 0) {
      moras = pronunciationToMoras(word.pronunciation);
      final leadingLongVowelAdjustment =
          moras.isNotEmpty && moras.first == 'ー' ? 1 : 0;
      if (moras.length != word.moraSize &&
          moras.length + leadingLongVowelAdjustment != word.moraSize) {
        throw _invalidWord(
          wordIndex,
          'pronunciation `${word.pronunciation}` produced ${moras.length} '
          'moras, but moraSize is ${word.moraSize}.',
        );
      }
    }

    final chainFlag =
        word.moraSize > 0 &&
        tokens.isNotEmpty &&
        tokens.last.moraSize > 0 &&
        (word.chainFlag || moras.first == 'ー');
    if (!chainFlag) {
      phraseAccent = null;
      phraseMoraCount = 0;
    }
    phraseAccent ??= word.accent;

    final accents = <int>[];
    for (var moraIndex = 0; moraIndex < moras.length; moraIndex++) {
      phraseMoraCount++;
      final accentState = switch (phraseAccent) {
        0 => phraseMoraCount == 1 ? 0 : (lastAccentState == 0 ? 1 : 2),
        final int accent when accent == phraseMoraCount => 3,
        final int accent
            when 1 < phraseMoraCount && phraseMoraCount < accent =>
          lastAccentState == 0 ? 1 : 2,
        _ => 0,
      };
      accents.add(accentState);
      lastAccentState = accentState;
    }

    final surface = _punctuationMap[word.surface] ?? word.surface;
    var whitespace = '';
    String? phonemes;
    String? pitch;
    if (moras.isNotEmpty) {
      final phonemeBuffer = StringBuffer();
      final pitchBuffer = StringBuffer();
      for (var moraIndex = 0; moraIndex < moras.length; moraIndex++) {
        final phoneme = _moraToPhoneme[moras[moraIndex]];
        if (phoneme == null) {
          throw MalformedDataException(
            'Japanese mora table has no phoneme for `${moras[moraIndex]}`.',
          );
        }
        phonemeBuffer.write(phoneme);
        final pitchSymbol = switch (accents[moraIndex]) {
          0 => '_',
          3 => '^',
          _ => '-',
        };
        _writeRepeated(pitchBuffer, pitchSymbol, _scalarLength(phoneme));
      }
      phonemes = phonemeBuffer.toString();
      pitch = pitchBuffer.toString();
    } else if (_isPunctuation(surface)) {
      phonemes = surface;
      final finalCharacter = _lastScalar(surface);
      if (_punctuationStops.contains(finalCharacter)) {
        whitespace = ' ';
        if (tokens.isNotEmpty) {
          tokens.last.whitespace = '';
        }
      } else if (_punctuationStarts.contains(finalCharacter) &&
          tokens.isNotEmpty &&
          tokens.last.whitespace.isEmpty) {
        tokens.last.whitespace = ' ';
      }
    }

    final mergesIntoPrevious =
        (tokens.isNotEmpty && phonemes == null && surface == '・') ||
        _isNonEmptyWhitespace(surface);
    if (mergesIntoPrevious) {
      if (tokens.isEmpty) {
        throw _invalidWord(
          wordIndex,
          'leading whitespace cannot attach to a preceding token.',
        );
      }
      tokens.last.whitespace = ' ';
      continue;
    }

    tokens.add(
      _WorkingJapaneseToken(
        text: surface,
        tag: word.partOfSpeech,
        whitespace: whitespace,
        phonemes: phonemes,
        pronunciation: word.pronunciation,
        accent: word.accent,
        moraSize: word.moraSize,
        chainFlag: chainFlag,
        moras: moras,
        accents: accents,
        pitch: pitch,
      ),
    );
  }

  return _render(tokens);
}