Mix.install([
{:kino, "~> 0.12.0"},
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.10"}
])
You are not expected to understand everything that's going on in this Livebook. It is solely meant to showcase some of what can be done using Elixir, Livebook and programming in general. While reading, try modifying small bits and pieces here and there, and see what changes.
Programming is more than just numbers. In this chapter, we'll take a whirlwind tour through some of the things you can do with Elixir and Livebook.
name = Kino.Input.text("Your name")
IO.puts("Hello, #{Kino.Input.read(name)}!")
button = Kino.Control.button("Click me!")
Kino.listen(button, fn event -> IO.inspect(event) end)
Let's have some fun. A grayscale image can be defined as a series of values in the range 0-255, prefixed with a header describing the height and width of the image.
height = 256
width = 256
# greyscale
greyscale = 1
pixels =
for x <- 0..(height - 1), y <- 0..(width - 1), into: <<>>, do: <<Integer.floor_div(x + y, 2)>>
image = <<height::integer-32, width::integer-32, greyscale, pixels::binary>>
Kino.Shorts.image(image, :pixel)
You don't have to understand everything that's going on here (we're using some new constructs, comprehensions and binaries). The interesting part is the Integer.floor_div(x + y, 2)
part. Here, x
and y
are pixel positions, starting from the top left corner of the image. For instance, when x
and y
are both 0, the result of the expression is also 0, meaning that the pixel in the top left corner of the image is completely black. Likewise, when both inputs are 255, the result is 255 which is completely white.
Try playing around with the function. For instance, can you make the gradient vertical instead of diagonal? What happens if you change +
to *
?
Let's try with colors
# RGB (Red-Green-Blue)
rgb = 3
pixels =
for x <- 0..(height - 1), y <- 0..(width - 1), into: <<>> do
<<
# red
height - 1 - x,
# green
width - 1 - y,
# blue
Integer.floor_div(x + y, 2)
>>
end
content = <<height::integer-32, width::integer-32, rgb, pixels::binary>>
Kino.Shorts.image(content, :pixel)
Let's get started with some audio
defmodule Music do
@volume 0.5
@output_hz 44100
@standard_pitch 440.0
def pitch(i) do
@standard_pitch * 2 ** (i / 12)
end
def num_samples(duration) do
ceil(@output_hz * duration)
end
def sample(p, i) do
@volume * :math.sin(2 * :math.pi() * i * p / @output_hz)
end
def note(i, duration) do
p = pitch(i)
n = num_samples(duration)
Stream.map(0..(n - 1), &sample(p, &1))
end
def c(duration) do
note(3, duration)
end
def d(duration) do
note(5, duration)
end
def e(duration) do
note(7, duration)
end
def break(duration) do
Stream.duplicate(0.0, num_samples(duration))
end
end
alias VegaLite, as: Vl
data =
Music.note(3, 2)
|> Enum.take(100)
|> Enum.with_index()
|> Enum.map(fn {y, x} -> %{time: x, sample: y} end)
|> Enum.to_list()
Vl.new(width: 400, height: 400)
|> Vl.data_from_values(data)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "time", type: :temporal)
|> Vl.encode_field(:y, "sample", type: :quantitative)
defmodule Wav do
def add_wav_header(data) do
bits_per_sample = <<16::little-integer-16>>
block_align_unit = <<2::little-integer-16>>
sample_rate = 44100
data_transmission_rate = <<sample_rate * 2::little-integer-32>>
sample_rate = <<sample_rate::little-integer-32>>
number_of_channels = <<1::little-integer-16>>
format = <<1::little-integer-16>>
data_chunk = <<"data", byte_size(data)::little-integer-32, data::binary>>
fmt =
<<format::binary, number_of_channels::binary, sample_rate::binary,
data_transmission_rate::binary, block_align_unit::binary, bits_per_sample::binary>>
fmt_chunk = <<"fmt ", byte_size(fmt)::little-integer-32, fmt::binary, data_chunk::binary>>
wave_chunk = <<"WAVE", fmt_chunk::binary>>
<<"RIFF", byte_size(wave_chunk)::little-integer-32, wave_chunk::binary>>
end
def from_samples(samples) do
samples
|> Stream.map(&round((2 ** 16 - 1) * &1))
|> Enum.into(
<<>>,
fn byte -> <<byte::little-integer-16>> end
)
|> add_wav_header()
end
end
res =
[
Music.break(0.1),
Music.c(0.4),
Music.break(0.1),
Music.d(0.4),
Music.break(0.1),
Music.e(0.4),
Music.break(0.1),
Music.c(0.4)
]
|> Enum.concat()
|> Wav.from_samples()
|> Kino.Shorts.audio(:wav)