freedom

Makefile

.PHONY: wav mp3 clean
.PRECIOUS: %.wav

wav: freedom.wav
mp3: freedom.mp3

clean:
    git clean -Xdf

%.mp3: %.wav %.id3
    tz tag $<
    ffmpeg -y -i $< $@

%.wav: def.mel %.mel
    cat $^ | tz mel $@

%_fitted.wav: %.wav def.mel %_fit.mel
    cat $(filter-out $<, $^) | tz mel $< - $@

url = https://janberges.de/freedom

%.wav: speak.py %.txt
    wget -O $@ $(url)/$(@F) || curl -o $@ $(url)/$(@F) || python3 $< $*
    touch $@

# The downloaded audio files have been recorded in 2012 using Google Translate,
# GarageBand, and a cable connecting the headphone and microphone jacks.

parts/b_hi.wav: melody/b_hi1.wav melody/b_hi1.wav melody/b_hi2.wav melody/b_hi1.wav
    tz stick $+ $@

parts/chords_a.wav: chords/g.wav chords/g.wav chords/g.wav chords/a.wav
    tz stick $+ $@

parts/chords_c.wav: chords/e.wav chords/e.wav chords/e.wav chords/a.wav
    tz stick $+ $@

parts/a1.wav: drums/hihat.wav parts/chords_a.wav
    tz stack $^ $@

parts/a2.wav: parts/a1.wav drums/bass.wav
    tz stack $^ $@

parts/a3.wav: parts/a2.wav melody/a_lo.wav
    tz stack $^ $@

parts/a4.wav: parts/a3.wav melody/a_mi.wav melody/a_hi.wav
    tz stack $^ $@

parts/b.wav: drums/bass.wav drums/hihat.wav chords/e.wav melody/b_lo.wav melody/b_mi.wav parts/b_hi.wav
    tz stack $^ $@

parts/c.wav: drums/bass.wav parts/chords_c.wav
    tz stack $^ $@

parts/intro.wav: parts/a1.wav parts/a2.wav
    tz stick $+ $@

parts/verse1.wav: parts/a3.wav lyrics/verse1_fitted.wav
    tz stack $^ $@

parts/verse2.wav: parts/a3.wav lyrics/verse2_fitted.wav
    tz stack $^ $@

parts/verse3.wav: parts/a3.wav lyrics/verse3_fitted.wav
    tz stack $^ $@

parts/chorus.wav: parts/a4.wav parts/a4.wav parts/b.wav
    tz stick $+ $@

parts/outro.wav: parts/c.wav lyrics/outro_fitted.wav
    tz stack $^ $@

freedom.wav: parts/intro.wav parts/verse1.wav parts/chorus.wav parts/c.wav parts/verse2.wav parts/chorus.wav parts/c.wav parts/verse3.wav parts/chorus.wav parts/outro.wav
    tz stick $+ $@

chords/a.mel

~ water

