Skip to content

Design Rationale

mbergevoet edited this page Jun 17, 2021 · 12 revisions

Debrief

De stichting Thunderboom ontwikkeld digitale popartiesten met behulp van artificial intelligence die de lyrics en muziek genereert. Door middel van CGI krijgen deze artiesten ook een uiterlijk. Joost de Boo en Max Tiel zijn de oprichters en opdrachtgevers. Joost is een graphic designer en werkt op CMD Avans hogeschool. Max is een muzikant en Joost en hij zijn al lang vrienden. Omdat ze allebei houden van nieuwe technologie zijn ze Thunderboom gestart om muziek te maken met behulp van artificial intelligence.

Het is aan ons om één van deze digitale artiesten een ‘stem’ te geven. We moeten eerste onderzoeken of het een instagram chatbot zou kunnen zijn. Dat heeft de voorkeur omdat instagram het meest populair is onder echte popartiesten van nu. Als dat niet kan bouwen we onze eigen chat omgeving. Daar kunnen we onze eigen regels bepalen en API’s gebruiken om de tekstberichten te kunnen lezen en een database maken met reacties of woorden en die aan de chat omgeving hangen.

Het eindproduct is een prototype van een chat omgeving waarin je een gesprek kunt hebben met één van de Thunderboom artiesten. Dit kunnen Joost en Max dan gebruiken al proof of voor synthesizers, beatmakers en andere partijen die thuis zijn de markt als ze het uiteindelijk willen uitbrengen.

Probleem definitie

Joost en Max willen het concept van hun digitale artiesten kunnen presenteren aan mensen uit de muziekindustrie en bedrijven waar ze later misschien mee zouden willen samenwerken. De muziek en de visuals zijn er al. Het enige wat mist is een persoonlijkheid. Daarom dachten ze dit te kunnen overbrengen door middel van een chatbot met een eigen persoonlijkheid waarmee je gesprekken kunt voeren. Het voordeel van een digitale artiest in vergelijking met een echte artiest is immers dat hij op meerdere plekken tegelijk kan zijn. Of in dit geval meerdere gesprekken kan voeren met fans tegelijkertijd.

Oplossing

Joost had als eerste het idee om een Instagram bot te maken voor één van hun digitale artiesten genaamt TWY (uitgesproken als twaai), maar nadat wij wat onderzoek hadden gedaan bleek dat Instagram geen chatbots toe laat. Wel was het mogelijk een een Facebook (messenger) bot or whatsapp bot te maken. Met deze bevindingen gingen we naar Vasilis en in ons coachgesprek legden we uit dat dit idee dus in ieder geval niet door kon gaan. Ook hadden we onderzoek gedaan naar eventueel een eigen chatbot maken, maar dit zou teveel met AI werken en dat ligt buiten onze expertise en sluit ook niet aan op wat we tijdens de minor geleerd hebben. Vasilis kwam toen met het idee om Yuri een workshop aan ons te laten geven. Yuri is docent bij de minor visual en thema semester emerging technologies. Hierin zou hij Flow AI aan ons uitleggen. Daarmee konden wij dan weer een eigen chatbot maken en in onze eigen omgeving te zetten. Yuri had ook bedacht om de chatbot een doel te geven, namelijk het maken van een liedje met TWY. Na een tijdje gewerkt te hebben met Flow AI kwamen we erachter dat het lastig was om hier packages aan toe te voegen en aanpassingen te maken. Dat wilden wij graag voor het concept wat wij bedacht hadden.

Na de onderzoek -en concept fasen zijn we zelf gaan bouwen en hebben we een aantal dingen laten vallen. Zo zijn we wel een chatbot gaan maken maar dan zonder Artificial Intelligence. De antwoorden van TWY zijn dus statisch maar wel te bewerken. Maar de hoofd functionaliteit is dat door te praten met TWY je je eigen unieke muziek kunt maken. Er zijn een aantal spraak commando’s die je kunt zeggen tegen TWY. Je kunt beginnen met een beat maken, die kan je langzamer of sneller later af laten spelen. Dan kun je er een melodie aan toevoegen, die kan ook langzamer of sneller en als laatste kun je nog een effect over de melodie heen zetten. Elke keer als je de pagina herlaad is de combinatie van de de beat en de melodie uniek. Er wordt een random drum patroon en samples ingeladen en de melodie is ook elke keer random. Voor nu is het effect dat over de melodie gezet kan worden elke keer hetzelfde.

