Composing music with
composed functions

Adam Giese (@AdamGieseDev)

Intro to FP

What is functional programming?

Programming with functions!
Intro to FP

What is a function?

A sequence of commandsabstracted into a reusable unit.
A relationship between aset of inputs and outputs.
Intro to FP

What makes a function pure?

1. Given the same arguments, it will return the same value.
2. No side effects.

This makes a function "referentially transparent."
Intro to FP
const middleC = {
name: 'C',
octave: 4,
};
const incrementOctave = note => ({...note, octave: note.octave + 1});
const highC = incrementOctave(middleC);
play(middleC);
play(highC);
play(incrementOctave(middleC));

Define a musical note with name and octave

Intro to FP
Intro to FP
Learnfunctionalprogramming
Intro to FP
LearnfunctionalprogrammingRemoveallside effects
Intro to FP
LearnfunctionalprogrammingRemoveallside effectsProgramisuselessProgramisuseless
Intro to FP

Side effects are NOT bad. They include:

  • Making an HTTP request
  • DOM manipulation
  • Printing to the console
  • Playing music
Tools of FP

Tools of functional programming

Tools of FP

Immutability

definition: a variable whose state cannot be modified after creation
Tools of FP
Tools of FP

Advantages of Immutability

  • Less for your brain to track
  • Prevents unintentional side effects
  • Preserves state
Tools of FP

Higher order functions

definition: a function that either accepts a function
as an argument or returns a function
Tools of FP
Yo dawg I heard you like functions so I made a functionthat takes a function and returns a function
Tools of FP
import Tone from 'tone';
const synth = new Tone.Synth().toMaster()
const playNote = note => () => synth.triggerAttackRelease(note, '2n');
const playMiddleC = playNote('C4');
const button = getElementById('middleCTrigger');
button.addEventListener('click', playMiddleC);

 

Tools of FP

Dealing with arrays

Tools of FP

Array.filter

  • Accepts a function run again every element
  • fn(element, index, array)
  • Returns a subset of the original array
Tools of FP
import Tone from 'tone';
const melody = [
'C4', 'D4', 'Eb4', 'G3',
'F4', 'Ab3', 'Eb4', 'D4', 'C4'
];
const freq = note => Tone.Frequency(note).toFrequency();
const isHigh = note => freq(note) >= freq('C4');
const highNotes = melody.filter(isHigh);
// ["C4", "D4", "Eb4", "F4", "Eb4", "D4", "C4"]

 

Tools of FP
Tools of FP

Array.map

  • Accepts a function run again every element
  • fn(element, index, array)
  • Returns array of same length with new values
Tools of FP
const notes = [
{ name: 'C', octave: 4 },
{ name: 'D', octave: 4 },
{ name: 'E', octave: 4 },
{ name: 'G', octave: 3 },
];
const incrementOctave = note => ({...note, octave: note.octave + 1});
const highNotes = notes.map(incrementOctave);
/*
{ name: 'C', octave: 5 },
{ name: 'D', octave: 5 },
{ name: 'E', octave: 5 },
{ name: 'G', octave: 4 },
*/

 

Tools of FP
Tools of FP

Array.reduce

  • Accepts a function run against every element
  • fn(accumulator, element, index, array)
  • Returns an 'accumulated' value
  • Also known as 'fold' and 'accumulate'
  • More generic than 'filter' and 'map'
Tools of FP
import Tone from 'tone';
const melody = [
{ name: 'D4', length: '16n' },
{ name: 'E4', length: '16n' },
{ name: 'F4', length: '16n' },
{ name: 'G4', length: '16n' },
{ name: 'A4', length: '16n' },
{ name: 'Bb4', length: '16n' },
{ name: 'C#4', length: '16n' },
{ name: 'Bb4', length: '16n' },
{ name: 'A4', length: '16n' },
{ name: 'G4', length: '16n' },
{ name: 'F4', length: '16n' },
{ name: 'E4', length: '16n' },
{ name: 'F4', length: '16n' },
];
const toLength = (total, note) =>
total + Tone.Time(note.length).toSeconds();
const length = melody.reduce(toLength, 0);
// 1.625

 

Tools of FP
0.000
Tools of FP

Partial Application

definition: a technique to "pre-set" or "fix"
some arguments of a function
Tools of FP
import transpose from 'fictional-library';
// (dir, interval, note) => newNote
const upMajorThird = note => transpose('up', '3M', note);
upMajorThird({note: 'C', octave: 4}); // {note: 'E', octave: 4}
upMajorThird({note: 'Bb', octave: 2}); // {note: 'D', octave: 3}
/* Stage 1 TC39 Native Partial Application Syntax */
const downMinorSeventh = transpose('down', '7m', ?);

 

Tools of FP
Tools of FP

Composition

definition: a technique to combine multiple functions into a new function
Tools of FP
import Tone from 'tone';
const freq = name =>
Tone.Frequency(name).toFrequency();
const multiply = a => b => a * b;
const detuneUp = multiply(1.01);
const compose = (a,b) => (...args) => a(b(...args));
const getDetunedFrequency =
compose(detuneUp, freq);
/* Stage 1 TC39 Native Pipeline operator Syntax */
const getDetunedFrequency = note => note
|> freq
|> detuneUp;

Here, we will take a note name and return a slightly detuned frequency.

Tools of FP
Putting it Together

Putting it all together

Writing a function that converts a human readable shorthand into a computer readable array.

Putting it Together
import {
pipe,
split,
map,
match,
head,
applySpec,
join,
addIndex,
last,
add,
filter,
} from 'ramda';
import Tone from 'tone';
const { Time } = Tone;
const parseName = pipe(
match(/^[A-G](b|#)?/),
head,
);
const parseOctave = pipe(
match(/\d*(?=@)/),
Number,
);
const parseDuration = pipe(
split('@'),
last,
);
const parseTime = (note, index, notes) => notes
.slice(0,index)
.map(pipe(parseDuration, Time))
.reduce(add, 0);
const arrayMap = addIndex(map);
const parseShorthand = pipe(
split(/[ ,]+/),
arrayMap(
applySpec({
name: parseName,
octave: parseOctave,
length: parseDuration,
time: parseTime,
})
),
);
export default parseShorthand;

 

Putting it Together
`C4@16n E4@16n G4@16n C5@16n E5@16n G4@16n C5@16n E5@16n`

This input string becomes...

Putting it Together
[ 
  "C4@16n", "E4@16n", "G4@16n", "C5@16n",
  "E5@16n", "G4@16n", "C5@16n", "E5@16n", 
]

...this array of strings, which becomes...

Putting it Together
[
  { name: "C", octave: 4, length: "16n", time: 0 },
  { name: "E", octave: 4, length: "16n", time: 0.125 },
  { name: "G", octave: 4, length: "16n", time: 0.25 },
  { name: "C", octave: 5, length: "16n", time: 0.375 },
  { name: "E", octave: 5, length: "16n", time: 0.5 },
  { name: "G", octave: 4, length: "16n", time: 0.625 },
  { name: "C", octave: 5, length: "16n", time: 0.75 },
  { name: "E", octave: 5, length: "16n", time: 0.875 }
]

...this array of note objects!

Putting it Together
 `C4@16n E4@16n G4@16n C5@16n E5@16n G4@16n C5@16n E5@16n 
  C4@16n E4@16n G4@16n C5@16n E5@16n G4@16n C5@16n E5@16n
  C4@16n D4@16n A4@16n D5@16n F5@16n A4@16n D5@16n F5@16n
  C4@16n D4@16n A4@16n D5@16n F5@16n A4@16n D5@16n F5@16n
  B3@16n D4@16n G4@16n D5@16n F5@16n G4@16n D5@16n F5@16n
  B3@16n D4@16n G4@16n D5@16n F5@16n G4@16n D5@16n F5@16n
  C4@16n E4@16n G4@16n C5@16n E5@16n G4@16n C5@16n E5@16n
  C4@16n E4@16n G4@16n C5@16n E5@16n G4@16n C5@16n E5@16n`;
Putting it Together
Putting it Together
 `E5@16n E5@8n E5@8n C5@16n E5@8n G5@4n G4@4n
  C5@8n. G4@8n. E4@8n. A4@8n B4@8n Bb4@16n A4@8n
  G4@8t E5@8t G5@8t A5@8n F5@16n G5@8n
  E5@8n C5@16n D5@16n B4@8n.
  C5@8n. G4@8n. E4@8n. A4@8n B4@8n Bb4@16n A4@8n
  G4@8t E5@8t G5@8t A5@8n F5@16n G5@8n
  E5@8n C5@16n D5@16n B4@8n.`;
Putting it Together
Wrapping Up

Why?

It forces you to break down problems into their smallest parts
Wrapping Up

Go further

  • "Professor Frisby's Mostly Adequate Guide to Functional Programming"
  • Functional utility library (Ramda or lodash-fp)
  • LambdaCast
Wrapping Up

Thank You!

Questions? Catch me afterwords or message me @AdamGieseDev

Slides available at: https://composed.adamgiese.com