&0.3849
M P0 C5 (20 <25 '8
W P0 E5 (00 <25 '8
W P0 A5 )20 <25 '8

chords/e.mel

~ water

&0.3849
M P0 G5 (20 <25 '8
W P0 B5 (00 <25 '8
W P0 E6 )20 <25 '8

chords/g.mel

~ water

&0.3849
M P0 D5 (20 <25 '8
W P0 G5 (00 <25 '8
W P0 B5 )20 <25 '8

def.mel

$44100 O2

|1:4 T equal G

N smooth 0.025

drums/bass.mel

~ harmonic
N smooth 0.001

!25
E3 \30 ,0 '1 \0 ,25 ''
B3 \30 ,0 '1 \0 ,25 '
G3 \30 ,0 '1 \0 ,25 ''

drums/hihat.mel

&0.7071 ,25

N smooth 0.0025

M %1:1000 I

~ random @

!3 ' !8 ' !13 '

W ]1000:1 J

freedom.id3

TIT2 Freiheit ist Sklaverei
TALB Tonbandfetzen
TPE1 Jan Berges
APIC ../../logo/Tonbandfetzen.png

lyrics/outro.txt

Heute ist mein ganzes Leben dadurch bestimmt,
mein unstillbares Verlangen zu besänftigen.
Du hast mich ganz in der Hand.

lyrics/outro_fit.mel

~ #1
N smooth 0

&75 @1:16 '64

lyrics/verse1.txt

Manchmal erinnere ich mich noch an die Zeit,
als ich mit der Welt tief verwurzelt und im Reinen war.
Ich war nie weit gekommen,
doch selbst das Fremde erschien mir vertraut.

lyrics/verse1_fit.mel

~ #1
N smooth 0

&75 @1:16 '64

lyrics/verse2.txt

Eines Tages hast du mich gefunden und mir Geschichten von der Freiheit erzählt,
die in mir eine Sehnsucht geweckt haben,
die so stark war,
dass ich dich meine Wurzeln freilegen und mich mit auf deine Reise nehmen ließ.
Ich war davon verzaubert,
jeden Tag so viel Neues zu sehen,
dass ich wünschte,
wir hielten nie wieder an.

lyrics/verse2_fit.mel

~ #1
N smooth 0

&75 @1:32 '128

lyrics/verse3.txt

Ich wollte mehr.
Irgendwann spürte ich dann erstmals diesen Durst aufkommen,
der mich immer öfter dazu zwang,
dich um etwas zu trinken zu bitten.
Dem bist du auch immer mit einem Lächeln auf den Lippen nachgekommen.

lyrics/verse3_fit.mel

~ #1
N smooth 0

&75 @1:16 '64

melody/a_hi.mel

~ circular

&1:2 !20 [20 }40
E4 ' G4  ' E4 ' B3 ' E4 ' G4  ' E4 ' B3 '
E4 ' F#4 ' E4 ' B3 ' E4 ' F#4 ' E4 ' D4 '
B3 ' C4  ' B3 ' D4 ' B3 ' C4  ' B3 ' D4 '
B3 ' C4  ' B3 ' D4 ' B3 ' C4  ' B3 ' D4 '

melody/a_lo.mel

~ circular

M1
&1:2 !20 [00 {00
E2 ' E2 ' E2 ' E2 ' E2 ' E2 ' E2 ' E2 '
E2 ' E2 ' E2 ' E2 ' E2 ' E2 ' E2 ' E2 '
G2 ' G2 ' G2 ' G2 ' G2 ' G2 ' G2 ' G2 '
A2 ' A2 ' A2 ' A2 ' A2 ' A2 ' A2 ' A2 '
M2

~ harmonic
X flanger 1 2 0.0125 2

melody/a_mi.mel

~ circular

&1:2 !20 ]20 {40
E3 ' G3  ' D3  ' F#3 ' D4 ' C4  ' B3  ' A3  '
G3 ' C4  ' F#3 ' B3  ' E3 ' G3  ' D3  ' F#3 '
G3 ' F#3 ' B3  ' F#3 ' G3 ' F#3 ' D4  ' F#3 '
A3 ' G3  ' F#3 ' G3  ' A3 ' G3  ' F#3 ' B3  '

melody/b_hi1.mel

~ circular

&1:2 !20 [20 }40
G4 ' E4 ' D4 ' E4 ' G4 ' E4 ' D4 ' E4 '

melody/b_hi2.mel

~ circular

&1:2 !20 [20 }40
A4 ' E4 ' D4 ' E4 ' A4 ' E4 ' D4 ' E4 '

melody/b_lo.mel

~ circular

M1
&1:2 !20 [00 {00
C3 ' C3 ' C3 ' C3 ' C3 ' C3 ' D3 ' C3 '
E3 ' E3 ' E3 ' E3 ' E3 ' E3 ' A2 ' B2 '
M2

~ harmonic
X flanger 1 2 0.00625 2

melody/b_mi.mel

~ circular

&1:2 !20 ]20 {40
E3 ' G3 ' C4 ' D4  ' C4 ' A3 ' B3 ' G3 '
E3 ' B3 ' G3 ' F#3 ' E3 ' D3 ' E3 ' G3 '

speak.py

#!/usr/bin/env python3

import re
import subprocess
import sys

job = sys.argv[1]

def speak(speed):
    subprocess.call(['espeak-ng', '-vde+f5', '-s%g' % speed,
        '-f%s.txt' % job, '-w%s.wav' % job])

    time = float(re.search(r'Duration:\s*(\S+)\s*s\b',
        str(subprocess.check_output(['inspect', '%s.wav' % job]))).group(1))

    print('%s takes %g seconds at %g words per minute' % (job, time, speed))

    return time

with open('%s.fit' % job) as lines:
    for line in lines:
        for match in re.findall(r'@1:([\d.]+)', line):
            t = float(match)

s0 = 120.0
t0 = speak(s0)
speak(s0 * t0 / t)