Liquidsoap 1.2.0 : Liquidsoap Workshop, part III

This part is pretty open. We describe below a few advanced features of liquidsoap, including video and midi. We provide some examples or simply propose things to do, to give you an idea of what's in the near future of liquidsoap and hopefully draw you into contributing to shaping that future, if only through discussions.

Visualization

A liquidsoap script is like any other program: you can in principle predict what it does, but there's always a point where you miss something. In such cases, you need to debug it: control all the relevant parameters until you spot what's wrong.

In liquidsoap, the most important parameter is probably the availability of a source. You'll end up creating devices dealing with various sources going on and off. To test them you can create sources whose availability is controlled by you, and you should also be able to monitor their availability.

As an exercise, you can try to write a simple logging function that periodically displays source.is_ready(s) for some source s. The mix operator can be useful too: it is a mixing table for liquidsoap, allowing you to monitor and cancel the availability of its inputs; the best way to use it is through liGuidsoap.

Audio volume

You can visualize the audio volume on screen using visu.volume:

d = 1.
t = "lin"
s = fade.in(duration=d,type=t,
      fade.out(duration=d,type=t,sine(duration=3.*d)))
output.ao(visu.volume(s))

Video streams

Video may be used very simply in liquidsoap: common operators such as single and playlist will attempt to decode their files as video if their content type is appropriate, which is dictated by the output operator. For example you can do output.sdl(single("video.ogg")) to display the video part of an audio+video stream; to hear the audio part, insert your favorite audio output operator together with drop_audio and drop_video at the right places (most outputs do not silently drop irrelevant data).

Slideshow

The following script displays a slideshow of images, while playing a playlist of audio files. Pass it the images playlist/directory as the first argument on the command line (after --) and (optionally) the audio playlist/directory as the second argument.

def images
  video.fade.in(duration=1.,video.fade.out(duration=1.,
    video.add_text(metadata="filename","<no filename>",
                   size=12,
                   playlist(prefix="annotate:duration=3:",argv(1)))))
end

def sound
  playlist(argv(2))
end

output.ao(fallible=true,sound)
clock(id="video", output.sdl(fallible=true,images))

# You can also combine the two in a Theora file.
# Play the result in VLC rather than mplayer (or even liquidsoap)
# as it is a sequentialized ogg stream.
# output.file(%ogg(%vorbis,%theora),"slideshow.ogg",
#   mksafe(mux_video(video=images,sound)))

If you experience transparency problems... it's a known bug (see notably #393). You may also experience segfaults... it does not seem to happen with a selected list avoiding too large sizes or too exotic formats.

Audio volume

You can render the audio volume visualization as a video stream, that you can then process as any other video stream:

d = 1.
t = "lin"
s = fade.in(duration=d,type=t,
      fade.out(duration=d,type=t,sine(duration=3.*d)))
output.sdl(drop_audio(video.volume(s)))

Static image over audio track

TODO: the youtube encoder

Transitions

Playing a video file video.ogv is simply achieved by

s = single("video.ogv")
output.sdl(s)

There are many useful (or not) effects in Liquidsoap which can be used to modify the video. These should be inserted between the first and the second line of the script above. For example, the image can be converted to sepia by adding

s = video.sepia(s)

Common operations include adding a logo (stored in a PPM image file image.ppm):

s = video.add_image(width=30, height=30, x=10, y=10, file="image.ppm", s)

and displaying a scrolling text:

s = video.add_text("Hello people!", s)

Try modifying the scrolling text example so that you can modify the contents of the text over the telnet interface.

Very similarly to audio transitions (fade in, fade out, etc.) there are some video transitions implemented. For example fading in the video is simply done using

s = video.fade.in(s)

The kind of transition that should be used is controlled by the transition parameter of video.fade.in. Try disc for example, as well as the others that you can find in the documentation.

Of course, the add operator of liquidsoap also works on video streams. So, in order to add a rotating image on a video you could use

s = add([s,
         video.rotate(
           video.add_image(
             width=50, height=50, x=150, y=150,
             file="image.ppm",
             blank()))])

Overloaded demo

file = single("bus.ogg")

istring = interactive.string

v = add([file,
         # video.text("Hello world..."),
         # video.text("Mip mip!",color=0xff0000,y=-1,speed=300),
         video.text(istring("a",""),color=0xff0000,font=mono,size=30,y=-01),
         video.text(istring("b",""),color=0xff0000,font=mono,size=30,y=-31),
         video.text(istring("c",""),color=0xff0000,font=mono,size=30,y=-61),
])

