nightlight/music.h

157 lines
4.7 KiB
C

#ifndef __MUSIC_H__
#define __MUSIC_H__
#include <math.h>
#include <stdlib.h>
#include "pitches.h"
struct Note {
unsigned short frequency;
float duration;
};
struct Song {
unsigned short tempo; // Notes per minute to play.
unsigned int size; // The number of notes.
struct Note* notes; // The notes data.
unsigned short maxFreq;
unsigned short minFreq;
};
struct PlayingSong {
bool playing;
unsigned int elapsed_ms;
unsigned int next_note_at;
size_t current_note;
struct Song song;
};
struct Note makeNote(unsigned short f, float t) {
struct Note n;
n.frequency = f;
n.duration = t;
return n;
}
struct Song makeSong(unsigned short tempo, unsigned int size, struct Note* notes) {
struct Song s;
s.tempo = tempo;
s.size = size;
s.notes = notes;
s.maxFreq = notes[0].frequency;
s.minFreq = notes[0].frequency;
for (unsigned int i = 1; i < size; i++) {
unsigned short f = notes[i].frequency;
if (f != NOTE_REST && f > s.maxFreq) s.maxFreq = f;
if (f != NOTE_REST && f < s.minFreq) s.minFreq = f;
}
return s;
}
void playSong(struct PlayingSong* ps, struct Song s) {
ps->playing = true;
ps->elapsed_ms = 0;
ps->current_note = 0;
ps->next_note_at = 0;
ps->song = s;
}
unsigned int getMillisecondsPerNote(struct Song s) {
return 240000 / s.tempo;
}
unsigned int getNoteDuration(struct Song s, size_t note) {
return getMillisecondsPerNote(s) / s.notes[note].duration;
}
unsigned int getSongDuration(struct Song s) {
return getMillisecondsPerNote(s) * s.size;
}
float getCurrentNoteProgress(struct PlayingSong ps) {
unsigned int timeLeft = ps.next_note_at - ps.elapsed_ms;
return getNoteDuration(ps.song, ps.current_note) - timeLeft;
}
float getRelativeNoteIntensity(struct Note n, struct Song s) {
float min2 = sqrt(s.minFreq);
float max2 = sqrt(s.maxFreq);
float f2 = sqrt(n.frequency);
//float f = ((float) n.frequency - s.minFreq) / (s.maxFreq - s.minFreq);
float f = (f2 - min2) / (max2 - min2);
if (f < 0) return 0;
if (f > 1) return 1;
return f;
}
// Updates the given playing song, and returns a reference to a note to play, if any. Otherwise, nullptr is returned.
struct Note* updateSong(struct PlayingSong* ps, unsigned int elapsed_ms) {
if (!ps->playing) return NULL;
ps->elapsed_ms += elapsed_ms;
struct Note* note_to_play = NULL;
if (ps->elapsed_ms > ps->next_note_at) {
ps->current_note++;
if (ps->current_note < ps->song.size) {
note_to_play = &ps->song.notes[ps->current_note];
ps->next_note_at = ps->elapsed_ms + (getMillisecondsPerNote(ps->song) / note_to_play->duration);
} else {
ps->playing = false;
}
}
return note_to_play;
}
float getInterpolatedRelativeIntensity(struct PlayingSong* ps) {
//return getRelativeNoteIntensity(ps->song.notes[ps->current_note], ps->song);
// TODO: Figure out cubic bezier interpolation.
struct Note note_a;
struct Note note_b;
float dur_a;
float dur_b;
unsigned int elapsed_time; // Elapsed ms between note a and b.
unsigned int time_until_next_note = ps->next_note_at - ps->elapsed_ms;
unsigned int current_note_dur = getNoteDuration(ps->song, ps->current_note);
unsigned int current_note_elapsed_time = (current_note_dur - time_until_next_note);
if (current_note_elapsed_time < current_note_dur / 2) {
// We are on the lower-half of the current note.
if (ps->current_note == 0) {
note_a = makeNote(ps->song.minFreq, 4);
} else {
note_a = ps->song.notes[ps->current_note - 1];
}
note_b = ps->song.notes[ps->current_note];
unsigned int dur_a = getMillisecondsPerNote(ps->song) / note_a.duration;
elapsed_time = dur_a / 2 + current_note_elapsed_time;
} else {
// We are on the upper half of the current note.
if (ps->current_note == ps->song.size - 1) {
note_b = makeNote(ps->song.minFreq, 4);
} else {
note_b = ps->song.notes[ps->current_note + 1];
}
note_a = ps->song.notes[ps->current_note];
elapsed_time = current_note_elapsed_time - (current_note_dur / 2);
}
dur_a = getMillisecondsPerNote(ps->song) / note_a.duration;
dur_b = getMillisecondsPerNote(ps->song) / note_b.duration;
float t = ((float) elapsed_time) / (dur_a / 2 + dur_b / 2);
float intensity_a = getRelativeNoteIntensity(note_a, ps->song);
float intensity_b = getRelativeNoteIntensity(note_b, ps->song);
// linear
//return (1 - t) * intensity_a + t * intensity_b;
// cubic
float y0 = intensity_a;
float y1 = y0;
float y2 = intensity_b;
float y3 = y2;
float n = 1 - t;
return n*n*n*y0 + 3*t*n*n*y1 + 3*t*t*n*y2 + t*t*t*y3;
}
#endif