De chatbot is helemaal met je stem te bedienen. Er komen geen invoervelden aan bod. Door op de microfoon knop te drukken activeer je de microfoon van je computer of telefoon. Als je begint te praten verschijnen de woorden op het scherm. De code kijkt naar welk keyword wordt gezegd en aan de hand daarvan stuurt TWY een berichtje terug en word de muziek afgespeeld. Je kunt de muziek altijd pauzeren door op de pauzeknop te drukken. Die verandert weer in een playbutton zodra je er op klikt. Je kunt na het pauzeren ook weer verder luisteren. Als alle lagen van de muziek zijn toegevoegd en er geen commando’s meer over zijn verschijnt na de effect input een berichtje met een button om te pagina de herladen en het process opnieuw te beginnen.

De applicatie werkt volledig op desktop, mobiel en alles daar tussen in.

desktop

Uitleg van de code

Speech recognition

Sergio heeft al eens eerder gewerkt met speech recognition gewerkt voor het vak human centered design. Merlijn en Inju hebben daar nog kleine testjes op gedaan. De code er voor is heel erg simpel naar onze verbazing. Speech recognition is een deel van de Web Speech API, dit zit dus gewoon al standaard in JavaScript, wat het makkelijker maakt om het te implementeren.

Er zijn functies die kijken wanneer je wat inspreekt, wanneer je klaar bent met inspreken, wanneer de recognition een result heeft gevonden en wanneer er geen match is. Ook kan je spelen met andere talen, en dat scheelt, want wij hebben dit project nu in het Nederlands gemaakt, maar dat kan ook makkelijk naar het Engels veranderd worden.

De basis code om Speech Recognition te laten werken, is eigenlijk heel makkelijk, omdat het dus al een JavaScript API is

var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
var recognition = new SpeechRecognition()
recognition.interimResults = true

Je kan de functies op bepaalde manieren schrijven, en om als voorbeeld te nemen om te zien of er een resultaat is, kun je in ieder geval deze 2 opties gebruiken:

Optie 1

recognition.onresult = function(event) { ... }

