diff --git a/firmware/doom/lib/doomgeneric/src/d_alloc.c b/firmware/doom/lib/doomgeneric/src/d_alloc.c index 4f331622..71ab0d35 100644 --- a/firmware/doom/lib/doomgeneric/src/d_alloc.c +++ b/firmware/doom/lib/doomgeneric/src/d_alloc.c @@ -25,3 +25,11 @@ void D_FreeBuffers() { R_FreeThings(); R_FreeBSP(); } + +// void* ps_malloc(size_t size) { +// void* ptr = heap_caps_malloc(size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM); +// if (ptr == NULL) { +// DG_printf("ps_malloc: failed to allocate %d bytes\n", size); +// } +// return ptr; +// } diff --git a/firmware/doom/lib/doomgeneric/src/d_alloc.h b/firmware/doom/lib/doomgeneric/src/d_alloc.h index 0c110f23..5ffb6fcc 100644 --- a/firmware/doom/lib/doomgeneric/src/d_alloc.h +++ b/firmware/doom/lib/doomgeneric/src/d_alloc.h @@ -1,4 +1,39 @@ +#ifndef D_ALLOC_H +#define D_ALLOC_H + +#include #include "d_log.h" -extern void D_AllocBuffers (void); -extern void D_FreeBuffers (void); +extern void D_AllocBuffers(void); +extern void D_FreeBuffers(void); + +#ifndef MALLOC_CAP_EXEC + +#define MALLOC_CAP_EXEC (1 << 0) ///< Memory must be able to run executable code +#define MALLOC_CAP_32BIT (1 << 1) ///< Memory must allow for aligned 32-bit data accesses +#define MALLOC_CAP_8BIT (1 << 2) ///< Memory must allow for 8/16/...-bit data accesses +#define MALLOC_CAP_DMA (1 << 3) ///< Memory must be able to accessed by DMA +#define MALLOC_CAP_PID2 (1 << 4) ///< Memory must be mapped to PID2 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID3 (1 << 5) ///< Memory must be mapped to PID3 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID4 (1 << 6) ///< Memory must be mapped to PID4 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID5 (1 << 7) ///< Memory must be mapped to PID5 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID6 (1 << 8) ///< Memory must be mapped to PID6 memory space (PIDs are not currently used) +#define MALLOC_CAP_PID7 (1 << 9) ///< Memory must be mapped to PID7 memory space (PIDs are not currently used) +#define MALLOC_CAP_SPIRAM (1 << 10) ///< Memory must be in SPI RAM +#define MALLOC_CAP_INTERNAL \ + (1 << 11) ///< Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off +#define MALLOC_CAP_DEFAULT \ + (1 << 12) ///< Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call +#define MALLOC_CAP_IRAM_8BIT (1 << 13) ///< Memory must be in IRAM and allow unaligned access +#define MALLOC_CAP_RETENTION (1 << 14) ///< Memory must be able to accessed by retention DMA +#define MALLOC_CAP_RTCRAM (1 << 15) ///< Memory must be in RTC fast memory + +#define MALLOC_CAP_INVALID (1 << 31) ///< Memory can't be used / list end marker + +#endif + +// Including breaks some literals (like "false") +extern void* heap_caps_malloc(size_t size, uint32_t caps); +extern void* ps_malloc(size_t size); + +#endif // D_ALLOC_H diff --git a/firmware/doom/lib/doomgeneric/src/doomfeatures.h b/firmware/doom/lib/doomgeneric/src/doomfeatures.h index f7b15f75..05d79639 100644 --- a/firmware/doom/lib/doomgeneric/src/doomfeatures.h +++ b/firmware/doom/lib/doomgeneric/src/doomfeatures.h @@ -33,7 +33,8 @@ // Enables sound output -#undef FEATURE_SOUND +#define FEATURE_SOUND +// #undef FEATURE_SOUND #endif /* #ifndef DOOM_FEATURES_H */ diff --git a/firmware/doom/lib/doomgeneric/src/i_sound.c b/firmware/doom/lib/doomgeneric/src/i_sound.c index 57f74763..5e57b66e 100644 --- a/firmware/doom/lib/doomgeneric/src/i_sound.c +++ b/firmware/doom/lib/doomgeneric/src/i_sound.c @@ -89,7 +89,7 @@ static int snd_mport = 0; // Compiled-in sound modules: -static sound_module_t *sound_modules[] = +static sound_module_t *sound_modules[] = { #ifdef FEATURE_SOUND &DG_sound_module, @@ -129,7 +129,7 @@ static void InitSfxModule(boolean use_sfx_prefix) // Is the sfx device in the list of devices supported by // this module? - if (SndDeviceInList(snd_sfxdevice, + if (SndDeviceInList(snd_sfxdevice, sound_modules[i]->sound_devices, sound_modules[i]->num_sound_devices)) { @@ -160,7 +160,7 @@ static void InitMusicModule(void) // void I_InitSound(boolean use_sfx_prefix) -{ +{ boolean nosound, nosfx, nomusic; //! @@ -174,7 +174,7 @@ void I_InitSound(boolean use_sfx_prefix) //! // @vanilla // - // Disable sound effects. + // Disable sound effects. // nosfx = M_CheckParm("-nosfx") > 0; @@ -230,7 +230,7 @@ void I_ShutdownSound(void) int I_GetSfxLumpNum(sfxinfo_t *sfxinfo) { - if (sound_module != NULL) + if (sound_module != NULL) { return sound_module->GetSfxLumpNum(sfxinfo); } @@ -407,7 +407,7 @@ boolean I_MusicIsPlaying(void) { return false; } - + } void I_BindSoundVariables(void) diff --git a/firmware/doom/lib/doomgeneric/src/i_swap.h b/firmware/doom/lib/doomgeneric/src/i_swap.h index d6a5b484..2b8ada15 100644 --- a/firmware/doom/lib/doomgeneric/src/i_swap.h +++ b/firmware/doom/lib/doomgeneric/src/i_swap.h @@ -20,9 +20,9 @@ #ifndef __I_SWAP__ #define __I_SWAP__ -#ifdef FEATURE_SOUND +// #ifdef FEATURE_SOUND -#include +// #include // Endianess handling. // WAD files are stored little endian. @@ -32,37 +32,22 @@ // These are deliberately cast to signed values; this is the behaviour // of the macros in the original source and some code relies on it. -#define SHORT(x) ((signed short) SDL_SwapLE16(x)) -#define LONG(x) ((signed int) SDL_SwapLE32(x)) +#define SHORT(x) ((signed short) x) +#define LONG(x) ((signed int) x) // Defines for checking the endianness of the system. -#if SDL_BYTEORDER == SYS_LIL_ENDIAN -#define SYS_LITTLE_ENDIAN -#elif SDL_BYTEORDER == SYS_BIG_ENDIAN -#define SYS_BIG_ENDIAN -#endif - // cosmito from lsdldoom #define doom_swap_s(x) \ ((short int)((((unsigned short int)(x) & 0x00ff) << 8) | \ - (((unsigned short int)(x) & 0xff00) >> 8))) + (((unsigned short int)(x) & 0xff00) >> 8))) -#if ( SDL_BYTEORDER == SDL_BIG_ENDIAN ) -#define doom_wtohs(x) doom_swap_s(x) -#else #define doom_wtohs(x) (short int)(x) -#endif - -#else - -#define SHORT(x) ((signed short) (x)) -#define LONG(x) ((signed int) (x)) #define SYS_LITTLE_ENDIAN -#endif /* FEATURE_SOUND */ +// #endif /* FEATURE_SOUND */ #endif diff --git a/firmware/doom/lib/doomgeneric/src/i_system.c b/firmware/doom/lib/doomgeneric/src/i_system.c index 1e844a81..af0545fd 100644 --- a/firmware/doom/lib/doomgeneric/src/i_system.c +++ b/firmware/doom/lib/doomgeneric/src/i_system.c @@ -45,6 +45,7 @@ #include "i_sound.h" #include "i_timer.h" #include "i_video.h" +#include "d_alloc.h" #include "i_system.h" @@ -58,7 +59,6 @@ #define DEFAULT_RAM 6 /* MiB */ #define MIN_RAM 6 /* MiB */ - typedef struct atexit_listentry_s atexit_listentry_t; struct atexit_listentry_s @@ -116,7 +116,8 @@ static byte *AutoAllocMemory(int *size, int default_ram, int min_ram) *size = default_ram * 1024 * 1024; - zonemem = malloc(*size); + // zonemem = malloc(*size); + zonemem = ps_malloc(*size); // Failed to allocate? Reduce zone size until we reach a size // that is acceptable. @@ -157,7 +158,7 @@ byte *I_ZoneBase (int *size) zonemem = AutoAllocMemory(size, default_ram, min_ram); - DG_printf("zone memory: %p, %x allocated for zone\n", + DG_printf("zone memory: %p, %x allocated for zone\n", zonemem, *size); return zonemem; @@ -191,7 +192,7 @@ void I_PrintStartupBanner(char *gamedescription) I_PrintDivider(); I_PrintBanner(gamedescription); I_PrintDivider(); - + DG_printf( " " PACKAGE_NAME " is free software, covered by the GNU General Public\n" " License. There is NO warranty; not even for MERCHANTABILITY or FITNESS\n" @@ -201,7 +202,7 @@ void I_PrintStartupBanner(char *gamedescription) I_PrintDivider(); } -// +// // I_ConsoleStdout // // Returns true if stdout is a real console, false if it is a file @@ -248,8 +249,8 @@ void I_Quit (void) atexit_listentry_t *entry; // Run through all exit functions - - entry = exit_funcs; + + entry = exit_funcs; while (entry != NULL) { diff --git a/firmware/doom/lib/doomgeneric/src/r_main.c b/firmware/doom/lib/doomgeneric/src/r_main.c index e210320e..4f930a1d 100644 --- a/firmware/doom/lib/doomgeneric/src/r_main.c +++ b/firmware/doom/lib/doomgeneric/src/r_main.c @@ -28,6 +28,7 @@ #include "doomdef.h" #include "d_loop.h" +#include "d_alloc.h" #include "m_bbox.h" #include "m_menu.h" @@ -116,7 +117,7 @@ void (*spanfunc) (void); int R_AllocMain (void) { int viewangletox_size = sizeof(int) * FINEANGLES/2; - viewangletox = malloc (viewangletox_size); + viewangletox = ps_malloc (viewangletox_size); if (viewangletox == NULL) { return -1; diff --git a/firmware/doom/lib/doomgeneric/src/r_plane.c b/firmware/doom/lib/doomgeneric/src/r_plane.c index 3e5050de..53ba6475 100644 --- a/firmware/doom/lib/doomgeneric/src/r_plane.c +++ b/firmware/doom/lib/doomgeneric/src/r_plane.c @@ -25,6 +25,7 @@ #include "i_system.h" #include "z_zone.h" #include "w_wad.h" +#include "d_alloc.h" #include "doomdef.h" #include "doomstat.h" @@ -109,7 +110,8 @@ int R_AllocPlanes (void) // return -1; // } int openings_size = sizeof(short) * MAXOPENINGS; - openings = malloc (openings_size); + // openings = malloc(openings_size); + openings = ps_malloc(openings_size); if (openings == NULL) { // free (visplanes); diff --git a/firmware/doom/lib/doomgeneric/src/r_things.c b/firmware/doom/lib/doomgeneric/src/r_things.c index a659674c..2dcfbd03 100644 --- a/firmware/doom/lib/doomgeneric/src/r_things.c +++ b/firmware/doom/lib/doomgeneric/src/r_things.c @@ -30,6 +30,7 @@ #include "i_system.h" #include "z_zone.h" #include "w_wad.h" +#include "d_alloc.h" #include "r_local.h" @@ -93,7 +94,7 @@ char* spritename; int R_AllocThings (void) { int vissprites_size = MAXVISSPRITES * sizeof(vissprite_t); - vissprites = malloc (vissprites_size); + vissprites = ps_malloc (vissprites_size); if (vissprites == NULL) { return -1; diff --git a/firmware/doom/src/i_buzzersound.cpp b/firmware/doom/src/i_buzzersound.cpp new file mode 100644 index 00000000..5da64ce3 --- /dev/null +++ b/firmware/doom/src/i_buzzersound.cpp @@ -0,0 +1,239 @@ +#include +#include "driver/i2s.h" + +extern "C" { +#include "config.h" +#include "i_sound.h" +#include "deh_str.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" +} + +static boolean use_sfx_prefix; + +void soundTask(void* param); +void queueSound(uint8_t* data, uint32_t length, uint32_t sample_rate, uint8_t vol); + +static snddevice_t sound_devices[] = { + SNDDEVICE_SB, + SNDDEVICE_PAS, + SNDDEVICE_GUS, + SNDDEVICE_WAVEBLASTER, + SNDDEVICE_SOUNDCANVAS, + SNDDEVICE_AWE32, +}; + +const float doomNoteToFreq[] = { + // Notes start with 175 Hz (F3) + // Each octave has 24 notes (not 12), thus 2 notes per semitone + // Table taken from https://www.doomworld.com/idgames/sounds/pcspkr10 + 0, // Silence + 175.00, // F-3 + 180.02, + 185.01, // F#3 + 190.02, + 196.02, // G-3 + 202.02, + 208.01, // G#3 + 214.02, + 220.02, // A-3 + 226.02, + 233.04, // A#3 + 240.02, + 247.03, // B-3 + 254.03, + 262.00, // C-4 + 269.03, + 277.03, // C#4 + 285.04, + 294.03, // D-4 + 302.07, + 311.04, // D#4 + 320.05, + 330.06, // E-4 + 339.06, + 349.08, // F-4 + 359.06, + 370.09, // F#4 + 381.08, + 392.10, // G-4 + 403.10, + 415.01, // G#4 + 427.05, + 440.12, // A-4 + 453.16, + 466.08, // A#4 + 480.15, + 494.07, // B-4 + 508.16, + 523.09, // C-5 + 539.16, + 554.19, // C#5 + 571.17, + 587.19, // D-5 + 604.14, + 622.09, // D#5 + 640.11, + 659.21, // E-5 + 679.10, + 698.17, // F-5 + 719.21, + 740.18, // F#5 + 762.41, + 784.47, // G-5 + 807.29, + 831.48, // G#5 + 855.32, + 880.57, // A-5 + 906.67, + 932.17, // A#5 + 960.69, + 988.55, // B-5 + 1017.20, + 1046.64, // C-6 + 1077.85, + 1109.93, // C#6 + 1141.79, + 1175.54, // D-6 + 1210.12, + 1244.19, // D#6 + 1281.61, + 1318.43, // E-6 + 1357.42, + 1397.16, // F-6 + 1439.30, + 1480.37, // F#6 + 1523.85, + 1569.97, // G-6 + 1614.58, + 1661.81, // G#6 + 1711.87, + 1762.45, // A-6 + 1813.34, + 1864.34, // A#6 + 1921.38, + 1975.46, // B-6 + 2036.14, + 2093.29, // C-7 + 2157.64, + 2217.80, // C#7 + 2285.78, + 2353.41, // D-7 + 2420.24, + 2490.98, // D#7 + 2565.97, + 2639.77, // E-7 + +}; + +static boolean I_Buzzer_InitSound(bool _use_sfx_prefix) { + lilka::serial_log("I_Buzzer_InitSound"); + use_sfx_prefix = _use_sfx_prefix; + return true; +} + +static void I_Buzzer_ShutdownSound(void) { + lilka::serial_log("I_Buzzer_ShutdownSound"); +} + +static void GetSfxLumpName(sfxinfo_t* sfx, char* buf, size_t buf_len) { + // Linked sfx lumps? Get the lump number for the sound linked to. + + if (sfx->link != NULL) { + sfx = sfx->link; + } + + // DP prefix means PC speaker sound lump. + + if (use_sfx_prefix) { + M_snprintf(buf, buf_len, "dp%s", DEH_String(sfx->name)); + } else { + M_StringCopy(buf, DEH_String(sfx->name), buf_len); + } +} + +static int I_Buzzer_GetSfxLumpNum(sfxinfo_t* sfx) { + char namebuf[9]; + + GetSfxLumpName(sfx, namebuf, sizeof(namebuf)); + + return W_GetNumForName(namebuf); +} + +static void I_Buzzer_UpdateSound(void) { + // Nothing to do here. +} + +static void I_Buzzer_UpdateSoundParams(int handle, int vol, int sep) { + // Nothing to do here. +} + +static int I_Buzzer_StartSound(sfxinfo_t* sfxinfo, int channel, int vol, int sep) { + // lilka::serial_log("I_Buzzer_StartSound: %s, channel: %d", DEH_String(sfxinfo->name), channel); + // Get sound lump + int lump = I_Buzzer_GetSfxLumpNum(sfxinfo); + if (lump == -1) { + lilka::serial_log("I_Buzzer_StartSound: I_Buzzer_GetSfxLumpNum failed"); + return -1; + } + + uint32_t lumplen = W_LumpLength(lump); + // lilka::serial_log("I_Buzzer_StartSound: lump %d, length %d", lump, lumplen); + + // Load sound lump + byte* data = static_cast(W_CacheLumpNum(lump, PU_CACHE)); // TODO - free + if (data == NULL) { + lilka::serial_log("I_Buzzer_StartSound: W_CacheLumpNum failed"); + return -1; + } + + if (lumplen < 4) { + // Invalid sound + lilka::serial_log("I_Buzzer_StartSound: Invalid sound"); + return false; + } + + uint32_t length = data[2] | (data[3] << 8); + + // Skip header + data += 4; + length -= 4; + + lilka::Tone* tone = new lilka::Tone[length]; + for (int i = 0; i < length; i++) { + tone[i].size = 1; + tone[i].frequency = doomNoteToFreq[data[i]]; + } + // Each sample is played for 1/140 s, so BPM is 8400 + lilka::buzzer.playMelody(const_cast(tone), length, 8400); + delete[] tone; + + return 0; // Return channel number? +} + +static void I_Buzzer_StopSound(int handle) { + // Nothing to do here. +} + +static boolean I_Buzzer_SoundIsPlaying(int handle) { + return false; // TODO +} + +static void I_Buzzer_PrecacheSounds(sfxinfo_t* sounds, int num_sounds) { + // Nothing to do here. +} + +sound_module_t sound_module_Buzzer = { + sound_devices, + arrlen(sound_devices), + I_Buzzer_InitSound, + I_Buzzer_ShutdownSound, + I_Buzzer_GetSfxLumpNum, + I_Buzzer_UpdateSound, + I_Buzzer_UpdateSoundParams, + I_Buzzer_StartSound, + I_Buzzer_StopSound, + I_Buzzer_SoundIsPlaying, + I_Buzzer_PrecacheSounds, +}; diff --git a/firmware/doom/src/i_defaultmusic.cpp b/firmware/doom/src/i_defaultmusic.cpp new file mode 100644 index 00000000..d91924cd --- /dev/null +++ b/firmware/doom/src/i_defaultmusic.cpp @@ -0,0 +1,54 @@ +extern "C" { +#include "i_sound.h" +} + +static snddevice_t music_devices[] = { + // SNDDEVICE_PAS, + // SNDDEVICE_GUS, + // SNDDEVICE_WAVEBLASTER, + // SNDDEVICE_SOUNDCANVAS, + // SNDDEVICE_GENMIDI, + // SNDDEVICE_AWE32, +}; + +static boolean I_Default_InitMusic(void) { + return false; +} +static void I_Default_ShutdownMusic(void) { +} +static void I_Default_SetMusicVolume(int volume) { +} +static void I_Default_PlaySong(void* handle, boolean looping) { +} +static void I_Default_PauseSong(void) { +} +static void I_Default_ResumeSong(void) { +} +static void I_Default_StopSong(void) { +} +static void* I_Default_RegisterSong(void* data, int len) { + return 0; +} +static void I_Default_UnRegisterSong(void* handle) { +} +static boolean I_Default_MusicIsPlaying(void) { + return false; +} +static void I_Default_PollMusic(void) { +} + +music_module_t DG_music_module = { + music_devices, + arrlen(music_devices), + I_Default_InitMusic, + I_Default_ShutdownMusic, + I_Default_SetMusicVolume, + I_Default_PauseSong, + I_Default_ResumeSong, + I_Default_RegisterSong, + I_Default_UnRegisterSong, + I_Default_PlaySong, + I_Default_StopSong, + I_Default_MusicIsPlaying, + I_Default_PollMusic, +}; diff --git a/firmware/doom/src/i_i2ssound.cpp b/firmware/doom/src/i_i2ssound.cpp new file mode 100644 index 00000000..1f1342a0 --- /dev/null +++ b/firmware/doom/src/i_i2ssound.cpp @@ -0,0 +1,248 @@ +#include +#include +#include "driver/i2s.h" + +extern "C" { +#include "config.h" +#include "i_sound.h" +#include "deh_str.h" +#include "m_misc.h" +#include "w_wad.h" +#include "z_zone.h" +} + +static boolean use_sfx_prefix; + +TaskHandle_t soundTaskHandle = NULL; +SemaphoreHandle_t soundMutexHandle = NULL; + +// Звук - це складно! :D /AD +// Ring buffer for mixing sound +uint16_t* mixerBuffer; +uint32_t mixerBufferStart = 0; +uint32_t mixerBufferEnd = 0; + +extern SemaphoreHandle_t backBufferMutex; + +void soundTask(void* param); +void queueSound(const uint8_t* data, uint32_t length, uint32_t sample_rate, uint8_t vol); + +static snddevice_t sound_devices[] = { + SNDDEVICE_SB, + SNDDEVICE_PAS, + SNDDEVICE_GUS, + SNDDEVICE_WAVEBLASTER, + SNDDEVICE_SOUNDCANVAS, + SNDDEVICE_AWE32, +}; + +static boolean I_I2S_InitSound(bool _use_sfx_prefix) { + lilka::serial_log("I_I2S_InitSound"); + use_sfx_prefix = _use_sfx_prefix; + soundMutexHandle = xSemaphoreCreateMutex(); + + mixerBuffer = static_cast(ps_malloc(65536 * sizeof(uint16_t))); + + xSemaphoreTake( + backBufferMutex, portMAX_DELAY + ); // Acquire back buffer mutex to prevent drawing while initializing I2S + esp_i2s::i2s_config_t cfg = { + .mode = (esp_i2s::i2s_mode_t)(esp_i2s::I2S_MODE_MASTER | esp_i2s::I2S_MODE_TX), + .sample_rate = 11025, + .bits_per_sample = esp_i2s::I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = esp_i2s::I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = + (esp_i2s::i2s_comm_format_t)(esp_i2s::I2S_COMM_FORMAT_STAND_I2S | esp_i2s::I2S_COMM_FORMAT_STAND_MSB), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 2, + .dma_buf_len = 1024, + .use_apll = false, + .tx_desc_auto_clear = true, + }; + // i2s_driver_install(esp_i2s::I2S_NUM_0, &cfg, 2, &soundQueue); + i2s_driver_install(esp_i2s::I2S_NUM_0, &cfg, 0, NULL); + // esp_i2s::i2s_pin_config_t pin_cfg = { + // .bck_io_num = LILKA_I2S_BCLK, + // .ws_io_num = LILKA_I2S_LRCK, + // .data_out_num = LILKA_I2S_DOUT, + // .data_in_num = -1, + // }; + // i2s_set_pin(esp_i2s::I2S_NUM_0, &pin_cfg); + i2s_zero_dma_buffer(esp_i2s::I2S_NUM_0); + xSemaphoreGive(backBufferMutex); + + if (xTaskCreatePinnedToCore(soundTask, "soundTask", 2048, NULL, 1, &soundTaskHandle, 0) != pdPASS) { + lilka::serial_log("I_I2S_StartSound: xTaskCreatePinnedToCore failed"); + } + + return true; +} + +static void I_I2S_ShutdownSound(void) { + lilka::serial_log("I_I2S_ShutdownSound"); + xSemaphoreTake(soundMutexHandle, portMAX_DELAY); + vTaskDelete(soundTaskHandle); + i2s_driver_uninstall(esp_i2s::I2S_NUM_0); + // free(soundTaskStack); + free(mixerBuffer); + xSemaphoreGive(soundMutexHandle); + vSemaphoreDelete(soundMutexHandle); +} + +static void GetSfxLumpName(sfxinfo_t* sfx, char* buf, size_t buf_len) { + // Linked sfx lumps? Get the lump number for the sound linked to. + + if (sfx->link != NULL) { + sfx = sfx->link; + } + + // Doom adds a DS* prefix to sound lumps; Heretic and Hexen don't + // do this. + + if (use_sfx_prefix) { + M_snprintf(buf, buf_len, "ds%s", DEH_String(sfx->name)); + } else { + M_StringCopy(buf, DEH_String(sfx->name), buf_len); + } +} + +static int I_I2S_GetSfxLumpNum(sfxinfo_t* sfx) { + char namebuf[9]; + + GetSfxLumpName(sfx, namebuf, sizeof(namebuf)); + + return W_GetNumForName(namebuf); +} + +static void I_I2S_UpdateSound(void) { + // Nothing to do here. +} + +static void I_I2S_UpdateSoundParams(int handle, int vol, int sep) { + // Nothing to do here. +} + +static int I_I2S_StartSound(sfxinfo_t* sfxinfo, int channel, int vol, int sep) { + // lilka::serial_log("I_I2S_StartSound: %s, channel: %d", DEH_String(sfxinfo->name), channel); + // Get sound lump + int lump = I_I2S_GetSfxLumpNum(sfxinfo); + if (lump == -1) { + lilka::serial_log("I_I2S_StartSound: I_I2S_GetSfxLumpNum failed"); + return -1; + } + + uint32_t lumplen = W_LumpLength(lump); + // lilka::serial_log("I_I2S_StartSound: lump %d, length %d", lump, lumplen); + + // Load sound lump + byte* data = static_cast(W_CacheLumpNum(lump, PU_CACHE)); // TODO - free + if (data == NULL) { + lilka::serial_log("I_I2S_StartSound: W_CacheLumpNum failed"); + return -1; + } + + if (lumplen < 8 || data[0] != 0x03 || data[1] != 0x00) { + // Invalid sound + lilka::serial_log("I_I2S_StartSound: Invalid sound"); + return false; + } + + uint16_t samplerate = (data[3] << 8) | data[2]; + uint32_t length = (data[7] << 24) | (data[6] << 16) | (data[5] << 8) | data[4]; + + if (length > lumplen - 8 || length <= 48) { + lilka::serial_log("I_I2S_StartSound: Invalid sound length"); + return false; + } + + // The DMX sound library seems to skip the first 16 and last 16 + // bytes of the lump - reason unknown. + data += 16; + length -= 32; + + queueSound(data, length, samplerate, vol); + + return 0; // Return channel number? +} + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +void queueSound(const uint8_t* data, uint32_t length, uint32_t sample_rate, uint8_t vol) { + xSemaphoreTake(soundMutexHandle, portMAX_DELAY); + uint32_t pos = mixerBufferStart; + // lilka::serial_log("Buffer: %d -> %d", mixerBufferStart, mixerBufferEnd); + bool mixing = true; + for (int i = 0; i < length; i++) { + uint16_t sample = (data[i] << 6) * vol / 127; + if (pos == mixerBufferEnd) { + mixing = false; + } + mixerBuffer[pos] = + mixing ? (mixerBuffer[pos] * (127 - vol) / 127 + sample * vol / 127) / 2 : sample * vol / 127; + pos++; + if (pos == 65536) { + pos = 0; + } + } + if (!mixing) { + mixerBufferEnd = pos; + // lilka::serial_log("New end: %d", mixerBufferEnd); + } + xSemaphoreGive(soundMutexHandle); +} + +void soundTask(void* param) { + while (1) { + if (mixerBufferStart != mixerBufferEnd) { + xSemaphoreTake(soundMutexHandle, portMAX_DELAY); + size_t written = 0; + TickType_t xLastWakeTime = xTaskGetTickCount(); + esp_i2s::i2s_write( + esp_i2s::I2S_NUM_0, + mixerBuffer + mixerBufferStart, + MIN(mixerBufferEnd - mixerBufferStart, 512) * 2, + &written, + portMAX_DELAY + ); + mixerBufferStart += written / 2; + if (mixerBufferStart >= 65536) { + mixerBufferStart = 0; + } + xSemaphoreGive(soundMutexHandle); + // Try to avoid starting next chunk too early. + // We do this to prevent blocking in i2s_write and thus acquiring the mutex for too long. + // This is done by calculating delay from sample rate & bytes written. + // TODO: BTW - ring buffer mixing sucks with variable sample rates... /AD + vTaskDelayUntil( + &xLastWakeTime, written / 2 * 1000 / 11025 / portTICK_PERIOD_MS * 4 / 5 + ); // Wait 4/5 of the time to prevent buffer underrun. Not the best solution, but works for now. + } + taskYIELD(); + } +} + +static void I_I2S_StopSound(int handle) { + // Nothing to do here. +} + +static boolean I_I2S_SoundIsPlaying(int handle) { + return false; // TODO +} + +static void I_I2S_PrecacheSounds(sfxinfo_t* sounds, int num_sounds) { + // Nothing to do here. +} + +sound_module_t sound_module_I2S = { + sound_devices, + arrlen(sound_devices), + I_I2S_InitSound, + I_I2S_ShutdownSound, + I_I2S_GetSfxLumpNum, + I_I2S_UpdateSound, + I_I2S_UpdateSoundParams, + I_I2S_StartSound, + I_I2S_StopSound, + I_I2S_SoundIsPlaying, + I_I2S_PrecacheSounds, +}; diff --git a/firmware/doom/src/i_nosound.cpp b/firmware/doom/src/i_nosound.cpp new file mode 100644 index 00000000..16c1ce41 --- /dev/null +++ b/firmware/doom/src/i_nosound.cpp @@ -0,0 +1,24 @@ +#include "i_sound.h" + +static snddevice_t sound_devices[] = {}; + +static boolean I_NoSound_InitSound(bool _use_sfx_prefix) { + return false; +} + +static void I_NoSound_ShutdownSound(void) { +} + +sound_module_t sound_module_NoSound = { + sound_devices, + 0, + I_NoSound_InitSound, + I_NoSound_ShutdownSound, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, +}; diff --git a/firmware/doom/src/main.cpp b/firmware/doom/src/main.cpp index 31f213ca..a3809085 100644 --- a/firmware/doom/src/main.cpp +++ b/firmware/doom/src/main.cpp @@ -3,8 +3,8 @@ #include "doom_splash.h" extern "C" { +#include "i_sound.h" #include "doomkeys.h" -#include "m_argv.h" #include "doomgeneric.h" #include "d_alloc.h" } @@ -30,6 +30,13 @@ TaskHandle_t drawTaskHandle; uint32_t* backBuffer = NULL; +sound_module_t DG_sound_module; +extern sound_module_t sound_module_I2S; +extern sound_module_t sound_module_Buzzer; +extern sound_module_t sound_module_NoSound; +int use_libsamplerate = 0; +float libsamplerate_scale = 0.65f; + void gameTask(void* arg); void drawTask(void* arg); @@ -153,6 +160,32 @@ void setup() { } char* argv[3] = {arg, arg2, arg3}; + // Select sound device + lilka::Menu soundMenu("Звуковий пристрій"); + soundMenu.addItem("I2S DAC"); + soundMenu.addItem("П'єзо-динамік"); + soundMenu.addItem("Без звуку"); + lilka::Canvas canvas; + while (!soundMenu.isFinished()) { + soundMenu.update(); + soundMenu.draw(&canvas); + lilka::display.drawCanvas(&canvas); + } + int soundDevice = soundMenu.getCursor(); + + if (soundDevice == 0) { + // I2S DAC + DG_sound_module = sound_module_I2S; + } else if (soundDevice == 1) { + // Buzzer + DG_sound_module = sound_module_Buzzer; + } else { + // No sound + DG_sound_module = sound_module_NoSound; + } + + lilka::display.fillScreen(lilka::colors::Black); + DG_printf("Doomgeneric starting, WAD file: %s\n", arg3); D_AllocBuffers(); @@ -185,6 +218,13 @@ void gameTask(void* arg) { while (1) { doomgeneric_Tick(); taskYIELD(); + // Print free memory + // Serial.print("Free heap: "); + // Serial.print(ESP.getFreeHeap()); + + // Print free stack + // Serial.print(" | Game task free stack: "); + // Serial.println(uxTaskGetStackHighWaterMark(NULL)); } } @@ -217,6 +257,7 @@ void drawTask(void* arg) { lilka::display.setTextBound(0, 0, LILKA_DISPLAY_WIDTH, LILKA_DISPLAY_HEIGHT); lilka::display.setCursor(32, 16); lilka::display.setTextColor(lilka::colors::White, lilka::colors::Black); + lilka::display.fillRect(32, 0, 64, 20, lilka::colors::Black); lilka::display.setFont(FONT_6x12); lilka::display.print(" FPS: "); lilka::display.print(1000 / delta); @@ -275,6 +316,7 @@ bool hadNewLine = true; extern "C" void DG_printf(const char* format, ...) { // Save string to buffer + xSemaphoreTake(backBufferMutex, portMAX_DELAY); char buffer[256]; va_list args; va_start(args, format); @@ -295,6 +337,7 @@ extern "C" void DG_printf(const char* format, ...) { hadNewLine = true; } } + xSemaphoreGive(backBufferMutex); } void loop() { diff --git a/sdk/lib/lilka/src/lilka/buzzer.h b/sdk/lib/lilka/src/lilka/buzzer.h index cb680523..c8492595 100644 --- a/sdk/lib/lilka/src/lilka/buzzer.h +++ b/sdk/lib/lilka/src/lilka/buzzer.h @@ -13,14 +13,14 @@ namespace lilka { // clang-format off typedef enum { NOTE_B0 = 31, - NOTE_C1 = 33, NOTE_CS1 = 35, NOTE_D1 = 37, NOTE_DS1 = 39, NOTE_E1 = 41, NOTE_F1 = 44, NOTE_FS1 = 46, NOTE_G1 = 49, NOTE_GS1 = 52, NOTE_A1 = 55, NOTE_AS1 = 58, NOTE_B1 = 62, - NOTE_C2 = 65, NOTE_CS2 = 69, NOTE_D2 = 73, NOTE_DS2 = 78, NOTE_E2 = 82, NOTE_F2 = 87, NOTE_FS2 = 93, NOTE_G2 = 98, NOTE_GS2 = 104, NOTE_A2 = 110, NOTE_AS2 = 117, NOTE_B2 = 123, - NOTE_C3 = 131, NOTE_CS3 = 139, NOTE_D3 = 147, NOTE_DS3 = 156, NOTE_E3 = 165, NOTE_F3 = 175, NOTE_FS3 = 185, NOTE_G3 = 196, NOTE_GS3 = 208, NOTE_A3 = 220, NOTE_AS3 = 233, NOTE_B3 = 247, - NOTE_C4 = 262, NOTE_CS4 = 277, NOTE_D4 = 294, NOTE_DS4 = 311, NOTE_E4 = 330, NOTE_F4 = 349, NOTE_FS4 = 370, NOTE_G4 = 392, NOTE_GS4 = 415, NOTE_A4 = 440, NOTE_AS4 = 466, NOTE_B4 = 494, - NOTE_C5 = 523, NOTE_CS5 = 554, NOTE_D5 = 587, NOTE_DS5 = 622, NOTE_E5 = 659, NOTE_F5 = 698, NOTE_FS5 = 740, NOTE_G5 = 784, NOTE_GS5 = 831, NOTE_A5 = 880, NOTE_AS5 = 932, NOTE_B5 = 988, - NOTE_C6 = 1047, NOTE_CS6 = 1109, NOTE_D6 = 1175, NOTE_DS6 = 1245, NOTE_E6 = 1319, NOTE_F6 = 1397, NOTE_FS6 = 1480, NOTE_G6 = 1568, NOTE_GS6 = 1661, NOTE_A6 = 1760, NOTE_AS6 = 1865, NOTE_B6 = 1976, - NOTE_C7 = 2093, NOTE_CS7 = 2217, NOTE_D7 = 2349, NOTE_DS7 = 2489, NOTE_E7 = 2637, NOTE_F7 = 2794, NOTE_FS7 = 2960, NOTE_G7 = 3136, NOTE_GS7 = 3322, NOTE_A7 = 3520, NOTE_AS7 = 3729, NOTE_B7 = 3951, - NOTE_C8 = 4186, NOTE_CS8 = 4435, NOTE_D8 = 4699, NOTE_DS8 = 4978, + NOTE_C1 = 33, NOTE_CS1 = 35, NOTE_D1 = 37, NOTE_DS1 = 39, NOTE_E1 = 41, NOTE_F1 = 44, NOTE_FS1 = 46, NOTE_G1 = 49, NOTE_GS1 = 52, NOTE_A1 = 55, NOTE_AS1 = 58, NOTE_B1 = 62, + NOTE_C2 = 65, NOTE_CS2 = 69, NOTE_D2 = 73, NOTE_DS2 = 78, NOTE_E2 = 82, NOTE_F2 = 87, NOTE_FS2 = 93, NOTE_G2 = 98, NOTE_GS2 = 104, NOTE_A2 = 110, NOTE_AS2 = 117, NOTE_B2 = 123, + NOTE_C3 = 131, NOTE_CS3 = 139, NOTE_D3 = 147, NOTE_DS3 = 156, NOTE_E3 = 165, NOTE_F3 = 175, NOTE_FS3 = 185, NOTE_G3 = 196, NOTE_GS3 = 208, NOTE_A3 = 220, NOTE_AS3 = 233, NOTE_B3 = 247, + NOTE_C4 = 262, NOTE_CS4 = 277, NOTE_D4 = 294, NOTE_DS4 = 311, NOTE_E4 = 330, NOTE_F4 = 349, NOTE_FS4 = 370, NOTE_G4 = 392, NOTE_GS4 = 415, NOTE_A4 = 440, NOTE_AS4 = 466, NOTE_B4 = 494, + NOTE_C5 = 523, NOTE_CS5 = 554, NOTE_D5 = 587, NOTE_DS5 = 622, NOTE_E5 = 659, NOTE_F5 = 698, NOTE_FS5 = 740, NOTE_G5 = 784, NOTE_GS5 = 831, NOTE_A5 = 880, NOTE_AS5 = 932, NOTE_B5 = 988, + NOTE_C6 = 1047, NOTE_CS6 = 1109, NOTE_D6 = 1175, NOTE_DS6 = 1245, NOTE_E6 = 1319, NOTE_F6 = 1397, NOTE_FS6 = 1480, NOTE_G6 = 1568, NOTE_GS6 = 1661, NOTE_A6 = 1760, NOTE_AS6 = 1865, NOTE_B6 = 1976, + NOTE_C7 = 2093, NOTE_CS7 = 2217, NOTE_D7 = 2349, NOTE_DS7 = 2489, NOTE_E7 = 2637, NOTE_F7 = 2794, NOTE_FS7 = 2960, NOTE_G7 = 3136, NOTE_GS7 = 3322, NOTE_A7 = 3520, NOTE_AS7 = 3729, NOTE_B7 = 3951, + NOTE_C8 = 4186, NOTE_CS8 = 4435, NOTE_D8 = 4699, NOTE_DS8 = 4978, NOTE_E8 = 5274, NOTE_F8 = 5588, NOTE_FS8 = 5920, NOTE_G8 = 6272, NOTE_GS8 = 6645, NOTE_A8 = 7040, NOTE_AS8 = 7459, NOTE_B8 = 7902, REST = 0 } Note; // clang-format on diff --git a/sdk/lib/lilka/src/lilka/display.cpp b/sdk/lib/lilka/src/lilka/display.cpp index 06228584..a32ab0af 100644 --- a/sdk/lib/lilka/src/lilka/display.cpp +++ b/sdk/lib/lilka/src/lilka/display.cpp @@ -167,7 +167,7 @@ void Display::drawCanvas(Canvas* canvas) { draw16bitRGBBitmap(canvas->x(), canvas->y(), canvas->getFramebuffer(), canvas->width(), canvas->height()); } -Canvas::Canvas() : Arduino_Canvas(LILKA_DISPLAY_WIDTH, LILKA_DISPLAY_HEIGHT, NULL) { +Canvas::Canvas() : Arduino_Canvas(display.width(), display.height(), NULL) { setFont(u8g2_font_10x20_t_cyrillic); setUTF8Print(true); begin(); diff --git a/sdk/lib/lilka/src/lilka/multiboot.cpp b/sdk/lib/lilka/src/lilka/multiboot.cpp index e0e35594..f49996ac 100644 --- a/sdk/lib/lilka/src/lilka/multiboot.cpp +++ b/sdk/lib/lilka/src/lilka/multiboot.cpp @@ -226,7 +226,7 @@ int MultiBoot::finishAndReboot() { String MultiBoot::getFirmwarePath() { Preferences prefs; - prefs.begin("lilka", true); + prefs.begin("lilka", false); String arg = ""; if (prefs.isKey(MULTIBOOT_PATH_KEY)) { arg = prefs.getString(MULTIBOOT_PATH_KEY);