Add option to repeat schedule in player.

Add end time of a schedule.
Fix negative wait times.
This commit is contained in:
Pim Kunis 2020-10-10 20:30:28 +02:00
parent 39f5f592a7
commit dcc2391094

View file

@ -1,4 +1,4 @@
defmodule Player do defmodule MIDITools.Player do
use GenServer use GenServer
# Client API # Client API
@ -7,67 +7,105 @@ defmodule Player do
GenServer.start_link(__MODULE__, arg, name: __MODULE__) GenServer.start_link(__MODULE__, arg, name: __MODULE__)
end end
def set_schedule(schedule) do def set_schedule(schedule, end_time) do
GenServer.call(__MODULE__, {:set_schedule, schedule}) GenServer.call(__MODULE__, {:set_schedule, schedule, end_time})
end end
def play do def play do
GenServer.call(__MODULE__, :play) GenServer.call(__MODULE__, :play)
end end
def set_repeat(repeat) do
GenServer.call(__MODULE__, {:set_repeat, repeat})
end
# Server callbacks # Server callbacks
@impl true @impl true
def init(_arg) do def init(_arg) do
{:ok, synth} = MIDISynth.start_link([]) {:ok, synth} = MIDISynth.start_link([])
epoch = Timex.epoch() |> Timex.to_datetime() epoch = Timex.epoch() |> Timex.to_datetime()
{:ok, %{timer: nil, schedule: [], schedule_left: [], start_time: epoch, synth: synth}}
{:ok,
%{
timer: nil,
schedule: [],
schedule_left: [],
start_time: epoch,
end_time: 0,
synth: synth,
repeat: false
}}
end end
@impl true @impl true
def handle_call({:set_schedule, schedule}, _from, state) do def handle_call({:set_schedule, schedule, end_time}, _from, state) do
{:reply, :ok, %{state | schedule: schedule, schedule_left: schedule}} {:reply, :ok, %{state | schedule: schedule, schedule_left: schedule, end_time: end_time}}
end end
def handle_call(:play, _from, %{schedule_left: schedule_left} = state) do def handle_call(:play, _from, %{timer: timer} = state) when timer != nil do
{:reply, :already_started, state}
end
def handle_call(:play, _from, %{schedule: schedule} = state) do
start_time = Timex.now() start_time = Timex.now()
state = %{state | start_time: start_time} timer = start_timer(schedule, start_time)
timer = start_timer(schedule_left, start_time) {:reply, :ok, %{state | timer: timer, start_time: start_time, schedule_left: schedule}}
{:reply, :ok, %{state | timer: timer}} end
def handle_call({:set_repeat, repeat}, _from, state) do
{:reply, :ok, %{state | repeat: repeat}}
end end
@impl true @impl true
def handle_info( def handle_info(
:play, :play,
%{schedule_left: schedule_left, start_time: start_time, synth: synth} = state %{
schedule_left: schedule_left,
start_time: start_time,
synth: synth,
repeat: repeat,
end_time: end_time,
schedule: schedule
} = state
) do ) do
schedule_left = play_next_midi(schedule_left, start_time, synth) {timer, schedule_left} = play_next_command(schedule_left, start_time, synth)
{:noreply, %{state | schedule_left: schedule_left}} state = %{state | timer: timer, schedule_left: schedule_left}
if repeat and schedule_left == [] do
start_time = DateTime.add(start_time, end_time, :millisecond)
timer = start_timer(schedule, start_time)
{:noreply, %{state | start_time: start_time, timer: timer, schedule_left: schedule}}
else
{:noreply, state}
end
end end
defp play_next_midi([], _start_time, _synth), do: [] # Private functions
defp play_next_midi([{offset, command} | next_schedule] = schedule_left, start_time, synth) do defp start_timer([], _start_time), do: nil
defp start_timer([{offset, _command} | _], start_time) do
next_time = DateTime.add(start_time, offset, :millisecond)
delay = max(Timex.diff(next_time, Timex.now(), :millisecond), 0)
Process.send_after(self(), :play, delay)
end
defp play_next_command([], _start_time, _synth), do: {nil, []}
defp play_next_command([{offset, command} | next_schedule] = schedule_left, start_time, synth) do
next_time = DateTime.add(start_time, offset, :millisecond) next_time = DateTime.add(start_time, offset, :millisecond)
micro_diff = Timex.diff(next_time, Timex.now(), :microsecond) micro_diff = Timex.diff(next_time, Timex.now(), :microsecond)
if micro_diff < 500 do if micro_diff < 500 do
# Play command, and try to play next command too. # Play command, and try to play next command too.
MIDISynth.midi(synth, command) MIDISynth.midi(synth, command)
play_next_midi(next_schedule, start_time, synth) play_next_command(next_schedule, start_time, synth)
else else
# Command is too far in the future, schedule next timer. # Command is too far in the future, schedule next timer.
delay = ceil(micro_diff / 1000) delay = max(ceil(micro_diff / 1000), 0)
Process.send_after(self(), :play, delay) timer = Process.send_after(self(), :play, delay)
schedule_left {timer, schedule_left}
end end
end end
defp start_timer([], _start_time), do: nil
defp start_timer([{offset, _command} | _], start_time) do
next_time = DateTime.add(start_time, offset, :millisecond)
delay = Timex.diff(next_time, Timex.now(), :millisecond)
Process.send_after(self(), :play, delay)
end
end end