output.alsa(drop_video(file))

# output.file.theora("out.ogv",
output.sdl(add([video.fade.in(video.fade.in(transition="disc",v)),
                # video.rotate(video.scale(coef=0.3,offset_x=20,offset_y=20,v)),
               video.image("chameau.pnm",width=50,height=50,
                           x=-10,y=10,alpha=0xffffff),
]))

text = fun (v,s) -> ignore(server.execute('var.set #{v} = "#{s}"'))

def pingouin()
  text("c","(o_ ")
  text("b","//\\ ")
  text("a","v_/_")
end

def pan()
  text("c","(X_ ")
end

server.register("pingouin", fun (_) -> begin pingouin() "Oui seigneur..." end)
server.register("pan", fun (_) -> begin pan() "Zog zog!" end)

server.register("anim", fun (s) -> begin
  s = if s=="t" then begin
    pan()
    "f"
  else
    pingouin()
    "t"
  end
  add_timeout(1.,{ ignore(server.execute("anim #{s}")) (-1.) })
  "Done."
end)

Manipulating MIDI data

Playing with the keyboard

MIDI is a format for describing steams of notes, scores, etc. Liquidsoap has a basic support for such streams.

In order to generate a MIDI stream, the input.keyboard.sdl operator can be used. It will convert typing onto the keyboard of your operator into notes. In order to be able to hear the notes of a stream, you have to synthesize them, which means to convert them to wave sound. Various operators can be used for this, each corresponding to a different instrument. For example, a synthesizer with sawtooth waves is provided by the operator synth.saw. A mini-keyboard synthesizer can thus be programmed using the following script

s = input.keyboard.sdl()
s = synth.saw(s)
out(s)

You can also test synth.sine or synth.square for other kinds of simple sounds.

In order to check the MIDI data contained in streams, the midimeter operator is very convenient: it prints on the standard output the notes currently being played.

Playing MIDI files

Liquidsoap comes with built-in support for MIDI files: when such a file is played it is detected as such and decoded as a MIDI stream. Usually, MIDI files contain multiple channels of notes (typically one for each instrument). In order to use the sawtooth synthesizer on all channels, the synth.all.saw operator should be used (with the synth.saw operator, only the first channel will be synthesized).

So, a MIDI file named file.mid can be played using the following script

s = single("file.mid")
s = midi.remove([9],s)
s = mux_audio(audio=blank(),s)
s = synth.all.saw(s)
s = drop_midi(s)
out(s)

The second line removes all the notes from the channel 9 which is usually used for drums (and would thus sound bad with our basic synthesizer). The third line adds an audio channel to the stream s, in which the sound will be synthesized.

Playing chords

The implementation of MIDI-related operators in Liquidsoap is still in early stage and their implementation gives us the possibility to simply test new ideas... we would be glad hear yours too!

For example, we thought it would be nice to be able to play chords in Liquidsoap. This is actually pretty simple using metadata. First, describe your sequence of chords in a file named chords.txt with the following contents:

1 "chord" "C"
2 "chord" "Am"
3 "chord" "F"
4 "chord" "G"

This file just contains a list of metadata: on each line, the first number indicate when (in seconds) the metadata should occur, the string in second indicates the name of the metadata (chord here) and the string in third position indicates the value of the metadata (the chord to be played here). This file format for metadata is supported natively by Liquidsoap. Now, the metadata containing the chord names can be converted to MIDI notes by using the chord operator. The sequence of chords above can thus be heard using the following script:

s = single("chord.txt")
s = midi.chord(s)
s = mux_audio(audio=blank(),s)
s = synth.saw(s)
s = drop_midi(s)
out(s)

Open problems

If you feel like hacking seriously, here are some tasks from the Savonet community.

Listener-sensitive radio

Write a script that checks whether an icecast mount point is being listened to, and use it to switch a radio to some dummy source when nobody is listening, and switch back to normal when listeners come back. This can be useful to avoid using the hard drive when unnecessary – it is noisy, calorific, and simply not so long-lived.

Liquidsoap script generator

Write a script (even better, a web page) that generates a simple liquidsoap script with a few options: input from playlist, live relay, output to icecast and/or soundcard.

Re-usable tools for radios

The open-source radio community needs re-usable tools that can be interfaced with existing streamers: indexer, database generator, scheduler, crossfading editor, etc.