mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-01 20:29:00 +00:00
apps/games/NXDoom: Add support for RTTTL DOOM theme
Implements and RTTTL player music module to play the DOOM theme song over RTTTL. Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
This commit is contained in:
parent
57e761f724
commit
a5c0e9c188
5 changed files with 567 additions and 10 deletions
|
|
@ -107,6 +107,25 @@ config GAMES_NXDOOM_SOUND
|
|||
---help---
|
||||
Not ported! Will not work.
|
||||
|
||||
if GAMES_NXDOOM_SOUND
|
||||
|
||||
config GAMES_NXDOOM_RTTTL_MUSIC
|
||||
bool "RTTTL music"
|
||||
default n
|
||||
depends on AUDIOUTILS_RTTTL_C
|
||||
depends on !DISABLE_PTHREAD
|
||||
---help---
|
||||
Play music using RTTTL.
|
||||
|
||||
config GAMES_NXDOOM_RTTTL_DEVICE
|
||||
string "RTTTL device"
|
||||
default "/dev/pwm0"
|
||||
depends on GAMES_NXDOOM_RTTTL_MUSIC
|
||||
---help---
|
||||
Device for RTTTL output (only PWM currently supported).
|
||||
|
||||
endif # GAMES_NXDOOM_SOUND
|
||||
|
||||
comment "Graphics options"
|
||||
|
||||
config GAMES_NXDOOM_FBPATH
|
||||
|
|
|
|||
|
|
@ -198,6 +198,12 @@ CSRCS += $(DOOMSRCDIR)/s_sound.c
|
|||
CSRCS += $(DOOMSRCDIR)/deh_sound.c
|
||||
endif
|
||||
|
||||
# If we have chosen RTTTL music, then add that source
|
||||
|
||||
ifeq ($(CONFIG_GAMES_NXDOOM_RTTTL_MUSIC),y)
|
||||
CSRCS += $(COMMONSRCDIR)/i_rtttl_music.c
|
||||
endif
|
||||
|
||||
MAINSRC = $(COMMONSRCDIR)/i_main.c
|
||||
|
||||
include $(APPDIR)/Application.mk
|
||||
|
|
|
|||
522
games/NXDoom/src/i_rtttl_music.c
Normal file
522
games/NXDoom/src/i_rtttl_music.c
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
/****************************************************************************
|
||||
* apps/games/NXDoom/src/i_rtttl_music.c
|
||||
*
|
||||
* SPDX-License-Identifier: GPLv2
|
||||
*
|
||||
* Copyright(C) 1993-1996 Id Software, Inc.
|
||||
* Copyright(C) 2005-2014 Simon Howard
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* DESCRIPTION:
|
||||
* System interface for music.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "i_glob.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "doomtype.h"
|
||||
#include "memio.h"
|
||||
#include "mus2mid.h"
|
||||
|
||||
#include "deh_str.h"
|
||||
#include "gusconf.h"
|
||||
#include "i_sound.h"
|
||||
#include "i_swap.h"
|
||||
#include "i_system.h"
|
||||
#include "m_argv.h"
|
||||
#include "m_config.h"
|
||||
#include "m_misc.h"
|
||||
#include "sha1.h"
|
||||
#include "w_wad.h"
|
||||
#include "z_zone.h"
|
||||
|
||||
#ifdef CONFIG_PWM
|
||||
#include <nuttx/timers/pwm.h>
|
||||
#endif
|
||||
|
||||
#include <audioutils/rtttl.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static boolean i_rtttl_init_music(void);
|
||||
static void i_rtttl_shutdown_music(void);
|
||||
static void i_rtttl_set_music_volume(int volume);
|
||||
static void i_rtttl_pause_song(void);
|
||||
static void i_rtttl_resume_song(void);
|
||||
static void *i_rtttl_register_song(void *data, int len);
|
||||
static void i_rtttl_unregister_song(void *handle);
|
||||
static void i_rtttl_play_song(void *handle, boolean looping);
|
||||
static void i_rtttl_stop_song(void);
|
||||
static boolean i_rtttl_musicisplaying(void);
|
||||
static void i_rtttl_pollmusic(void);
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
const char g_doom_rtttl[] =
|
||||
"Doom:d=32,o=5,b=56:f,f,f6,f,f,d#6,f,f,c#6,f,f,b,f,f,c6,c#6,f,f,f6,f,f,d#"
|
||||
"6,f,f,c#6,f,f,8b.,f,f,f6,f,f,d#6,f,f,c#6,f,f,b,f,f,c6,c#6,f,f,f6,f,f,d#"
|
||||
"6,f,f,c#6,f,f,8b.,a#,a#,a#6,a#,a#,g#6,a#,a#,f#6,a#,a#,e6,a#,a#,f6,f#6,a#"
|
||||
",a#,a#6,a#,a#,g#6,a#,a#,f#6,a#,a#,8e6";
|
||||
|
||||
/* List of sound devices that this sound module is used for.
|
||||
* It appears in i_sound.c that the default music device is 'SNDDEVICE_SB',
|
||||
* so I have just put that here. In reality, we may either wish to add an
|
||||
* additional sound device for RTTL, or put this as something else
|
||||
* (PCSPEAKER?). I don't know what most of the snddevice_t enum options
|
||||
* refer to.
|
||||
*/
|
||||
|
||||
static const snddevice_t g_rtttl_sound_devices[] =
|
||||
{
|
||||
SNDDEVICE_SB,
|
||||
};
|
||||
|
||||
/* Player state */
|
||||
|
||||
static void (*g_player)(struct rtttl_tone);
|
||||
static int g_fd = -1;
|
||||
static bool g_playing = false;
|
||||
static bool g_loop = true;
|
||||
|
||||
/* Player thread state */
|
||||
|
||||
static pthread_t g_thread;
|
||||
static pthread_mutex_t g_player_lock;
|
||||
static pthread_cond_t g_pausecond;
|
||||
static pthread_cond_t g_playingcond;
|
||||
static bool g_paused;
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
****************************************************************************/
|
||||
|
||||
const music_module_t g_rtttl_music_module =
|
||||
{
|
||||
.sound_devices = g_rtttl_sound_devices,
|
||||
.num_sound_devices = arrlen(g_rtttl_sound_devices),
|
||||
.init = i_rtttl_init_music,
|
||||
.shutdown = i_rtttl_shutdown_music,
|
||||
.set_music_volume = i_rtttl_set_music_volume,
|
||||
.pause_music = i_rtttl_pause_song,
|
||||
.resume_music = i_rtttl_resume_song,
|
||||
.register_song = i_rtttl_register_song,
|
||||
.unregister_song = i_rtttl_unregister_song,
|
||||
.play_song = i_rtttl_play_song,
|
||||
.stop_song = i_rtttl_stop_song,
|
||||
.music_is_playing = i_rtttl_musicisplaying,
|
||||
.poll = i_rtttl_pollmusic,
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wait_while_paused
|
||||
*
|
||||
* Description:
|
||||
* Wait while the music is paused.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void wait_while_paused(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
|
||||
while (g_paused)
|
||||
{
|
||||
pthread_cond_wait(&g_pausecond, &g_player_lock);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wait_while_stopped
|
||||
*
|
||||
* Description:
|
||||
* Wait until we are told that a song is started (i.e. while song stopped
|
||||
* and nothing is playing).
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void wait_while_stopped(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
|
||||
while (!g_playing)
|
||||
{
|
||||
pthread_cond_wait(&g_playingcond, &g_player_lock);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: play_default
|
||||
*
|
||||
* Description:
|
||||
* Default player which does nothing.
|
||||
*
|
||||
* Input Parameters:
|
||||
* sound - The sound to play
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void play_default(struct rtttl_tone sound)
|
||||
{
|
||||
wait_while_paused();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PWM
|
||||
|
||||
/****************************************************************************
|
||||
* Name: play_pwm
|
||||
*
|
||||
* Description:
|
||||
* RTTTL player for audio sinks using PWM devices
|
||||
*
|
||||
* Input Parameters:
|
||||
* sound - The sound to play
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void play_pwm(struct rtttl_tone sound)
|
||||
{
|
||||
int err;
|
||||
int i;
|
||||
struct pwm_info_s info;
|
||||
|
||||
wait_while_paused(); /* Wait until unpaused */
|
||||
|
||||
/* If we are not meant to be playing (i.e. song was stopped), we should
|
||||
* return from this function.
|
||||
*/
|
||||
|
||||
if (!g_playing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
info.channels[0].channel = 1;
|
||||
info.channels[0].duty = b16divi(uitoub16(50) - 1, 100); /* 50% duty cycle */
|
||||
info.channels[0].cpol = PWM_CPOL_NDEF; /* Default */
|
||||
info.channels[0].dcpol = PWM_DCPOL_NDEF; /* Default */
|
||||
info.frequency = sound.frequency_100hz / 100;
|
||||
|
||||
/* Copy settings to all channels */
|
||||
|
||||
for (i = 1; i < CONFIG_PWM_NCHANNELS; i++)
|
||||
{
|
||||
info.channels[i] = info.channels[0];
|
||||
info.channels[i].channel = i + 1;
|
||||
}
|
||||
|
||||
err = ioctl(g_fd, PWMIOC_SETCHARACTERISTICS, &info);
|
||||
if (err < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to set PWM characteristics: %d\n", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
err = ioctl(g_fd, PWMIOC_START, 0);
|
||||
if (err < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to start PWM: %d\n", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
usleep(sound.duration_us); /* Wait */
|
||||
|
||||
err = ioctl(g_fd, PWMIOC_STOP, 0);
|
||||
if (err < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to stop PWM: %d\n", errno);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void *player_thread(void *arg)
|
||||
{
|
||||
for (; ; )
|
||||
{
|
||||
/* If there is nothing playing (i.e. song stopped or done), block here
|
||||
* until the main DOOM player logic tells us it wants to play a song.
|
||||
*/
|
||||
|
||||
wait_while_stopped();
|
||||
|
||||
rtttl_play(g_doom_rtttl, g_player);
|
||||
|
||||
/* If we are not meant to loop, then after one play we will indicate
|
||||
* that we are 'not playing'. This will causes the thread to block.
|
||||
* Otherwise, this will continue forever.
|
||||
*/
|
||||
|
||||
if (!g_loop)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
g_playing = false;
|
||||
g_paused = false;
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_init_music
|
||||
*
|
||||
* Description:
|
||||
* Choose the appropriate player implementation for RTTTl based on the
|
||||
* configured music device.
|
||||
*
|
||||
* Returned Value:
|
||||
* True on success, false otherwise.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static boolean i_rtttl_init_music(void)
|
||||
{
|
||||
int err;
|
||||
g_player = play_default;
|
||||
#ifdef CONFIG_PWM
|
||||
if (strstr(CONFIG_GAMES_NXDOOM_RTTTL_DEVICE, "pwm"))
|
||||
{
|
||||
g_player = play_pwm;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Initialize thread state */
|
||||
|
||||
pthread_mutex_init(&g_player_lock, NULL);
|
||||
pthread_cond_init(&g_pausecond, NULL);
|
||||
pthread_cond_init(&g_playingcond, NULL);
|
||||
g_paused = true;
|
||||
g_loop = false;
|
||||
g_playing = false;
|
||||
|
||||
/* Start the music playing thread */
|
||||
|
||||
err = pthread_create(&g_thread, NULL, player_thread, NULL);
|
||||
if (err)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We only managed to match with the default player. */
|
||||
|
||||
if (g_player == play_default)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If we're not using the default player, we need to open a file
|
||||
* descriptor.
|
||||
*/
|
||||
|
||||
g_fd = open(CONFIG_GAMES_NXDOOM_RTTTL_DEVICE, O_RDWR);
|
||||
if (g_fd < 0)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Couldn't open RTTL device '" CONFIG_GAMES_NXDOOM_RTTTL_DEVICE
|
||||
"': %d",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_shutdown_music
|
||||
*
|
||||
* Description:
|
||||
* Turns off the music and de-initializes the module.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_shutdown_music(void)
|
||||
{
|
||||
i_rtttl_stop_song(); /* Stop music */
|
||||
pthread_cancel(g_thread); /* Kill thread */
|
||||
close(g_fd); /* Clean up device */
|
||||
}
|
||||
|
||||
static void i_rtttl_set_music_volume(int volume)
|
||||
{
|
||||
return; /* Currently not possible */
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_pause_song
|
||||
*
|
||||
* Description:
|
||||
* Pauses the currently playing song. Right now, this is implemented by
|
||||
* causing the RTTL `play` function to block on a condition variable.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_pause_song(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
g_paused = true;
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_resume_song
|
||||
*
|
||||
* Description:
|
||||
* Resume the currently paused song. Unblocks the RTTL `play` function by
|
||||
* signalling the condition variable.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_resume_song(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
g_paused = false;
|
||||
pthread_cond_signal(&g_pausecond);
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_register_song
|
||||
*
|
||||
* Description:
|
||||
* Appears to be used to register song data from the DOOM lump. Right now,
|
||||
* we are using a statically stored RTTL string so we just ignore this. It
|
||||
* would be good to have RTTL data substituted for lump data in the future
|
||||
* so we can play the different songs provided by DOOM WADS.
|
||||
*
|
||||
* Input Parameters:
|
||||
* data - Song data
|
||||
* len - The length of the song data in bytes
|
||||
*
|
||||
* Returns: In theory, a handle representing the registered song. However, in
|
||||
* practice, we return NULL to indicate this function isn't doing anything.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void *i_rtttl_register_song(void *data, int len)
|
||||
{
|
||||
/* TODO: no idea what this would be for; right now we play only the theme */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_unregister_song
|
||||
*
|
||||
* Description:
|
||||
* Unregisters one of the registered songs from `i_rtttl_register_song`.
|
||||
* Currently unimplemented since we register nothing.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The returned handle to the registered song
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_unregister_song(void *handle)
|
||||
{
|
||||
/* TODO: no idea what this would be for */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_play_song
|
||||
*
|
||||
* Description:
|
||||
* Plays a previously registered song via the RTTTL library.
|
||||
*
|
||||
* Input Parameters:
|
||||
* handle - The handle to a previously registered song
|
||||
* looping - True if the song should loop, false otherwise
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_play_song(void *handle, boolean looping)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
g_playing = true;
|
||||
g_paused = false;
|
||||
g_loop = looping;
|
||||
pthread_cond_signal(&g_playingcond);
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: i_rtttl_stop_song
|
||||
*
|
||||
* Description:
|
||||
* Stops playing the currently playing song (fully, not paused).
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void i_rtttl_stop_song(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_player_lock);
|
||||
g_playing = false;
|
||||
|
||||
/* Song not paused, allows play function to return early by also unblocking
|
||||
* it. Then control is returned to the thread's main loop instead of the
|
||||
* loop inside the RTTL library thread, where the thread can then block
|
||||
* until a new song is played.
|
||||
*/
|
||||
|
||||
if (g_paused)
|
||||
{
|
||||
g_paused = false;
|
||||
pthread_cond_signal(&g_pausecond);
|
||||
}
|
||||
|
||||
g_loop = false;
|
||||
pthread_mutex_unlock(&g_player_lock);
|
||||
}
|
||||
|
||||
static boolean i_rtttl_musicisplaying(void)
|
||||
{
|
||||
return g_playing;
|
||||
}
|
||||
|
||||
static void i_rtttl_pollmusic(void)
|
||||
{
|
||||
/* Unsure what this function does */
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
|
@ -118,10 +118,13 @@ static const sound_module_t *sound_modules[] =
|
|||
|
||||
/* Compiled-in music modules: */
|
||||
|
||||
static const music_module_t *music_modules[] =
|
||||
static const music_module_t *g_music_modules[] =
|
||||
{
|
||||
#if 0
|
||||
&music_opl_module,
|
||||
#endif
|
||||
#ifdef CONFIG_GAMES_NXDOOM_RTTTL_MUSIC
|
||||
&g_rtttl_music_module,
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
|
@ -186,21 +189,21 @@ static void init_music_module(void)
|
|||
|
||||
music_module = NULL;
|
||||
|
||||
for (i = 0; music_modules[i] != NULL; ++i)
|
||||
for (i = 0; g_music_modules[i] != NULL; ++i)
|
||||
{
|
||||
/* Is the music device in the list of devices supported
|
||||
* by this module?
|
||||
*/
|
||||
|
||||
if (snd_device_in_list(snd_musicdevice,
|
||||
music_modules[i]->sound_devices,
|
||||
music_modules[i]->num_sound_devices))
|
||||
g_music_modules[i]->sound_devices,
|
||||
g_music_modules[i]->num_sound_devices))
|
||||
{
|
||||
/* initialize the module */
|
||||
|
||||
if (music_modules[i]->init())
|
||||
if (g_music_modules[i]->init())
|
||||
{
|
||||
music_module = music_modules[i];
|
||||
music_module = g_music_modules[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -239,7 +242,10 @@ static void check_volume_separation(int *vol, int *sep)
|
|||
|
||||
void i_init_sound(gamemission_t mission)
|
||||
{
|
||||
boolean nosound, nosfx, nomusic, nomusicpacks;
|
||||
boolean nosound;
|
||||
boolean nosfx;
|
||||
boolean nomusic;
|
||||
boolean nomusicpacks;
|
||||
|
||||
/* @vanilla
|
||||
*
|
||||
|
|
@ -401,6 +407,7 @@ void i_precache_sounds(sfxinfo_t *sounds, int num_sounds)
|
|||
|
||||
void i_init_music(void)
|
||||
{
|
||||
printf("i_init_music: Music initialized\n");
|
||||
}
|
||||
|
||||
void i_shutdown_music(void)
|
||||
|
|
@ -479,6 +486,7 @@ void i_unregister_song(void *handle)
|
|||
|
||||
void i_play_song(void *handle, boolean looping)
|
||||
{
|
||||
printf("i_play_song: Playing on %p\n", active_music_module);
|
||||
if (active_music_module != NULL)
|
||||
{
|
||||
active_music_module->play_song(handle, looping);
|
||||
|
|
|
|||
|
|
@ -266,11 +266,13 @@ extern int snd_pitchshift;
|
|||
extern int use_libsamplerate;
|
||||
extern float libsamplerate_scale;
|
||||
|
||||
extern const sound_module_t sound_sdl_module;
|
||||
extern const sound_module_t sound_pcsound_module;
|
||||
extern const music_module_t music_sdl_module;
|
||||
|
||||
extern const music_module_t music_pack_module;
|
||||
extern const music_module_t music_fl_module;
|
||||
|
||||
#ifdef CONFIG_GAMES_NXDOOM_RTTTL_MUSIC
|
||||
extern const music_module_t g_rtttl_music_module;
|
||||
#endif
|
||||
|
||||
/* For native music module: */
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue