Skip to content

Commit

Permalink
Clean up client, add more comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jacbz committed Jul 16, 2021
1 parent 8bae5f1 commit 8d51073
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 163 deletions.
7 changes: 4 additions & 3 deletions client/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,21 @@ export const octShift = (note: string, octaves: number) => {
export const octShiftAll = (notes: string[], octaves: number) =>
notes.map((n) => octShift(n, octaves));

/** e.g. 8 -> [0, 1] */
/** Maps a given note number to a scale degree and octave, e.g. 8 -> [0, 1] */
export const mapNote = (note: number) => {
const scaleDegreeIndex = (note - 1) % 7;
const octave = Math.floor((note - 1) / 7);
return [scaleDegreeIndex, octave];
};

/** e.g. in G major: */
/** Mounts given note numbers on a given scale */
export const mountNotesOnScale = (offsetScaleDegree: number, notes: number[], scale: string[]) =>
notes.map((n) => {
const [scaleDegreeIndex, octave] = mapNote(n + offsetScaleDegree - 1);
return octShift(scale[scaleDegreeIndex], octave);
});

/** 2 => 'C#' */
/** Converts a key number to string, e.g. 2 => 'C#' */
export const keyNumberToString = (key: number): string =>
Tonal.Scale.get('C chromatic').notes[key - 1];

Expand All @@ -62,6 +62,7 @@ export const addTime = (time1: Time, time2: Time) => {
return Tone.Time(time).toBarsBeatsSixteenths();
};

/** Substracts one Tone.js Time objects to another */
export const subtractTime = (time1: Time, time2: Time) => {
const time = Tone.Time(time1).toSeconds() - Tone.Time(time2).toSeconds();
return Tone.Time(time).toBarsBeatsSixteenths();
Expand Down
5 changes: 2 additions & 3 deletions client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Sortable from 'sortablejs';
import Player, { RepeatMode } from './player';
import Producer from './producer';
import { HIDDEN_SIZE, OutputParams } from './params';
import { LATENT_SPACE_DIMENSIONS, OutputParams } from './params';
import { decompress, randn } from './helper';
import { decode } from './api';

Expand Down Expand Up @@ -30,7 +30,6 @@ if (localStorageAvailable) {
}
}
}

const updateLocalStorage = () => {
if (localStorageAvailable) {
localStorage.setItem('playlist', JSON.stringify(player.playlist.map((t) => t.outputParams)));
Expand Down Expand Up @@ -67,7 +66,7 @@ if (playlistToLoad.length > 0) {
// Sliders
const slidersEl = document.getElementById('sliders');
const sliders: HTMLInputElement[] = [];
for (let i = 0; i < HIDDEN_SIZE; i += 1) {
for (let i = 0; i < LATENT_SPACE_DIMENSIONS; i += 1) {
const slider = document.createElement('input') as HTMLInputElement;
slider.type = 'range';
slider.min = '-4';
Expand Down
41 changes: 0 additions & 41 deletions client/src/instruments.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as Tone from 'tone';
import { FrequencyShifter } from 'tone';

export enum Instrument {
/** Salamander grand piano, velocity 6 */
Expand Down Expand Up @@ -240,40 +239,6 @@ export const DefaultFilters = [

export const getInstrumentFilters = (instrument: Instrument) => {
switch (instrument) {
case Instrument.Piano: {
return [
...DefaultFilters
// new Tone.Filter({
// type: 'lowpass',
// frequency: 3600,
// Q: 0.2
// }),
// new Tone.Filter({
// type: 'highpass',
// frequency: 700,
// Q: 0.2
// })
];
}

case Instrument.SoftPiano: {
return [
...DefaultFilters
];
}

case Instrument.ElectricPiano: {
return [
...DefaultFilters
];
}

case Instrument.AcousticGuitar: {
return [
...DefaultFilters
];
}

case Instrument.ElectricGuitar: {
return [
...DefaultFilters,
Expand All @@ -296,12 +261,6 @@ export const getInstrumentFilters = (instrument: Instrument) => {
];
}

case Instrument.Synth: {
return [
...DefaultFilters
];
}

default:
return [
...DefaultFilters
Expand Down
20 changes: 1 addition & 19 deletions client/src/params.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,4 @@
import { randomFromInterval } from './helper';

export const getRandomOutputParams = () => {
const params: OutputParams = {
title: null,
// key: 8,
key: randomFromInterval(1, 12),
mode: 6,
// mode: Math.random() < 0.5 ? 6 : 1,
bpm: randomFromInterval(70, 90),
energy: Math.random(),
valence: Math.random(),
chords: [1, 4, 6, 5, 1, 4, 6, 5],
melodies: [] as number[][]
};
return JSON.stringify(params, null, 2);
};

export const HIDDEN_SIZE = 100;
export const LATENT_SPACE_DIMENSIONS = 100;
export class OutputParams {
/** Optional: title */
title: string;
Expand Down
18 changes: 14 additions & 4 deletions client/src/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Track } from './track';
import { compress } from './helper';

/**
* The Player plays a Track through Tone.js.
* A class that plays a Track by synthesizing events in Tone.js.
*/
class Player {
/** Current list of tracks in queue */
playlist: Track[] = [];

/** Current track in playlist being played */
currentPlayingIndex: number;

/** Current track. Can be undefined */
Expand Down Expand Up @@ -90,10 +92,13 @@ class Player {
/** Update local storage playlist when it changes */
updateLocalStorage: () => void;

/** Map of sample group to players */
samplePlayers: Map<string, Tone.Player[]>;

/** Map of instrument to samplers */
instruments: Map<Instrument, any>;

/** Master gain */
gain: Tone.Gain;

constructor() {
Expand Down Expand Up @@ -177,12 +182,11 @@ class Player {
instrumentVolumes.set(toneInstrument, toneInstrument.volume.value);
}

// set swing
// set up swing
Tone.Transport.swing = this.currentTrack.swing ? 2 / 3 : 0;

// wait until all samples are loaded
await Tone.loaded();
// await this.reverb.generate();

for (const sampleLoop of this.currentTrack.sampleLoops) {
const samplePlayer = this.samplePlayers.get(sampleLoop.sampleGroupName)[
Expand Down Expand Up @@ -210,10 +214,13 @@ class Player {
}
}

// connect analyzer for visualizations
const analyzer = new Tone.Analyser('fft', 16);
this.gain.connect(analyzer);

const fadeOutBegin = this.currentTrack.length - this.currentTrack.fadeOutDuration;

// schedule events to do every 100ms
Tone.Transport.scheduleRepeat((time) => {
this.isLoading = false;

Expand All @@ -226,7 +233,7 @@ class Player {
this.playNext();
}

// fade out
// schedule fade out in the last seconds
const volumeOffset = seconds < fadeOutBegin ? 0 : (seconds - fadeOutBegin) * 4;
for (const [sampler, volume] of instrumentVolumes) {
sampler.volume.value = volume - volumeOffset;
Expand Down Expand Up @@ -365,6 +372,9 @@ class Player {
return `${window.location.origin}${window.location.pathname}?${compressed}`.replace(
'home.in.tum.de/~zhangja/lofi',
'lofi.jacobzhang.de'
).replace(
'localhost:8080',
'lofi.jacobzhang.de'
);
}

Expand Down
32 changes: 17 additions & 15 deletions client/src/producer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class Producer {
/** Drum beat timings, as tuples of (isStart, Time) */
drumbeatTimings: [boolean, Time][] = [];

/** Takes OutputParams and deterministically produces a Track */
produce(params: OutputParams): Track {
// must be 70, 75, 80, 85, 90, 95 or 100
let bpm = Math.round(params.bpm / 5) * 5;
Expand Down Expand Up @@ -103,6 +104,7 @@ class Producer {

this.preset = Presets.selectPreset(this.valence, this.energy);

// swing with probability 1/10
const swing = randomFromInterval(1, 10, this.energy) <= 1;

this.chordsTonal = this.chords.map((c, chordNo) => {
Expand Down Expand Up @@ -160,13 +162,15 @@ class Producer {
return track;
}

/** Produces the track's intro and returns the number of measures */
produceIntro(): number {
// TODO: produce a more interesting intro

// measure of silence
return 1;
}

/** Produces the track's main part and returns the number of measures */
produceMain(): number {
const numberOfIterations = Math.ceil(24 / this.chords.length);
const length = this.chords.length * numberOfIterations;
Expand All @@ -188,6 +192,7 @@ class Producer {
return length;
}

/** Produces the track's outro and returns the number of measures */
produceOutro(): number {
// the measure where the outro part starts
const measureStart = this.introLength + this.mainLength;
Expand All @@ -202,6 +207,7 @@ class Producer {
return length;
}

/** Produces FX for the whole track */
produceFx() {
if (this.valence < 0.5 && this.modeNum === 6) {
// add rain
Expand All @@ -216,6 +222,7 @@ class Producer {
}
}

/** Produces a single iteration of the chord progression; can be cut off prematurely */
produceIteration(iterationMeasure: number, cutoff?: number) {
let noDrumBeatCurrently = false;
const chords = cutoff ? this.chords.slice(0, cutoff) : this.chords;
Expand Down Expand Up @@ -270,7 +277,11 @@ class Producer {
// first beat arpeggio
if (this.preset.firstBeatArpeggio) {
const arpeggioNotes = octShiftAll(
mountNotesOnScale(scaleDegree, this.preset.firstBeatArpeggioPattern, this.notesInScalePitched),
mountNotesOnScale(
scaleDegree,
this.preset.firstBeatArpeggioPattern,
this.notesInScalePitched
),
this.preset.firstBeatArpeggio.octaveShift
);
this.addArpeggio(
Expand All @@ -282,19 +293,6 @@ class Producer {
this.preset.firstBeatArpeggio.volume
);
}

// second beat arpeggio
if (this.preset.secondBeatArpeggio) {
const arpeggioNotes = octShiftAll(chord.notes, this.preset.secondBeatArpeggio.octaveShift);
this.addArpeggio(
this.preset.secondBeatArpeggio.instrument,
arpeggioNotes,
'0:3',
'16n',
`${measure}:1`,
this.preset.secondBeatArpeggio.volume
);
}
}

// this should not happen
Expand Down Expand Up @@ -341,7 +339,7 @@ class Producer {
return chords.length;
}

/** simplify key signature, e.g. Db major instead of C# major */
/** Simplifies key signature, e.g. Db major instead of C# major */
simplifyKeySignature() {
if (this.mode === 'ionian') {
this.mode = 'major';
Expand All @@ -361,21 +359,25 @@ class Producer {
}
}

/** Starts the drumbeat at the given time */
startDrumbeat(time: Time) {
this.drumbeatTimings.push([true, time]);
}

/** Stops the drumbeat at the given time */
endDrumbeat(time: Time) {
this.drumbeatTimings.push([false, time]);
}

/** Adds a given sample to the track */
addSample(sample: string, sampleIndex: number, startTime: Time, stopTime: Time) {
if (!this.samples.some(([s, i]) => s === sample && i === sampleIndex)) {
this.samples.push([sample, sampleIndex]);
}
this.sampleLoops.push(new SampleLoop(sample, sampleIndex, startTime, stopTime));
}

/** Adds a given instrument note to the track */
addNote(
instrument: Instrument,
pitch: string | string[],
Expand Down
Loading

0 comments on commit 8d51073

Please sign in to comment.