!examples/audio_rtttl: Implement example using RTTTL library

This commit fixes the audio_rtttl example (which was undocumented and
previously used kernel-level functions) such that now it uses the RTTTL
parsing library. Contributors can implement 'players' for different
types of audio sinks. Currently, PWM is supported.

BREAKING: The previous version of this example only supported a single
board (Spresense) and uses public interfaces from the kernel code. Now
it only uses user-space interfaces and supports any board implementing
those interfaces. Users who relied on the old behaviour should submit a
patch to add a new player to this application which uses the desired
audio sink for the Spresense through user-space interfaces.

Signed-off-by: Matteo Golin <matteo.golin@gmail.com>
This commit is contained in:
Matteo Golin 2026-07-11 19:31:00 -04:00 committed by Xiang Xiao
parent 3762a64bc6
commit b39228cd41
7 changed files with 319 additions and 388 deletions

View file

@ -29,5 +29,5 @@ if(CONFIG_EXAMPLES_AUDIO_SOUND)
STACKSIZE
${CONFIG_EXAMPLES_AUDIO_SOUND_STACKSIZE}
SRCS
audio_rttl.cxx)
audio_rttl_main.c)
endif()

View file

@ -5,9 +5,11 @@
config EXAMPLES_AUDIO_SOUND
bool "Audio tone generator example (RTTL player)"
depends on AUDIO
depends on AUDIOUTILS_RTTTL_C
default n
---help---
Enable the audio RTTL player example
Enable the audio RTTL player example.
if EXAMPLES_AUDIO_SOUND
@ -23,4 +25,12 @@ config EXAMPLES_AUDIO_SOUND_STACKSIZE
int "Audio sound stack size"
default DEFAULT_TASK_STACKSIZE
comment "Program options"
config EXAMPLES_AUDIO_SOUND_MAXLEN
int "Maximum RTTL string length"
default 256
---help---
The maximum length of an RTTL string that can be read from a file.
endif

View file

@ -30,6 +30,6 @@ STACKSIZE = $(CONFIG_EXAMPLES_AUDIO_SOUND_STACKSIZE)
# Audio Example
MAINSRC = audio_rttl.cxx
MAINSRC = audio_rttl_main.c
include $(APPDIR)/Application.mk

View file

@ -1,361 +0,0 @@
/****************************************************************************
* apps/examples/audio_rttl/audio_rttl.cxx
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <arch/board/board.h>
#include <arch/chip/audio.h>
#include "audio_rttl.h"
/****************************************************************************
* Private Types
****************************************************************************/
/* Note - note freq on octave 1 */
#define C 16.35
#define CD 17.32
#define D 18.35
#define DE 19.45
#define E 20.60
#define F 21.83
#define FG 23.12
#define G 24.50
#define A 27.50
#define AB 29.14
#define B 30.84
/* Octave - base freq multiplier for the base freq */
#define OCT2 4
#define OCT3 8
#define OCT4 16
#define OCT5 32
#define OCT6 64
#define OCT7 128
#define OCT8 256
/****************************************************************************
* Public Functions
****************************************************************************/
extern "C" int main(int argc, FAR char *argv[])
{
int freq;
float base_freq;
char temp_note;
int octave;
int duration;
int note_duration;
int base_duration;
int bpm;
int cnt;
/* Set I/O parameters for power on. */
if (!board_audio_power_control(true))
{
printf("Error: board_audio_power_control() failure.\n");
return 1;
}
printf("Start Audio RTTL player\n");
bpm = 0;
octave = 0;
base_duration = 0;
note_duration = 0;
while (*g_song != ':')
{
g_song++;
}
g_song++;
if (*g_song == 'd')
{
g_song = g_song + 2;
base_duration = *g_song - '0';
g_song++;
if ((*g_song >= '0') && (*g_song <= '9'))
{
base_duration = (base_duration * 10) + (*g_song - '0');
g_song++;
}
g_song++;
}
if (*g_song == 'o')
{
g_song = g_song + 2;
octave = *g_song - '0';
g_song = g_song + 2;
}
if (octave == 2)
{
octave = OCT2;
}
else if (octave == 3)
{
octave = OCT3;
}
else if (octave == 4)
{
octave = OCT4;
}
else if (octave == 5)
{
octave = OCT5;
}
else if (octave == 6)
{
octave = OCT6;
}
else if (octave == 7)
{
octave = OCT7;
}
else if (octave == 8)
{
octave = OCT8;
}
if (*g_song == 'b')
{
g_song = g_song + 2;
bpm = 0;
for (cnt = 1; cnt <= 3; cnt++)
{
if (*g_song != ':')
{
bpm = bpm * 10 + (*g_song - '0');
g_song++;
}
}
g_song++;
}
do
{
if ((*g_song >= '0') && (*g_song <= '9'))
{
note_duration = *g_song - '0';
g_song++;
}
if ((*g_song >= '0') && (*g_song <= '9'))
{
note_duration = (note_duration * 10) + (*g_song - '0');
g_song++;
}
if (note_duration > 0)
{
duration = ((60 * 1000L / bpm) * 4) / note_duration;
}
else
{
duration = ((60 * 1000L / bpm) * 4) / base_duration;
}
base_freq = 0;
temp_note = ' ';
switch (*g_song)
{
case 'c':
{
temp_note = 'c';
base_freq = C;
break;
}
case 'd':
{
temp_note = 'd';
base_freq = D;
break;
}
case 'e':
{
temp_note = 'e';
base_freq = E;
break;
}
case 'f':
{
temp_note = 'f';
base_freq = F;
break;
}
case 'g':
{
temp_note = 'g';
base_freq = G;
break;
}
case 'a':
{
temp_note = 'a';
base_freq = A;
break;
}
case 'b':
{
temp_note = 'b';
base_freq = B;
break;
}
case 'p':
{
temp_note = 'p';
break;
}
default:
break;
}
g_song++;
if (*g_song == '.')
{
duration += duration / 2;
g_song++;
}
if (*g_song == '#')
{
if (temp_note == 'c')
{
base_freq = CD;
}
if (temp_note == 'd')
{
base_freq = DE;
}
if (temp_note == 'f')
{
base_freq = FG;
}
if (temp_note == 'a')
{
base_freq = AB;
}
g_song++;
}
if ((*g_song - '0') == '2')
{
octave = OCT2;
}
if ((*g_song - '0') == '3')
{
octave = OCT3;
}
if ((*g_song - '0') == '4')
{
octave = OCT4;
}
if ((*g_song - '0') == '5')
{
octave = OCT5;
}
if ((*g_song - '0') == '6')
{
octave = OCT6;
}
if ((*g_song - '0') == '7')
{
octave = OCT7;
}
if ((*g_song - '0') == '8')
{
octave = OCT8;
}
g_song++;
if (*g_song == ',')
{
g_song++;
}
/* play the sound */
freq = ceil(base_freq * octave);
if (!board_audio_tone_generator(1, -40, freq))
{
break;
}
usleep(duration * 1000L);
}
while (*g_song);
/* Sound off. */
if (!board_audio_tone_generator(0, 0, 0))
{
printf("Error: board_audio_tone_generator() failure.\n");
return 1;
}
/* Set I/O parameters for power off. */
if (!board_audio_power_control(false))
{
printf("Error: board_audio_power_control() failure.\n");
return 1;
}
printf("Done.\n");
return 0;
}

View file

@ -1,24 +0,0 @@
/****************************************************************************
* apps/examples/audio_rttl/audio_rttl.h
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
FAR char *g_song = (FAR char *)
"Test:d=4,o=6,b=90:c,c#,d,d#,e,f,f#,g,a,a#,b,p,b,a#,a,g,f#,f,e,d#,d,c#,c";

View file

@ -0,0 +1,305 @@
/****************************************************************************
* apps/examples/audio_rttl/audio_rttl_main.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#ifdef CONFIG_PWM
#include <nuttx/timers/pwm.h>
#endif
#include <audioutils/rtttl.h>
/****************************************************************************
* Private Types
****************************************************************************/
struct player_s
{
void (*play)(struct rtttl_tone); /* Play RTTTL sound function */
void (*teardown)(void); /* Clean-up function */
void *arg; /* Private data for functions */
};
/****************************************************************************
* Private Data
****************************************************************************/
/* Default song if none was passed. Otherwise, used as buffer for reading
* from a file.
*/
static char g_default_song[CONFIG_EXAMPLES_AUDIO_SOUND_MAXLEN] =
"Test:d=4,o=6,b=90:c,c#,d,d#,e,f,f#,g,a,a#,b,p,b,a#,a,g,"
"f#,f,e,d#,d,c#,c";
/* The selected player is a global variable so it can be accessed from within
* the player functions properly.
*/
static struct player_s g_player;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: play_default
*
* Description:
* The default player choice; prints an error and exits.
*
* Input Parameters:
* sound - The sound to play
*
****************************************************************************/
static void play_default(struct rtttl_tone sound)
{
fprintf(stderr, "No player selected!\n");
return;
}
/****************************************************************************
* Name: teardown_default
*
* Description:
* Default tear-down function. Does nothing.
*
****************************************************************************/
static void teardown_default(void)
{
/* No-op */
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;
int fd = *((int *)g_player.arg);
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(fd, PWMIOC_SETCHARACTERISTICS, &info);
if (err < 0)
{
fprintf(stderr, "Failed to set PWM characteristics: %d\n", errno);
return;
}
err = ioctl(fd, PWMIOC_START, 0);
if (err < 0)
{
fprintf(stderr, "Failed to start PWM: %d\n", errno);
return;
}
usleep(sound.duration_us); /* Wait */
err = ioctl(fd, PWMIOC_STOP, 0);
if (err < 0)
{
fprintf(stderr, "Failed to stop PWM: %d\n", errno);
}
}
/****************************************************************************
* Name: teardown_pwm
*
* Description:
* Teardown function for PWM players
*
****************************************************************************/
static void teardown_pwm(void)
{
int fd = *((int *)g_player.arg);
close(fd);
}
#endif /* CONFIG_PWM */
/****************************************************************************
* Name: print_usage
*
* Description:
* Prints out the usage information for the program.
*
* Input Parameters:
* sink - The stream to output the usage information
*
****************************************************************************/
static void print_usage(FILE *sink)
{
fprintf(sink, "USAGE: audio_rttl <device path> [-s string] [-f file]\n");
fprintf(sink, "Ex: audio_rttl /dev/tone0\n");
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
int err;
int fd;
int c;
FILE *fptr;
size_t bread;
const char *song = g_default_song;
const char *device = NULL;
const char *file = NULL;
g_player.play = play_default;
g_player.teardown = teardown_default;
g_player.arg = NULL;
while ((c = getopt(argc, argv, ":hs:f:")) != -1)
{
switch (c)
{
case 'h':
print_usage(stdout);
break;
case 's':
song = optarg;
break;
case 'f':
file = optarg;
break;
case '?':
fprintf(stderr, "Unknown option -%c\n", optopt);
print_usage(stderr);
return EXIT_FAILURE;
}
}
if (argc <= optind)
{
fprintf(stderr, "Missing argument for device path.\n");
return EXIT_FAILURE;
}
device = argv[optind++];
/* Based on the device path, we choose which player should be used and
* perform the setup.
*/
#ifdef CONFIG_PWM
if (strstr(device, "pwm"))
{
fd = open(device, O_RDWR);
if (fd < 0)
{
fprintf(stderr, "Failed to open %s: %d\n", device, errno);
return EXIT_FAILURE;
}
else
{
g_player.arg = &fd;
}
g_player.play = play_pwm;
g_player.teardown = teardown_pwm;
}
#endif /* CONFIG_PWM */
/* If the user passed a file and a song string, use the string. Otherwise,
* read from the file
*/
if (file != NULL && song == g_default_song)
{
fptr = fopen(file, "r");
if (fptr == NULL)
{
fprintf(stderr, "Couldn't open file '%s': %d", file, errno);
g_player.teardown();
return EXIT_FAILURE;
}
bread =
fread(g_default_song, sizeof(char), sizeof(g_default_song), fptr);
/* Ensure null termination of the string. */
if (bread <= sizeof(g_default_song))
{
g_default_song[bread - 1] = '\0';
}
fclose(fptr);
}
/* Play the song */
err = rtttl_play(song, g_player.play);
if (err != 0)
{
fprintf(stderr, "Failed to play song: %d\n", err);
return err;
}
/* Tear down the player and exit */
g_player.teardown();
return EXIT_SUCCESS;
}

1
include/.gitignore vendored
View file

@ -1,2 +1,3 @@
/netutils/cJSON.h
/netutils/cJSON_Utils.h
/audioutils/rtttl.h