1245c5fe61
The implementation is not very complicated but there are a few things worth noting. There was a previous "speaker enable" setting but it was a boolean. I decided to replace it with a choice setting that has 2 options (on, off) if headphones cannot be detect on this target, or 3 options (on, off, auto) if we can detect headphones. This will break the old setting on target that cannot detect jack but it makes the code more uniform and avoid maintaining two settings with more #ifdef. The third option (auto) uses the LANG_AUTO text, which I think is clear enough (disable speaker on jack plug). In order to avoid code duplication (both in apps and firmware), I decided to keep the audiohw_enable_speaker function as-is: it takes a boolean and doesn't care about the speaker policy. I introduced a new audio_enable_speaker that takes directly the mode (which follows the setting encoding): 0=off, 1=on and 2=auto. This way one calls audio_enable_speaker and it changes the speaker once to reflect the request mode. The apps code then uses this function in the places where it makes sense: on setting load, setting change and jack (un)plug event. Change-Id: I027873f698eb4bc365d7c02b515297806355d9e2
2238 lines
72 KiB
C
2238 lines
72 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2002 by 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 "system.h"
|
|
#include "power.h"
|
|
#include "powermgmt.h"
|
|
#include "lcd.h"
|
|
#include "led.h"
|
|
#include "audio.h"
|
|
#if CONFIG_CODEC == SWCODEC
|
|
#include "thread.h"
|
|
#include "enc_config.h"
|
|
#include "playback.h"
|
|
#if defined(HAVE_SPDIF_IN) || defined(HAVE_SPDIF_OUT)
|
|
#include "spdif.h"
|
|
#endif
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
#include "pcm_record.h"
|
|
#include "recording.h"
|
|
#include "mp3_playback.h"
|
|
#include "button.h"
|
|
#include "kernel.h"
|
|
#include "settings.h"
|
|
#include "lang.h"
|
|
#include "font.h"
|
|
#include "icons.h"
|
|
#include "icon.h"
|
|
#include "screens.h"
|
|
#include "peakmeter.h"
|
|
#include "menu.h"
|
|
#include "sound_menu.h"
|
|
#include "timefuncs.h"
|
|
#include "debug.h"
|
|
#include "misc.h"
|
|
#include "pathfuncs.h"
|
|
#include "tree.h"
|
|
#include "string.h"
|
|
#include "dir.h"
|
|
#include "errno.h"
|
|
#include "talk.h"
|
|
#include "sound.h"
|
|
#include "storage.h"
|
|
#if (CONFIG_STORAGE & STORAGE_ATA) && (CONFIG_LED == LED_REAL) \
|
|
&& !defined(SIMULATOR)
|
|
#include "ata.h"
|
|
#endif
|
|
#include "splash.h"
|
|
#include "screen_access.h"
|
|
#include "action.h"
|
|
#include "radio.h"
|
|
#include "viewport.h"
|
|
#include "list.h"
|
|
#include "general.h"
|
|
#include "appevents.h"
|
|
|
|
#ifdef HAVE_RECORDING
|
|
/* This array holds the record timer interval lengths, in seconds */
|
|
static const unsigned long rec_timer_seconds[] =
|
|
{
|
|
0, /* 0 means OFF */
|
|
5*60, /* 00:05 */
|
|
10*60, /* 00:10 */
|
|
15*60, /* 00:15 */
|
|
30*60, /* 00:30 */
|
|
60*60, /* 01:00 */
|
|
74*60, /* 01:14 */
|
|
80*60, /* 01:20 */
|
|
2*60*60, /* 02:00 */
|
|
4*60*60, /* 04:00 */
|
|
6*60*60, /* 06:00 */
|
|
8*60*60, /* 08:00 */
|
|
10L*60*60, /* 10:00 */
|
|
12L*60*60, /* 12:00 */
|
|
18L*60*60, /* 18:00 */
|
|
24L*60*60 /* 24:00 */
|
|
};
|
|
|
|
static unsigned int rec_timesplit_seconds(void)
|
|
{
|
|
return rec_timer_seconds[global_settings.rec_timesplit];
|
|
}
|
|
|
|
/* This array holds the record size interval lengths, in bytes */
|
|
static const unsigned long rec_size_bytes[] =
|
|
{
|
|
0, /* 0 means OFF */
|
|
5*1024*1024, /* 5MB */
|
|
10*1024*1024, /* 10MB */
|
|
15*1024*1024, /* 15MB */
|
|
32*1024*1024, /* 32MB */
|
|
64*1024*1024, /* 64MB */
|
|
75*1024*1024, /* 75MB */
|
|
100*1024*1024, /* 100MB */
|
|
128*1024*1024, /* 128MB */
|
|
256*1024*1024, /* 256MB */
|
|
512*1024*1024, /* 512MB */
|
|
650*1024*1024, /* 650MB */
|
|
700*1024*1024, /* 700MB */
|
|
1024*1024*1024, /* 1GB */
|
|
1536*1024*1024, /* 1.5GB */
|
|
1792*1024*1024, /* 1.75GB */
|
|
};
|
|
|
|
static unsigned long rec_sizesplit_bytes(void)
|
|
{
|
|
return rec_size_bytes[global_settings.rec_sizesplit];
|
|
}
|
|
|
|
void settings_apply_trigger(void)
|
|
{
|
|
int start_thres, stop_thres;
|
|
if (global_settings.peak_meter_dbfs)
|
|
{
|
|
start_thres = global_settings.rec_start_thres_db - 1;
|
|
stop_thres = global_settings.rec_stop_thres_db - 1;
|
|
}
|
|
else
|
|
{
|
|
start_thres = global_settings.rec_start_thres_linear;
|
|
stop_thres = global_settings.rec_stop_thres_linear;
|
|
}
|
|
|
|
peak_meter_define_trigger(
|
|
start_thres,
|
|
global_settings.rec_start_duration*HZ,
|
|
MIN(global_settings.rec_start_duration*HZ / 2, 2*HZ),
|
|
stop_thres,
|
|
global_settings.rec_stop_postrec*HZ,
|
|
global_settings.rec_stop_gap*HZ
|
|
);
|
|
}
|
|
/* recording screen status flags */
|
|
enum rec_status_flags
|
|
{
|
|
RCSTAT_IN_RECSCREEN = 0x00000001,
|
|
RCSTAT_BEEN_IN_USB_MODE = 0x00000002,
|
|
RCSTAT_CREATED_DIRECTORY = 0x00000004,
|
|
RCSTAT_HAVE_RECORDED = 0x00000008,
|
|
};
|
|
|
|
static int rec_status = 0;
|
|
|
|
bool in_recording_screen(void)
|
|
{
|
|
return (rec_status & RCSTAT_IN_RECSCREEN) != 0;
|
|
}
|
|
|
|
#if CONFIG_KEYPAD == RECORDER_PAD
|
|
static bool f2_rec_screen(void);
|
|
static bool f3_rec_screen(void);
|
|
#endif
|
|
|
|
#define MAX_FILE_SIZE 0x7F800000 /* 2 GB - 4 MB */
|
|
|
|
#ifndef HAVE_REMOTE_LCD
|
|
static const int screen_update = NB_SCREENS;
|
|
#else
|
|
static int screen_update = NB_SCREENS;
|
|
static bool remote_display_on = true;
|
|
#endif
|
|
|
|
/* as we have the ability to disable the remote, we need an alternative loop */
|
|
#define FOR_NB_ACTIVE_SCREENS(i) for(int i = 0; i < screen_update; i++)
|
|
|
|
static bool update_list = false; /* (GIU) list needs updating */
|
|
|
|
/** File name creation **/
|
|
#if CONFIG_RTC == 0
|
|
/* current file number to assist in creating unique numbered filenames
|
|
without actually having to create the file on disk */
|
|
static int file_number = -1;
|
|
#endif /* CONFIG_RTC */
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
|
|
#define REC_FILE_ENDING(rec_format) \
|
|
(audio_formats[rec_format_afmt[rec_format]].ext_list)
|
|
|
|
#else /* CONFIG_CODEC != SWCODEC */
|
|
|
|
/* default record file extension for HWCODEC */
|
|
#define REC_FILE_ENDING(rec_format) \
|
|
(audio_formats[AFMT_MPA_L3].ext_list)
|
|
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
|
|
/* path for current file */
|
|
static char path_buffer[MAX_PATH];
|
|
|
|
/** Automatic Gain Control (AGC) **/
|
|
#ifdef HAVE_AGC
|
|
/* Timing counters:
|
|
* peak_time is incremented every 0.2s, every 2nd run of record screen loop.
|
|
* hist_time is incremented every 0.5s, display update.
|
|
* peak_time is the counter of the peak hold read and agc process,
|
|
* overflow every 13 years 8-)
|
|
*/
|
|
static long peak_time = 0;
|
|
|
|
static short peak_valid_mem[4];
|
|
#define BAL_MEM_SIZE 24
|
|
static short balance_mem[BAL_MEM_SIZE];
|
|
|
|
/* Automatic Gain Control */
|
|
#define AGC_MODE_SIZE 5
|
|
#define AGC_SAFETY_MODE 0
|
|
|
|
static const char* agc_preset_str[] =
|
|
{ "Off", "S", "L", "D", "M", "V" };
|
|
/* "Off",
|
|
"Safety (clip)",
|
|
"Live (slow)",
|
|
"DJ-Set (slow)",
|
|
"Medium",
|
|
"Voice (fast)" */
|
|
#define AGC_CLIP 32766
|
|
#define AGC_PEAK 29883 /* fast gain reduction threshold -0.8dB */
|
|
#define AGC_HIGH 27254 /* accelerated gain reduction threshold -1.6dB */
|
|
#define AGC_IMG 823 /* threshold for balance control -32dB */
|
|
/* autogain high level thresholds (-3dB, -7dB, -4dB, -5dB, -5dB) */
|
|
static const short agc_th_hi[AGC_MODE_SIZE] =
|
|
{ 23197, 14637, 21156, 18428, 18426 };
|
|
/* autogain low level thresholds (-14dB, -11dB, -6dB, -7dB, -8dB) */
|
|
static const short agc_th_lo[AGC_MODE_SIZE] =
|
|
{ 6538, 9235, 16422, 14636, 13045 };
|
|
/* autogain threshold times [1/5s] or [200ms] */
|
|
static const short agc_tdrop[AGC_MODE_SIZE] =
|
|
{ 900, 225, 150, 60, 8 };
|
|
static const short agc_trise[AGC_MODE_SIZE] =
|
|
{ 9000, 750, 400, 150, 20 };
|
|
static const short agc_tbal[AGC_MODE_SIZE] =
|
|
{ 4500, 500, 300, 100, 15 };
|
|
/* AGC operation */
|
|
static bool agc_enable = true;
|
|
static short agc_preset;
|
|
/* AGC levels */
|
|
static int agc_left = 0;
|
|
static int agc_right = 0;
|
|
/* AGC time since high target volume was exceeded */
|
|
static short agc_droptime = 0;
|
|
/* AGC time since volume fallen below low target */
|
|
static short agc_risetime = 0;
|
|
/* AGC balance time exceeding +/- 0.7dB */
|
|
static short agc_baltime = 0;
|
|
/* AGC maximum gain */
|
|
static short agc_maxgain;
|
|
#endif /* HAVE_AGC */
|
|
#if defined(HAVE_AGC)
|
|
static long hist_time = 0;
|
|
#endif /* HAVE_AGC */
|
|
|
|
static void set_gain(void)
|
|
{
|
|
#ifdef HAVE_MIC_REC
|
|
if(global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
if (global_settings.rec_mic_gain > sound_max(SOUND_MIC_GAIN))
|
|
global_settings.rec_mic_gain = sound_max(SOUND_MIC_GAIN);
|
|
|
|
if (global_settings.rec_mic_gain < sound_min(SOUND_MIC_GAIN))
|
|
global_settings.rec_mic_gain = sound_min(SOUND_MIC_GAIN);
|
|
|
|
audio_set_recording_gain(global_settings.rec_mic_gain,
|
|
0, AUDIO_GAIN_MIC);
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
if (global_settings.rec_left_gain > sound_max(SOUND_LEFT_GAIN))
|
|
global_settings.rec_left_gain = sound_max(SOUND_LEFT_GAIN);
|
|
|
|
if (global_settings.rec_left_gain < sound_min(SOUND_LEFT_GAIN))
|
|
global_settings.rec_left_gain = sound_min(SOUND_LEFT_GAIN);
|
|
|
|
if (global_settings.rec_right_gain > sound_max(SOUND_RIGHT_GAIN))
|
|
global_settings.rec_right_gain = sound_max(SOUND_RIGHT_GAIN);
|
|
|
|
if (global_settings.rec_right_gain < sound_min(SOUND_RIGHT_GAIN))
|
|
global_settings.rec_right_gain = sound_min(SOUND_RIGHT_GAIN);
|
|
|
|
/* AUDIO_SRC_LINEIN, AUDIO_SRC_FMRADIO, AUDIO_SRC_SPDIF */
|
|
audio_set_recording_gain(global_settings.rec_left_gain,
|
|
global_settings.rec_right_gain,
|
|
AUDIO_GAIN_LINEIN);
|
|
}
|
|
/* reset the clipping indicators */
|
|
peak_meter_set_clip_hold(global_settings.peak_meter_clip_hold);
|
|
update_list = true;
|
|
}
|
|
|
|
#ifdef HAVE_AGC
|
|
/* Read peak meter values & calculate balance.
|
|
* Returns validity of peak values.
|
|
* Used for automatic gain control and history diagram.
|
|
*/
|
|
static bool read_peak_levels(int *peak_l, int *peak_r, int *balance)
|
|
{
|
|
peak_meter_get_peakhold(peak_l, peak_r);
|
|
peak_valid_mem[peak_time % 3] = *peak_l;
|
|
if (((peak_valid_mem[0] == peak_valid_mem[1]) &&
|
|
(peak_valid_mem[1] == peak_valid_mem[2])) &&
|
|
((*peak_l < 32767) || storage_disk_is_active()))
|
|
return false;
|
|
|
|
if (*peak_r > *peak_l)
|
|
balance_mem[peak_time % BAL_MEM_SIZE] = (*peak_l ?
|
|
MIN((10000 * *peak_r) / *peak_l - 10000, 15118) : 15118);
|
|
else
|
|
balance_mem[peak_time % BAL_MEM_SIZE] = (*peak_r ?
|
|
MAX(10000 - (10000 * *peak_l) / *peak_r, -15118) : -15118);
|
|
*balance = 0;
|
|
int i;
|
|
for (i = 0; i < BAL_MEM_SIZE; i++)
|
|
*balance += balance_mem[i];
|
|
*balance = *balance / BAL_MEM_SIZE;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* AGC helper function to check if maximum gain is reached */
|
|
static bool agc_gain_is_max(bool left, bool right)
|
|
{
|
|
/* range -128...+108 [0.5dB] */
|
|
short gain_current_l;
|
|
short gain_current_r;
|
|
|
|
if (agc_preset == 0)
|
|
return false;
|
|
|
|
switch (global_settings.rec_source)
|
|
{
|
|
#if defined(HAVE_LINE_REC) || defined(HAVE_FMRADIO_REC)
|
|
HAVE_LINE_REC_(case AUDIO_SRC_LINEIN:)
|
|
HAVE_FMRADIO_REC_(case AUDIO_SRC_FMRADIO:)
|
|
gain_current_l = global_settings.rec_left_gain;
|
|
gain_current_r = global_settings.rec_right_gain;
|
|
break;
|
|
#endif /* LINE, FMRADIO */
|
|
#if defined(HAVE_MIC_REC)
|
|
case AUDIO_SRC_MIC:
|
|
default:
|
|
gain_current_l = global_settings.rec_mic_gain;
|
|
gain_current_r = global_settings.rec_mic_gain;
|
|
#endif /* MIC */
|
|
}
|
|
|
|
return ((left && (gain_current_l >= agc_maxgain)) ||
|
|
(right && (gain_current_r >= agc_maxgain)));
|
|
}
|
|
|
|
static void change_recording_gain(bool increment, bool left, bool right)
|
|
{
|
|
int factor = (increment ? 1 : -1);
|
|
|
|
switch (global_settings.rec_source)
|
|
{
|
|
#if defined(HAVE_LINE_REC) || defined(HAVE_FMRADIO_REC)
|
|
HAVE_LINE_REC_(case AUDIO_SRC_LINEIN:)
|
|
HAVE_FMRADIO_REC_(case AUDIO_SRC_FMRADIO:)
|
|
if (left) global_settings.rec_left_gain +=
|
|
factor * sound_steps(SOUND_LEFT_GAIN);
|
|
if (right) global_settings.rec_right_gain +=
|
|
factor * sound_steps(SOUND_RIGHT_GAIN);
|
|
break;
|
|
#endif /* LINE, FMRADIO */
|
|
#if defined(HAVE_MIC_REC)
|
|
case AUDIO_SRC_MIC:
|
|
global_settings.rec_mic_gain += factor * sound_steps(SOUND_MIC_GAIN);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle automatic gain control (AGC).
|
|
* Change recording gain if peak_x levels are above or below
|
|
* target volume for specified timeouts.
|
|
*/
|
|
static void auto_gain_control(int *peak_l, int *peak_r, int *balance)
|
|
{
|
|
int agc_mono;
|
|
short agc_mode;
|
|
bool increment;
|
|
|
|
if (*peak_l > agc_left)
|
|
agc_left = *peak_l;
|
|
else
|
|
agc_left -= (agc_left - *peak_l + 3) >> 2;
|
|
if (*peak_r > agc_right)
|
|
agc_right = *peak_r;
|
|
else
|
|
agc_right -= (agc_right - *peak_r + 3) >> 2;
|
|
agc_mono = (agc_left + agc_right) / 2;
|
|
|
|
agc_mode = abs(agc_preset) - 1;
|
|
if (agc_mode < 0) {
|
|
agc_enable = false;
|
|
return;
|
|
}
|
|
|
|
if (agc_mode != AGC_SAFETY_MODE) {
|
|
/* Automatic balance control - only if not in safety mode */
|
|
if ((agc_left > AGC_IMG) && (agc_right > AGC_IMG))
|
|
{
|
|
if (*balance < -556)
|
|
{
|
|
if (*balance > -900)
|
|
agc_baltime -= !(peak_time % 4); /* 0.47 - 0.75dB */
|
|
else if (*balance > -4125)
|
|
agc_baltime--; /* 0.75 - 3.00dB */
|
|
else if (*balance > -7579)
|
|
agc_baltime -= 2; /* 3.00 - 4.90dB */
|
|
else
|
|
agc_baltime -= !(peak_time % 8); /* 4.90 - inf dB */
|
|
if (agc_baltime > 0)
|
|
agc_baltime -= (peak_time % 2);
|
|
}
|
|
else if (*balance > 556)
|
|
{
|
|
if (*balance < 900)
|
|
agc_baltime += !(peak_time % 4);
|
|
else if (*balance < 4125)
|
|
agc_baltime++;
|
|
else if (*balance < 7579)
|
|
agc_baltime += 2;
|
|
else
|
|
agc_baltime += !(peak_time % 8);
|
|
if (agc_baltime < 0)
|
|
agc_baltime += (peak_time % 2);
|
|
}
|
|
|
|
if ((*balance * agc_baltime) < 0)
|
|
{
|
|
if (*balance < 0)
|
|
agc_baltime -= peak_time % 2;
|
|
else
|
|
agc_baltime += peak_time % 2;
|
|
}
|
|
|
|
increment = ((agc_risetime / 2) > agc_droptime);
|
|
|
|
if (agc_baltime < -agc_tbal[agc_mode])
|
|
{
|
|
if (!increment || !agc_gain_is_max(!increment, increment)) {
|
|
change_recording_gain(increment, !increment, increment);
|
|
set_gain();
|
|
}
|
|
agc_baltime = 0;
|
|
}
|
|
else if (agc_baltime > +agc_tbal[agc_mode])
|
|
{
|
|
if (!increment || !agc_gain_is_max(increment, !increment)) {
|
|
change_recording_gain(increment, increment, !increment);
|
|
set_gain();
|
|
}
|
|
agc_baltime = 0;
|
|
}
|
|
}
|
|
else if (!(hist_time % 4))
|
|
{
|
|
if (agc_baltime < 0)
|
|
agc_baltime++;
|
|
else
|
|
agc_baltime--;
|
|
}
|
|
}
|
|
|
|
/* Automatic gain control */
|
|
if ((agc_left > agc_th_hi[agc_mode]) || (agc_right > agc_th_hi[agc_mode]))
|
|
{
|
|
if ((agc_left > AGC_CLIP) || (agc_right > AGC_CLIP))
|
|
agc_droptime += agc_tdrop[agc_mode] /
|
|
(global_settings.rec_agc_cliptime + 1);
|
|
if (agc_left > AGC_HIGH) {
|
|
agc_droptime++;
|
|
agc_risetime = 0;
|
|
if (agc_left > AGC_PEAK)
|
|
agc_droptime += 2;
|
|
}
|
|
if (agc_right > AGC_HIGH) {
|
|
agc_droptime++;
|
|
agc_risetime=0;
|
|
if (agc_right > AGC_PEAK)
|
|
agc_droptime += 2;
|
|
}
|
|
if (agc_mono > agc_th_hi[agc_mode])
|
|
agc_droptime++;
|
|
else
|
|
agc_droptime += !(peak_time % 2);
|
|
|
|
if (agc_droptime >= agc_tdrop[agc_mode])
|
|
{
|
|
change_recording_gain(false, true, true);
|
|
agc_droptime = 0;
|
|
agc_risetime = 0;
|
|
set_gain();
|
|
}
|
|
agc_risetime = MAX(agc_risetime - 1, 0);
|
|
}
|
|
else if (agc_mono < agc_th_lo[agc_mode])
|
|
{
|
|
if (agc_mono < (agc_th_lo[agc_mode] / 8))
|
|
agc_risetime += !(peak_time % 5);
|
|
else if (agc_mono < (agc_th_lo[agc_mode] / 2))
|
|
agc_risetime += 2;
|
|
else
|
|
agc_risetime++;
|
|
|
|
if (agc_risetime >= agc_trise[agc_mode]) {
|
|
if ((agc_mode != AGC_SAFETY_MODE) &&
|
|
(!agc_gain_is_max(true, true))) {
|
|
change_recording_gain(true, true, true);
|
|
set_gain();
|
|
}
|
|
agc_risetime = 0;
|
|
agc_droptime = 0;
|
|
}
|
|
agc_droptime = MAX(agc_droptime - 1, 0);
|
|
}
|
|
else if (!(peak_time % 6)) /* on target level every 1.2 sec */
|
|
{
|
|
agc_risetime = MAX(agc_risetime - 1, 0);
|
|
agc_droptime = MAX(agc_droptime - 1, 0);
|
|
}
|
|
}
|
|
#endif /* HAVE_AGC */
|
|
|
|
static const char* const fmtstr[] =
|
|
{
|
|
"%c%d %s", /* no decimals */
|
|
"%c%d.%d %s ", /* 1 decimal */
|
|
"%c%d.%02d %s " /* 2 decimals */
|
|
};
|
|
|
|
static const char factor[] = {1, 10, 100};
|
|
|
|
static char *fmt_gain(int snd, int val, char *str, int len)
|
|
{
|
|
int i, d, numdec;
|
|
const char *unit;
|
|
char sign = ' ';
|
|
|
|
val = sound_val2phys(snd, val);
|
|
if(val < 0)
|
|
{
|
|
sign = '-';
|
|
val = -val;
|
|
}
|
|
numdec = sound_numdecimals(snd);
|
|
unit = sound_unit(snd);
|
|
|
|
if(numdec)
|
|
{
|
|
i = val / factor[numdec];
|
|
d = val % factor[numdec];
|
|
snprintf(str, len, fmtstr[numdec], sign, i, d, unit);
|
|
}
|
|
else
|
|
snprintf(str, len, fmtstr[numdec], sign, val, unit);
|
|
|
|
return str;
|
|
}
|
|
|
|
/* the list below must match enum audio_sources in audio.h */
|
|
static const char* const prestr[] =
|
|
{
|
|
HAVE_MIC_IN_([AUDIO_SRC_MIC] = "R_MIC_",)
|
|
HAVE_LINE_REC_([AUDIO_SRC_LINEIN] = "R_LINE_",)
|
|
HAVE_SPDIF_IN_([AUDIO_SRC_SPDIF] = "R_SPDIF_",)
|
|
HAVE_FMRADIO_REC_([AUDIO_SRC_FMRADIO] = "R_FM_",)
|
|
};
|
|
|
|
char *rec_create_filename(char *buffer)
|
|
{
|
|
char ext[16];
|
|
const char *pref = "R_";
|
|
|
|
/* Directory existence and writeablility should have already been
|
|
* verified - do not pass NULL pointers to pcmrec */
|
|
|
|
if((unsigned)global_settings.rec_source < AUDIO_NUM_SOURCES)
|
|
{
|
|
pref = prestr[global_settings.rec_source];
|
|
}
|
|
|
|
strcpy(buffer, !strcmp(global_settings.rec_directory, "/")?
|
|
"": global_settings.rec_directory);
|
|
|
|
snprintf(ext, sizeof(ext), ".%s",
|
|
REC_FILE_ENDING(global_settings.rec_format));
|
|
|
|
#if CONFIG_RTC == 0
|
|
return create_numbered_filename(buffer, buffer, pref, ext, 4,
|
|
&file_number);
|
|
#else
|
|
/* We'll wait at least up to the start of the next second so no duplicate
|
|
names are created */
|
|
return create_datetime_filename(buffer, buffer, pref, ext, true);
|
|
#endif
|
|
}
|
|
|
|
#if CONFIG_RTC == 0
|
|
/* Hit disk to get a starting filename for the type */
|
|
static void rec_init_filename(void)
|
|
{
|
|
file_number = -1;
|
|
rec_create_filename(path_buffer);
|
|
file_number--;
|
|
}
|
|
#endif
|
|
|
|
int rec_create_directory(void)
|
|
{
|
|
int rc = 0;
|
|
const char * const folder = global_settings.rec_directory;
|
|
|
|
if (strcmp(folder, "/") && !dir_exists(folder))
|
|
{
|
|
rc = mkdir(folder);
|
|
|
|
if(rc < 0)
|
|
{
|
|
while (action_userabort(HZ) == false)
|
|
{
|
|
splashf(0, "%s %s",
|
|
str(LANG_REC_DIR_NOT_WRITABLE),
|
|
str(LANG_OFF_ABORT));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rec_status |= RCSTAT_CREATED_DIRECTORY;
|
|
rc = 1;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
void rec_init_recording_options(struct audio_recording_options *options)
|
|
{
|
|
options->rec_source = global_settings.rec_source;
|
|
options->rec_frequency = global_settings.rec_frequency;
|
|
options->rec_channels = global_settings.rec_channels;
|
|
options->rec_prerecord_time = global_settings.rec_prerecord_time;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
options->rec_mono_mode = global_settings.rec_mono_mode;
|
|
options->rec_source_flags = 0;
|
|
options->enc_config.rec_format = global_settings.rec_format;
|
|
global_to_encoder_config(&options->enc_config);
|
|
#else
|
|
options->rec_quality = global_settings.rec_quality;
|
|
options->rec_editable = global_settings.rec_editable;
|
|
#endif
|
|
}
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
void rec_set_source(int source, unsigned flags)
|
|
{
|
|
/* Set audio input source, power up/down devices */
|
|
audio_set_input_source(source, flags);
|
|
|
|
/* Set peakmeters for recording or reset to playback */
|
|
peak_meter_playback((flags & SRCF_RECORDING) == 0);
|
|
peak_meter_enable(true);
|
|
}
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
|
|
void rec_set_recording_options(struct audio_recording_options *options)
|
|
{
|
|
#if CONFIG_CODEC == SWCODEC
|
|
rec_set_source(options->rec_source,
|
|
options->rec_source_flags | SRCF_RECORDING);
|
|
#endif
|
|
audio_set_recording_options(options);
|
|
}
|
|
|
|
void rec_command(enum recording_command cmd)
|
|
{
|
|
switch(cmd)
|
|
{
|
|
case RECORDING_CMD_STOP_SHUTDOWN:
|
|
pm_activate_clipcount(false);
|
|
audio_stop_recording();
|
|
#if CONFIG_CODEC == SWCODEC
|
|
audio_close_recording();
|
|
#endif
|
|
sys_poweroff();
|
|
break;
|
|
case RECORDING_CMD_STOP:
|
|
pm_activate_clipcount(false);
|
|
audio_stop_recording();
|
|
break;
|
|
case RECORDING_CMD_START:
|
|
/* steal mp3 buffer, create unique filename and start recording */
|
|
pm_reset_clipcount();
|
|
pm_activate_clipcount(true);
|
|
audio_record(rec_create_filename(path_buffer));
|
|
break;
|
|
case RECORDING_CMD_START_NEWFILE:
|
|
/* create unique filename and start recording*/
|
|
pm_reset_clipcount();
|
|
pm_activate_clipcount(true); /* just to be sure */
|
|
audio_new_file(rec_create_filename(path_buffer));
|
|
break;
|
|
case RECORDING_CMD_PAUSE:
|
|
pm_activate_clipcount(false);
|
|
audio_pause_recording();
|
|
break;
|
|
case RECORDING_CMD_RESUME:
|
|
pm_activate_clipcount(true);
|
|
audio_resume_recording();
|
|
break;
|
|
}
|
|
update_list = true;
|
|
}
|
|
|
|
/* used in trigger_listerner and recording_screen */
|
|
static unsigned int last_seconds = 0;
|
|
|
|
/**
|
|
* Callback function so that the peak meter code can send an event
|
|
* to this application. This function can be passed to
|
|
* peak_meter_set_trigger_listener in order to activate the trigger.
|
|
*/
|
|
static void trigger_listener(int trigger_status)
|
|
{
|
|
switch (trigger_status)
|
|
{
|
|
case TRIG_GO:
|
|
if(!(audio_status() & AUDIO_STATUS_RECORD))
|
|
{
|
|
rec_status |= RCSTAT_HAVE_RECORDED;
|
|
rec_command(RECORDING_CMD_START);
|
|
#if CONFIG_CODEC != SWCODEC
|
|
/* give control to mpeg thread so that it can start
|
|
recording */
|
|
yield(); yield(); yield();
|
|
#endif
|
|
}
|
|
|
|
/* if we're already recording this is a retrigger */
|
|
else
|
|
{
|
|
if((audio_status() & AUDIO_STATUS_PAUSE) &&
|
|
(global_settings.rec_trigger_type == TRIG_TYPE_PAUSE))
|
|
{
|
|
rec_command(RECORDING_CMD_RESUME);
|
|
}
|
|
/* New file on trig start*/
|
|
else if (global_settings.rec_trigger_type != TRIG_TYPE_NEW_FILE)
|
|
{
|
|
rec_command(RECORDING_CMD_START_NEWFILE);
|
|
/* tell recording_screen to reset the time */
|
|
last_seconds = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* A _change_ to TRIG_READY means the current recording has stopped */
|
|
case TRIG_READY:
|
|
if(audio_status() & AUDIO_STATUS_RECORD)
|
|
{
|
|
switch(global_settings.rec_trigger_type)
|
|
{
|
|
case TRIG_TYPE_STOP: /* Stop */
|
|
rec_command(RECORDING_CMD_STOP);
|
|
break;
|
|
|
|
case TRIG_TYPE_PAUSE: /* Pause */
|
|
rec_command(RECORDING_CMD_PAUSE);
|
|
break;
|
|
|
|
case TRIG_TYPE_NEW_FILE: /* New file on trig stop*/
|
|
rec_command(RECORDING_CMD_START_NEWFILE);
|
|
/* tell recording_screen to reset the time */
|
|
last_seconds = 0;
|
|
break;
|
|
|
|
case 3: /* Stop and shutdown */
|
|
rec_command(RECORDING_CMD_STOP_SHUTDOWN);
|
|
break;
|
|
}
|
|
|
|
if (global_settings.rec_trigger_mode != TRIG_MODE_REARM)
|
|
{
|
|
peak_meter_set_trigger_listener(NULL);
|
|
peak_meter_trigger(false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Stuff for drawing the screen */
|
|
|
|
enum rec_list_items_stereo {
|
|
ITEM_VOLUME = 0,
|
|
ITEM_GAIN = 1,
|
|
ITEM_GAIN_L = 2,
|
|
ITEM_GAIN_R = 3,
|
|
#ifdef HAVE_AGC
|
|
ITEM_AGC_MODE = 4,
|
|
ITEM_AGC_MAXDB = 5,
|
|
ITEM_FILENAME = 7,
|
|
ITEM_COUNT = 7,
|
|
#else
|
|
ITEM_FILENAME = 7,
|
|
ITEM_COUNT = 5,
|
|
#endif
|
|
};
|
|
|
|
enum rec_list_items_mono {
|
|
ITEM_VOLUME_M = 0,
|
|
ITEM_GAIN_M = 1,
|
|
#ifdef HAVE_AGC
|
|
ITEM_AGC_MODE_M = 4,
|
|
ITEM_AGC_MAXDB_M = 5,
|
|
ITEM_FILENAME_M = 7,
|
|
ITEM_COUNT_M = 5,
|
|
#else
|
|
ITEM_FILENAME_M = 7,
|
|
ITEM_COUNT_M = 3,
|
|
#endif
|
|
};
|
|
|
|
#ifdef HAVE_SPDIF_REC
|
|
enum rec_list_items_spdif {
|
|
ITEM_VOLUME_D = 0,
|
|
#if CONFIG_CODEC == SWCODEC
|
|
ITEM_SAMPLERATE_D = 6,
|
|
ITEM_FILENAME_D = 7,
|
|
ITEM_COUNT_D = 3,
|
|
#else
|
|
ITEM_FILENAME_D = 7,
|
|
ITEM_COUNT_D = 2,
|
|
#endif
|
|
};
|
|
#endif
|
|
|
|
static int listid_to_enum[ITEM_COUNT];
|
|
|
|
static const char* reclist_get_name(int selected_item, void * data,
|
|
char * buffer, size_t buffer_len)
|
|
{
|
|
char buf2[32];
|
|
#ifdef HAVE_AGC
|
|
char buf3[32];
|
|
#endif
|
|
(void)data; /* not used */
|
|
if(selected_item >= ITEM_COUNT)
|
|
return "";
|
|
|
|
switch (listid_to_enum[selected_item])
|
|
{
|
|
case ITEM_VOLUME:
|
|
snprintf(buffer, buffer_len, "%s: %s", str(LANG_VOLUME),
|
|
fmt_gain(SOUND_VOLUME,
|
|
global_settings.volume,
|
|
buf2, sizeof(buf2)));
|
|
break;
|
|
case ITEM_GAIN:
|
|
#ifdef HAVE_MIC_REC
|
|
if(global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
/* Draw MIC recording gain */
|
|
snprintf(buffer, buffer_len, "%s: %s", str(LANG_GAIN),
|
|
fmt_gain(SOUND_MIC_GAIN,
|
|
global_settings.rec_mic_gain,
|
|
buf2, sizeof(buf2)));
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
int avg_gain = (global_settings.rec_left_gain +
|
|
global_settings.rec_right_gain) / 2;
|
|
snprintf(buffer, buffer_len, "%s: %s", str(LANG_GAIN),
|
|
fmt_gain(SOUND_LEFT_GAIN,
|
|
avg_gain,
|
|
buf2, sizeof(buf2)));
|
|
}
|
|
break;
|
|
case ITEM_GAIN_L:
|
|
snprintf(buffer, buffer_len, "%s: %s",
|
|
str(LANG_GAIN_LEFT),
|
|
fmt_gain(SOUND_LEFT_GAIN,
|
|
global_settings.rec_left_gain,
|
|
buf2, sizeof(buf2)));
|
|
break;
|
|
case ITEM_GAIN_R:
|
|
snprintf(buffer, buffer_len, "%s: %s",
|
|
str(LANG_GAIN_RIGHT),
|
|
fmt_gain(SOUND_RIGHT_GAIN,
|
|
global_settings.rec_right_gain,
|
|
buf2, sizeof(buf2)));
|
|
break;
|
|
#ifdef HAVE_AGC
|
|
case ITEM_AGC_MODE:
|
|
snprintf(buffer, buffer_len, "%s: %s",
|
|
str(LANG_RECORDING_AGC_PRESET),
|
|
agc_preset_str[agc_preset]);
|
|
break;
|
|
case ITEM_AGC_MAXDB:
|
|
if (agc_preset == 0)
|
|
snprintf(buffer, buffer_len, "%s: %s",
|
|
str(LANG_RECORDING_AGC_MAXGAIN),
|
|
fmt_gain(SOUND_LEFT_GAIN,
|
|
agc_maxgain, buf2, sizeof(buf2)));
|
|
#ifdef HAVE_MIC_REC
|
|
else if (global_settings.rec_source == AUDIO_SRC_MIC)
|
|
snprintf(buffer, buffer_len, "%s: %s (%s)",
|
|
str(LANG_RECORDING_AGC_MAXGAIN),
|
|
fmt_gain(SOUND_MIC_GAIN,
|
|
agc_maxgain, buf2, sizeof(buf2)),
|
|
fmt_gain(SOUND_MIC_GAIN,
|
|
agc_maxgain - global_settings.rec_mic_gain,
|
|
buf3, sizeof(buf3)));
|
|
else
|
|
#endif /* MIC */
|
|
snprintf(buffer, buffer_len, "%s: %s (%s)",
|
|
str(LANG_RECORDING_AGC_MAXGAIN),
|
|
fmt_gain(SOUND_LEFT_GAIN,
|
|
agc_maxgain, buf2, sizeof(buf2)),
|
|
fmt_gain(SOUND_LEFT_GAIN,
|
|
agc_maxgain -
|
|
(global_settings.rec_left_gain +
|
|
global_settings.rec_right_gain)/2,
|
|
buf3, sizeof(buf3)));
|
|
break;
|
|
#endif
|
|
#if CONFIG_CODEC == SWCODEC
|
|
#ifdef HAVE_SPDIF_REC
|
|
case ITEM_SAMPLERATE_D:
|
|
snprintf(buffer, buffer_len, "%s: %lu",
|
|
str(LANG_FREQUENCY), pcm_rec_sample_rate());
|
|
break;
|
|
#endif
|
|
#endif
|
|
case ITEM_FILENAME:
|
|
{
|
|
if(audio_status() & AUDIO_STATUS_RECORD)
|
|
{
|
|
size_t tot_len = strlen(path_buffer) +
|
|
strlen(str(LANG_RECORDING_FILENAME)) + 1;
|
|
if(tot_len > buffer_len)
|
|
{
|
|
snprintf(buffer, buffer_len, "%s %s",
|
|
str(LANG_RECORDING_FILENAME),
|
|
path_buffer + tot_len - buffer_len);
|
|
}
|
|
else
|
|
{
|
|
snprintf(buffer, buffer_len, "%s %s",
|
|
str(LANG_RECORDING_FILENAME), path_buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return str(LANG_RECORDING_FILENAME);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return "";
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
|
|
bool recording_start_automatic = false;
|
|
|
|
bool recording_screen(bool no_source)
|
|
{
|
|
int button;
|
|
int done = -1; /* negative to re-init, positive to quit, zero to run */
|
|
char buf[32]; /* for preparing strings */
|
|
char buf2[32]; /* for preparing strings */
|
|
int w, h; /* character width/height */
|
|
int update_countdown = 0; /* refresh counter */
|
|
unsigned int seconds;
|
|
int hours, minutes;
|
|
int audio_stat = 0; /* status of the audio system */
|
|
int last_audio_stat = -1; /* previous status so we can act on changes */
|
|
struct viewport vp_list[NB_SCREENS], vp_top[NB_SCREENS]; /* the viewports */
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
int warning_counter = 0;
|
|
#define WARNING_PERIOD 7
|
|
#endif
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
#ifdef HAVE_SPDIF_REC
|
|
unsigned long prev_sample_rate = 0;
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef HAVE_FMRADIO_REC
|
|
/* Radio is left on if:
|
|
* 1) Is was on at the start and the initial source is FM Radio
|
|
* 2) 1) and the source was never changed to something else
|
|
*/
|
|
int radio_status = (global_settings.rec_source != AUDIO_SRC_FMRADIO) ?
|
|
FMRADIO_OFF : get_radio_status();
|
|
#endif
|
|
#if (CONFIG_LED == LED_REAL)
|
|
bool led_state = false;
|
|
int led_countdown = 2;
|
|
#endif
|
|
#ifdef HAVE_AGC
|
|
bool peak_read = false;
|
|
int peak_l, peak_r;
|
|
int balance = 0;
|
|
#endif
|
|
int pm_x[NB_SCREENS]; /* peakmeter (and trigger bar) x pos */
|
|
int pm_y[NB_SCREENS]; /* peakmeter y pos */
|
|
int pm_h[NB_SCREENS]; /* peakmeter height */
|
|
int trig_ypos[NB_SCREENS]; /* trigger bar y pos */
|
|
int trig_width[NB_SCREENS]; /* trigger bar width */
|
|
int top_height_req[NB_SCREENS]; /* required height for top half */
|
|
/* tweak layout tiny screens / big fonts */
|
|
bool compact_view[NB_SCREENS] = { false };
|
|
struct gui_synclist lists; /* the list in the bottom vp */
|
|
#if defined(HAVE_AGC)
|
|
bool peak_valid = false;
|
|
#endif
|
|
#if defined(HAVE_HISTOGRAM)
|
|
unsigned short hist_pos_y = 0;
|
|
unsigned short hist_size_h = 0;
|
|
#endif
|
|
#ifdef HAVE_FMRADIO_REC
|
|
int prev_rec_source = global_settings.rec_source; /* detect source change */
|
|
#endif
|
|
|
|
struct audio_recording_options rec_options;
|
|
rec_status = RCSTAT_IN_RECSCREEN;
|
|
push_current_activity(ACTIVITY_RECORDING);
|
|
|
|
#if (CONFIG_STORAGE & STORAGE_ATA) && (CONFIG_LED == LED_REAL) \
|
|
&& !defined(SIMULATOR)
|
|
ata_set_led_enabled(false);
|
|
#endif
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
/* hardware samplerate gets messed up so prevent mixer playing */
|
|
int keyclick = global_settings.keyclick;
|
|
global_settings.keyclick = 0;
|
|
|
|
/* recording_menu gets messed up: so prevent manus talking */
|
|
talk_disable(true);
|
|
/* audio_init_recording stops anything playing when it takes the audio
|
|
buffer */
|
|
#else
|
|
/* Yes, we use the D/A for monitoring */
|
|
peak_meter_enable(true);
|
|
peak_meter_playback(true);
|
|
#endif
|
|
|
|
#ifdef HAVE_AGC
|
|
peak_meter_get_peakhold(&peak_l, &peak_r);
|
|
#endif
|
|
|
|
pm_reset_clipcount();
|
|
pm_activate_clipcount(false);
|
|
settings_apply_trigger();
|
|
|
|
#ifdef HAVE_AGC
|
|
agc_preset_str[0] = str(LANG_OFF);
|
|
agc_preset_str[1] = str(LANG_AGC_SAFETY);
|
|
agc_preset_str[2] = str(LANG_AGC_LIVE);
|
|
agc_preset_str[3] = str(LANG_AGC_DJSET);
|
|
agc_preset_str[4] = str(LANG_AGC_MEDIUM);
|
|
agc_preset_str[5] = str(LANG_AGC_VOICE);
|
|
#endif /* HAVE_AGC */
|
|
|
|
#ifdef HAVE_SPEAKER
|
|
/* Disable speaker to prevent feedback */
|
|
audio_enable_speaker(0);
|
|
#endif
|
|
|
|
audio_init_recording();
|
|
sound_set_volume(global_settings.volume);
|
|
|
|
#if CONFIG_RTC == 0
|
|
/* Create new filename for recording start */
|
|
rec_init_filename();
|
|
#endif
|
|
|
|
/* start of the loop: we stay in this loop until user quits recscreen */
|
|
while(done <= 0)
|
|
{
|
|
if(done < 0)
|
|
{
|
|
/* request to re-init stuff, done after settings screen */
|
|
done = 0;
|
|
#ifdef HAVE_FMRADIO_REC
|
|
/* If input changes away from FM Radio,
|
|
radio will remain off when recording screen closes. */
|
|
if (global_settings.rec_source != prev_rec_source
|
|
&& prev_rec_source == AUDIO_SRC_FMRADIO)
|
|
radio_status = FMRADIO_OFF;
|
|
prev_rec_source = global_settings.rec_source;
|
|
#endif
|
|
|
|
/* viewport init and calculations that only needs to be done once */
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
struct viewport *v;
|
|
/* top vp, 4 lines, force sys font if total screen < 6 lines
|
|
NOTE: one could limit the list to 1 line and get away with 5 lines */
|
|
top_height_req[i] = 4;
|
|
#if defined(HAVE_HISTOGRAM)
|
|
if((global_settings.histogram_interval) && (!i))
|
|
top_height_req[i] += 1; /* use one line for histogram */
|
|
#endif
|
|
v = &vp_top[i];
|
|
viewport_set_defaults(v, i);
|
|
if (viewport_get_nb_lines(v) < top_height_req[i])
|
|
{
|
|
/* compact needs 4 lines total */
|
|
v->font = FONT_SYSFIXED;
|
|
compact_view[i] = false;
|
|
}
|
|
else
|
|
{
|
|
/*top=4,list=2*/
|
|
if (viewport_get_nb_lines(v) < (top_height_req[i]+2))
|
|
compact_view[i] = true;
|
|
else
|
|
compact_view[i] = false;
|
|
}
|
|
vp_list[i] = *v; /* get a copy now so it can be sized more easily */
|
|
v->height = (font_get(v->font)->height)*(compact_view[i] ? 3 :
|
|
top_height_req[i]);
|
|
|
|
/* list section, rest of the screen */
|
|
vp_list[i].y += vp_top[i].height;
|
|
vp_list[i].height -= vp_top[i].height;
|
|
screens[i].set_viewport(&vp_top[i]); /* req for next calls */
|
|
|
|
screens[i].getstringsize("W", &w, &h);
|
|
pm_y[i] = font_get(vp_top[i].font)->height * 2;
|
|
trig_ypos[i] = font_get(vp_top[i].font)->height * 3;
|
|
if(compact_view[i])
|
|
trig_ypos[i] -= (font_get(vp_top[i].font)->height)/2;
|
|
}
|
|
|
|
/* init the bottom list */
|
|
gui_synclist_init(&lists, reclist_get_name, NULL, false, 1, vp_list);
|
|
gui_synclist_set_title(&lists, NULL, Icon_NOICON);
|
|
|
|
send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); /* force a redraw */
|
|
|
|
#if defined(HAVE_HISTOGRAM)
|
|
hist_pos_y = (compact_view[0] ? 3 : 4) * (font_get(vp_top[0].font)->height)
|
|
+ 1;
|
|
hist_size_h = font_get(vp_top[0].font)->height - 2;
|
|
histogram_init();
|
|
#endif
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
pm_x[i] = 0;
|
|
if(global_settings.peak_meter_clipcounter)
|
|
{
|
|
int clipwidth = 0;
|
|
screens[i].getstringsize(str(LANG_PM_CLIPCOUNT),
|
|
&clipwidth, &h); /* h is same */
|
|
pm_x[i] = clipwidth+1;
|
|
}
|
|
if(global_settings.rec_trigger_mode == TRIG_MODE_OFF)
|
|
pm_h[i] = font_get(vp_top[i].font)->height * 2;
|
|
else
|
|
pm_h[i] = font_get(vp_top[i].font)->height;
|
|
if(compact_view[i])
|
|
pm_h[i] /= 2;
|
|
trig_width[i] = vp_top[i].width - pm_x[i];
|
|
}
|
|
|
|
rec_init_recording_options(&rec_options);
|
|
rec_set_recording_options(&rec_options);
|
|
|
|
if(rec_create_directory() < 0)
|
|
{
|
|
rec_status = 0;
|
|
goto rec_abort;
|
|
}
|
|
|
|
#if CONFIG_CODEC == SWCODEC && CONFIG_RTC == 0
|
|
/* If format changed, a new number is required */
|
|
rec_init_filename();
|
|
#endif
|
|
|
|
#ifdef HAVE_AGC
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC) {
|
|
agc_preset = global_settings.rec_agc_preset_mic;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_mic;
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
agc_preset = global_settings.rec_agc_preset_line;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_line;
|
|
}
|
|
#endif /* HAVE_AGC */
|
|
|
|
set_gain();
|
|
update_countdown = 0; /* Update immediately */
|
|
|
|
/* populate translation table for list id -> enum */
|
|
#ifdef HAVE_SPDIF_REC
|
|
if(global_settings.rec_source == AUDIO_SRC_SPDIF)
|
|
{
|
|
listid_to_enum[0] = ITEM_VOLUME_D;
|
|
#if CONFIG_CODEC == SWCODEC
|
|
listid_to_enum[1] = ITEM_SAMPLERATE_D;
|
|
listid_to_enum[2] = ITEM_FILENAME_D;
|
|
#else
|
|
listid_to_enum[1] = ITEM_FILENAME_D;
|
|
#endif
|
|
|
|
gui_synclist_set_nb_items(&lists, ITEM_COUNT_D); /* spdif */
|
|
}
|
|
else
|
|
#endif
|
|
if(HAVE_MIC_REC_((global_settings.rec_source == AUDIO_SRC_MIC) || )
|
|
(global_settings.rec_channels == 1))
|
|
{
|
|
listid_to_enum[0] = ITEM_VOLUME_M;
|
|
listid_to_enum[1] = ITEM_GAIN_M;
|
|
#ifdef HAVE_AGC
|
|
listid_to_enum[2] = ITEM_AGC_MODE_M;
|
|
listid_to_enum[3] = ITEM_AGC_MAXDB_M;
|
|
listid_to_enum[4] = ITEM_FILENAME_M;
|
|
#else
|
|
listid_to_enum[2] = ITEM_FILENAME_M;
|
|
#endif
|
|
gui_synclist_set_nb_items(&lists, ITEM_COUNT_M); /* mono */
|
|
}
|
|
else
|
|
{
|
|
listid_to_enum[0] = ITEM_VOLUME;
|
|
listid_to_enum[1] = ITEM_GAIN;
|
|
listid_to_enum[2] = ITEM_GAIN_L;
|
|
listid_to_enum[3] = ITEM_GAIN_R;
|
|
#ifdef HAVE_AGC
|
|
listid_to_enum[4] = ITEM_AGC_MODE;
|
|
listid_to_enum[5] = ITEM_AGC_MAXDB;
|
|
listid_to_enum[6] = ITEM_FILENAME;
|
|
#else
|
|
listid_to_enum[4] = ITEM_FILENAME;
|
|
#endif
|
|
gui_synclist_set_nb_items(&lists, ITEM_COUNT); /* stereo */
|
|
}
|
|
|
|
gui_synclist_draw(&lists);
|
|
} /* if(done < 0) */
|
|
|
|
audio_stat = audio_status();
|
|
|
|
#if (CONFIG_LED == LED_REAL)
|
|
|
|
/*
|
|
* Flash the LED while waiting to record. Turn it on while
|
|
* recording.
|
|
*/
|
|
if(audio_stat & AUDIO_STATUS_RECORD)
|
|
{
|
|
if (audio_stat & AUDIO_STATUS_PAUSE)
|
|
{
|
|
if (--led_countdown <= 0)
|
|
{
|
|
led_state = !led_state;
|
|
led(led_state);
|
|
led_countdown = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* trigger is on in status TRIG_READY (no check needed) */
|
|
led(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int trig_stat = peak_meter_trigger_status();
|
|
/*
|
|
* other trigger stati than trig_off and trig_steady
|
|
* already imply that we are recording.
|
|
*/
|
|
if (trig_stat == TRIG_STEADY)
|
|
{
|
|
if (--led_countdown <= 0)
|
|
{
|
|
led_state = !led_state;
|
|
led(led_state);
|
|
led_countdown = 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* trigger is on in status TRIG_READY (no check needed) */
|
|
led(false);
|
|
}
|
|
}
|
|
#endif /* CONFIG_LED */
|
|
|
|
/* Wait for a button a while (HZ/10) drawing the peak meter */
|
|
button = peak_meter_draw_get_btn(CONTEXT_RECSCREEN,
|
|
pm_x, pm_y, pm_h,
|
|
screen_update, vp_top);
|
|
if (last_audio_stat != audio_stat)
|
|
{
|
|
if (audio_stat & AUDIO_STATUS_RECORD)
|
|
{
|
|
rec_status |= RCSTAT_HAVE_RECORDED;
|
|
}
|
|
last_audio_stat = audio_stat;
|
|
update_list = true;
|
|
}
|
|
|
|
if (recording_start_automatic)
|
|
{
|
|
/* simulate a button press */
|
|
button = ACTION_REC_PAUSE;
|
|
recording_start_automatic = false;
|
|
}
|
|
|
|
/* let list handle the button */
|
|
gui_synclist_do_button(&lists, &button, LIST_WRAP_UNLESS_HELD);
|
|
|
|
|
|
switch(button)
|
|
{
|
|
case ACTION_SETTINGS_INC:
|
|
case ACTION_SETTINGS_INCREPEAT:
|
|
switch (listid_to_enum[gui_synclist_get_sel_pos(&lists)])
|
|
{
|
|
case ITEM_VOLUME:
|
|
global_settings.volume += sound_steps(SOUND_VOLUME);
|
|
setvol();
|
|
break;
|
|
case ITEM_GAIN:
|
|
#ifdef HAVE_MIC_REC
|
|
if(global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
global_settings.rec_mic_gain +=
|
|
sound_steps(SOUND_MIC_GAIN);
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
global_settings.rec_left_gain +=
|
|
sound_steps(SOUND_LEFT_GAIN);
|
|
global_settings.rec_right_gain +=
|
|
sound_steps(SOUND_RIGHT_GAIN);
|
|
}
|
|
break;
|
|
case ITEM_GAIN_L:
|
|
global_settings.rec_left_gain +=
|
|
sound_steps(SOUND_LEFT_GAIN);
|
|
|
|
break;
|
|
case ITEM_GAIN_R:
|
|
global_settings.rec_right_gain +=
|
|
sound_steps(SOUND_RIGHT_GAIN);
|
|
|
|
break;
|
|
#ifdef HAVE_AGC
|
|
case ITEM_AGC_MODE:
|
|
agc_preset = MIN(agc_preset + 1, AGC_MODE_SIZE);
|
|
agc_enable = (agc_preset != 0);
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC) {
|
|
global_settings.rec_agc_preset_mic = agc_preset;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_mic;
|
|
} else
|
|
#endif /* MIC */
|
|
{
|
|
global_settings.rec_agc_preset_line = agc_preset;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_line;
|
|
}
|
|
break;
|
|
case ITEM_AGC_MAXDB:
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
agc_maxgain = MIN(agc_maxgain + 1,
|
|
sound_max(SOUND_MIC_GAIN));
|
|
global_settings.rec_agc_maxgain_mic = agc_maxgain;
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
agc_maxgain = MIN(agc_maxgain + 1,
|
|
sound_max(SOUND_LEFT_GAIN));
|
|
global_settings.rec_agc_maxgain_line = agc_maxgain;
|
|
}
|
|
break;
|
|
#endif /* HAVE_AGC */
|
|
}
|
|
set_gain();
|
|
update_countdown = 0; /* Update immediately */
|
|
break;
|
|
case ACTION_SETTINGS_DEC:
|
|
case ACTION_SETTINGS_DECREPEAT:
|
|
switch (listid_to_enum[gui_synclist_get_sel_pos(&lists)])
|
|
{
|
|
case ITEM_VOLUME:
|
|
global_settings.volume -= sound_steps(SOUND_VOLUME);
|
|
|
|
/* check range and update */
|
|
setvol();
|
|
break;
|
|
case ITEM_GAIN:
|
|
#ifdef HAVE_MIC_REC
|
|
if(global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
global_settings.rec_mic_gain -=
|
|
sound_steps(SOUND_MIC_GAIN);
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
global_settings.rec_left_gain -=
|
|
sound_steps(SOUND_LEFT_GAIN);
|
|
|
|
global_settings.rec_right_gain -=
|
|
sound_steps(SOUND_RIGHT_GAIN);
|
|
}
|
|
break;
|
|
case ITEM_GAIN_L:
|
|
global_settings.rec_left_gain -=
|
|
sound_steps(SOUND_LEFT_GAIN);
|
|
|
|
break;
|
|
case ITEM_GAIN_R:
|
|
global_settings.rec_right_gain -=
|
|
sound_steps(SOUND_RIGHT_GAIN);
|
|
|
|
break;
|
|
#ifdef HAVE_AGC
|
|
case ITEM_AGC_MODE:
|
|
agc_preset = MAX(agc_preset - 1, 0);
|
|
agc_enable = (agc_preset != 0);
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC) {
|
|
global_settings.rec_agc_preset_mic = agc_preset;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_mic;
|
|
} else
|
|
#endif /* MIC */
|
|
{
|
|
global_settings.rec_agc_preset_line = agc_preset;
|
|
agc_maxgain = global_settings.rec_agc_maxgain_line;
|
|
}
|
|
break;
|
|
case ITEM_AGC_MAXDB:
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
agc_maxgain = MAX(agc_maxgain - 1,
|
|
sound_min(SOUND_MIC_GAIN));
|
|
global_settings.rec_agc_maxgain_mic = agc_maxgain;
|
|
} else
|
|
#endif /* MIC */
|
|
{
|
|
agc_maxgain = MAX(agc_maxgain - 1,
|
|
sound_min(SOUND_LEFT_GAIN));
|
|
global_settings.rec_agc_maxgain_line = agc_maxgain;
|
|
}
|
|
break;
|
|
#endif /* HAVE_AGC */
|
|
}
|
|
set_gain();
|
|
update_countdown = 0; /* Update immediately */
|
|
break;
|
|
case ACTION_STD_CANCEL:
|
|
/* turn off the trigger */
|
|
peak_meter_trigger(false);
|
|
peak_meter_set_trigger_listener(NULL);
|
|
|
|
if(audio_stat & AUDIO_STATUS_RECORD)
|
|
{
|
|
rec_command(RECORDING_CMD_STOP);
|
|
}
|
|
else
|
|
{
|
|
#if CONFIG_CODEC != SWCODEC
|
|
peak_meter_playback(true);
|
|
peak_meter_enable(false);
|
|
#endif
|
|
done = 1;
|
|
}
|
|
update_countdown = 0; /* Update immediately */
|
|
break;
|
|
#ifdef HAVE_REMOTE_LCD
|
|
case ACTION_REC_LCD:
|
|
/* this feature exists for some h1x0/h3x0 targets that suffer
|
|
from noise caused by remote LCD updates
|
|
NOTE 1: this will leave the list on the remote
|
|
NOTE 2: to be replaced by a global LCD_off() routine */
|
|
if(remote_display_on)
|
|
{
|
|
/* switch to single screen, leave message on remote */
|
|
screen_update = 1;
|
|
screens[1].clear_viewport();
|
|
screens[1].puts(0, 0, str(LANG_REMOTE_LCD_OFF));
|
|
screens[1].puts(0, 1, str(LANG_REMOTE_LCD_ON));
|
|
screens[1].update_viewport();
|
|
}
|
|
else
|
|
{
|
|
/* remote switched on again */
|
|
update_list = true;
|
|
screen_update = NB_SCREENS;
|
|
}
|
|
remote_display_on = !remote_display_on; /* toggle */
|
|
update_countdown = 0; /* Update immediately */
|
|
break;
|
|
#endif
|
|
case ACTION_REC_PAUSE:
|
|
case ACTION_REC_NEWFILE:
|
|
/* Only act if the mpeg is stopped */
|
|
if(!(audio_stat & AUDIO_STATUS_RECORD))
|
|
{
|
|
/* is this manual or triggered recording? */
|
|
if ((global_settings.rec_trigger_mode == TRIG_MODE_OFF) ||
|
|
(peak_meter_trigger_status() != TRIG_OFF))
|
|
{
|
|
/* manual recording */
|
|
rec_status |= RCSTAT_HAVE_RECORDED;
|
|
rec_command(RECORDING_CMD_START);
|
|
last_seconds = 0;
|
|
if (global_settings.talk_menu)
|
|
{
|
|
/* no voice possible here, but a beep */
|
|
audio_beep(HZ/2); /* longer beep on start */
|
|
}
|
|
}
|
|
/* this is triggered recording */
|
|
else
|
|
{
|
|
/* we don't start recording now, but enable the
|
|
trigger and let the callback function
|
|
trigger_listener control when the recording starts */
|
|
peak_meter_trigger(true);
|
|
peak_meter_set_trigger_listener(&trigger_listener);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*if new file button pressed, start new file */
|
|
if (button == ACTION_REC_NEWFILE)
|
|
{
|
|
rec_command(RECORDING_CMD_START_NEWFILE);
|
|
last_seconds = 0;
|
|
}
|
|
else
|
|
/* if pause button pressed, pause or resume */
|
|
{
|
|
if(audio_stat & AUDIO_STATUS_PAUSE)
|
|
{
|
|
rec_command(RECORDING_CMD_RESUME);
|
|
if (global_settings.talk_menu)
|
|
{
|
|
/* no voice possible here, but a beep */
|
|
audio_beep(HZ/4); /* short beep on resume */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rec_command(RECORDING_CMD_PAUSE);
|
|
}
|
|
}
|
|
}
|
|
update_countdown = 0; /* Update immediately */
|
|
break;
|
|
case ACTION_STD_MENU:
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if(!(audio_stat & AUDIO_STATUS_RECORD))
|
|
#else
|
|
if(audio_stat != AUDIO_STATUS_RECORD)
|
|
#endif
|
|
{
|
|
#if (CONFIG_LED == LED_REAL)
|
|
/* led is restored at begin of loop / end of function */
|
|
led(false);
|
|
#endif
|
|
if (recording_menu(no_source))
|
|
{
|
|
done = 1;
|
|
rec_status |= RCSTAT_BEEN_IN_USB_MODE;
|
|
#ifdef HAVE_FMRADIO_REC
|
|
radio_status = FMRADIO_OFF;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
done = -1;
|
|
/* the init is now done at the beginning of the loop */
|
|
}
|
|
}
|
|
break;
|
|
|
|
#if CONFIG_KEYPAD == RECORDER_PAD
|
|
case ACTION_REC_F2:
|
|
if(audio_stat != AUDIO_STATUS_RECORD)
|
|
{
|
|
#if (CONFIG_LED == LED_REAL)
|
|
/* led is restored at begin of loop / end of function */
|
|
led(false);
|
|
#endif
|
|
if (f2_rec_screen())
|
|
{
|
|
rec_status |= RCSTAT_HAVE_RECORDED;
|
|
done = true;
|
|
}
|
|
else
|
|
update_countdown = 0; /* Update immediately */
|
|
}
|
|
break;
|
|
|
|
case ACTION_REC_F3:
|
|
if(audio_stat & AUDIO_STATUS_RECORD)
|
|
{
|
|
rec_command(RECORDING_CMD_START_NEWFILE);
|
|
last_seconds = 0;
|
|
}
|
|
else
|
|
{
|
|
#if (CONFIG_LED == LED_REAL)
|
|
/* led is restored at begin of loop / end of function */
|
|
led(false);
|
|
#endif
|
|
if (f3_rec_screen())
|
|
{
|
|
rec_status |= RCSTAT_HAVE_RECORDED;
|
|
done = true;
|
|
}
|
|
else
|
|
update_countdown = 0; /* Update immediately */
|
|
}
|
|
break;
|
|
#endif /* CONFIG_KEYPAD == RECORDER_PAD */
|
|
|
|
case SYS_USB_CONNECTED:
|
|
/* Only accept USB connection when not recording */
|
|
if(!(audio_stat & AUDIO_STATUS_RECORD))
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].set_viewport(NULL);
|
|
default_event_handler(SYS_USB_CONNECTED);
|
|
done = true;
|
|
rec_status |= RCSTAT_BEEN_IN_USB_MODE;
|
|
#ifdef HAVE_FMRADIO_REC
|
|
radio_status = FMRADIO_OFF;
|
|
#endif
|
|
}
|
|
break;
|
|
} /*switch(button)*/
|
|
|
|
#ifdef HAVE_AGC
|
|
peak_read = !peak_read;
|
|
if (peak_read) { /* every 2nd run of loop */
|
|
peak_time++;
|
|
peak_valid = read_peak_levels(&peak_l, &peak_r, &balance);
|
|
}
|
|
|
|
/* Handle AGC every 200ms when enabled and peak data is valid */
|
|
if (peak_read && agc_enable && peak_valid)
|
|
auto_gain_control(&peak_l, &peak_r, &balance);
|
|
#endif
|
|
|
|
seconds = audio_recorded_time() / HZ;
|
|
|
|
/* start of vp_top drawing */
|
|
if(update_countdown-- == 0 || seconds > last_seconds)
|
|
{
|
|
unsigned int dseconds, dhours, dminutes;
|
|
unsigned long num_recorded_bytes, dsize, dmb;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].set_viewport(&vp_top[i]);
|
|
screens[i].clear_viewport();
|
|
}
|
|
update_countdown = 5;
|
|
last_seconds = seconds;
|
|
|
|
dseconds = rec_timesplit_seconds();
|
|
dsize = rec_sizesplit_bytes();
|
|
num_recorded_bytes = audio_num_recorded_bytes();
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
if ((audio_stat & AUDIO_STATUS_WARNING)
|
|
&& (warning_counter++ % WARNING_PERIOD) < WARNING_PERIOD/2)
|
|
{
|
|
/* Switch back and forth displaying warning on first available
|
|
line to ensure visibility - the motion should also help
|
|
draw attention */
|
|
/* Don't use language string unless agreed upon to make this
|
|
method permanent - could do something in the statusbar */
|
|
snprintf(buf, sizeof(buf), "Warning: %08lX",
|
|
(unsigned long)pcm_rec_get_warnings());
|
|
}
|
|
else
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
if ((global_settings.rec_sizesplit) &&
|
|
(global_settings.rec_split_method))
|
|
{
|
|
dmb = dsize/1024/1024;
|
|
snprintf(buf, sizeof(buf), "%s %luMB",
|
|
str(LANG_SPLIT_SIZE), dmb);
|
|
}
|
|
else
|
|
{
|
|
hours = seconds / 3600;
|
|
minutes = (seconds - (hours * 3600)) / 60;
|
|
snprintf(buf, sizeof(buf), "%s %02d:%02d:%02d",
|
|
str(LANG_RECORDING_TIME),
|
|
hours, minutes, seconds%60);
|
|
}
|
|
|
|
FOR_NB_ACTIVE_SCREENS(i)
|
|
screens[i].puts(0, 0, buf);
|
|
|
|
if(audio_stat & AUDIO_STATUS_PRERECORD)
|
|
{
|
|
#if CONFIG_CODEC == SWCODEC
|
|
/* Tracks amount of prerecorded data in buffer */
|
|
snprintf(buf, sizeof(buf), "%s (%lu/%ds)...",
|
|
str(LANG_RECORD_PRERECORD),
|
|
audio_prerecorded_time() / HZ,
|
|
global_settings.rec_prerecord_time);
|
|
#else /* !SWCODEC */
|
|
snprintf(buf, sizeof(buf), "%s...",
|
|
str(LANG_RECORD_PRERECORD));
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
}
|
|
else
|
|
{
|
|
/* Display the split interval if the record timesplit
|
|
is active */
|
|
if ((global_settings.rec_timesplit) &&
|
|
!(global_settings.rec_split_method))
|
|
{
|
|
/* Display the record timesplit interval rather
|
|
than the file size if the record timer is active */
|
|
dhours = dseconds / 3600;
|
|
dminutes = (dseconds - (dhours * 3600)) / 60;
|
|
snprintf(buf, sizeof(buf), "%s %02d:%02d",
|
|
str(LANG_RECORDING_TIMESPLIT_REC),
|
|
dhours, dminutes);
|
|
}
|
|
else
|
|
{
|
|
output_dyn_value(buf2, sizeof buf2,
|
|
num_recorded_bytes,
|
|
byte_units, true);
|
|
snprintf(buf, sizeof(buf), "%s %s",
|
|
str(LANG_RECORDING_SIZE), buf2);
|
|
}
|
|
}
|
|
|
|
FOR_NB_ACTIVE_SCREENS(i)
|
|
screens[i].puts(0, 1, buf);
|
|
|
|
/* We will do file splitting regardless, either at the end of
|
|
a split interval, or when the filesize approaches the 2GB
|
|
FAT file size (compatibility) limit. */
|
|
if ((audio_stat && !(global_settings.rec_split_method)
|
|
&& global_settings.rec_timesplit && (seconds >= dseconds))
|
|
|| (audio_stat && global_settings.rec_split_method
|
|
&& global_settings.rec_sizesplit
|
|
&& (num_recorded_bytes >= dsize))
|
|
|| (num_recorded_bytes >= MAX_FILE_SIZE))
|
|
{
|
|
if (!(global_settings.rec_split_type)
|
|
|| (num_recorded_bytes >= MAX_FILE_SIZE))
|
|
{
|
|
rec_command(RECORDING_CMD_START_NEWFILE);
|
|
last_seconds = 0;
|
|
}
|
|
else
|
|
{
|
|
peak_meter_trigger(false);
|
|
peak_meter_set_trigger_listener(NULL);
|
|
if( global_settings.rec_split_type == 1)
|
|
rec_command(RECORDING_CMD_STOP);
|
|
else
|
|
rec_command(RECORDING_CMD_STOP_SHUTDOWN);
|
|
}
|
|
update_countdown = 0;
|
|
}
|
|
|
|
/* draw the clipcounter just in front of the peakmeter */
|
|
if(global_settings.peak_meter_clipcounter)
|
|
{
|
|
int clipcount = pm_get_clipcount();
|
|
FOR_NB_ACTIVE_SCREENS(i)
|
|
{
|
|
if(!compact_view[i])
|
|
{
|
|
screens[i].puts(0, 2, str(LANG_PM_CLIPCOUNT));
|
|
screens[i].putsf(0, 3, "%4d", clipcount);
|
|
}
|
|
else
|
|
screens[i].putsf(0, 2, "%4d", clipcount);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_HISTOGRAM
|
|
if(global_settings.histogram_interval)
|
|
{
|
|
histogram_draw(0,
|
|
screens[0].getwidth()/2,
|
|
hist_pos_y,
|
|
hist_pos_y,
|
|
screens[0].getwidth()/2,
|
|
hist_size_h);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_AGC
|
|
hist_time++;
|
|
#endif
|
|
/* draw the trigger status */
|
|
if (peak_meter_trigger_status() != TRIG_OFF)
|
|
{
|
|
peak_meter_draw_trig(pm_x, trig_ypos, trig_width,
|
|
screen_update);
|
|
FOR_NB_ACTIVE_SCREENS(i)
|
|
screens[i].update_viewport_rect(pm_x[i], trig_ypos[i],
|
|
trig_width[i] + 2, TRIG_HEIGHT);
|
|
}
|
|
|
|
#ifdef HAVE_AGC
|
|
#ifdef HAVE_MIC_REC
|
|
if (global_settings.rec_source == AUDIO_SRC_MIC)
|
|
{
|
|
if(agc_maxgain < (global_settings.rec_mic_gain))
|
|
change_recording_gain(false, true, true);
|
|
}
|
|
else
|
|
#endif /* MIC */
|
|
{
|
|
if(agc_maxgain < (global_settings.rec_left_gain))
|
|
change_recording_gain(false, true, false);
|
|
if(agc_maxgain < (global_settings.rec_right_gain))
|
|
change_recording_gain(false, false, true);
|
|
}
|
|
#endif /* HAVE_AGC */
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
#ifdef HAVE_SPDIF_REC
|
|
if((global_settings.rec_source == AUDIO_SRC_SPDIF) &&
|
|
(prev_sample_rate != pcm_rec_sample_rate()))
|
|
{
|
|
/* spdif samplerate changed */
|
|
prev_sample_rate = pcm_rec_sample_rate();
|
|
update_list = true;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if(update_list)
|
|
{
|
|
/* update_list is set whenever content changes */
|
|
update_list = false;
|
|
gui_synclist_draw(&lists);
|
|
}
|
|
|
|
/* draw peakmeter again (check if this can be removed) */
|
|
FOR_NB_ACTIVE_SCREENS(i)
|
|
{
|
|
screens[i].set_viewport(&vp_top[i]);
|
|
peak_meter_screen(&screens[i], pm_x[i], pm_y[i], pm_h[i]);
|
|
screens[i].update();
|
|
}
|
|
} /* display update every second */
|
|
|
|
if(audio_stat & AUDIO_STATUS_ERROR)
|
|
{
|
|
done = true;
|
|
}
|
|
} /* end while(!done) */
|
|
|
|
audio_stat = audio_status();
|
|
if (audio_stat & AUDIO_STATUS_ERROR)
|
|
{
|
|
splash(0, str(LANG_DISK_FULL));
|
|
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].update();
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
/* stop recording first and try to finish saving whatever it can */
|
|
rec_command(RECORDING_CMD_STOP);
|
|
audio_close_recording();
|
|
#endif
|
|
|
|
audio_error_clear();
|
|
|
|
while(1)
|
|
{
|
|
if (action_userabort(TIMEOUT_NOBLOCK))
|
|
break;
|
|
}
|
|
}
|
|
|
|
rec_abort:
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
rec_command(RECORDING_CMD_STOP);
|
|
audio_close_recording();
|
|
|
|
#ifdef HAVE_FMRADIO_REC
|
|
if (radio_status != FMRADIO_OFF)
|
|
/* Restore radio playback - radio_status should be unchanged if started
|
|
through fm radio screen (barring usb connect) */
|
|
rec_set_source(AUDIO_SRC_FMRADIO, (radio_status & FMRADIO_PAUSED) ?
|
|
SRCF_FMRADIO_PAUSED : SRCF_FMRADIO_PLAYING);
|
|
else
|
|
#endif
|
|
/* Go back to playback mode */
|
|
rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK);
|
|
|
|
/* restore talking */
|
|
talk_disable(false);
|
|
|
|
/* restore keyclick */
|
|
global_settings.keyclick = keyclick;
|
|
#else /* !SWCODEC */
|
|
audio_init_playback();
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
|
|
|
#ifdef HAVE_SPEAKER
|
|
/* Re-enable speaker */
|
|
audio_enable_speaker(global_settings.speaker_mode);
|
|
#endif
|
|
|
|
/* make sure the trigger is really turned off */
|
|
peak_meter_trigger(false);
|
|
peak_meter_set_trigger_listener(NULL);
|
|
|
|
rec_status &= ~RCSTAT_IN_RECSCREEN;
|
|
sound_settings_apply();
|
|
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].setfont(FONT_UI);
|
|
|
|
/* if the directory was created or recording happened, make sure the
|
|
browser is updated */
|
|
if (rec_status & (RCSTAT_CREATED_DIRECTORY | RCSTAT_HAVE_RECORDED))
|
|
reload_directory();
|
|
|
|
#if (CONFIG_STORAGE & STORAGE_ATA) && (CONFIG_LED == LED_REAL) \
|
|
&& !defined(SIMULATOR)
|
|
ata_set_led_enabled(true);
|
|
#endif
|
|
|
|
settings_save();
|
|
pop_current_activity();
|
|
return (rec_status & RCSTAT_BEEN_IN_USB_MODE) != 0;
|
|
} /* recording_screen */
|
|
|
|
#if CONFIG_KEYPAD == RECORDER_PAD
|
|
static bool f2_rec_screen(void)
|
|
{
|
|
static const char* const freq_str[6] =
|
|
{
|
|
"44.1kHz",
|
|
"48kHz",
|
|
"32kHz",
|
|
"22.05kHz",
|
|
"24kHz",
|
|
"16kHz"
|
|
};
|
|
|
|
bool exit = false;
|
|
bool used = false;
|
|
int w, h;
|
|
char buf[32];
|
|
int button;
|
|
struct audio_recording_options rec_options;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].set_viewport(NULL);
|
|
screens[i].setfont(FONT_SYSFIXED);
|
|
screens[i].getstringsize("A",&w,&h);
|
|
}
|
|
|
|
while (!exit) {
|
|
const char* ptr;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].clear_display();
|
|
|
|
/* Recording quality */
|
|
screens[i].putsxy(0, LCD_HEIGHT/2 - h*2,
|
|
str(LANG_SYSFONT_RECORDING_QUALITY));
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "%d", global_settings.rec_quality);
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].putsxy(0, LCD_HEIGHT/2-h, buf);
|
|
screens[i].mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
|
|
LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8);
|
|
}
|
|
|
|
/* Frequency */
|
|
snprintf(buf, sizeof buf, "%s:", str(LANG_SYSFONT_RECORDING_FREQUENCY));
|
|
ptr = freq_str[global_settings.rec_frequency];
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].getstringsize(buf,&w,&h);
|
|
screens[i].putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h*2, buf);
|
|
screens[i].getstringsize(ptr, &w, &h);
|
|
screens[i].putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h, ptr);
|
|
screens[i].mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
|
|
LCD_WIDTH/2 - 3, LCD_HEIGHT - h*3, 7, 8);
|
|
}
|
|
|
|
/* Channel mode */
|
|
switch ( global_settings.rec_channels ) {
|
|
case 0:
|
|
ptr = str(LANG_SYSFONT_CHANNEL_STEREO);
|
|
break;
|
|
|
|
case 1:
|
|
ptr = str(LANG_SYSFONT_CHANNEL_MONO);
|
|
break;
|
|
}
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].getstringsize(str(LANG_SYSFONT_CHANNELS), &w, &h);
|
|
screens[i].putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h*2,
|
|
str(LANG_SYSFONT_CHANNELS));
|
|
screens[i].getstringsize(str(LANG_SYSFONT_MODE), &w, &h);
|
|
screens[i].putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h,
|
|
str(LANG_SYSFONT_MODE));
|
|
screens[i].getstringsize(ptr, &w, &h);
|
|
screens[i].putsxy(LCD_WIDTH - w, LCD_HEIGHT/2, ptr);
|
|
screens[i].mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
|
|
LCD_WIDTH/2 + 8, LCD_HEIGHT/2 - 4, 7, 8);
|
|
|
|
screens[i].update();
|
|
}
|
|
|
|
button = button_get(true);
|
|
switch (button) {
|
|
case BUTTON_LEFT:
|
|
case BUTTON_F2 | BUTTON_LEFT:
|
|
global_settings.rec_quality++;
|
|
if(global_settings.rec_quality > 7)
|
|
global_settings.rec_quality = 0;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_DOWN:
|
|
case BUTTON_F2 | BUTTON_DOWN:
|
|
global_settings.rec_frequency++;
|
|
if(global_settings.rec_frequency > 5)
|
|
global_settings.rec_frequency = 0;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_RIGHT:
|
|
case BUTTON_F2 | BUTTON_RIGHT:
|
|
global_settings.rec_channels++;
|
|
if(global_settings.rec_channels > 1)
|
|
global_settings.rec_channels = 0;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_F2 | BUTTON_REL:
|
|
if ( used )
|
|
exit = true;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_F2 | BUTTON_REPEAT:
|
|
used = true;
|
|
break;
|
|
|
|
default:
|
|
if(default_event_handler(button) == SYS_USB_CONNECTED)
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rec_init_recording_options(&rec_options);
|
|
rec_set_recording_options(&rec_options);
|
|
|
|
set_gain();
|
|
|
|
settings_save();
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].setfont(FONT_UI);
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool f3_rec_screen(void)
|
|
{
|
|
bool exit = false;
|
|
bool used = false;
|
|
int w, h;
|
|
int button;
|
|
const char *src_str[] =
|
|
{
|
|
str(LANG_SYSFONT_RECORDING_SRC_MIC),
|
|
str(LANG_SYSFONT_LINE_IN),
|
|
str(LANG_SYSFONT_RECORDING_SRC_DIGITAL)
|
|
};
|
|
struct audio_recording_options rec_options;
|
|
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].set_viewport(NULL);
|
|
screens[i].setfont(FONT_SYSFIXED);
|
|
screens[i].getstringsize("A",&w,&h);
|
|
}
|
|
|
|
while (!exit) {
|
|
const char* ptr = src_str[global_settings.rec_source];
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].clear_display();
|
|
|
|
/* Recording source */
|
|
screens[i].putsxy(0, LCD_HEIGHT/2 - h*2,
|
|
str(LANG_SYSFONT_RECORDING_SOURCE));
|
|
|
|
screens[i].getstringsize(ptr, &w, &h);
|
|
screens[i].putsxy(0, LCD_HEIGHT/2-h, ptr);
|
|
screens[i].mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
|
|
LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8);
|
|
}
|
|
|
|
/* trigger setup */
|
|
ptr = str(LANG_SYSFONT_RECORD_TRIGGER);
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
screens[i].getstringsize(ptr,&w,&h);
|
|
screens[i].putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT - h*2, ptr);
|
|
screens[i].mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
|
|
LCD_WIDTH/2 - 3, LCD_HEIGHT - h*3, 7, 8);
|
|
|
|
screens[i].update();
|
|
}
|
|
|
|
button = button_get(true);
|
|
switch (button) {
|
|
case BUTTON_DOWN:
|
|
case BUTTON_F3 | BUTTON_DOWN:
|
|
#ifndef SIMULATOR
|
|
rectrigger();
|
|
settings_apply_trigger();
|
|
#endif
|
|
exit = true;
|
|
break;
|
|
|
|
case BUTTON_LEFT:
|
|
case BUTTON_F3 | BUTTON_LEFT:
|
|
global_settings.rec_source++;
|
|
if(global_settings.rec_source > AUDIO_SRC_MAX)
|
|
global_settings.rec_source = 0;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_F3 | BUTTON_REL:
|
|
if ( used )
|
|
exit = true;
|
|
used = true;
|
|
break;
|
|
|
|
case BUTTON_F3 | BUTTON_REPEAT:
|
|
used = true;
|
|
break;
|
|
|
|
default:
|
|
if(default_event_handler(button) == SYS_USB_CONNECTED)
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rec_init_recording_options(&rec_options);
|
|
rec_set_recording_options(&rec_options);
|
|
|
|
set_gain();
|
|
|
|
settings_save();
|
|
FOR_NB_SCREENS(i)
|
|
screens[i].setfont(FONT_UI);
|
|
|
|
return false;
|
|
}
|
|
#endif /* CONFIG_KEYPAD == RECORDER_PAD */
|
|
|
|
#if CONFIG_CODEC == SWCODEC
|
|
void audio_beep(int duration)
|
|
{
|
|
/* dummy */
|
|
(void)duration;
|
|
}
|
|
#endif /* #ifdef CONFIG_CODEC == SWCODEC */
|
|
|
|
#endif /* HAVE_RECORDING */
|