All Downloads are FREE. Search and download functionalities are using the official Maven repository.

simpleivr.Sayables.scala Maven / Gradle / Ivy

package simpleivr

import java.time.LocalTime


abstract class Sayables(val audioFileBackend: AudioFileBackend) extends Speaks {
  lazy val
    `An error occurred.`,
    `Please say`,
    `after the tone, and press pound when finished.`,
    `is`,
    `Is that correct?`,
    `You must enter `,
    `You must enter at least`,
    `Please make a selection`,
    `That is not one of the choices.`,
    `Press 1 for yes, or 2 for no.`,
    `Press`,
    `star`,
    `pound`,
    `For more choices`,
    `For the previous choices`,
    `for any of the following:`,
    `To return to the previous menu`,
    `digit`,
    `digits`,
    `You cannot enter more than`,
    `That entry is not valid`,
    `and`,
    `or`,
    `now`,
    `Please enter`,
    `A`,
    `M`,
    `O`,
    `P`,
    `clock`
      = speak

  lazy val
    `zero`, `one`, `two`, `three`, `four`, `five`,
    `six`, `seven`, `eight`, `nine`, `ten`,
    `eleven`, `twelve`, `thirteen`, `fourteen`, `fifteen`,
    `sixteen`, `seventeen`, `eighteen`, `nineteen`, `twenty`,
    `thirty`, `forty`, `fifty`, `sixty`, `seventy`, `eighty`, `ninety`,
    `hundred`, `thousand`, `million`, `negative`
      = speak


  def numberWords(number: Int): Sayable = {
    /*
     * Based on an article by Richard Carr, at http://www.blackwasp.co.uk/NumberToWords.aspx
     */
    val smallNumbers = collection.IndexedSeq(
      `one`, `two`, `three`, `four`, `five`, `six`, `seven`, `eight`, `nine`, `ten`,
      `eleven`, `twelve`, `thirteen`, `fourteen`, `fifteen`, `sixteen`, `seventeen`, `eighteen`, `nineteen`
    )

    // Tens number names from twenty upwards
    val _tens = collection.IndexedSeq(
      `twenty`, `thirty`, `forty`, `fifty`, `sixty`, `seventy`, `eighty`, `ninety`
    )

    // Scale number names for use during recombination
    val scaleNumbers = List(Sayable.Empty, `thousand`, `million`)

    def ifs(cond: Boolean)(s: => Sayable) = if (cond) s else Sayable.Empty

    if (number == 0) `zero` else {
      @annotation.tailrec def split(num: Int)(agg: List[Int]): List[Int] =
        if (num == 0) agg
        else split(num / 1000)((num % 1000) :: agg)

      val digitGroups = split(math.abs(number))(Nil).reverse

      val groupText = digitGroups map { num =>
        val hundreds = num / 100
        val tensUnits = num % 100
        val tens = tensUnits / 10
        val units = tensUnits % 10

        val text = ifs(hundreds != 0)(smallNumbers(hundreds - 1) & `hundred` & ifs(tensUnits != 0)(`and`)) &
          ifs(tens >= 2)(_tens(tens - 2) & ifs(units != 0)(smallNumbers(units - 1))) &
          ifs(tens < 2 && tensUnits != 0)(smallNumbers(tensUnits - 1))

        (num, text)
      }

      def render(groups: List[(Int, Sayable)], scales: List[Sayable], first: Boolean): Sayable = groups match {
        case Nil                 =>
          Sayable.Empty
        case (num, text) :: rest =>
          val next = render(rest, scales.tail, first = false)
          val cur = ifs(num > 0)(text & scales.head)
          val sep =
            ifs(Sayable.Empty != next && Sayable.Empty != cur)(Pause(250) & ifs(first && num > 0 && num < 100)(`and`))
          next & sep & cur
      }

      ifs(number < 0)(`negative`) & render(groupText, scaleNumbers, first = true)
    }
  }

  def digitWords(digitString: String): Sayable =
    Sayable.Many(
      digitString
        .map(_ - '0')
        .filter(d => d >= 0 && d <= 9)
        .map(numberWords)
    )

  def dtmfWord(c: DTMF): Sayable = c match {
    case DTMF.`#` => `pound`
    case DTMF.*   => `star`
    case _        => digitWords(c.toString)
  }

  def timeWords(time: LocalTime) =
    numberWords((time.getHour + 11) % 12 + 1) &
      (if (time.getMinute < 10) `O` else Sayable.Empty) &
      (if (time.getMinute == 0) `clock` else numberWords(time.getMinute)) &
      (if (time.getHour < 12) `A` else `P`) & `M`
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy