Added source.

This commit is contained in:
Andrew Lalis 2022-11-27 14:41:49 +01:00
parent 7801dbc6f2
commit c4aafd1d9b
5 changed files with 669 additions and 0 deletions

107
light.h Normal file
View File

@ -0,0 +1,107 @@
#define LC_PERIOD 60000
#define LC_FREQ 1.0 / LC_PERIOD
struct Color {
float r, g, b;
};
#define COLOR_RED {1, 0, 0}
#define COLOR_GREEN {0, 1, 0}
#define COLOR_BLUE {0, 0, 1}
#define COLOR_ORANGE {1, 0.3, 0}
#define COLOR_YELLOW {1, 1, 0}
#define COLOR_PURPLE {0.4, 0, 1}
Color lc_multiplyColor(Color c, float x) {
return {c.r * x, c.g * x, c.b * x};
}
Color lc_interpolateColor(Color c1, Color c2, float x) {
Color c;
c.r = (1 - x) * c1.r + x * c2.r;
c.g = (1 - x) * c1.g + x * c2.g;
c.b = (1 - x) * c1.b + x * c2.b;
return c;
}
typedef Color (*lc_timeFunction)(unsigned int);
// Simple sin function with the same period as the standard LC_PERIOD.
Color lc_tf_greenLight(unsigned int elapsed_ms) {
double x = pow(sin(PI * LC_FREQ * 10 * elapsed_ms), 2);
return {0, x, 0};
}
// Fast sin function.
Color lc_tf_SinFast(unsigned int elapsed_ms) {
double x = (sin(2 * PI * LC_FREQ * 100 * elapsed_ms) + 1) / 2;
return {0, x, 0};
}
// Helper function for color-pulsing time functions.
Color lc_colorPulsar(unsigned int elapsed_ms, Color* colors, unsigned int count) {
const unsigned int colorPeriod = LC_PERIOD / count;
double x = (sin(2 * PI * LC_FREQ * count * elapsed_ms - PI / 2) + 1) / 2;
for (unsigned int i = 1; i <= count; i++) {
if (elapsed_ms < colorPeriod * i) {
return lc_multiplyColor(colors[i - 1], x);
}
}
return lc_multiplyColor({1, 1, 1}, x);
}
// Red, Green, then Blue pulse.
Color lc_tf_rgbPulse(unsigned int elapsed_ms) {
const Color colors[3];
colors[0] = COLOR_RED;
colors[1] = COLOR_GREEN;
colors[2] = COLOR_BLUE;
return lc_colorPulsar(elapsed_ms, colors, 3);
}
// Rainbow color function.
Color lc_tf_rainbowPulse(unsigned int elapsed_ms) {
const Color colors[6];
colors[0] = COLOR_RED;
colors[1] = COLOR_ORANGE;
colors[2] = COLOR_YELLOW;
colors[3] = COLOR_GREEN;
colors[4] = COLOR_BLUE;
colors[5] = COLOR_PURPLE;
return lc_colorPulsar(elapsed_ms, colors, 6);
}
Color lc_tf_yellowGreen(unsigned int elapsed_ms) {
const Color colors[2];
colors[0] = COLOR_YELLOW;
colors[1] = COLOR_GREEN;
return lc_colorPulsar(elapsed_ms, colors, 2);
}
// Flame
Color lc_tf_flame(unsigned int elapsed_ms) {
float colorValue = (sin(2 * PI * LC_FREQ * 10 * elapsed_ms) + 1) / 2;
float flameCoefficients[] = {
0.1, 0.35, 0.04, 0.75, 0.92, 0.55, 0.4, 0.22, 0.64, 0.85
};
const unsigned int fc_count = 10;
float flameIntensity = 0;
for (int i = 0; i < fc_count; i++) {
flameIntensity += (sin(2 * flameCoefficients[i] * elapsed_ms) + 1) / 2;
}
flameIntensity /= fc_count;
Color flame = lc_interpolateColor({1.0, 0.025, 0}, {1.0, 0.1, 0}, colorValue);
return lc_multiplyColor(flame, flameIntensity);
}
lc_timeFunction LC_TIME_FUNCTIONS[] = {
&lc_tf_flame,
&lc_tf_greenLight,
&lc_tf_yellowGreen,
&lc_tf_SinFast,
&lc_tf_rgbPulse,
&lc_tf_rainbowPulse
};
#define LC_TIME_FUNCTION_COUNT 6

156
music.h Normal file
View File

@ -0,0 +1,156 @@
#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

121
nightlight.ino Normal file
View File

