Add option to repeat schedule in player.
Add end time of a schedule. Fix negative wait times.
This commit is contained in:
parent
39f5f592a7
commit
dcc2391094
1 changed files with 65 additions and 27 deletions
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue