Browse Source

A whole bunch + CV

main
parent
commit
71bfb73b7d
17 changed files with 339 additions and 27 deletions
  1. +48
    -26
      builder.js
  2. +1
    -1
      push.sh
  3. +104
    -0
      sources/markdown/out/cv.md
  4. +49
    -0
      sources/markdown/out/tutorials/better-music-loops.md
  5. BIN
      sources/public/FiraCode-Light.eot
  6. BIN
      sources/public/FiraCode-Light.ttf
  7. BIN
      sources/public/FiraCode-Light.woff
  8. BIN
      sources/public/FiraCode-Light.woff2
  9. BIN
      sources/public/Roboto-Bold.ttf
  10. BIN
      sources/public/Roboto-Light.ttf
  11. +105
    -0
      sources/public/cv-style.css
  12. BIN
      sources/public/fira-sans-latin-ext.woff2
  13. BIN
      sources/public/fira-sans-latin.woff2
  14. BIN
      sources/public/tutorials/furious-skies.mp3
  15. BIN
      sources/public/tutorials/furious-skies.ogg
  16. BIN
      sources/public/tutorials/renoise-sections.png
  17. +32
    -0
      sources/pug/cvlayout.pug

+ 48
- 26
builder.js View File

@@ -111,42 +111,52 @@ function getFirstImageURL(mdstr){
return first;
}

function splitYAML(cwd, config){
function yamlGrabber(cwd, config){
return through.obj(function(file, enc, next){
var strings = file.contents.toString('utf8').split(/(?=%YAML)/);
var split = splitYAML(file.contents, file.path, config);
var base = path.join(file.path, '..');
if (strings.length>0){
if (split.markdown.length>0){
var markdown = new vinyl({
cwd: cwd,
path: path.join(base,path.basename(file.path)),
contents: Buffer.from(strings[0])
})
path: path.join(base,path.basename(file.path)),
contents: Buffer.from(split.markdown)
});
this.push(markdown)
}
if (strings.length>1){
var data = yaml.safeLoad(strings[1])
var mdpath = file.path;
data.preview = data.hasOwnProperty("preview") ? data.preview : getFirstParagraph(strings[0]);
data.content = getAbsoluteHTML(strings[0], config);
data.imageURL = data.hasOwnProperty("imageURL") ? data.imageURL : getFirstImageURL(strings[0]);
data.updated = data.hasOwnProperty("updated") ? data.updated : new Date(child_process.execSync('git log -1 --pretty="format:%ci" '+mdpath));
data.updated = isNaN(data.updated) ? new Date() : data.updated;
data.created = data.hasOwnProperty("created") ? data.created : new Date(child_process.execSync('git log --pretty="format:%ci" '+mdpath+' | tail -1'))
data.created = isNaN(data.created) ? data.updated : data.created;
data.status = data.hasOwnProperty("status") ? data.status : "published";
data.url = "/"+path.relative(path.join(config.root,config.markdown),file.path).replace(".md","");
if (split.data){
var yamlob = new vinyl({
cwd: cwd,
path: path.join(base,path.basename(file.path,".md")+".yaml"),
contents: Buffer.from(yaml.safeDump(data))
})
this.push(yamlob)
path: path.join(base,path.basename(file.path,".md")+".yaml"),
contents: Buffer.from(yaml.safeDump(split.data))
})
this.push(yamlob);
}
next();
})
}


function splitYAML(contents, mdpath, config){
var strings = contents.toString('utf8').split(/(?=%YAML)/);
var markdown = strings.length>0 ? strings[0] : "";
var data = false;
if (strings.length>1){
var data = yaml.safeLoad(strings[1])
data.preview = data.hasOwnProperty("preview") ? data.preview : getFirstParagraph(strings[0]);
data.content = getAbsoluteHTML(strings[0], config);
data.imageURL = data.hasOwnProperty("imageURL") ? data.imageURL : getFirstImageURL(strings[0]);
data.updated = data.hasOwnProperty("updated") ? data.updated : new Date(child_process.execSync('git log -1 --pretty="format:%ci" '+mdpath));
data.updated = isNaN(data.updated) ? new Date() : data.updated;
data.created = data.hasOwnProperty("created") ? data.created : new Date(child_process.execSync('git log --pretty="format:%ci" '+mdpath+' | tail -1'))
data.created = isNaN(data.created) ? data.updated : data.created;
data.status = data.hasOwnProperty("status") ? data.status : "published";
data.layout = data.hasOwnProperty("layout") ? data.layout : config.pugLayout;
data.url = "/"+path.relative(path.join(config.root,config.markdown),mdpath).replace(".md","");
}
return {
markdown: markdown,
data: data
}
}

