rockbox/apps/radio/presets.c
William Wilgus ad64336e69 [Bugfix] Autoscan when radio off hangs player, record static
Clipzip, ClipPlus other devices may be similar
Autoscan from the main menu and the device hangs at scanning 88.1

try to record with no station set and it just records static

Change-Id: I59fe17dde80736e4fae8445c0aea7fcf5e476317
2026-06-20 11:43:02 -04:00

805 lines
22 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2003 Linus Nielsen Feltzing
*
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include "settings.h"
#include "rbpaths.h"
#include "general.h"
#include "radio.h"
#include "tuner.h"
#include "file.h"
#include "string-extra.h"
#include "misc.h"
#include "pathfuncs.h"
#include "lang.h"
#include "action.h"
#include "list.h"
#include "splash.h"
#include "menu.h"
#include "yesno.h"
#include "keyboard.h"
#include "talk.h"
#include "filetree.h"
#include "dir.h"
#include "presets.h"
#include "core_alloc.h"
static int curr_preset = -1;
int snap_freq_to_grid(int freq);
void remember_frequency(void);
#define FREQ_DISP_DIVISOR (10000)
#define MAX_PRESETS 64
static bool presets_loaded = false;
static bool presets_changed = false;
static int preset_handle = 0;
static int num_presets = 0; /* The number of presets in the preset list */
struct fmstation
{
int frequency; /* In Hz */
char name[MAX_FMPRESET_LEN+1];
};
struct fmstation_buf /* Allocd on first use of presets */
{
struct fmstation fmstation[MAX_PRESETS];
char filepreset[MAX_PATH];
};
static struct fmstation_buf *presets_get(void)
{
if (preset_handle <= 0)
{
preset_handle = core_alloc(sizeof(struct fmstation_buf));
}
if (preset_handle > 0)
{
return core_get_data_pinned(preset_handle);
}
return NULL;
}
static void presets_put(struct fmstation_buf *presets)
{
if (presets != NULL)
{
core_put_data_pinned(presets);
}
}
int radio_current_preset(void)
{
return curr_preset;
}
int radio_preset_count(void)
{
return num_presets;
}
bool presets_have_changed(void)
{
return presets_changed;
}
/* Find a matching preset to freq */
int preset_find(int freq)
{
int ret = -1;
int i;
if(num_presets < 1)
return -1;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
for(i = 0;i < MAX_PRESETS;i++)
{
if(freq == presets->fmstation[i].frequency)
{
ret = i;
break;
}
}
presets_put(presets);
}
return ret;
}
/* Return the closest preset encountered in the search direction with
wraparound. */
static int find_closest_preset(int freq, int direction)
{
int i;
int lowpreset = 0;
int highpreset = 0;
int closest = -1;
if (direction == 0) /* direction == 0 isn't really used */
return 0;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
for (i = 0; i < num_presets; i++)
{
int f = presets->fmstation[i].frequency;
if (f == freq)
{
closest = i; /* Exact match = stop */
break;
}
/* remember the highest and lowest presets for wraparound */
if (f < presets->fmstation[lowpreset].frequency)
lowpreset = i;
if (f > presets->fmstation[highpreset].frequency)
highpreset = i;
/* find the closest preset in the given direction */
if (direction > 0 && f > freq)
{
if (closest < 0 || f < presets->fmstation[closest].frequency)
closest = i;
}
else if (direction < 0 && f < freq)
{
if (closest < 0 || f > presets->fmstation[closest].frequency)
closest = i;
}
}
if (closest < 0)
{
/* no presets in the given direction */
/* wrap around depending on direction */
if (direction < 0)
closest = highpreset;
else
closest = lowpreset;
}
presets_put(presets);
}
return closest;
}
void preset_next(int direction)
{
if (num_presets < 1)
return;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
int curr_freq = radio_get_current_frequency();
if (curr_preset == -1)
curr_preset = find_closest_preset(curr_freq, direction);
else
curr_preset = (curr_preset + direction + num_presets) % num_presets;
/* Must stay on the current grid for the region */
curr_freq = snap_freq_to_grid(presets->fmstation[curr_preset].frequency);
radio_set_current_frequency(curr_freq);
tuner_set(RADIO_FREQUENCY, curr_freq);
remember_frequency();
presets_put(presets);
}
}
void preset_set_current(int preset)
{
curr_preset = preset;
}
/* Speak a preset by number or by spelling its name, depending on settings. */
void preset_talk(int preset, bool fallback, bool enqueue)
{
if (global_settings.talk_file == 1) /* number */
talk_number(preset + 1, enqueue);
else
{ /* spell */
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
if(presets->fmstation[preset].name[0])
talk_spell(presets->fmstation[preset].name, enqueue);
else if(fallback)
talk_value_decimal(presets->fmstation[preset].frequency, UNIT_INT, 6, enqueue);
presets_put(presets);
}
}
}
void radio_save_presets(void)
{
int fd;
int i;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
char *filepreset = presets->filepreset;
fd = creat(filepreset, 0666);
if(fd >= 0)
{
for(i = 0;i < num_presets;i++)
{
fdprintf(fd, "%d:%s\n", presets->fmstation[i].frequency, presets->fmstation[i].name);
}
close(fd);
if (strcasestr(filepreset, FMPRESET_PATH))
set_file(filepreset, global_settings.fmr_file);
presets_changed = false;
}
else
{
splash(HZ, ID2P(LANG_FM_PRESET_SAVE_FAILED));
}
presets_put(presets);
}
}
void radio_load_presets(const char *filename)
{
int fd;
int rc;
char buf[128];
char *freq;
char *name;
bool done = false;
int f;
num_presets = 0;
struct fmstation_buf *presets = presets_get();
if (presets == NULL)
return;
memset(presets->fmstation, 0, sizeof(presets->fmstation));
char *filepreset = presets->filepreset;
/* No Preset in configuration. */
if(filename[0] == '\0' || filename[0] == '-')
{
filepreset[0] = '\0';
presets_put(presets);
return;
}
splash(0, ID2P(LANG_WAIT));
if(filename[0] != '/') /* Preset within radio screen */
{
snprintf(filepreset, MAX_PATH, "%s/%s.fmr",
FMPRESET_PATH, filename);;
}
else
{
strmemccpy(filepreset, filename, MAX_PATH);
}
/* Preset inside the default folder? */
if (strcasestr(filepreset, FMPRESET_PATH))
set_file(filepreset, global_settings.fmr_file);
/* else Temporary preset, loaded until player shuts down. */
fd = open_utf8(filepreset, O_RDONLY);
if(fd >= 0)
{
while(!done && num_presets < MAX_PRESETS)
{
rc = read_line(fd, buf, sizeof(buf));
if(rc > 0)
{
if(settings_parseline(buf, &freq, &name))
{
f = atoi(freq);
if(f) /* For backwards compatibility */
{
struct fmstation * const fms = &presets->fmstation[num_presets];
fms->frequency = f;
strmemccpy(fms->name, name, MAX_FMPRESET_LEN+1);
num_presets++;
}
}
}
else
done = true;
}
close(fd);
}
else /* invalid file name? */
filepreset[0] = '\0';
presets_loaded = num_presets > 0;
presets_changed = false;
presets_put(presets);
}
int radio_get_preset_freq(int preset)
{
int freq = -1;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
if (preset < num_presets)
freq = presets->fmstation[preset].frequency;
presets_put(presets);
}
return freq;
}
int handle_radio_add_preset(void)
{
int ret = 0;
char buf[MAX_FMPRESET_LEN + 1];
if(preset_handle > 0 && num_presets < MAX_PRESETS)
{
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
buf[0] = '\0';
if (!kbd_input(buf, sizeof(buf), NULL))
{
struct fmstation * const fms = &presets->fmstation[num_presets];
strcpy(fms->name, buf);
fms->frequency = radio_get_current_frequency();
num_presets++;
presets_changed = true;
presets_loaded = num_presets > 0;
ret = 1;
}
presets_put(presets);
}
}
else
{
splash(HZ, ID2P(LANG_FM_NO_FREE_PRESETS));
}
return ret;
}
/* needed to know which preset we are edit/delete-ing */
static int selected_preset = -1;
static int radio_edit_preset(void)
{
char buf[MAX_FMPRESET_LEN + 1];
if (num_presets > 0)
{
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
struct fmstation * const fms = &presets->fmstation[selected_preset];
strcpy(buf, fms->name);
if (!kbd_input(buf, MAX_FMPRESET_LEN + 1, NULL))
{
strcpy(fms->name, buf);
presets_changed = true;
}
presets_put(presets);
}
}
return 1;
}
static int radio_delete_preset(void)
{
if (num_presets > 0)
{
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
struct fmstation * const fms = &presets->fmstation[selected_preset];
if (selected_preset >= --num_presets)
selected_preset = num_presets - 1;
memmove(fms, fms + 1, (uintptr_t)(fms + num_presets) -
(uintptr_t)fms);
if (curr_preset >= num_presets)
--curr_preset;
presets_put(presets);
}
}
/* Don't ask to save when all presets are deleted. */
presets_changed = num_presets > 0;
if (!presets_changed)
{
/* The preset list will be cleared, switch to Scan Mode. */
radio_set_mode(RADIO_SCAN_MODE);
curr_preset = -1;
presets_loaded = false;
}
return 1;
}
int preset_list_load(void)
{
char selected[MAX_PATH];
snprintf(selected, sizeof(selected), "%s.%s", global_settings.fmr_file, "fmr");
struct browse_context browse = {
.dirfilter = SHOW_FMR,
.title = str(LANG_FM_PRESET_LOAD),
.icon = Icon_NOICON,
.root = FMPRESET_PATH,
.selected = selected,
};
return !rockbox_browse(&browse);
}
int preset_list_save(void)
{
if(num_presets > 0)
{
bool bad_file_name = true;
if(!dir_exists(FMPRESET_PATH)) /* Check if there is preset folder */
mkdir(FMPRESET_PATH);
struct fmstation_buf *p = presets_get();
char *filepreset = p->filepreset;
create_numbered_filename(filepreset, FMPRESET_PATH, "preset",
".fmr", 2 IF_CNFN_NUM_(, NULL));
while(bad_file_name)
{
if(!kbd_input(filepreset, MAX_PATH, NULL))
{
/* check the name: max MAX_FILENAME (20) chars */
char* p2;
char* p1;
int len;
p1 = strrchr(filepreset, '/');
p2 = p1;
while((p1) && (*p2) && (*p2 != '.'))
p2++;
len = (int)(p2-p1) - 1;
if((!p1) || (len > MAX_FILENAME) || (len == 0))
{
/* no slash, too long or too short */
splash(HZ, ID2P(LANG_INVALID_FILENAME));
}
else
{
/* add correct extension (easier to always write)
at this point, p2 points to 0 or the extension dot */
*p2 = '\0';
strcat(filepreset,".fmr");
bad_file_name = false;
radio_save_presets();
}
}
else
{
/* user aborted */
break;
}
}
presets_put(p);
}
else
splash(HZ, ID2P(LANG_FM_NO_PRESETS));
return 0;
}
int preset_list_clear(void)
{
if (preset_handle <= 0)
return 0;
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
/* Clear all the preset entries */
memset(presets->fmstation, 0, sizeof(presets->fmstation));
num_presets = 0;
presets_loaded = false;
/* The preset list will be cleared switch to Scan Mode. */
radio_set_mode(RADIO_SCAN_MODE);
curr_preset = -1;
presets_changed = false; /* Don't ask to save when clearing the list. */
global_settings.fmr_file[0] = '-';
presets_put(presets);
}
return 0;
}
MENUITEM_FUNCTION(radio_edit_preset_item, MENU_FUNC_CHECK_RETVAL,
ID2P(LANG_FM_EDIT_PRESET),
radio_edit_preset, NULL, Icon_NOICON);
MENUITEM_FUNCTION(radio_delete_preset_item, MENU_FUNC_CHECK_RETVAL,
ID2P(LANG_FM_DELETE_PRESET),
radio_delete_preset, NULL, Icon_NOICON);
static int radio_preset_callback(int action,
const struct menu_item_ex *this_item,
struct gui_synclist *this_list)
{
if (action == ACTION_STD_OK)
action = ACTION_EXIT_AFTER_THIS_MENUITEM;
return action;
(void)this_item;
(void)this_list;
}
MAKE_MENU(handle_radio_preset_menu, ID2P(LANG_PRESET),
radio_preset_callback, Icon_NOICON, &radio_edit_preset_item,
&radio_delete_preset_item);
/* present a list of preset stations */
static const char* presets_get_name(int selected_item, void *data,
char *buffer, size_t buffer_len)
{
struct fmstation * stations = (struct fmstation *) data;
struct fmstation *p = &stations[selected_item];
if(p->name[0])
{
snprintf(buffer, buffer_len, "%s", p->name);
}
else
{
int freq = p->frequency / FREQ_DISP_DIVISOR;
int frac = freq % 100;
freq /= 100;
snprintf(buffer, buffer_len,
str(LANG_FM_DEFAULT_PRESET_NAME), freq, frac);
}
return buffer;
}
static int presets_speak_name(int selected_item, void * data)
{
(void)data;
preset_talk(selected_item, true, false);
return 0;
}
const char* radio_get_preset_name(int preset, char *buffer, size_t buffer_len)
{
buffer[0] = '\0'; /* skin_tokens.c get_radio_token() doesn't check for success */
struct fmstation_buf *presets = presets_get();
if (presets != NULL)
{
presets_get_name(preset, presets->fmstation, buffer, buffer_len);
presets_put(presets);
return buffer;
}
return NULL;
}
int handle_radio_presets(void)
{
struct gui_synclist lists;
int result = 0;
int action = ACTION_NONE;
if(presets_loaded == false)
return result;
struct fmstation_buf *presets = presets_get();
if (presets == NULL)
return result;
gui_synclist_init(&lists, presets_get_name, presets->fmstation, false, 1, NULL);
gui_synclist_set_title(&lists, str(LANG_PRESET), NOICON);
if(global_settings.talk_file)
gui_synclist_set_voice_callback(&lists, presets_speak_name);
gui_synclist_set_nb_items(&lists, num_presets);
gui_synclist_select_item(&lists, curr_preset<0 ? 0 : curr_preset);
gui_synclist_speak_item(&lists);
while (result == 0)
{
gui_synclist_draw(&lists);
list_do_action(CONTEXT_STD, TIMEOUT_BLOCK, &lists, &action);
switch (action)
{
case ACTION_STD_MENU:
if (handle_radio_add_preset())
{
gui_synclist_set_nb_items(&lists, num_presets);
gui_synclist_select_item(&lists, num_presets - 1);
}
break;
case ACTION_STD_CANCEL:
result = 1;
break;
case ACTION_STD_OK:
curr_preset = gui_synclist_get_sel_pos(&lists);
radio_set_current_frequency(presets->fmstation[curr_preset].frequency);
next_station(0);
result = 1;
break;
case ACTION_STD_CONTEXT:
selected_preset = gui_synclist_get_sel_pos(&lists);
do_menu(&handle_radio_preset_menu, NULL, NULL, false);
gui_synclist_set_nb_items(&lists, num_presets);
gui_synclist_select_item(&lists, selected_preset);
gui_synclist_speak_item(&lists);
break;
default:
if(default_event_handler(action) == SYS_USB_CONNECTED)
result = 2;
}
}
presets_put(presets);
return result - 1;
}
int presets_scan(void *viewports)
{
bool do_scan = true;
int curr_freq = radio_get_current_frequency();
long talked_tick = 0;
struct viewport *vp = (struct viewport *)viewports;
FOR_NB_SCREENS(i)
screens[i].set_viewport(vp?&vp[i]:NULL);
if(num_presets > 0) /* Do that to avoid 2 questions. */
do_scan = yesno_pop(ID2P(LANG_FM_CLEAR_PRESETS));
if(do_scan)
{
struct fmstation_buf *presets = presets_get();
if (presets == NULL)
return 1;
if(get_radio_status() == FMRADIO_OFF)
{
audio_stop();
/* turn on radio */
/* This should be done before touching audio settings */
while (!pcm_is_initialized())
sleep(0);
audio_set_input_source(AUDIO_SRC_FMRADIO, SRCF_FMRADIO_PLAYING);
}
const struct fm_region_data * const fmr =
&fm_region_data[global_settings.fm_region];
curr_freq = fmr->freq_min;
num_presets = 0;
memset(presets->fmstation, 0, sizeof(presets->fmstation));
tuner_set(RADIO_MUTE, 1);
while(curr_freq <= fmr->freq_max)
{
int freq, frac;
if(num_presets >= MAX_PRESETS || action_userabort(TIMEOUT_NOBLOCK))
break;
freq = curr_freq / FREQ_DISP_DIVISOR;
frac = freq % 100;
freq /= 100;
if (global_settings.talk_menu &&
TIME_AFTER(current_tick, talked_tick + (HZ * 5)))
{
talked_tick = current_tick;
talk_id(LANG_FM_SCANNING, false);
talk_value_decimal(curr_freq, UNIT_INT, 6, true);
}
/* (voiced above) */
splashf(0, str(LANG_FM_SCANNING), freq, frac);
if(tuner_set(RADIO_SCAN_FREQUENCY, curr_freq))
{
/* add preset */
presets->fmstation[num_presets].name[0] = '\0';
presets->fmstation[num_presets].frequency = curr_freq;
num_presets++;
}
curr_freq += fmr->freq_step;
yield(); /* (voice_thread) */
}
radio_set_current_frequency(curr_freq);
if (get_radio_status() == FMRADIO_PLAYING)
tuner_set(RADIO_MUTE, 0);
presets_changed = true;
FOR_NB_SCREENS(i)
{
screens[i].clear_viewport();
screens[i].update_viewport();
}
if(num_presets > 0)
{
radio_set_current_frequency(presets->fmstation[0].frequency);
radio_set_mode(RADIO_PRESET_MODE);
presets_loaded = true;
next_station(0);
}
else
{
/* Wrap it to beginning or we'll be past end of band */
presets_loaded = false;
next_station(1);
}
presets_put(presets);
}
return 1;
}
void presets_save(void)
{
struct fmstation_buf *p = presets_get();
if (p != NULL)
{
if(p->filepreset[0] == '\0')
preset_list_save();
else
radio_save_presets();
presets_put(p);
}
}
#if 0 /* disabled in draw_progressbar() */
static inline void draw_vertical_line_mark(struct screen * screen,
int x, int y, int h)
{
screen->set_drawmode(DRMODE_COMPLEMENT);
screen->vline(x, y, y+h-1);
}
/* draw the preset markers for a track of length "tracklen",
between (x,y) and (x+w,y) */
void presets_draw_markers(struct screen *screen,
int x, int y, int w, int h)
{
int i,xi;
const struct fm_region_data *region_data =
&(fm_region_data[global_settings.fm_region]);
int len = region_data->freq_max - region_data->freq_min;
for (i=0; i < radio_preset_count(); i++)
{
int freq = radio_get_preset(i)->frequency;
int diff = freq - region_data->freq_min;
xi = x + (w * diff)/len;
draw_vertical_line_mark(screen, xi, y, h);
}
}
#endif