Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions audioutils/rtttl-c/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/rtttl-c
17 changes: 17 additions & 0 deletions audioutils/rtttl-c/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
config AUDIOUTILS_RTTTL_C
bool "RTTTL parsing library"
default n
---help---
Simple library for parsing Ring Tone Text Transfer Language (RTTTL)

Define what to do with a tone:

void
play_tone(struct rtttl_tone tone)
{
/* TODO */
}

and then play RTTTL string:

rtttl_play("...", play_tone);
25 changes: 25 additions & 0 deletions audioutils/rtttl-c/Make.defs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
############################################################################
# apps/audioutils/rtttl-c/Make.defs
#
# 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.
#
############################################################################

ifneq ($(CONFIG_AUDIOUTILS_RTTTL_C),)
CONFIGURED_APPS += $(APPDIR)/audioutils/rtttl-c
endif
36 changes: 36 additions & 0 deletions audioutils/rtttl-c/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
############################################################################
# apps/audioutils/rtttl-c/Makefile
#
# 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.
#
############################################################################

include $(APPDIR)/Make.defs

CSRCS = rtttl.c

create_includes: rtttl.h
$(Q) cp $< $(APPDIR)/include/audioutils

context::
$(Q) $(MAKE) create_includes

distclean::
$(call DELFILE, $(APPDIR)/include/audioutils/rtttl.h)

include $(APPDIR)/Application.mk
271 changes: 271 additions & 0 deletions audioutils/rtttl-c/rtttl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/*

Check failure on line 1 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

C comment opening on separate line
* SPDX-FileCopyrightText: 2026 Jiri Vlasak

Check failure on line 2 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Path relative to repository other than "nuttx" must begin with the root directory
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: MIT
*/

#include "rtttl.h"

Check warning on line 8 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

#include outside of 'Included Files' section

static int
is_allowed_in_part1(char c)
{
return ':' != c && ' ' <= c && c < 0x7f;

Check failure on line 13 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

TABs found. First detected
}

static int
isnum(char c)
{
return '0' <= c && c <= '9';
}

static int
getnum(char const **p)
{
int num = (int)(**p - '0');
(*p)++;

while (isnum(**p)) {

Check failure on line 28 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
num *= 10;
num += (int)(**p - '0');
(*p)++;
}

return num;
}

static int
is_duration(int n)
{
return 1 == n || 2 == n || 4 == n || 8 == n || 16 == n || 32 == n;
}

static int
is_octave(int n)
{
return 4 == n || 5 == n || 6 == n || 7 == n;
}

static void
skip_spaces(char const **p)
{
while (' ' == **p || '\t' == **p) {

Check failure on line 52 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
(*p)++;
}
}

struct rtttl_conf
rtttl_parse_conf(char const *rtttl_string)
{
char const *p = rtttl_string;
int num;
struct rtttl_conf conf = {

Check failure on line 62 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
.error = 0,
.duration = 4,
.octave = 6,
.beat = 63,
};

/* Skip part 1 */

while (is_allowed_in_part1(*p)) {

Check failure on line 71 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
p++;
}
p++;

/* Parse part 2 */

while (':' != *p && '\0' != *p) {

Check failure on line 78 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
skip_spaces(&p); /* Not by specification, but hey. */

switch (*p) {

Check failure on line 81 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
case 'd':
p++;
if ('=' != *p) {

Check failure on line 84 in audioutils/rtttl-c/rtttl.c

View workflow job for this annotation

GitHub Actions / check

Left bracket not on separate line
conf.error = -2;
}
p++;
num = -1;
if (isnum(*p)) {
num = getnum(&p);
} else {
conf.error = -3;
}
if (is_duration(num)) {
conf.duration = num;
} else {
conf.error = -4;
}
break;
case 'o':
p++;
if ('=' != *p) {
conf.error = -2;
}
p++;
num = -1;
if (isnum(*p)) {
num = getnum(&p);
} else {
conf.error = -5;
}
if (is_octave(num)) {
conf.octave = num;
} else {
conf.error = -6;
}
break;
case 'b':
p++;
if ('=' != *p) {
conf.error = -2;
}
p++;
num = -1;
if (isnum(*p)) {
conf.beat = getnum(&p);
} else {
conf.error = -7;
}
break;
default:
conf.error = -2;
}
if (',' == *p) {
p++;
}
}

return conf;
}

static int
tone_frequency(char const **p)
{
/* See https://muted.io/note-frequencies/ */

char tone = **p;
(*p)++;
int hash = **p == '#';

if (hash) {
(*p)++;
}

switch (tone) {
case 'p':
case 'P':
return 0;
case 'c':
case 'C':
return hash ? 27718 : 26163;
case 'd':
case 'D':
return hash ? 31113 : 29366;
case 'e':
case 'E':
return hash ? 34923 : 32963;
case 'f':
case 'F':
return hash ? 36999 : 34923;
case 'g':
case 'G':
return hash ? 41530 : 39200;
case 'a':
case 'A':
return hash ? 46616 : 44000;
case 'b':
case 'B':
return hash ? 52325 : 49388;
}
return -8;
}

int
rtttl_play(char const *rtttl_string, void (*make_tone)(struct rtttl_tone))
{
struct rtttl_conf conf = rtttl_parse_conf(rtttl_string);
if (0 != conf.error) {
return conf.error;
}

int num;
struct rtttl_tone tone;
char const *p = rtttl_string;

/* Skip part 1 */

while (is_allowed_in_part1(*p)) {
p++;
}
p++;

/* Skip part 2 */

while (':' != *p && '\0' != *p) {
p++;
}
p++;

/* Play part 3 */

while ('\0' != *p) {
skip_spaces(&p); /* Not by specification, but hey. */

tone.duration_us = 60 * 1000000;
tone.duration_us /= conf.beat;
tone.duration_us *= 4; /* Usually quarter notes per minute. */
num = -1;
if (isnum(*p)) {
num = getnum(&p);
}
if (!is_duration(num)) {
num = conf.duration;
}
tone.duration_us /= num;

tone.frequency_100hz = tone_frequency(&p);
if (0 > tone.frequency_100hz) {
return tone.frequency_100hz;
}

/* People sometimes write dot here. Why? */
if ('.' == *p) {
tone.duration_us >>= 1;
tone.duration_us *= 3;
p++;
}

num = -1;
if (isnum(*p)) {
num = getnum(&p);
}
if (!is_octave(num)) {
num = conf.octave;
}
tone.frequency_100hz <<= (num - 4); /* Double frequency? */

/* Frequency is returned in *100 Hz due to scaling. */
if (0 != tone.frequency_100hz) {
tone.period_us = 100 * 1000000 / tone.frequency_100hz;
} else {
tone.period_us = 0; /* Pause. */
}

/* People should put dot here if they want to. */
if ('.' == *p) {
tone.duration_us >>= 1;
tone.duration_us *= 3;
p++;
}

make_tone(tone);

if (',' == *p) {
p++;
} else if ('\0' != *p) {
return -9;
}
}
return 0;
}
Loading
Loading