var builder = function(config){
function getDefaultTitle(file, ext){
@@ -171,7 +181,17 @@ var builder = function(config){
.pipe(errorHandler())
.pipe(modify({
fileModifier: function(file, contents){
var pugLayout = fs.readFileSync(path.join(config.root, config.pugLayout),'utf8');
var layout = config.pugLayout;
var dir = path.dirname(file.path);
var filename = path.basename(file.path,".md");
var yamlPath = path.join(dir,filename+".yaml");
if (fs.existsSync(yamlPath)){
var data = yaml.safeLoad(fs.readFileSync(yamlPath, 'utf8'));
if (data.hasOwnProperty("layout")){
layout = data.layout;
}
}
var pugLayout = fs.readFileSync(path.join(config.root, layout),'utf8');
return pugLayout
.replace(/\[\[markdown\]\]/g, path.basename(file.path));
}
@@ -282,7 +302,7 @@ var builder = function(config){
try {
return gulp.src(path.join(config.root, config.markdown+config.markglob))
.pipe(errorHandler())
.pipe(splitYAML(path.join(config.root, "markdown", "out"), config))
.pipe(yamlGrabber(path.join(config.root, "markdown", "out"), config))
.pipe(gulp.dest(config.temp))
} catch (e) {
console.error(e)
@@ -309,7 +329,8 @@ var builder = function(config){
var tags = data.tags;
var preview = data.preview;
var content = data.content;
var imageURL = data.imageURL
var imageURL = data.imageURL;
var layout = data.layout;
if (data.hasOwnProperty("tags")){
data.tags.unshift("all")
for (var i = 0; i < data.tags.length; i++) {
@@ -327,6 +348,7 @@ var builder = function(config){
preview: preview,
content: content,
imageURL: imageURL,
layout: layout,
tags: tags
});
memo[tag] = memo[tag].sort((a,b)=>{


+ 1
- 1
push.sh View File

@@ -1 +1 @@
rsync -avrh ./build gaeel@spaceshipsin.space:/www/spaceships --delete
rsync -avrh ./build/* gaeel@spaceshipsin.space:www --delete

+ 104
- 0
sources/markdown/out/cv.md View File

@@ -0,0 +1,104 @@
# Kevin “Gaeel” Bradshaw-Rodriguez

### <center>**`<--code—design—noise-->`**</center>

## Contact
* 📞 +33 6 18 95 96 38
* 🖥 [gaeel@spaceshipsin.space](mailto:gaeel@spaceshipsin.space)
* 🌍 [spaceshipsin.space](http://spaceshipsin.space)


## Experience
### *2016 -> Ongoing* · Teacher · [**E-Artsup**](https://www.e-artsup.net/ecole-graphisme-design-infographie-lille.aspx)

*Game design and digital art school*
Unity3D-based courses
Gameplay programming and introduction to programming
Applied game design & game tooling
Year-long courses & workshops, with mobile, PC/Console and VR prototypes throughout the year


### *2015 -> 2016* · Programme Manager · [**Supinfogame Rubika**](https://rubika-edu.com/)

*Game design and management school*
Creation of a learning programme, recruitment of teachers, coaching and advice on academic projects

### *2015 -> 2016* · Developer · [**Ys Interactive**](http://studioysinteractive.com/)

*Video game studio*
Creation of a prototype for a video game adaptation of the Blacksad visual novels
Story, cutscene, investigation and dialogue systems and mechanics
Story editing and asset integration tools
Custom shader set for a watercolour effect
The game was released in November 2019 as [Blacksad: Under the Skin](https://www.mobygames.com/game/windows/blacksad-under-the-skin)

### *2014 -> 2016* · Teacher · [**Supinfogame Rubika**](https://rubika-edu.com/)

*Game design and management school*
Gameplay programming and introduction to programming
Students ranging from 1st to 4th year
Game programming patterns, Unity3D, software architecture, Javascript
Workshop coach and jury member

### *2014 -> 2015* · Developer · [**NaturalPad**](http://www.naturalpad.fr/en/)

*Motion-based games built in collaboration with physical therapists to assist in functional rehabilitation*
Web service configuration and software development
Prototypes and games intended for physical therapy and/or use in a hospital setting:
**Zether**, **Plume**, along with a dozen other abandoned, undeveloped or rejected prototypes
**Plume** is used in physical therapy sessions, a [gameplay video is available on Youtube](https://www.youtube.com/watch?v=hfr0D9UwcJg)
**Zether** later went through reskinning and redesigns, and was also [released in November 2018 on Steam](https://store.steampowered.com/app/924830/Zether/) for non-medical play

### *2012 -> 2015* · Co-Founder / Secretary · **Baptême du Jeu**
*A society for uniting and celebrating small local game creators and digital artists*
Organisation and management of video game events: Game Jams, Parties, Expositions
Documentation, coaching and communication for events, technology and general information in the field of video game development

## Projects

### *2020* · VFX Developer · **Sadhana** (ARTE France & La Générale De Production)

*A narrative mobile game inspired by indian mythology*
Created fire and flare effects that are used throughout the game to indicate progression to the player, and add flourishes to the hand-animated characters and environments
Optimised elements for better performance on lower spec devices




### *2018 -> Ongoing* · Activist SysAdmin · stream.void.garden & [tilde.rocks](https://tilde.rocks)

*An attempt to put some ideas about community and internet freedom into practice*
Creation and administration of a micro-scale social network and shared computing
Experiments to provide online & decentralised tools and spaces for micro-communities

### *2017* · Developer · [**Paper Sail**](https://papersail.lab.arte.tv/) (Ex Nihilo, NFB & ARTE)

*Mobile WebGL multiplayer mini-game for the Very Very Short project by ARTE*
*Made in collaboration with Cosmografik*
*The player explores a shared world in a boat, they can meet other player's boats, and interact with the magical fauna and flora of a village pond*
Built a set of procedural generators, for the world and fauna animations
Built a drop-in, drop-out multiplayer system

### *2016 -> 2017* · Developer · [**Zero Impunity**](https://zeroimpunity.com/?lang=en) (a_BAHN)
*Virtual street march connected to a set of Change.org petitions*
*Each signature on the petitions becomes represented by a marcher who joins an ever-growing crowd*
Built an API wrapper to enable secure and quick access to Change.org data
Built a Unity WebGL app that displays the results of the petitions as a large crowd of petitioners, each with a semi-random appearance and available to be interacted with to display the message the corresponding petitioner posted to Change.org

### *2014* · Developer · [**Aïko Virtual Visit**](https://aiko-creative.fr/realite-virtuelle/vr-immobilier.p15) (Aïko Creative)

*A virtual reality guided tour powered by the Oculus Rift DK1*
Created a VR navigation system and tools for importing and optimising architectural models in realtime VR

### *2014* · Developer · **AXA Blocks** (Assurances AXA)

*Large screen sliding block puzzle game for trade shows and other promotional events*
Ported game code the company had purchased to work on the large touch screen technology they intended to use, and created tools for the art team to integrate new assets



%YAML 1.2
---
layout: pug/cvlayout.pug
title: CV
status: unpublished

+ 49
- 0
sources/markdown/out/tutorials/better-music-loops.md View File

@@ -0,0 +1,49 @@
# Better music loops in video games (easy mode)

This tutorial will feature screenshots, code, and methods specific to [Renoise](https://www.renoise.com/) and [Unity](https://unity.com/), but the overall technique should be applicable for any audio software and game engine pairing.
At the end of the article, there will be some notes for things you need to look out for if you're using other software.
## What we're making today

Because the player is usually in control of what happens moment to moment in a video game, any music used in the game will typically be set to loop throughout any given scene. This often leads to a few awkward compromises.
Music usually features intro and outro segments, ramping the intensity and mood to and from the main parts, and when a piece of music has several main parts, there are sections between to flow gracefully from one to the next. But if we want to use simple looping sound files, we're very limited with how we deal with this.
When the player moves from one scene to the next, we'll often just fade out whatever music was playing, and then fade in the next music. If we're feeling particularly fancy, we'll sync both of the loops up, but it still sounds a bit off. There's no "in between" part, we're just crossfading between two moods.
One solution to this is to hire a very smart programmer who will work real tight with the composer, and they'll put together some dynamic music system that will make for an awesome GDC talk.
Another is to write a single piece of music that flows from section to section, and then use just a handful of lines of code to continue looping one chunk of the sound file until we want to transition to the next.
This is what we're making today.


## First, let's make the music

I used [Renoise](https://www.renoise.com/) to make a track for an arcadey action game. Here's how the music should behave during the game:
* When the player is in the menu, loop a moody bassline
* When the game is launched, play a buildup, then loop the first section
* When the player reaches the "hard bit", move to the second, more intense section
* And finally, when the player beats the game, transition back to a moody bit and loop that
Here's what this sequence looks like in Renoise
![Screenshot of Renoise's "Pattern Sequence Matrix" with markers for the various parts](/tutorials/renoise-sections.700w.jpg)

And here's the full track
<audio controls style="width: 100%;">
<source src="/tutorials/furious-skies.ogg" type="audio/ogg">
<source src="/tutorials/furious-skies.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>

I've tagged the various parts, and to keep things simple for myself, all of the "chunks" are the same length.
In all, we have 14 chunks, and as the game rolls on, we're going to be looping the following chunks:
* Intro: loop chunk 0
* Part one: loop chunks 3 & 4
* Part two: loop chunks 7 & 8
* Outro: loop chunks 11 & 12

In between the chunks there are the bits we need to make the transitions sound good, like a drum flurry before each drop, and a little choppy effet when we remove the "reese" synth for the outro.






BIN
sources/public/FiraCode-Light.eot View File


BIN
sources/public/FiraCode-Light.ttf View File


BIN
sources/public/FiraCode-Light.woff View File


BIN
sources/public/FiraCode-Light.woff2 View File


BIN
sources/public/Roboto-Bold.ttf View File


BIN
sources/public/Roboto-Light.ttf View File


+ 105
- 0
sources/public/cv-style.css View File

@@ -0,0 +1,105 @@
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 300;
src: local('Roboto Light'), local('Roboto-Light'), url('/Roboto-Light.ttf') format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'), url('/Roboto-Bold.ttf') format('truetype');
}
@font-face{
font-family: 'Fira Code';
src: url('/FiraCode-Light.eot');
src: url('/FiraCode-Light.eot') format('embedded-opentype'),
url('/FiraCode-Light.woff2') format('woff2'),
url('/FiraCode-Light.woff') format('woff'),
url('/FiraCode-Light.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}

@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 400;
src: local('Fira Sans Regular'), local('FiraSans-Regular'), url(/fira-sans-latin-ext.woff2) format('woff2');
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
font-family: 'Fira Sans';
font-style: normal;
font-weight: 400;
src: local('Fira Sans Regular'), local('FiraSans-Regular'), url(/fira-sans-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

body {
font-family: "fira sans";
background-color: #223;
color: #DDD;
background-color: #112;
}
a {
color: #DDD;
}
a:active {
color: #DDD;
}
a:selected {
color: #DDD;
}
#menu {
cursor: default;
}
#curriculum-vitae {
background-color: #334;
margin: 0 auto;
padding: 20px;
max-width: 600pt;
}

h1, h2, h3, h4, h5, .code {
color: #FFF;
font-family: "fira code";
font-weight: 100;
}
h1, h2 {
text-align: center;
}

em {
font-style: unset;
color: #E64;
}

p > em {
font-style: unset;
color: #AAE7FF;
}

strong {
font-style: unset;
color: #3EB;
}

code {
font-family: unset;
}

li > strong {
font-family: "Roboto";
font-weight: 700;
color: #DDD;
}
li > em {
font-family: "Roboto";
font-weight: 300;
color: #DDD;
}
#switchlang {
cursor: pointer;
}

BIN
sources/public/fira-sans-latin-ext.woff2 View File


BIN
sources/public/fira-sans-latin.woff2 View File


BIN
sources/public/tutorials/furious-skies.mp3 View File


BIN
sources/public/tutorials/furious-skies.ogg View File


BIN
sources/public/tutorials/renoise-sections.png View File

Before After
Width: 714  |  Height: 1238  |  Size: 176 KiB

+ 32
- 0
sources/pug/cvlayout.pug View File

@@ -0,0 +1,32 @@
include /utils
doctype html
if (created)
- var datesEqual = new Date(updated).getTime() == new Date(created).getTime();
html(lang="en")
head
title Gaeel Bradshaw's CV
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible" content="IE=edge")
meta(name="viewport" content="width=device-width, initial-scale=1")
link(rel='stylesheet', href='/cv-style.css')
meta(name="description" content="Gaeel's CV")
if (created)
// OpenGraph & Twitter tags
meta(property="og:site_name" content="Spaceships in Space")
meta(property="og:title" content=title)
meta(property="og:url" content="https://spaceshipsin.space"+url)
meta(property="og:description" content=preview)
meta(property="og:image" content="https://spaceshipsin.space"+imageURL)
meta(property="og:type" content="article")
meta(property="article:published_time" content=created)
unless datesEqual
meta(property="article:modified_time" content=updated)
if tags
each tag in tags
meta(property="article:tag" content=tag)
meta(property="twitter:card" content="summary")
meta(property="twitter:site" content="@_gaeel_")
meta(property="twitter:creator" content="@_gaeel_")
body
#curriculum-vitae
include:marked [[markdown]]

Loading…
Cancel
Save