From 6974e00026f4dc3beb93b3366917a89dedfdc075 Mon Sep 17 00:00:00 2001 From: Brett Heath-Wlaz Date: Fri, 31 May 2024 09:34:23 -0400 Subject: [PATCH] add visual equivalent for rail TTS audio (#755) --- lib/content/audio.ex | 2 +- lib/content/audio/approaching.ex | 7 +++++- lib/content/audio/boarding_button.ex | 5 +++- lib/content/audio/closure.ex | 6 ++++- lib/content/audio/custom.ex | 2 +- lib/content/audio/first_train_scheduled.ex | 2 +- lib/content/audio/following_train.ex | 6 ++++- lib/content/audio/next_train_countdown.ex | 6 ++++- .../audio/no_service_to_destination.ex | 6 ++++- lib/content/audio/passthrough.ex | 7 +++++- lib/content/audio/service_ended.ex | 14 +++++++---- lib/content/audio/stopped_train.ex | 6 ++++- lib/content/audio/track_change.ex | 3 ++- lib/content/audio/train_is_arriving.ex | 7 +++++- lib/content/audio/train_is_boarding.ex | 8 +++++-- lib/content/audio/vehicles_to_destination.ex | 16 ++++++++----- lib/pa_ess/utilities.ex | 24 +++++++++++++++++++ test/pa_ess/utilities_test.exs | 8 +++++++ 18 files changed, 109 insertions(+), 26 deletions(-) diff --git a/lib/content/audio.ex b/lib/content/audio.ex index c7c6ceac2..58eb4066b 100644 --- a/lib/content/audio.ex +++ b/lib/content/audio.ex @@ -19,6 +19,6 @@ defprotocol Content.Audio do @doc "Converts an audio struct to the mid/vars params for the PA system" @spec to_params(Content.Audio.t()) :: value() def to_params(audio) - @spec to_tts(Content.Audio.t()) :: String.t() + @spec to_tts(Content.Audio.t()) :: {String.t(), [{String.t(), String.t(), integer()}] | nil} def to_tts(audio) end diff --git a/lib/content/audio/approaching.ex b/lib/content/audio/approaching.ex index 2de253487..87677fb55 100644 --- a/lib/content/audio/approaching.ex +++ b/lib/content/audio/approaching.ex @@ -50,7 +50,7 @@ defmodule Content.Audio.Approaching do case {destination_var(audio.destination, audio.platform, audio.route_id), crowding_description} do {nil, _} -> - {:ad_hoc, {Content.Audio.to_tts(audio), :audio_visual}} + {:ad_hoc, {tts_text(audio), :audio_visual}} {var, nil} -> Utilities.take_message([var], :audio_visual) @@ -82,6 +82,11 @@ defmodule Content.Audio.Approaching do end def to_tts(%Content.Audio.Approaching{} = audio) do + text = tts_text(audio) + {text, PaEss.Utilities.paginate_text(text)} + end + + defp tts_text(%Content.Audio.Approaching{} = audio) do train = Utilities.train_description(audio.destination, audio.route_id) crowding = PaEss.Utilities.crowding_text(audio.crowding_description) diff --git a/lib/content/audio/boarding_button.ex b/lib/content/audio/boarding_button.ex index 2211521c5..a9284d813 100644 --- a/lib/content/audio/boarding_button.ex +++ b/lib/content/audio/boarding_button.ex @@ -10,7 +10,10 @@ defmodule Content.Audio.BoardingButton do end def to_tts(%Content.Audio.BoardingButton{}) do - "Attention Passengers: To board the next train, please push the button on either side of the door." + text = + "Attention Passengers: To board the next train, please push the button on either side of the door." + + {text, PaEss.Utilities.paginate_text(text)} end end end diff --git a/lib/content/audio/closure.ex b/lib/content/audio/closure.ex index 5dada8027..b5415a77d 100644 --- a/lib/content/audio/closure.ex +++ b/lib/content/audio/closure.ex @@ -54,10 +54,14 @@ defmodule Content.Audio.Closure do # Hardcoded for Union Square def to_params(%Content.Audio.Closure{alert: :use_routes_alert, routes: []} = audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end def to_tts(%Content.Audio.Closure{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.Closure{} = audio) do if audio.alert == :use_routes_alert do # Hardcoded for Union Square "No Train Service. Use routes 86, 87, or 91" diff --git a/lib/content/audio/custom.ex b/lib/content/audio/custom.ex index 642f8e43e..e588de7b0 100644 --- a/lib/content/audio/custom.ex +++ b/lib/content/audio/custom.ex @@ -39,7 +39,7 @@ defmodule Content.Audio.Custom do end def to_tts(%Content.Audio.Custom{} = audio) do - audio.message + {audio.message, nil} end end end diff --git a/lib/content/audio/first_train_scheduled.ex b/lib/content/audio/first_train_scheduled.ex index 3398291f8..32a2206fa 100644 --- a/lib/content/audio/first_train_scheduled.ex +++ b/lib/content/audio/first_train_scheduled.ex @@ -58,7 +58,7 @@ defmodule Content.Audio.FirstTrainScheduled do def to_tts(%Content.Audio.FirstTrainScheduled{} = audio) do train = PaEss.Utilities.train_description(audio.destination, nil) time = Content.Utilities.render_datetime_as_time(audio.scheduled_time) - "The first #{train} is scheduled to arrive at #{time}" + {"The first #{train} is scheduled to arrive at #{time}", nil} end end end diff --git a/lib/content/audio/following_train.ex b/lib/content/audio/following_train.ex index b721a5925..e19cdbd9a 100644 --- a/lib/content/audio/following_train.ex +++ b/lib/content/audio/following_train.ex @@ -83,6 +83,10 @@ defmodule Content.Audio.FollowingTrain do end def to_tts(%Content.Audio.FollowingTrain{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.FollowingTrain{} = audio) do train = Utilities.train_description(audio.destination, audio.route_id) arrives_or_departs = if audio.verb == :arrives, do: "arrives", else: "departs" min_or_mins = if audio.minutes == 1, do: "minute", else: "minutes" @@ -90,7 +94,7 @@ defmodule Content.Audio.FollowingTrain do end defp do_ad_hoc_message(audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end @spec green_line_with_branch_params( diff --git a/lib/content/audio/next_train_countdown.ex b/lib/content/audio/next_train_countdown.ex index 44c75582f..913972964 100644 --- a/lib/content/audio/next_train_countdown.ex +++ b/lib/content/audio/next_train_countdown.ex @@ -94,6 +94,10 @@ defmodule Content.Audio.NextTrainCountdown do end def to_tts(%Content.Audio.NextTrainCountdown{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.NextTrainCountdown{} = audio) do train = Utilities.train_description(audio.destination, audio.route_id) arrives_or_departs = if audio.verb == :arrives, do: "arrives", else: "departs" min_or_mins = if audio.minutes == 1, do: "minute", else: "minutes" @@ -123,7 +127,7 @@ defmodule Content.Audio.NextTrainCountdown do end defp do_ad_hoc_message(audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end @spec green_line_with_branch_params( diff --git a/lib/content/audio/no_service_to_destination.ex b/lib/content/audio/no_service_to_destination.ex index e7a337ae2..de6496f27 100644 --- a/lib/content/audio/no_service_to_destination.ex +++ b/lib/content/audio/no_service_to_destination.ex @@ -23,10 +23,14 @@ defmodule Content.Audio.NoServiceToDestination do defimpl Content.Audio do def to_params(%Content.Audio.NoServiceToDestination{} = audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end def to_tts(%Content.Audio.NoServiceToDestination{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.NoServiceToDestination{} = audio) do {:ok, destination_text} = PaEss.Utilities.destination_to_ad_hoc_string(audio.destination) shuttle = if(audio.use_shuttle, do: " Use shuttle.", else: "") "No #{destination_text} service.#{shuttle}" diff --git a/lib/content/audio/passthrough.ex b/lib/content/audio/passthrough.ex index 32fc0fcaf..dee2bb6df 100644 --- a/lib/content/audio/passthrough.ex +++ b/lib/content/audio/passthrough.ex @@ -23,7 +23,7 @@ defmodule Content.Audio.Passthrough do def to_params(audio) do case destination_var(audio.destination, audio.route_id) do nil -> - {:ad_hoc, {Content.Audio.to_tts(audio), :audio_visual}} + {:ad_hoc, {tts_text(audio), :audio_visual}} var -> {:canned, {"103", [var], :audio_visual}} @@ -31,6 +31,11 @@ defmodule Content.Audio.Passthrough do end def to_tts(%Content.Audio.Passthrough{} = audio) do + text = tts_text(audio) + {text, PaEss.Utilities.paginate_text(text)} + end + + defp tts_text(%Content.Audio.Passthrough{} = audio) do train = PaEss.Utilities.train_description(audio.destination, audio.route_id) "The next #{train} does not take customers. Please stand back from the yellow line." end diff --git a/lib/content/audio/service_ended.ex b/lib/content/audio/service_ended.ex index 1627cd9c8..5312c497c 100644 --- a/lib/content/audio/service_ended.ex +++ b/lib/content/audio/service_ended.ex @@ -38,7 +38,7 @@ defmodule Content.Audio.ServiceEnded do Utilities.take_message([@platform_closed, destination_var, @service_ended], :audio) {:error, :unknown} -> - to_tts(audio) + {:ad_hoc, {tts_text(audio), :audio}} end end @@ -50,15 +50,19 @@ defmodule Content.Audio.ServiceEnded do Utilities.take_message([destination_var, @service_ended], :audio) {:error, :unknown} -> - to_tts(audio) + {:ad_hoc, {tts_text(audio), :audio}} end end - def to_tts(%Content.Audio.ServiceEnded{location: :station}) do + def to_tts(%Content.Audio.ServiceEnded{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.ServiceEnded{location: :station}) do "This station is closed. Service has ended for the night." end - def to_tts(%Content.Audio.ServiceEnded{location: :platform, destination: destination}) do + defp tts_text(%Content.Audio.ServiceEnded{location: :platform, destination: destination}) do {:ok, destination_string} = Utilities.destination_to_ad_hoc_string(destination) service_ended = @@ -69,7 +73,7 @@ defmodule Content.Audio.ServiceEnded do "This platform is closed. #{service_ended}" end - def to_tts(%Content.Audio.ServiceEnded{location: :direction, destination: destination}) do + defp tts_text(%Content.Audio.ServiceEnded{location: :direction, destination: destination}) do {:ok, destination_string} = Utilities.destination_to_ad_hoc_string(destination) "#{destination_string} service has ended for the night." diff --git a/lib/content/audio/stopped_train.ex b/lib/content/audio/stopped_train.ex index 79692156e..e8deeaf90 100644 --- a/lib/content/audio/stopped_train.ex +++ b/lib/content/audio/stopped_train.ex @@ -65,13 +65,17 @@ defmodule Content.Audio.StoppedTrain do end def to_tts(%Content.Audio.StoppedTrain{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.StoppedTrain{} = audio) do train = Utilities.train_description(audio.destination, audio.route_id) stop_or_stops = if audio.stops_away == 1, do: "stop", else: "stops" "The next #{train} is stopped #{audio.stops_away} #{stop_or_stops} away" end defp do_ad_hoc_message(audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end defp stops_away_var(1), do: "535" diff --git a/lib/content/audio/track_change.ex b/lib/content/audio/track_change.ex index 295117509..463e7a2be 100644 --- a/lib/content/audio/track_change.ex +++ b/lib/content/audio/track_change.ex @@ -82,7 +82,8 @@ defmodule Content.Audio.TrackChange do "70199" -> "E" end - "Track change: The next #{train} is now boarding on the #{platform} platform" + text = "Track change: The next #{train} is now boarding on the #{platform} platform" + {text, PaEss.Utilities.paginate_text(text)} end defp track_change_message(msg_id) do diff --git a/lib/content/audio/train_is_arriving.ex b/lib/content/audio/train_is_arriving.ex index a465e8577..b5d9dfe8d 100644 --- a/lib/content/audio/train_is_arriving.ex +++ b/lib/content/audio/train_is_arriving.ex @@ -36,7 +36,7 @@ defmodule Content.Audio.TrainIsArriving do ) do case {dest_param(audio), crowding_description} do {nil, _} -> - {:ad_hoc, {Content.Audio.to_tts(audio), :audio_visual}} + {:ad_hoc, {tts_text(audio), :audio_visual}} {var, nil} -> Utilities.take_message([var], :audio_visual) @@ -50,6 +50,11 @@ defmodule Content.Audio.TrainIsArriving do end def to_tts(%Content.Audio.TrainIsArriving{} = audio) do + text = tts_text(audio) + {text, PaEss.Utilities.paginate_text(text)} + end + + defp tts_text(%Content.Audio.TrainIsArriving{} = audio) do train = Utilities.train_description(audio.destination, audio.route_id) crowding = PaEss.Utilities.crowding_text(audio.crowding_description) "Attention passengers: The next #{train} is now arriving.#{crowding}" diff --git a/lib/content/audio/train_is_boarding.ex b/lib/content/audio/train_is_boarding.ex index 4c37db400..be996dd17 100644 --- a/lib/content/audio/train_is_boarding.ex +++ b/lib/content/audio/train_is_boarding.ex @@ -64,14 +64,18 @@ defmodule Content.Audio.TrainIsBoarding do end end - def to_tts(audio) do + def to_tts(%Content.Audio.TrainIsBoarding{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.TrainIsBoarding{} = audio) do train = PaEss.Utilities.train_description(audio.destination, audio.route_id) track = if(audio.track_number, do: " on track #{audio.track_number}", else: ".") "The next #{train} is now boarding#{track}" end defp do_ad_hoc_message(audio) do - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end defp do_to_params(%{destination: destination, route_id: "Green-" <> _branch}, destination_var) diff --git a/lib/content/audio/vehicles_to_destination.ex b/lib/content/audio/vehicles_to_destination.ex index 4d73b764f..44428c4d1 100644 --- a/lib/content/audio/vehicles_to_destination.ex +++ b/lib/content/audio/vehicles_to_destination.ex @@ -55,15 +55,19 @@ defmodule Content.Audio.VehiclesToDestination do if low_var && high_var && destination do {:canned, {message_id(audio), [low_var, high_var], :audio}} else - {:ad_hoc, {Content.Audio.to_tts(audio), :audio}} + {:ad_hoc, {tts_text(audio), :audio}} end end - def to_tts(%Content.Audio.VehiclesToDestination{ - headway_range: {range_low, range_high}, - destination: destination, - routes: routes - }) do + def to_tts(%Content.Audio.VehiclesToDestination{} = audio) do + {tts_text(audio), nil} + end + + defp tts_text(%Content.Audio.VehiclesToDestination{ + headway_range: {range_low, range_high}, + destination: destination, + routes: routes + }) do trains = case {destination, routes} do {destination, nil} -> diff --git a/lib/pa_ess/utilities.ex b/lib/pa_ess/utilities.ex index 4381a1884..248d0d8c3 100644 --- a/lib/pa_ess/utilities.ex +++ b/lib/pa_ess/utilities.ex @@ -697,4 +697,28 @@ defmodule PaEss.Utilities do def audio_take({:route, route}), do: @route_take_lookup[route] def audio_take(atom) when is_atom(atom), do: @atom_take_lookup[atom] + + @spec paginate_text(String.t(), integer()) :: [{String.t(), String.t(), integer()}] + def paginate_text(text, max_length \\ 24) do + String.split(text) + |> Stream.chunk_while( + nil, + fn word, acc -> + if is_nil(acc) do + {:cont, word} + else + new_acc = acc <> " " <> word + + if String.length(new_acc) > max_length do + {:cont, acc, word} + else + {:cont, new_acc} + end + end + end, + fn acc -> {:cont, acc, nil} end + ) + |> Stream.chunk_every(2, 2, [""]) + |> Enum.map(fn [top, bottom] -> {top, bottom, 3} end) + end end diff --git a/test/pa_ess/utilities_test.exs b/test/pa_ess/utilities_test.exs index 29911a39c..3df0bda2f 100644 --- a/test/pa_ess/utilities_test.exs +++ b/test/pa_ess/utilities_test.exs @@ -109,4 +109,12 @@ defmodule Content.Audio.UtilitiesTest do assert replace_abbreviations("SvC, OK") == "Service, OK" end end + + test "paginate_text" do + assert [{"Attention passengers:", "the next Braintree train", 3}, {"is now arriving", "", 3}] = + paginate_text("Attention passengers: the next Braintree train is now arriving", 24) + + assert [{"too-long", "word", 3}] = paginate_text(" too-long word ", 5) + assert [{"fits", "", 3}] = paginate_text("fits", 24) + end end