CHGame 0.3.0
Libraries for the CHGame handheld
Loading...
Searching...
No Matches
Audio.h
Go to the documentation of this file.
1/// @file Audio.h
2/// @brief Sound and the status LED: a small sequencer for the piezo.
3///
4/// One pin plays one note at a time, driven by TIM1 channel 2 on PB10 and
5/// stepped by the core's 1 kHz SysTick hook, so no other timer is used. It
6/// plays two kinds of thing at once:
7///
8/// - **effects**: short step lists (a pitch, the pitch it sweeps to, a
9/// length), each with a priority: a new effect is refused while one of
10/// higher priority sounds, and cuts off any other;
11/// - **music**, under the effects: a Playtune score (several voices;
12/// tools/make_music.py writes them), rendered as a lead line or an
13/// arpeggio, or a melody (one line of notes, easy to write by hand).
14///
15/// The simulator is silent: the same interface with no hardware, which
16/// remembers the last effect asked for (audio::simLast) for scripts and
17/// tests. The game's tools/audio preview renders the real thing to WAV.
18#pragma once
19#include <stdint.h>
20
21/// @defgroup chgame_audio Sound and LED
22/// @ingroup lib_chgame
23/// @brief audio::: sound effects, music and the status LED.
24///
25/// A game lists its effects once, in the order of its own enum:
26/// @code
27/// enum class Sfx : uint8_t { Cursor, Win, COUNT };
28/// AUDIO_STEPS(CURSOR) = { AUDIO_STEP(2100, 0, 10) };
29/// AUDIO_STEPS(WIN) = { AUDIO_STEP(2093, 0, 60), AUDIO_STEP(4186, 0, 170) };
30/// const audio::Effect SOUNDS[] = { AUDIO_EFFECT(CURSOR, 0), AUDIO_EFFECT(WIN, 3) };
31///
32/// audio::begin(SOUNDS, (uint8_t)Sfx::COUNT); // in setup()
33/// audio::sfx(Sfx::Win); // any time
34/// audio::update(); // once per logic tick (the LED)
35/// @endcode
36/// @{
37
38/// @brief Sound effects, music and the status LED.
39namespace audio {
40
41/// @brief An effect step in three bytes. Write them with AUDIO_STEP() and AUDIO_REST().
42/// @details 20 Hz is under half a percent of a piezo's 1-4 kHz: no ear hears
43/// it, and the tables are half the size. A step lasts at most 510 ms.
44struct Step {
45 uint8_t hz, ///< The pitch in 20 Hz units; 0 = a rest.
46 endHz, ///< The pitch it sweeps to, in 20 Hz units; 0 = none (holds hz).
47 ms; ///< The length in 2 ms units.
48};
49/// @brief An effect step: a pitch, the pitch it sweeps to, a length.
50/// @param hz Start pitch in Hz (rounded to 20 Hz).
51/// @param end The pitch to sweep to in Hz, or 0 to hold hz.
52/// @param ms Length in milliseconds (rounded to 2 ms; at most 510).
53#define AUDIO_STEP(hz, end, ms) \
54 { (uint8_t)(((hz) + 10) / 20), (uint8_t)(((end) + 10) / 20), (uint8_t)(((ms) + 1) / 2) }
55/// @brief A silent effect step.
56/// @param ms Length in milliseconds (rounded to 2 ms; at most 510).
57#define AUDIO_REST(ms) { 0, 0, (uint8_t)(((ms) + 1) / 2) }
58/// @brief Declare an effect's step table, kept in flash.
59/// @param name The table's name, for AUDIO_EFFECT().
60/// @details Use it as `AUDIO_STEPS(WIN) = { AUDIO_STEP(...), ... };`. (A plain
61/// `static const` table of 8 bytes or less is "small data" to the compiler,
62/// and this core's link script copies small data into SRAM.)
63#if defined(__riscv) && !defined(CHSIM)
64#define AUDIO_STEPS(name) static const audio::Step name[] __attribute__((section(".rodata.audio." #name)))
65#else
66#define AUDIO_STEPS(name) static const audio::Step name[]
67#endif
68
69/// @brief Flags ORed into an effect's priority (0-15).
70enum : uint8_t {
71 SOFT = 0x10, ///< A narrow pulse: quieter than everything else (clocks, ticks).
72 GLIDE = 0x20, ///< Sweeps change pitch at a cycle's end instead of restarting it.
73};
74/// @brief An effect: its steps, and its priority with flags. Write them with AUDIO_EFFECT().
75struct Effect {
76 const Step *steps; ///< The step table (AUDIO_STEPS()).
77 uint8_t n, ///< Number of steps.
78 flags; ///< Priority 0-15, ORed with SOFT and GLIDE.
79};
80/// @brief An entry of the effects table.
81/// @param steps A table declared with AUDIO_STEPS().
82/// @param flags The priority (0-15), ORed with SOFT and GLIDE if wanted.
83#define AUDIO_EFFECT(steps, flags) { steps, (uint8_t)(sizeof(steps) / sizeof((steps)[0])), (uint8_t)(flags) }
84
85/// @brief Set up the speaker, the LED pin and the timer, with the game's effects.
86/// @param effects The effects table, in the order of the game's enum (kept by pointer).
87/// @param count The number of effects.
88/// @param on Whether sound starts on (a saved option, for instance).
89void begin(const Effect *effects, uint8_t count, bool on = true);
90/// @brief Turn sound on or off.
91/// @param on false: silent, effects refused, music stopped in place (it
92/// carries on from there when sound comes back).
93void setOn(bool on); // off: silent, effects refused, music stopped in place
94/// @brief Whether sound is on.
95/// @return true after begin() with sound on, until setOn(false).
96bool on();
97
98/// @brief Play an effect.
99/// @param id An index into the effects table, or the game's enum.
100/// @details Refused while an effect of higher priority sounds; cuts off any
101/// other effect.
102void sfx(uint8_t id);
103/// @brief Play an effect raised in pitch: a combo that climbs.
104/// @param id An index into the effects table.
105/// @param semitones How far to raise the whole effect, 0-12.
106void sfx(uint8_t id, uint8_t semitones);
107/// @brief Play an effect named by the game's enum.
108/// @param e The enum value (its index into the effects table).
109template <class E> inline void sfx(E e) { sfx((uint8_t)e); }
110/// @brief Play an effect named by the game's enum, raised in pitch.
111/// @param e The enum value.
112/// @param semitones How far to raise it, 0-12.
113template <class E> inline void sfx(E e, uint8_t semitones) { sfx((uint8_t)e, semitones); }
114/// @brief One note made up on the spot (a typewriter, a click, a counter rolling).
115/// @details It cuts off effects of priority 0 and 1, is refused over anything
116/// higher, and is itself priority 0. To never play over any effect:
117/// `if (!audio::playing()) audio::blip(...)`.
118/// @param hz Pitch in Hz.
119/// @param ms Length in milliseconds.
120/// @param soft true for the quieter narrow pulse (as SOFT).
121void blip(uint16_t hz, uint16_t ms, bool soft = false);
122/// @brief One note at a priority, like an effect.
123/// @param hz Pitch in Hz.
124/// @param ms Length in milliseconds.
125/// @param priority 0-15, as an effect's.
126void note(uint16_t hz, uint16_t ms, uint8_t priority);
127/// @brief Whether an effect is sounding.
128/// @return true while an effect, blip() or note() plays.
129bool playing(); // an effect is sounding
130
131/// @brief How music is rendered on the one pin.
132/// @details The music code is linked in only by a game that calls music() or melody().
133enum MusicMode : uint8_t {
134 MUSIC_OFF, ///< No music (held where it is).
135 ARPEGGIO, ///< A score's sounding voices take 6 ms turns (the default).
136 LEAD ///< The melody (channel 0) plays; the others fill only its real rests.
138/// @brief Choose how music is rendered.
139/// @param mode A MusicMode; MUSIC_OFF holds the tune where it is.
140void setMusic(uint8_t mode); // default ARPEGGIO; MUSIC_OFF holds the tune where it is
141/// @brief Play a Playtune score under the effects.
142/// @param score The score (from tools/make_music.py), or nullptr to stop.
143/// @param loop true (default): start again at the end.
144void music(const uint8_t *score, bool loop = true); // nullptr: stop
145
146/// @brief A melody: one line of notes, easy to write by hand.
147/// @details (MIDI note, length) pairs: note 0 is a rest, a length counts
148/// units of unitMs, and each note is let go gapMs early so repeated notes
149/// stay apart. Middle C is 60; the piezo sings best from C6 (84) to C8 (108).
150/// @code
151/// static const uint8_t TUNE[] = { 84,2, 88,2, 91,4, 0,2 };
152/// static const audio::Melody TITLE = { TUNE, sizeof TUNE / 2, 105, 14 };
153/// audio::melody(TITLE);
154/// @endcode
155struct Melody {
156 const uint8_t *notes; ///< (MIDI note, length) pairs.
157 uint8_t count, ///< Number of pairs.
158 unitMs, ///< Milliseconds per length unit.
159 gapMs; ///< Silence at the end of each note, in milliseconds.
160};
161/// @brief Play a melody under the effects (it replaces any score).
162/// @param m The melody (kept by reference: it must stay valid).
163/// @param loop true (default): start again at the end.
164void melody(const Melody &m, bool loop = true); // replaces any score
165/// @brief Stop the music.
167/// @brief Whether the music repeats.
168/// @param on false: the score stops at its end instead of repeating.
169void loopMusic(bool on); // off: the score stops at its end instead of repeating
170/// @brief Whether music is playing.
171/// @return true while a score or melody plays; false once it has ended, and
172/// while sound or music is off.
173bool musicPlaying(); // false while sound or music is off
174
175/// @brief Run the LED patterns: once per logic tick.
176void update(); // once per logic tick: the LED patterns
177
178/// @brief Status LED (PB9) patterns, for wins. Timed in update() ticks.
179enum Led : uint8_t {
180 LED_OFF, ///< Off.
181 LED_BLINK, ///< One blink (12 ticks).
182 LED_TRIPLE, ///< Three blinks.
183 LED_PARTY ///< Fast blinking for about 4 seconds at 60 fps.
185/// @brief Start an LED pattern (replacing the one running).
186/// @param pattern The pattern.
187void led(Led pattern);
188
189#ifdef CHSIM
190extern uint8_t simLast; // the last effect played (0xFF: none yet)
191#endif
192
193} // namespace audio
194
195/// @}
Sound effects, music and the status LED.
Definition Audio.h:39
Led
Status LED (PB9) patterns, for wins. Timed in update() ticks.
Definition Audio.h:179
@ LED_PARTY
Fast blinking for about 4 seconds at 60 fps.
Definition Audio.h:183
@ LED_OFF
Off.
Definition Audio.h:180
@ LED_BLINK
One blink (12 ticks).
Definition Audio.h:181
@ LED_TRIPLE
Three blinks.
Definition Audio.h:182
bool on()
Whether sound is on.
@ SOFT
A narrow pulse: quieter than everything else (clocks, ticks).
Definition Audio.h:71
@ GLIDE
Sweeps change pitch at a cycle's end instead of restarting it.
Definition Audio.h:72
void blip(uint16_t hz, uint16_t ms, bool soft=false)
One note made up on the spot (a typewriter, a click, a counter rolling).
void loopMusic(bool on)
Whether the music repeats.
void melody(const Melody &m, bool loop=true)
Play a melody under the effects (it replaces any score).
void setMusic(uint8_t mode)
Choose how music is rendered.
MusicMode
How music is rendered on the one pin.
Definition Audio.h:133
@ MUSIC_OFF
No music (held where it is).
Definition Audio.h:134
@ LEAD
The melody (channel 0) plays; the others fill only its real rests.
Definition Audio.h:136
@ ARPEGGIO
A score's sounding voices take 6 ms turns (the default).
Definition Audio.h:135
void setOn(bool on)
Turn sound on or off.
void update()
Run the LED patterns: once per logic tick.
void music(const uint8_t *score, bool loop=true)
Play a Playtune score under the effects.
bool playing()
Whether an effect is sounding.
bool musicPlaying()
Whether music is playing.
void note(uint16_t hz, uint16_t ms, uint8_t priority)
One note at a priority, like an effect.
void stopMusic()
Stop the music.
void led(Led pattern)
Start an LED pattern (replacing the one running).
void sfx(uint8_t id)
Play an effect.
void begin(const Effect *effects, uint8_t count, bool on=true)
Set up the speaker, the LED pin and the timer, with the game's effects.
An effect: its steps, and its priority with flags. Write them with AUDIO_EFFECT().
Definition Audio.h:75
uint8_t n
Number of steps.
Definition Audio.h:77
const Step * steps
The step table (AUDIO_STEPS()).
Definition Audio.h:76
uint8_t flags
Priority 0-15, ORed with SOFT and GLIDE.
Definition Audio.h:78
A melody: one line of notes, easy to write by hand.
Definition Audio.h:155
uint8_t gapMs
Silence at the end of each note, in milliseconds.
Definition Audio.h:159
const uint8_t * notes
(MIDI note, length) pairs.
Definition Audio.h:156
uint8_t count
Number of pairs.
Definition Audio.h:157
uint8_t unitMs
Milliseconds per length unit.
Definition Audio.h:158
An effect step in three bytes.
Definition Audio.h:44
uint8_t ms
The length in 2 ms units.
Definition Audio.h:47
uint8_t endHz
The pitch it sweeps to, in 20 Hz units; 0 = none (holds hz).
Definition Audio.h:46
uint8_t hz
The pitch in 20 Hz units; 0 = a rest.
Definition Audio.h:45