@ -0,0 +1,121 @@
#include "music.h"
#include "songs.h"
#include "light.h"
#define BUZZER 11
#define LED_RED 6
#define LED_GREEN 9
#define LED_BLUE 10
#define BUTTON_IN 12
// Standard delay of 30 milliseconds for slightly above 30fps.
#define STD_DELAY 30
// MUSIC CONTROL
struct PlayingSong mc_playing_song;
// Begins playing a new song.
void mc_play_song() {
long choice = random(0, SONG_COUNT);
playSong(&mc_playing_song, SONGS[choice]);
}
// Updates the music controller, playing a note if needed.
void mc_update(unsigned int elapsed_ms) {
struct Note* note_to_play = updateSong(&mc_playing_song, elapsed_ms);
if (note_to_play != NULL) {
tone(BUZZER, note_to_play->frequency, getMillisecondsPerNote(mc_playing_song.song) / note_to_play->duration);
}
}
// LIGHT CONTROL
unsigned int lc_elapsed_ms;
unsigned char lc_chosen_time_function = 0;
void lc_cycle_function() {
lc_chosen_time_function++;
if (lc_chosen_time_function >= LC_TIME_FUNCTION_COUNT) {
lc_chosen_time_function = 0;
}
}
void lc_set_led(float r, float g, float b) {
analogWrite(LED_RED, r * 255);
analogWrite(LED_GREEN, g * 255);
analogWrite(LED_BLUE, b * 255);
}
void lc_set_color(Color c) {
lc_set_led(c.r, c.g, c.b);
}
void lc_update(unsigned int elapsed_ms) {
lc_elapsed_ms += elapsed_ms;
if (lc_elapsed_ms > LC_PERIOD) lc_elapsed_ms -= LC_PERIOD;
if (mc_playing_song.playing) {
float i = getInterpolatedRelativeIntensity(&mc_playing_song);
//Serial.println(i);
lc_set_color({i, i, i});
} else {
lc_set_color(LC_TIME_FUNCTIONS[lc_chosen_time_function](lc_elapsed_ms));
}
}
// INPUT MANAGEMENT
#define IM_CLICK_DELAY 500
bool im_button_pressed = false;
unsigned long im_ms_since_press = 10000;
unsigned char im_press_count = 0;
void im_update(unsigned int elapsed_ms) {
im_ms_since_press += elapsed_ms;
if (im_ms_since_press > 1000000000) im_ms_since_press = 10000; // Reset counter to avoid overflow.
int state = digitalRead(BUTTON_IN);
if (!im_button_pressed && state == HIGH) {
im_button_pressed = true;
}
if (im_button_pressed && state == LOW) {
im_button_pressed = false;
im_press_count++;
im_ms_since_press = 0;
}
if (im_ms_since_press > IM_CLICK_DELAY) {
if (im_press_count == 1) {
mc_play_song();
} else if (im_press_count == 2) {
lc_cycle_function();
} else if (im_press_count == 3) {
mc_play_song();
lc_cycle_function();
}
im_press_count = 0;
}
}
void setup() {
randomSeed(analogRead(0)); // Read analog noise to inialize random numbers.
pinMode(BUZZER, OUTPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
pinMode(LED_BLUE, OUTPUT);
pinMode(BUTTON_IN, INPUT);
//playSong(&mc_playing_song, SONG_TEST);
pinMode(13, OUTPUT);
digitalWrite(12, LOW);
//Serial.begin(9600);
}
void loop() {
delay(STD_DELAY);
mc_update(STD_DELAY);
lc_update(STD_DELAY);
im_update(STD_DELAY);
}

90
pitches.h Normal file
View File

@ -0,0 +1,90 @@
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#define NOTE_REST 0

195
songs.h Normal file
View File

@ -0,0 +1,195 @@
#include "music.h"
const struct Note SONG_SKYRIM_NOTES[] = {
makeNote(NOTE_D5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_F5, 8 / 3),
makeNote(NOTE_F5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_G5, 8),
makeNote(NOTE_A5, 8 / 3),
makeNote(NOTE_A5, 8),
makeNote(NOTE_A5, 8),
makeNote(NOTE_C5, 8),//10
makeNote(NOTE_G5, 8 / 3),
makeNote(NOTE_G5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 8 / 3),
makeNote(NOTE_D5, 8),
makeNote(NOTE_D5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_F5, 8 / 3),
makeNote(NOTE_F5, 8),//20
makeNote(NOTE_F5, 8),
makeNote(NOTE_G5, 8),
makeNote(NOTE_A5, 8 / 3),
makeNote(NOTE_A5, 8),
makeNote(NOTE_A5, 8),
makeNote(NOTE_C5, 8),
makeNote(NOTE_D5, 8 / 3),
makeNote(NOTE_D5, 8),
makeNote(NOTE_C5, 8),
makeNote(NOTE_E5, 8),//30
makeNote(NOTE_D5, 8 / 3),
makeNote(NOTE_D5, 8),
makeNote(NOTE_D5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_F5, 4),
makeNote(NOTE_E5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 4),
makeNote(NOTE_C5, 4),
makeNote(NOTE_B5, 8),//40
makeNote(NOTE_B5, 8),
makeNote(NOTE_A5, 4),
makeNote(NOTE_G5, 8 / 3),
makeNote(NOTE_G5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_A5, 8),
makeNote(NOTE_G5, 8 / 3),
makeNote(NOTE_G5, 4),
makeNote(NOTE_F5, 16),
makeNote(NOTE_E5, 16),//50
makeNote(NOTE_F5, 4),
makeNote(NOTE_F5, 16),
makeNote(NOTE_E5, 16),
makeNote(NOTE_F5, 4),
makeNote(NOTE_F5, 16),
makeNote(NOTE_E5, 16),
makeNote(NOTE_G5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 4),//60
makeNote(NOTE_D5, 16),
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 4),
makeNote(NOTE_D5, 16),
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 4),
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 8),
makeNote(NOTE_F5, 8),//70
makeNote(NOTE_C5, 8),
makeNote(NOTE_D5, 4),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 16),
makeNote(NOTE_F5, 4),
makeNote(NOTE_F5, 16),
makeNote(NOTE_G5, 16),
makeNote(NOTE_A5, 4),
makeNote(NOTE_E5, 16),
makeNote(NOTE_F5, 16),//80
makeNote(NOTE_G5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 4),
makeNote(NOTE_D5, 16),
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 4),
makeNote(NOTE_D5, 16),
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 4),//90
makeNote(NOTE_C5, 16),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 8),
makeNote(NOTE_F5, 8),
makeNote(NOTE_C5, 8),
makeNote(NOTE_D5, 8 / 3),
makeNote(NOTE_D5, 4),
makeNote(NOTE_REST, 8),
makeNote(NOTE_D5, 4),
makeNote(NOTE_REST, 8),//100
makeNote(NOTE_D5, 4),
makeNote(NOTE_REST, 8),
makeNote(NOTE_D5, 4),
makeNote(NOTE_REST, 8)
};
const struct Song SONG_SKYRIM = makeSong(120, 104, SONG_SKYRIM_NOTES);
const struct Note SONG_HOBBIT_NOTES2[] = {
makeNote(NOTE_D2, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_D3, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_D2, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_D3, 4),
makeNote(NOTE_REST, 4),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 8),
makeNote(NOTE_FS5, 4),
makeNote(NOTE_A5, 4),
makeNote(NOTE_FS5, 8),
makeNote(NOTE_E5, 16),
makeNote(NOTE_FS5, 16),
makeNote(NOTE_E5, 16),
makeNote(NOTE_D5, 1.5),
makeNote(NOTE_REST, 8),
makeNote(NOTE_FS5, 4),
makeNote(NOTE_A5, 8),
makeNote(NOTE_B5, 4.0/3),
makeNote(NOTE_D6, 8),
makeNote(NOTE_CS6, 4.0/3),
makeNote(NOTE_A5, 8),
makeNote(NOTE_FS5, 4.0/3),
makeNote(NOTE_G5, 16),
makeNote(NOTE_FS5, 16),
makeNote(NOTE_E5, 4.0/3),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 16),
makeNote(NOTE_FS5, 4),
makeNote(NOTE_A5, 4),
makeNote(NOTE_FS5, 16),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 8),
makeNote(NOTE_E5, 16),
makeNote(NOTE_D5, 1.5),
makeNote(NOTE_REST, 8),
makeNote(NOTE_FS5, 4),
makeNote(NOTE_A5, 8),
makeNote(NOTE_B5, 1.5),
makeNote(NOTE_A5, 4),
makeNote(NOTE_FS5, 4),
makeNote(NOTE_FS5, 1.5),
makeNote(NOTE_E5, 1.5),
makeNote(NOTE_D5, 16),
makeNote(NOTE_E5, 8),
makeNote(NOTE_D5, 4.0/3),
makeNote(NOTE_D3, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_D2, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_D3, 4),
makeNote(NOTE_A2, 4)
};
const struct Song SONG_HOBBIT = makeSong(104, 54, SONG_HOBBIT_NOTES2);
const struct Note SONG_TEST_NOTES[] = {
makeNote(NOTE_A1, 4),
makeNote(NOTE_A2, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A4, 4),
makeNote(NOTE_A5, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4),
makeNote(NOTE_A3, 4),
makeNote(NOTE_A6, 4)
};
const struct Song SONG_TEST = makeSong(60, 18, SONG_TEST_NOTES);
const struct Song SONGS[] = {SONG_SKYRIM, SONG_HOBBIT};
#define SONG_COUNT 2