Database sonification

Sonification of an internal database of a german broadcaster which keeps track of all internal videos.

Morse code

Morse code

Supercollider

s.boot;

(
~morseDict = (
    // 0 short, 1 long
    // https://de.wikipedia.org/wiki/Morsecode
    $a: [0,1],
    $b: [1,0,0,0],
    $c: [1,0,1,0],
    $d: [1,0,0],
    $e: [0],
    $f: [0,0,1,0],
    $g: [1,1,0],
    $h: [0,0,0,0],
    $i: [0,0],
    $j: [0,1,1,1],
    $k: [1,0,1],
    $l: [0,1,0,0],
    $m: [1,1],
    $n: [1,0],
    $o: [1,1,1],
    $p: [0,1,1,0],
    $q: [1,1,0,1],
    $r: [0,1,0],
    $s: [0,0,0],
    $t: [1],
    $u: [0,0,1],
    $v: [0,0,0,1],
    $w: [0,1,1],
    $x: [0,1,1,0],
    $y: [1,0,1,1],
    $z: [1,1,0,0],
    $1: [0, 1, 1, 1, 1],
    $2: [0, 0, 1, 1, 1],
    $3: [0, 0, 0, 1, 1],
    $4: [0, 0, 0, 0, 1],
    $5: [0, 0, 0, 0, 0],
    $6: [1, 0, 0, 0, 0],
    $7: [1, 1, 0, 0, 0],
    $8: [1, 1, 1, 0, 0],
    $9: [1, 1, 1, 1, 0],
    $0: [1, 1, 1, 1, 1],
    : [0, 1, 0, 1],
    : [1, 1, 1, 0],
    : [0, 0, 1, 1],
    : [0, 0, 0, 1, 1, 0, 0],
    $.: [0, 1, 0, 1, 0, 1],
    $,: [1, 1, 0, 0, 1, 1],
    $:: [1, 1, 1, 0, 0, 0],
    $;: [1, 0, 1, 0, 1, 0],
    $?: [0, 0, 1, 1, 0, 0],
    $-: [1, 0, 0, 0, 0, 1],
    $_: [0, 0, 1, 1, 0, 1],
)
)


(
~getMorseCode = { |text, shortTime = 0.05, longTime = 0.1, symbolTime=0.15, charTime=0.2, wordTime=0.5|
    var levels, times, m;
    text = text.toLower;  // this should
    levels = [];
    times = [];
    for (0, text.size-1, { |i|

        // check if char is ' '
        if (text[i]==$ ,{
            levels.add(0);
            times.add(wordTime);
        }, {
            m = ~morseDict[text[i]];
            // @todo check nil!
            if(m.isNil, {
                //text[i].postln;
            }, {
                for(0, m.size-1, { |j|
                    if (m[j]==0,
                        {
                            // 0 - short
                            levels = levels.add(1);
                            levels = levels.add(0);
                            times = times.add(shortTime);
                            times = times.add(symbolTime);
                        },
                        {
                            // 1 - long
                            levels = levels.add(1);
                            levels = levels.add(0);
                            times = times.add(longTime);
                            times = times.add(symbolTime);
                        }
                    )
                });
            });
            levels = levels.add(0);
            times = times.add(charTime);
        })
    });
    [levels, times]
}
)


(
SynthDef(\foo, { |out, freq=440, dur=0.1, amp=0.2|
    var sig;
    sig = SinOsc.ar(freq, 0.0, amp);
    sig = EnvGen.kr(Env.linen(sustainTime: dur, releaseTime:0.01), doneAction: Done.freeSelf) * sig;
    Out.ar(out, sig!2);
}).add
)


(
~playSound = { |freq, text="foo", soundTime=2|
    var morseCode, playTask;

    morseCode = ~getMorseCode.value(text);

    playTask = Task.new({
        var binded = Pbind(
            \instrument, \foo,
            \freq, freq,
            \amp, Pseq(morseCode[0], inf)*0.2,
            \dur, Pseq(morseCode[1], inf),
        );
        var event = binded.play;
        soundTime.postln;
        soundTime.wait;
        event.stop;
    });
    playTask.play;
}
)


(
x = OSCFunc( { | msg, time, addr, port |
    var sJobID, nwMeldung, nwUeberschrift, ksSrcDesc, duration;

    sJobID = msg[1].asInteger;
    nwMeldung = msg[2].asString;
    nwUeberschrift = msg[3].asString;
    ksSrcDesc = msg[4].asString;
    duration = msg[5].asInteger;

    ([sJobID, nwMeldung, nwUeberschrift, ksSrcDesc, duration]).postln;

    ~playSound.value(freq: sJobID%duration, text: nwMeldung, soundTime: duration);

}, '/sjob' );
)

Python

import logging
import os
from datetime import datetime
import time

import sqlalchemy as sa
from pythonosc import udp_client

logging.basicConfig(
    format='%(asctime)s - %(levelname)s - %(message)s',
    level=logging.INFO
)

class SonicVpmsDB:
    def __init__(self, connection_string: str, osc_host: str = "localhost", osc_port: int = 57120):
        self.engine = sa.create_engine(connection_string)
        self.osc_client = udp_client.SimpleUDPClient(
            address=osc_host,
            port=osc_port
        )
        self.seen_vpms_ids = []

    def poll(self, poll_interval: int = 10):
        while True:
            self.get_update()
            time.sleep(poll_interval)

    def get_update(self):
        logging.debug('Get update from database')
        with self.engine.connect() as con:
            query = con.execute("""
            SELECT * FROM xxx.xxx SJ
            WHERE SJ.SOM < GETDATE() AND
                SJ.EOM > GETDATE()
            ORDER BY SJ.SOM
            """)

            db_sjobs = [dict(row) for row in query]

        for sjob in [sjob for sjob in db_sjobs if sjob["xxxID"] not in self.seen_vpms_ids]:
            logging.info(f'Found new SJob {sjob["xxxID"]}')
            self.send_message(sjob)

        self.seen_vpms_ids = [sjob["xxxxID"] for sjob in db_sjobs]

    def send_message(self, sjob: dict):
        logging.debug(f'Send SJob {sjob}')
        self.osc_client.send_message(
            '/sjob',
            [
                sjob['xxxID'],
                sjob['xxxText'],
                sjob['xxxText'],
                sjob['xxxSource'],
                (sjob['xxxStart'] - datetime.now()).seconds
            ]
        )

if __name__ == '__main__':
    sonic_vpms_db = SonicVpmsDB(
        connection_string=os.environ['VPMS_CONNECTION_STRING']
    )
    sonic_vpms_db.poll()