Optie 2`

recognition.addEventListener('result', (e) => { ... }

Speech recognition zou niks zijn als we niet de woorden konden uitlezen. Daarom komt er nog flink wat logica aan te pas om de verschillende stem commando's te kunnen herkennen.

Zodra je op de speech knop klikt worden er verschillende events afgevuurd.

let userMeta = document.createElement('p')
    userMeta.classList.add('text_meta')
    userMeta.innerText = 'You'

Eerst word er een element in de chat toegevoegd die laat zien wat je aan het zeggen bent.

recognition.addEventListener('result', (e) => {
    text = Array.from(e.results)
        .map(result => result[0])
        .map(result => result.transcript)
        .join('')

    userResponse.innerText = text
    newMessage.appendChild(userResponse)

    if (e.results[0].isFinal) {
        userResponse = document.createElement('p')
    }

    chatBox.scrollTop = chatBox.scrollHeight
})

Recognition krijgt een eventlistener die wat je zegt omzet in tekst en het plaatst in het berichtje was zojuist aan de chat is toegevoegd. Wanneer de gebruiker is gestopt met praten word een functie uitgevoerd die herkent wat er gezegd wordt. De tekst wordt dan eerst omzet naar lowercase letters. We hebben verschillende array's met termen waar de code op reageert. Die hebben we opgedeeld in vijf categorieën. Termen voor de beat, melodie, sneller, langzamer en effect. Dit hebben we gedaan omdat het zo makkelijker is om meer termen toe toevoegen als Joost en Max dat zouden willen.

const beat = ["beat", "doe maar wat", "random", "ritme", "slagwerk", "trommel", "kick", "kik", "snare", "hihat", "basedrum", "tom", "crash", "ride"]
const melody = ["melodie", "melody", "lied", "deun", "loop", "tune", "wijs", "muziekstuk", "riedel", "klank", "compositie"]
const slower = ["langzaam", "sloom", "slomer", "slow", "traag", "trager", "treuzelend", "zacht", "kalm", "rustig", "lui", "geleidelijk", "saai"]
const faster = ["fast", "speed", "snel", "hard", "rap", "vlug", "vlot", "gauw", "subiet"]
const effect = ["effect", "effekt", "transitie", "trans", "gevolg", "lijp"]

const checkIfExists = beat.concat(melody, slower, faster, effect)

Van de vijf arrays maken we er eerst één. Dat is om te checken of wat je zegt überhaupt voorkomt in één van de array's.

const existsToString = checkIfExists.toString()

if (speechToText.search(existsToString)) { ... }

De speechToText is in dit geval de lowercase tekst. existsToString is alle vijf de array's maar dan omgezet in string. Dit is nodig omdat de functie search() alleen string accepteerd. search() kijkt in de string of er iets in zit dat gelijk is aan de speechToText. Als dat zo is zijn er vijf filter functions die weer gaan kijken met welke term het ingesproken bericht overeenkomt binnen de welke van de vijf array's/ categorieën.

beat.filter((item) => {
            if (speechToText.includes(item)) {
                const reply = 'Wat vind je van deze sicke beat? Moet het tempo aangepast nog aangepast denk je? Anders gaan we verder met een melodie'
                twyResponseMessage(reply)

                setTimeout(() => {
                    setupBeat(underEight, underThirteen, underFive, randomKick, randomSnare, bpm)
                }, 1500)
            }
        })

Hier boven is de eerste filter functie te zien. Dus als er bijvoorbeeld beat word gezegd word er een berichtje terug gestuurd door TWY. twyResponseMessage() is een functie die we bij elke reactie gebruiken, hier in gebeurd het aanmaken van HTML elementen in het appenden er van de chat. Eerst verschijnt dat berichtje en na 1,5 seconden begint dan de beat te spelen. Dit proces is bij elk array bijna het zelfde alleen word er dus een ander berichtje terug gestuurd en de muziek past zich aan.

Om te voorkomen dat je gelijk ook de melodie krijgt als je de muziek sneller wilt laten spelen hebben we een boolean toegevoed die kijkt of de melodie al eens een keer is afgespeeld en als dat zo is word ook pas de melodie afgespeelt.

if (melodyPlayed == true) {
  setupMelody(randomMelody, bpm)
}

Blip.

Blip is een simpele library die we gebruiken om audio samples in te laden en om de drum loops mee te maken. Blip gebruiken we binnen een functie die wordt aangeroepen zodra een keyword wordt herkent in de speech recognition en de muziek dus begint met spelen.

blip.sampleLoader()
        .samples({
            'kick': `./sounds/kick.wav`,
        })
        .done(loaded)
        .load();

Eerst worden de samples ingeladen, in dit geval zijn dat .wav bestanden. Blip laadt de bestanden asynchroon in. Als die zijn ingeladen wordt de functie loaded() aangeroepen. Dit is eigenlijk de plek waar je iets kunt doen met de samples. Eerst moeten ze in een variabele gestopt worden. Daarna maken we er een loop mee. Binnen de loop zijn er drie aanpasbare functietjes. De eerste is het tempo of de snelheid van de loop, dit is een waarde die word uitgedrukt in beats per minute (bpm). Dan is er data. Hierin heef je aan op welk moment het sample moet worden afgespeeld. Dit kan een array zijn met 1 en 0 waarden waarbij 1 betekend dat het sample dan moet af spelen. In ons geval halen we deze uit de global scope omdat ze daar éénmalig random worden gegenereerd. Als als laatste is er de tick, hierin zeg je dus welk sample moet afspelen en worden het tempo en de data gebundeld tot een functionerende loop.

Met de functies .start() en .stop() kunnen we de loop starten en stoppen. In het voorbeeld hebben we ook nog loops voor de snaredrum en hihat die op he zelfde moment worden afgespeeld zodat het lijkt alsof het één beat is.

function loaded() {
  var kick = blip.clip().sample('kick')

  kickBeat = blip.loop()
     .tempo(beatsPerMinute)
     .data(randomKickPattern)
     .tick(function (t, d) {
       if (d) {
          kick.play(t)
               }
       })

kickBeat.start()

kickBeat.stop()
}

Zoals gezegd worden de drumpatterns en de bpm uit de globalscope meegegeven aan deze functie meegegeven om de logica op te splitsen maar ook om te zorgen dat de code die de patterns ranomized maar één keer word uitgevoerd. Ook word de bpm aangepast in de globalscope omdat je die moet kunnen veranderen met een stem commando.

Tone.js

Tone.js is een grotere library waarmee je muziek kunt maken. Tone.js bied vele mogelijkheden maar wij gebruiken het alleen om melodietjes mee af te spelen en om effecten toe te voegen aan die melodieën.

let synth = new Tone.Synth({
        oscillator: {
            type: "fatsawtooth",
            volume: -10
        },
        envelope: {
            attack: 0.05,
            decay: 0.5,
            sustain: 0.5,
            release: 1
        }
    }).toDestination();

Alle logica van het maken van de melodie staat ook in een aparte functie en module. Om te beginnen maken we een instrument aan of te wel een synth. Die geven we bepaalde properties mee. Welke vorm het geluid moet hebben fatsawtooth in dit geval. Dat kan ook zijn sine, cubic of sawtooth. Dan geven we het volume mee. Nu staat die op -10 omdat het anders veel te hard staat. Daar weer onder defineren we wat properties over de tonen. Eigenlijk staat daar hoe lang een toon te horen is. Attack betekend de fade in en release betekend de fade out. En als dat allemaal gedefinieerd is word de synth naar de destination gestuurd, de destination is eigenlijk het output device.

sequenceOne = new Tone.Sequence(function (time, note) {
        synth.triggerAttackRelease(note, 0.5);
        console.log(note);
    }, melody, '4n')

Dan maken we een sequence aan die er voor zorgt dat we een melodie kunnen afspelen met de synth die we al eerder hebben aangemaakt. Eerst zetten we de synth in de secquence. Dan geven we de melodie mee die uit de globalscope komt en zeggen we dat de maat 4n is. Dit betekend een vierkwarts maat.

Tone.Transport.bpm.value = beatsPerMinute
    Tone.Transport.start()
    sequenceOne.start()

Om de melodie daadwerkelijk te horen in de browser moeten we hem starten. Dat gebeurt door de Tone te starten samen met de sequence. Boven aan heven we ook nog de bpm mee die het zelfde is als de beat van Blip. zodat ze samen lopen.

Complexe stylings onderdelen

Augmented UI

Joost dacht dat de style van Cyberpunk wel bij TWY zou passen. Hierop hebben wij schetsen voor gemaakt, en bedacht hoe we dit zouden implementeren. Met wat zoeken, zijn we de npm package augmented-ui tegen gekomen. Dit zorgt ervoor dat we makkelijker cyberpunk en sci-fi achtige ui elementen kunnen toevoegen.

In de HTML voeg je aan het element waar je de styling op wilt, een nieuwe attribute toe. Hierin zet je je variables, die kan samen stellen op augmented-ui. Je kan er zelf voor kiezen of je een border, inlay of niks wilt. Deze code ziet er bijvoorbeeld als volgt uit:

<div data-augmented-ui="tl-clip-y t-clip-x tr-2-clip-x border"></div>

Hier heeft het element dus een border en komt het element er als volgt uit te zien:

buttons

Om dit te customizen, kan je een aantal CSS properties toevoegen. Dit element maakt gebruik van:

--aug-border-bg: white;

Dit maakt de border wit.

Animaties

Zodra er muziek wordt afgespeeld, worden er lichtjes geanimeerd. Dit gebeurd door een simpele CSS animatie, die wordt toegevoegd wanneer er muziek wordt afgespeeld.

@keyframes rainbow {
    0% {
        box-shadow: inset 0 -60px 60px -30px red;
    }
    12.5% {
        box-shadow: inset 0 -80px 60px -30px violet;
    }
    25% {
        box-shadow: inset 0 -40px 60px -30px indigo;
    }
    37.5% {
        box-shadow: inset 0 -80px 60px -30px blue;
    }
    50% {
        box-shadow: inset 0 -60px 60px -30px green;
    }
    62.5% {
        box-shadow: inset 0 -80px 60px -30px lightgreen;
    }
    75% {
        box-shadow: inset 0 -40px 60px -30px yellow;
    }
    87.5% {
        box-shadow: inset 0 -80px 60px -30px orange;
    }
    100% {
        box-shadow: inset 0 -60px 60px -30px red;
    }

De animatie wordt door middel van JavaScript toegevoegd, omdat het pas moet gebeuren als er muziek wordt afgespeeld. De animatie veranderd de inset box-shadow van kleur en de positie, zodat het als disco ligt er uit komt te zien.

.default {
    animation-iteration-count: infinite;
    animation-name: rainbow;
    animation-duration: 3s;
}

Onderzoek

📝 Debrief

🚀 Design Rationale

Research

💡 Leerdoelen

🤖 Onderzoek Chatbots

Design Keuzes

🍼 Backstory TWY

🧮 Figma Schermen

Individuele PB

🌈 Product Biografie Inju

🍕 Product Biografie Merlijn

🦄 Product Biografie Sergio

Zelf reflectie

🤯 Reflectie Inju

🤯 Reflectie Merlijn

🤯 Reflectie Sergio

Clone this wiki locally