audio.c
/** \file
* Onscreen audio meters
*/
/*
* Copyright (C) 2009 Trammell Hudson <hudson+ml@osresearch.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "dryos.h"
#include "bmp.h"
#include "config.h"
#include "property.h"
#include "menu.h"
#include "gui.h"
#if defined(CONFIG_50D) || defined(CONFIG_1100D)
#include "disable-this-module.h"
#endif
#define SOUND_RECORDING_ENABLED (sound_recording_mode != 1) // not 100% sure
#ifdef CONFIG_500D
int audio_thresholds[] = { 0x7fff, 0x7213, 0x65ab, 0x5a9d, 0x50c2, 0x47fa, 0x4026, 0x392c, 0x32f4, 0x2d6a, 0x2879, 0x2412, 0x2026, 0x1ca7, 0x1989, 0x16c2, 0x1449, 0x1214, 0x101d, 0xe5c, 0xccc, 0xb68, 0xa2a, 0x90f, 0x813, 0x732, 0x66a, 0x5b7, 0x518, 0x48a, 0x40c, 0x39b, 0x337, 0x2dd, 0x28d, 0x246, 0x207, 0x1ce, 0x19c, 0x16f, 0x147 };
#endif
static void audio_configure(int force);
static void volume_display();
static void audio_monitoring_display_headphones_connected_or_not();
static void audio_menus_init();
// Dump the audio registers to a file if defined
#undef CONFIG_AUDIO_REG_LOG
// Or on the scren
#undef CONFIG_AUDIO_REG_BMP
struct gain_struct
{
struct semaphore * sem;
unsigned alc1;
unsigned sig1;
unsigned sig2;
};
static struct gain_struct gain = {
.sem = (void*) 1,
};
// Set defaults
CONFIG_INT( "audio.mgain", mgain, 4 );
CONFIG_INT( "audio.dgain.l", dgain_l, 0 );
CONFIG_INT( "audio.dgain.r", dgain_r, 0 );
CONFIG_INT( "audio.mic-power", mic_power, 1 );
CONFIG_INT( "audio.lovl", lovl, 0 );
CONFIG_INT( "audio.o2gain", o2gain, 0 );
CONFIG_INT( "audio.alc-enable", alc_enable, 0 );
//CONFIG_INT( "audio.mic-in", mic_in, 0 ); // not used any more?
int loopback = 1;
//CONFIG_INT( "audio.input-source", input_source, 0 ); //0=internal; 1=L int, R ext; 2 = stereo ext; 3 = L int, R ext balanced
CONFIG_INT( "audio.input-choice", input_choice, 4 ); //0=internal; 1=L int, R ext; 2 = stereo ext; 3 = L int, R ext balanced, 4 = auto (0 or 1)
CONFIG_INT( "audio.filters", enable_filters, 1 ); //disable the HPF, LPF and pre-emphasis filters
CONFIG_INT("audio.draw-meters", cfg_draw_meters, 2);
CONFIG_INT("audio.monitoring", audio_monitoring, 1);
int do_draw_meters = 0;
int ext_cfg_draw_meters(void)
{
return cfg_draw_meters;
}
struct audio_level audio_levels[2];
struct audio_level *get_audio_levels(void)
{
return audio_levels;
}
// from Morgan Look
// THIS FUNCTION IS NOT TESTED!!! (it may work, or it may not)
void masked_audio_ic_write(
unsigned reg, // the register we wish to manipulate (eg AUDIO_IC_SIG1)
unsigned mask, // the range of bits we want to manipulate (eg 0x05 or b0000111) to only allow changes to b3,b2,b0
unsigned bits // the bits we wish to set (eg 0x02 or b000010 to set b1, while clearing others within scope of the mask)
)
{
unsigned old; // variable to store current register value
old = audio_ic_read(reg); // read current register value
old &= ~mask; // bitwise AND old value against inverted mask
bits &= mask; // limit scope of new bits with mask
_audio_ic_write(reg | bits | old); // bitwise OR everything together and call _audio_ic_write function
}
/** Returns a dB translated from the raw level
*
* Range is -40 to 0 dB
*/
int
audio_level_to_db(
int raw_level
)
{
int db;
for( db = 40 ; db ; db-- )
{
if( audio_thresholds[db] > raw_level )
return -db;
}
return 0;
}
#ifdef OSCOPE_METERS
void draw_meters(void)
{
#define MAX_SAMPLES 720
static int16_t levels[ MAX_SAMPLES ];
static uint32_t index;
levels[ index++ ] = audio_read_level();
if( index >= MAX_SAMPLES )
index = 0;
struct vram_info * vram = &vram_info[ vram_get_number(2) ];
//thunk audio_dev_compute_average_level = (void*) 0xFF9725C4;
//audio_dev_compute_average_level();
// The level goes from -40 to 0
uint32_t x;
for( x=0 ; x<MAX_SAMPLES && x<vram->width ; x++ )
{
uint16_t y = 256 + levels[ x ] / 128;
vram->vram[ y * vram->pitch + x ] = 0xFFFF;
}
uint32_t y;
for( y=0 ; y<128 ; y++ )
{
vram->vram[ y * vram->pitch + index ] = 0x888F;
}
}
#else
char left_label[10] = "LEFT ";
char right_label[10] = "RIGHT";
static uint8_t
db_to_color(
int db
)
{
if( db < -25 )
return 0x2F; // white
if( db < -12 )
return 0x06; // dark green
if( db < -3 )
return 0x0F; // yellow
return 0x0c; // dull red
}
static uint8_t
db_peak_to_color(
int db
)
{
if( db < -25 )
return 11; // dark blue
if( db < -12 )
return 11; // dark blue
if( db < -3 )
return 15; // bright yellow
return 0x08; // bright red
}
static void
draw_meter(
int x_origin,
int y_origin,
int meter_height,
struct audio_level * level,
char * label
)
{
const uint32_t width = 520; // bmp_width();
const uint32_t pitch = BMPPITCH;
uint32_t * row = (uint32_t*) bmp_vram();
if( !row )
return;
// Skip to the desired y coord and over the
// space for the numerical levels
// .. and the space for showing the channel and source.
row += (pitch/4) * y_origin + AUDIO_METER_OFFSET + x_origin/4;
const int db_peak_fast = audio_level_to_db( level->peak_fast );
const int db_peak = audio_level_to_db( level->peak );
// levels go from -40 to 0, so -40 * 12 == 520 (=width)
const uint32_t x_db_peak_fast = (width + db_peak_fast * 13) / 4;
const uint32_t x_db_peak = (width + db_peak * 13) / 4;
const uint8_t bar_color = db_to_color( db_peak_fast );
const uint8_t peak_color = db_peak_to_color( db_peak );
const uint32_t bar_color_word = color_word( bar_color );
const uint32_t peak_color_word = color_word( peak_color );
const uint32_t bg_color_word = color_word(COLOR_BLACK);
// Write the meter an entire scan line at a time
int y;
for( y=0 ; y<meter_height ; y++, row += pitch/4 )
{
uint32_t x;
for( x=0 ; x<width/4 ; x++ )
{
if( x < x_db_peak_fast )
row[x] = bar_color_word;
else
if( x < x_db_peak )
row[x] = bg_color_word;
else
if( x < x_db_peak + 4 )
row[x] = peak_color_word;
else
row[x] = bg_color_word;
}
}
// Write the current level
bmp_printf( FONT(FONT_SMALL, COLOR_WHITE, COLOR_BLACK), x_origin, y_origin, "%s %02d", label, MIN(db_peak, -1) );
}
static void
draw_ticks(
int x,
int y,
int tick_height
)
{
const uint32_t width = 520; // bmp_width();
const uint32_t pitch = BMPPITCH;
uint32_t * row = (uint32_t*) bmp_vram();
if( !row )
return;
row += (pitch/4) * y + AUDIO_METER_OFFSET - 2 + x/4;//seems to need less of an offset
const uint32_t white_word = 0
| ( COLOR_WHITE << 24 )
| ( COLOR_WHITE << 16 )
| ( COLOR_WHITE << 8 )
| ( COLOR_WHITE << 0 );
for( ; tick_height > 0 ; tick_height--, row += pitch/4 )
{
int db;
for( db=-40; db<= 0 ; db+=5 )
{
const uint32_t x_db = width + db * 13;
row[x_db/4] = white_word;
}
}
}
static int audio_cmd_to_gain_x1000(int cmd);
/* Normal VU meter */
static void draw_meters(void)
{
int screen_layout = get_screen_layout();
// The db values are multiplied by 8 to make them
// smoother.
int erase = 0;
int hs = get_halfshutter_pressed();
static int prev_hs = 0;
if (hs != prev_hs) erase = 1;
prev_hs = hs;
int x0 = 0;
int y0 = 0;
int small = 0;
get_yuv422_vram(); // just to refresh vram params
if (gui_menu_shown())
{
x0 = MAX(os.x0 + os.x_ex/2 - 360, 0);
y0 = MAX(os.y0 + os.y_ex/2 - 240, 0);
y0 += 380;
x0 += 10;
}
else
{
small = hs;
x0 = MAX(os.x0 + os.x_ex/2 - 360, 0);
if (screen_layout == SCREENLAYOUT_3_2_or_4_3) y0 = os.y0; // just above the 16:9 frame
else if (screen_layout == SCREENLAYOUT_16_9) { small = 1; y0 = os.y0 + os.off_169; } // meters just below 16:9 border
else if (screen_layout == SCREENLAYOUT_16_10) {small = 1; y0 = os.y0 + os.off_1610; } // meters just below 16:10 border
else if (screen_layout == SCREENLAYOUT_UNDER_3_2) y0 = MIN(os.y_max, 480 - 54);
else if (screen_layout == SCREENLAYOUT_UNDER_16_9) y0 = MIN(os.y_max - os.off_169, 480 - 54);
if (hdmi_code) small = 1;
if (screen_layout >= SCREENLAYOUT_UNDER_3_2) small = 1;
}
if (erase)
{
bmp_fill(
screen_layout >= SCREENLAYOUT_UNDER_3_2 ? BOTTOMBAR_BGCOLOR : TOPBAR_BGCOLOR,
x0, y0, 635, small ? 24 : 33
);
}
else if (hs) return; // will draw top bar instead
else if (!small)
{
draw_meter( x0, y0 + 0, 10, &audio_levels[0], left_label);
draw_ticks( x0, y0 + 10, 3 );
#ifndef CONFIG_500D // mono mic on 500d :(
draw_meter( x0, y0 + 12, 10, &audio_levels[1], right_label);
#endif
}
else
{
draw_meter( x0, y0 + 0, 7, &audio_levels[0], left_label);
draw_ticks( x0, y0 + 7, 2 );
#ifndef CONFIG_500D
draw_meter( x0, y0 + 8, 7, &audio_levels[1], right_label);
#endif
}
if (gui_menu_shown() && alc_enable)
{
int dgain_x1000 = audio_cmd_to_gain_x1000(audio_ic_read(AUDIO_IC_ALCVOL));
bmp_printf(FONT_MED, 550, 425, "AGC:%s%d.%03d dB", dgain_x1000 < 0 ? "-" : " ", ABS(dgain_x1000) / 1000, ABS(dgain_x1000) % 1000);
}
}
#endif
static void
compute_audio_levels(
int ch
)
{
struct audio_level * const level = &audio_levels[ch];
int raw = audio_read_level( ch );
if( raw < 0 )
raw = -raw;
level->last = raw;
level->avg = (level->avg * 15 + raw) / 16;
if( raw > level->peak )
level->peak = raw;
if( raw > level->peak_fast )
level->peak_fast = raw;
// Decay the peak to the average
level->peak = ( level->peak * 63 + level->avg ) / 64;
level->peak_fast = ( level->peak_fast * 7 + level->avg ) / 8;
}
int audio_meters_are_drawn()
{
if (!SOUND_RECORDING_ENABLED)
return 0;
return
(
is_movie_mode() && cfg_draw_meters && do_draw_meters && (zebra_should_run() || get_halfshutter_pressed()) && !gui_menu_shown()
)
||
(
gui_menu_shown() && is_menu_active("Audio") && cfg_draw_meters
);
}
/** Task to monitor the audio levels.
*
* Compute the average and peak level, periodically calling
* the draw_meters() function to display the results on screen.
* \todo Check that we have live-view enabled and the TFT is on
* before drawing.
*/
static void
meter_task( void* unused )
{
audio_menus_init();
TASK_LOOP
{
msleep(DISPLAY_IS_ON ? 50 : 500);
if (is_menu_help_active()) continue;
if (audio_meters_are_drawn())
{
if (!is_mvr_buffer_almost_full())
BMP_LOCK( draw_meters(); )
}
if (audio_monitoring)
{
static int hp = 0;
int h = AUDIO_MONITORING_HEADPHONES_CONNECTED;
if (h != hp)
{
audio_monitoring_display_headphones_connected_or_not();
}
hp = h;
}
}
}
TASK_CREATE( "audio_meter_task", meter_task, 0, 0x18, 0x1000 );
/** Monitor the audio levels very quickly */
static void
compute_audio_level_task( void* unused )
{
audio_levels[0].peak = audio_levels[1].peak = 0;
audio_levels[0].avg = audio_levels[1].avg = 0;
TASK_LOOP
{
msleep(MIN_MSLEEP);
compute_audio_levels( 0 );
compute_audio_levels( 1 );
}
}
TASK_CREATE( "audio_level_task", compute_audio_level_task, 0, 0x18, 0x1000 );
/** Write the MGAIN2-0 bits.
* Table 19 for the gain values (variable "bits"):
*
* 0 == +0 dB
* 1 == +20 dB
* 2 == +26 dB
* 3 == +32 dB
* 4 == +10 dB
* 5 == +17 dB
* 6 == +23 dB
* 7 == +29 dB
*
* Why is it split between two registers? I don't know.
==================================
* 500d mono chip gain settings - by: coutts
0 == +0 dB
1 == +20 dB
2 == +26 dB
3 == +32 dB
4 == +10 dB
5 == +17 dB
6 == +23 dB
7 == +29 dB
8 == +3 dB
9 == +6 dB
*/
#ifdef CONFIG_500D
static inline unsigned mgain_index2gain(int index) // sorted mgain values
{
static uint8_t gains[] = { 0, 3, 6, 10, 17, 20, 23, 26, 29, 32 };
index = COERCE(index, 0, 10);
return gains[index];
}
static inline unsigned mgain_index2bits(int index) // sorted mgain values
{
static uint8_t bitsv[] = { 0, 0x8, 0x9, 0x4, 0x5, 0x1, 0x6, 0x2, 0x7, 0x3 };
index = COERCE(index, 0, 10);
return bitsv[index];
}
#else
static inline unsigned mgain_index2gain(int index) // sorted mgain values
{
static uint8_t gains[] = { 0, 10, 17, 20, 23, 26, 29, 32 };
return gains[index & 0x7];
}
static inline unsigned mgain_index2bits(int index) // sorted mgain values
{
static uint8_t bitsv[] = { 0, 0x4, 0x5, 0x01, 0x06, 0x02, 0x07, 0x03 };
return bitsv[index & 0x7];
}
#endif
#ifdef CONFIG_500D
static inline void
audio_ic_set_mgain(
unsigned index
)
{
unsigned sig1 = audio_ic_read( AUDIO_IC_SIG1 );
unsigned sig2 = audio_ic_read( AUDIO_IC_SIG2 );
// setting the bits for each possible gain setting in the 500d.
switch (index)
{
case 0: // 0 dB
sig1 &= ~(1 << 0);
sig1 &= ~(1 << 1);
sig1 &= ~(1 << 3);
sig2 &= ~(1 << 5);
break;
case 1: // 3 dB
sig1 &= ~(1 << 0);
sig1 &= ~(1 << 1);
sig1 |= 1 << 3;
sig2 &= ~(1 << 5);
break;
case 2: // 6 dB
sig1 &= ~(1 << 1);
sig1 |= 1 << 0;
sig1 |= 1 << 3;
sig2 &= ~(1 << 5);
break;
case 3: // 10 dB
sig1 &= ~(1 << 0);
sig1 &= ~(1 << 3);
sig1 |= 1 << 1;
sig2 &= ~(1 << 5);
break;
case 4: // 17 dB
sig1 &= ~(1 << 3);
sig1 |= 1 << 0;
sig1 |= 1 << 1;
sig2 &= ~(1 << 5);
break;
case 5: // 20 dB
sig1 &= ~(1 << 1);
sig1 &= ~(1 << 3);
sig1 |= 1 << 0;
sig2 &= ~(1 << 5);
break;
case 6: // 23 dB
sig1 &= ~(1 << 0);
sig1 &= ~(1 << 3);
sig1 |= 1 << 1;
sig2 |= 1 << 5;
break;
case 7: // 26 dB
sig1 &= ~(1 << 0);
sig1 &= ~(1 << 1);
sig1 &= ~(1 << 3);
sig2 |= 1 << 5;
break;
case 8: // 29 dB
sig1 &= ~(1 << 3);
sig1 |= 1 << 0;
sig1 |= 1 << 1;
sig2 |= 1 << 5;
break;
case 9: // 32 dB
sig1 &= ~(1 << 1);
sig1 &= ~(1 << 3);
sig1 |= 1 << 0;
sig2 |= 1 << 5;
break;
}
audio_ic_write( AUDIO_IC_SIG1 | sig1 );
gain.sig1 = sig1;
audio_ic_write( AUDIO_IC_SIG2 | sig2 );
gain.sig2 = sig2;
}
#else
static inline void
audio_ic_set_mgain(
unsigned index
)
{
unsigned bits = mgain_index2bits(index);
bits &= 0x7;
unsigned sig1 = audio_ic_read( AUDIO_IC_SIG1 );
sig1 &= ~0x3;
sig1 |= (bits & 1);
sig1 |= (bits & 4) >> 1;
audio_ic_write( AUDIO_IC_SIG1 | sig1 );
gain.sig1 = sig1;
unsigned sig2 = audio_ic_read( AUDIO_IC_SIG2 );
sig2 &= ~(1<<5);
sig2 |= (bits & 2) << 4;
audio_ic_write( AUDIO_IC_SIG2 | sig2 );
gain.sig2 = sig2;
}
#endif
static inline uint8_t
audio_gain_to_cmd(
int gain
)
{
unsigned cmd = ( gain * 1000 ) / 375 + 145;
cmd &= 0xFF;
return cmd;
}
static int
audio_cmd_to_gain_x1000(
int cmd
)
{
int gain_x1000 = (cmd - 145) * 375;
return gain_x1000;
}
#ifndef CONFIG_500D //no support for anything but gain for now.
static inline void
audio_ic_set_input_volume(
int channel,
int gain
)
{
unsigned cmd = audio_gain_to_cmd( gain );
if( channel )
cmd |= AUDIO_IC_IVL;
else
cmd |= AUDIO_IC_IVR;
audio_ic_write( cmd );
}
#endif
#if defined(CONFIG_AUDIO_REG_LOG) || defined(CONFIG_AUDIO_REG_BMP)
// Do not write the value; just read them and record to a logfile
static uint16_t audio_regs[] = {
AUDIO_IC_PM1,
AUDIO_IC_PM2,
AUDIO_IC_SIG1,
AUDIO_IC_SIG2,
AUDIO_IC_ALC1,
AUDIO_IC_ALC2,
AUDIO_IC_IVL,
AUDIO_IC_IVR,
AUDIO_IC_OVL,
AUDIO_IC_OVR,
AUDIO_IC_ALCVOL,
AUDIO_IC_MODE3,
AUDIO_IC_MODE4,
AUDIO_IC_PM3,
AUDIO_IC_FIL1,
AUDIO_IC_HPF0,
AUDIO_IC_HPF1,
AUDIO_IC_HPF2,
AUDIO_IC_HPF3,
AUDIO_IC_LPF0,
AUDIO_IC_LPF1,
AUDIO_IC_LPF2,
AUDIO_IC_LPF3,
};
static const char * audio_reg_names[] = {
"AUDIO_IC_PM1",
"AUDIO_IC_PM2",
"AUDIO_IC_SIG1",
"AUDIO_IC_SIG2",
"AUDIO_IC_ALC1",
"AUDIO_IC_ALC2",
"AUDIO_IC_IVL",
"AUDIO_IC_IVR",
"AUDIO_IC_OVL",
"AUDIO_IC_OVR",
"AUDIO_IC_ALCVOL",
"AUDIO_IC_MODE3",
"AUDIO_IC_MODE4",
"AUDIO_IC_PM3",
"AUDIO_IC_FIL1",
"AUDIO_IC_HPF0",
"AUDIO_IC_HPF1",
"AUDIO_IC_HPF2",
"AUDIO_IC_HPF3",
"AUDIO_IC_LPF0",
"AUDIO_IC_LPF1",
"AUDIO_IC_LPF2",
"AUDIO_IC_LPF3",
};
static FILE * reg_file;
static void
audio_reg_dump( int force )
{
if( !reg_file )
return;
static uint16_t last_regs[ COUNT(audio_regs) ];
unsigned i;
int output = 0;
for( i=0 ; i<COUNT(audio_regs) ; i++ )
{
const uint16_t reg = audio_ic_read( audio_regs[i] );
if( reg != last_regs[i] || force )
{
my_fprintf(
reg_file,
"%s %02x\n",
audio_reg_names[i],
reg
);
output = 1;
}
last_regs[i] = reg;
}
if( output )
my_fprintf( reg_file, "%s\n", "" );
}
static void
audio_reg_close( void )
{
if( reg_file )
FIO_CloseFile( reg_file );
reg_file = NULL;
}
static void
audio_reg_dump_screen()
{
int i, x, y;
for( i=0 ; i<COUNT(audio_regs) ; i++ )
{
const uint16_t reg = audio_ic_read( audio_regs[i] );
x = 10 + (i / 30) * 200;
y = 50 + (i % 30) * 12;
bmp_printf(FONT_SMALL, x, y,
"%s %02x\n",
audio_reg_names[i],
reg
);
}
}
#endif
int mic_inserted = -1;
PROP_HANDLER( PROP_MIC_INSERTED )
{
if (mic_inserted != -1)
{
NotifyBox(2000,
"Microphone %s",
buf[0] ?
"connected" :
"disconnected");
}
mic_inserted = buf[0];
audio_configure( 1 );
//~ menu_set_dirty();
}
int get_input_source()
{
int input_source;
//setup input_source based on choice and mic pluggedinedness
if (input_choice == 4) {
input_source = mic_inserted ? 2 : 0;
} else {
input_source = input_choice;
}
return input_source;
}
int get_mic_power(int input_source)
{
return (input_source >= 2) ? mic_power : 1;
}
static void
audio_configure( int force )
{
#ifdef CONFIG_600D
return;
#endif
#ifdef CONFIG_AUDIO_REG_LOG
audio_reg_dump( force );
return;
#endif
#ifdef CONFIG_AUDIO_REG_BMP
audio_reg_dump_screen();
return;
#endif
int pm3[] = { 0x00, 0x05, 0x07, 0x11 }; //should this be in a header file?
#ifdef CONFIG_500D //500d only has internal mono audio :(
int input_source = 0;
#else
int input_source = get_input_source();
#endif
//those char*'s cause a memory corruption, don't know why
//char * left_labels[] = {"L INT", "L INT", "L EXT", "L INT"}; //these are used by draw_meters()
//char * right_labels[] = {"R INT", "R EXT", "R EXT", "R BAL"}; //but defined and set here, because a change to the pm3 array should be changed in them too.
switch (input_source)
{
case 0:
snprintf(left_label, sizeof(left_label), "L INT");
snprintf(right_label, sizeof(right_label), "R INT");
break;
case 1:
snprintf(left_label, sizeof(left_label), "L INT");
snprintf(right_label, sizeof(right_label), "R EXT");
break;
case 2:
snprintf(left_label, sizeof(left_label), "L EXT");
snprintf(right_label, sizeof(right_label), "R EXT");
break;
case 3:
snprintf(left_label, sizeof(left_label), "L INT");
snprintf(right_label, sizeof(right_label), "R BAL");
break;
}
if( !force )
{
// Check for ALC configuration; do nothing if it is
// already disabled
if( audio_ic_read( AUDIO_IC_ALC1 ) == gain.alc1
&& audio_ic_read( AUDIO_IC_SIG1 ) == gain.sig1
&& audio_ic_read( AUDIO_IC_SIG2 ) == gain.sig2
)
return;
DebugMsg( DM_AUDIO, 3, "%s: Reseting user settings", __func__ );
}
//~ static int iter=0;
//~ bmp_printf(FONT_MED, 0, 70, "audio configure(%d)", iter++);
audio_ic_write( AUDIO_IC_PM1 | 0x6D ); // power up ADC and DAC
//mic_power is forced on if input source is 0 or 1
int mic_pow = get_mic_power(input_source);
audio_ic_write( AUDIO_IC_SIG1
| 0x10
| ( mic_pow ? 0x4 : 0x0 )
); // power up, no gain
audio_ic_write( AUDIO_IC_SIG2
| 0x04 // external, no gain
| ( lovl & 0x3) << 0 // line output level
);
#ifdef CONFIG_500D
audio_ic_write( AUDIO_IC_SIG4 | pm3[input_source] );
#else
//PM3 is set according to the input choice
audio_ic_write( AUDIO_IC_PM3 | pm3[input_source] );
#endif
gain.alc1 = alc_enable ? (1<<5) : 0;
audio_ic_write( AUDIO_IC_ALC1 | gain.alc1 ); // disable all ALC
#ifndef CONFIG_500D
// Control left/right gain independently
audio_ic_write( AUDIO_IC_MODE4 | 0x00 );
audio_ic_set_input_volume( 0, dgain_r );
audio_ic_set_input_volume( 1, dgain_l );
#endif
audio_ic_set_mgain( mgain );
#ifdef CONFIG_500D
// nothing here yet.
#else
#ifndef CONFIG_550D // no sound with external mic?!
audio_ic_write( AUDIO_IC_FIL1 | (enable_filters ? 0x1 : 0));
#endif
// Enable loop mode and output digital volume2
uint32_t mode3 = audio_ic_read( AUDIO_IC_MODE3 );
mode3 &= ~0x5C; // disable loop, olvc, datt0/1
audio_ic_write( AUDIO_IC_MODE3
| mode3 // old value
| loopback << 6 // loop mode
| (o2gain & 0x3) << 2 // output volume
);
#endif
//draw_audio_regs();
/*bmp_printf( FONT_SMALL, 500, 450,
"Gain %d/%d Mgain %d Src %d",
dgain_l,
dgain_r,
mgain_index2gain(mgain),
input_source
);*/
DebugMsg( DM_AUDIO, 3,
"Gain mgain=%d dgain=%d/%d",
mgain_index2gain(mgain),
dgain_l,
dgain_r
);
}
/** Menu handlers */
static void
audio_binary_toggle( void * priv, int delta )
{
unsigned * ptr = priv;
*ptr = !*ptr;
audio_configure( 1 );
}
static void
audio_3bit_toggle( void * priv, int delta )
{
unsigned * ptr = priv;
*ptr = (*ptr + 0x1) & 0x3;
audio_configure( 1 );
}
static void
audio_3bit_toggle_reverse( void * priv, int delta )
{
unsigned * ptr = priv;
*ptr = (*ptr - 0x1) & 0x3;
audio_configure( 1 );
}
static void
audio_mgain_toggle( void * priv, int delta )
{
unsigned * ptr = priv;
#ifdef CONFIG_500D
*ptr = mod((*ptr + 0x1), 10);
#else
*ptr = (*ptr + 0x1) & 0x7;
#endif
audio_configure( 1 );
}
static void
audio_mgain_toggle_reverse( void * priv, int delta )
{
unsigned * ptr = priv;
#ifdef CONFIG_500D
*ptr = mod((*ptr - 0x1), 10);
#else
*ptr = (*ptr - 0x1) & 0x7;
#endif
audio_configure( 1 );
}
static void check_sound_recording_warning(int x, int y)
{
if (!SOUND_RECORDING_ENABLED)
{
if (was_sound_recording_disabled_by_fps_override())
menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Sound recording was disabled by FPS override.");
else
menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Sound recording is disabled. Enable it from Canon menu.");
}
}
static void
audio_mgain_display( void * priv, int x, int y, int selected )
{
unsigned gain_index = *(unsigned*) priv;
#ifdef CONFIG_500D
gain_index = COERCE(gain_index, 0, 10);
#else
gain_index &= 0x7;
#endif
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Analog Gain : %d dB",
mgain_index2gain(gain_index)
);
check_sound_recording_warning(x, y);
menu_draw_icon(x, y, MNI_PERCENT, mgain_index2gain(gain_index) * 100 / 32);
}
static void
audio_dgain_toggle( void * priv, int delta )
{
unsigned dgain = *(unsigned*) priv;
dgain += 6;
if( dgain > 40 )
dgain = 0;
*(unsigned*) priv = dgain;
audio_configure( 1 );
}
static void
audio_dgain_toggle_reverse( void * priv, int delta )
{
unsigned dgain = *(unsigned*) priv;
if( dgain <= 0 ) {
dgain = 36;
} else {
dgain -= 6;
}
*(unsigned*) priv = dgain;
audio_configure( 1 );
}
static void
audio_dgain_display( void * priv, int x, int y, int selected )
{
unsigned val = *(unsigned*) priv;
unsigned fnt = selected ? MENU_FONT_SEL : MENU_FONT;
bmp_printf(
FONT(fnt, val ? COLOR_RED : FONT_FG(fnt), FONT_BG(fnt)),
x, y,
// 23456789012
"%s-DigitalGain : %d dB",
priv == &dgain_l ? "L" : "R",
val
);
check_sound_recording_warning(x, y);
if (!alc_enable) menu_draw_icon(x, y, MNI_PERCENT, val * 100 / 36);
else menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "AGC is enabled");
}
static void
audio_lovl_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
//23456789012
"Output volume : %d dB",
2 * *(unsigned*) priv
);
check_sound_recording_warning(x, y);
if (audio_monitoring) menu_draw_icon(x, y, MNI_PERCENT, (2 * *(unsigned*) priv) * 100 / 6);
else menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Headphone monitoring is disabled");
}
static void
audio_meter_display( void * priv, int x, int y, int selected )
{
unsigned v = *(unsigned*) priv;
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Audio Meters : %s",
v ? "ON" : "OFF"
);
check_sound_recording_warning(x, y);
menu_draw_icon(x, y, MNI_BOOL_GDR(v));
}
#if 0
static void
audio_o2gain_display( void * priv, int x, int y, int selected )
{
static uint8_t gains[] = { 0, 6, 12, 18 };
unsigned gain_reg= *(unsigned*) priv;
gain_reg &= 0x3;
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
//23456789
"o2gain: -%2d dB",
gains[ gain_reg ]
);
}
#endif
static void
audio_alc_display( void * priv, int x, int y, int selected )
{
unsigned fnt = selected ? MENU_FONT_SEL : MENU_FONT;
bmp_printf(
FONT(fnt, alc_enable ? COLOR_RED : FONT_FG(fnt), FONT_BG(fnt)),
x, y,
//23456789012
"AGC : %s",
alc_enable ? "ON " : "OFF"
);
check_sound_recording_warning(x, y);
}
static const char* get_audio_input_string()
{
return
(input_choice == 0 ? "internal mic" :
(input_choice == 1 ? "L:int R:ext" :
(input_choice == 2 ? "external stereo" :
(input_choice == 3 ? "L:int R:balanced" :
(input_choice == 4 ? (mic_inserted ? "Auto int/EXT " : "Auto INT/ext") :
"error")))));
}
static void
audio_input_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Input Source : %s",
get_audio_input_string()
);
check_sound_recording_warning(x, y);
menu_draw_icon(x, y, input_choice == 4 ? MNI_AUTO : MNI_ON, 0);
}
static void
audio_input_toggle( void * priv, int delta )
{
menu_quinternary_toggle(priv, 1);
audio_configure( 1 );
}
static void
audio_input_toggle_reverse( void * priv, int delta )
{
menu_quinternary_toggle_reverse(priv, -1);
audio_configure( 1 );
}
/*
static void
audio_loopback_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Loopback : %s",
loopback ? "ON " : "OFF"
);
}*/
void audio_filters_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Wind Filter : %s",
enable_filters ? "ON" : "OFF"
);
check_sound_recording_warning(x, y);
}
/*
PROP_INT(PROP_WINDCUT_MODE, windcut_mode);
void windcut_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Wind Cut Mode : %d",
windcut_mode
);
}
void set_windcut(int value)
{
prop_request_change(PROP_WINDCUT_MODE, &value, 4);
}
void windcut_toggle(void* priv)
{
windcut_mode = !windcut_mode;
set_windcut(windcut_mode);
}*/
static void
audio_monitoring_display( void * priv, int x, int y, int selected )
{
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Headphone Mon.: %s",
audio_monitoring ? "ON" : "OFF"
);
check_sound_recording_warning(x, y);
}
static void
audio_micpower_display( void * priv, int x, int y, int selected )
{
unsigned int mic_pow = get_mic_power(get_input_source());
bmp_printf(
selected ? MENU_FONT_SEL : MENU_FONT,
x, y,
"Mic Power : %s",
mic_pow ? "ON (Low Z)" : "OFF (High Z)"
);
check_sound_recording_warning(x, y);
if (mic_pow != mic_power) menu_draw_icon(x,y, MNI_WARNING, (intptr_t) "Mic power is required by internal mic.");
}
PROP_INT(PROP_USBRCA_MONITOR, rca_monitor);
static void audio_monitoring_force_display(int x)
{
prop_deliver(*(int*)(HOTPLUG_VIDEO_OUT_PROP_DELIVER_ADDR), &x, 4, 0x0);
}
void audio_monitoring_display_headphones_connected_or_not()
{
NotifyBox(2000,
"Headphones %s",
AUDIO_MONITORING_HEADPHONES_CONNECTED ?
"connected" :
"disconnected");
}
static void audio_monitoring_update()
{
// kill video connect/disconnect event... or not
*(int*)HOTPLUG_VIDEO_OUT_STATUS_ADDR = audio_monitoring ? 2 : 0;
if (audio_monitoring && rca_monitor)
{
audio_monitoring_force_display(0);
msleep(1000);
audio_monitoring_display_headphones_connected_or_not();
}
}
static void
audio_monitoring_toggle( void * priv, int delta )
{
audio_monitoring = !audio_monitoring;
audio_monitoring_update();
}
static struct menu_entry audio_menus[] = {
#ifndef CONFIG_600D
#if 0
{
.priv = &o2gain,
.select = audio_o2gain_toggle,
.display = audio_o2gain_display,
},
#endif
{
.name = "Analog Gain (dB)",
.priv = &mgain,
.select = audio_mgain_toggle,
.select_reverse = audio_mgain_toggle_reverse,
.display = audio_mgain_display,
.help = "Gain applied to both inputs in analog domain (preferred).",
.essential = FOR_MOVIE,
.edit_mode = EM_MANY_VALUES,
},
#ifndef CONFIG_500D
{
.name = "L-DigitalGain",
.priv = &dgain_l,
.select = audio_dgain_toggle,
.select_reverse = audio_dgain_toggle_reverse,
.display = audio_dgain_display,
.help = "Digital gain (LEFT). Any nonzero value reduces quality.",
.edit_mode = EM_MANY_VALUES,
},
{
.name = "L-DigitalGain", // hack
.priv = &dgain_r,
.select = audio_dgain_toggle,
.select_reverse = audio_dgain_toggle_reverse,
.display = audio_dgain_display,
.help = "Digital gain (RIGHT). Any nonzero value reduces quality.",
.edit_mode = EM_MANY_VALUES,
},
#endif
#ifndef CONFIG_500D
{
.name = "Input source",
.priv = &input_choice,
.select = audio_input_toggle,
.select_reverse = audio_input_toggle_reverse,
.display = audio_input_display,
.help = "Audio input: internal / external / both / balanced / auto.",
.essential = FOR_MOVIE,
//~ .edit_mode = EM_MANY_VALUES,
},
#endif
/*{
.priv = &windcut_mode,
.select = windcut_toggle,
.display = windcut_display,
},*/
#if !defined(CONFIG_550D) && !defined(CONFIG_500D)
{
.priv = &enable_filters,
.select = audio_binary_toggle,
.display = audio_filters_display,
//~ .icon_type = IT_DISABLE_SOME_FEATURE,
.help = "High pass filter for wind noise reduction. AK4646.pdf p.34.",
.essential = FOR_MOVIE,
},
#endif
#ifdef CONFIG_AUDIO_REG_LOG
{
.priv = "Close register log",
.select = audio_reg_close,
.display = menu_print,
},
#endif
/*{
.priv = &loopback,
.select = audio_binary_toggle,
.display = audio_loopback_display,
},*/
#ifndef CONFIG_500D
{
.name = "Mic Power",
.priv = &mic_power,
.select = audio_binary_toggle,
.display = audio_micpower_display,
.help = "Needed for int. and some other mics, but lowers impedance.",
.essential = FOR_MOVIE,
},
{
.name = "AGC",
.priv = &alc_enable,
.select = audio_binary_toggle,
.display = audio_alc_display,
.help = "Automatic Gain Control - turn it off :)",
//~ .icon_type = IT_DISABLE_SOME_FEATURE_NEG,
.essential = FOR_MOVIE, // nobody needs to toggle this, but newbies want to see "AGC:OFF", manual controls are not enough...
},
{
.name = "Output volume (dB)",
.priv = &lovl,
.select = audio_3bit_toggle,
.select_reverse = audio_3bit_toggle_reverse,
.display = audio_lovl_display,
.help = "Output volume for audio monitoring (headphones only).",
//~ .edit_mode = EM_MANY_VALUES,
},
#endif
{
.name = "Headphone Monitoring",
.priv = &audio_monitoring,
.select = audio_monitoring_toggle,
.display = audio_monitoring_display,
.help = "Monitoring via A-V jack. Disable if you use a SD display.",
.essential = FOR_MOVIE,
},
#endif // 600D
{
.name = "Audio Meters",
.priv = &cfg_draw_meters,
.select = menu_binary_toggle,
.display = audio_meter_display,
.help = "Bar peak decay, -40...0 dB, yellow at -12 dB, red at -3 dB.",
.essential = FOR_MOVIE,
},
};
static void
enable_recording(
int mode
)
{
switch( mode )
{
case 0:
// Movie recording stopped; (fallthrough)
case 2:
// Movie recording started
give_semaphore( gain.sem );
break;
case 1:
// Movie recording about to start?
break;
default:
// Uh?
break;
}
}
static void
enable_meters(
int mode
)
{
loopback = do_draw_meters = !mode;
audio_configure( 1 );
}
PROP_HANDLER( PROP_LV_ACTION )
{
const unsigned mode = buf[0];
enable_meters( mode );
}
PROP_HANDLER( PROP_MVR_REC_START )
{
const unsigned mode = buf[0];
enable_recording( mode );
}
void sounddev_task();
/** Replace the sound dev task with our own to disable AGC.
*
* This task disables the AGC when the sound device is activated.
*/
static void
my_sounddev_task()
{
msleep( 1500 );
if (magic_is_off()) { sounddev_task(); return; }
hold_your_horses(1);
DebugMsg( DM_AUDIO, 3,
"!!!!! %s started sem=%x",
__func__,
(uint32_t) sounddev.sem_alc
);
//DIY debug ..
//~ bmp_printf( FONT_SMALL, 500, 400,
//~ "sddvtsk, param=%d",
//~ some_param
//~ );
gain.sem = create_named_semaphore( "audio_gain", 1 );
// Fake the sound dev task parameters
sounddev.sem_alc = create_named_semaphore( 0, 0 );
sounddev_active_in(0,0);
audio_configure( 1 ); // force it this time
#ifdef CONFIG_AUDIO_REG_LOG
// Create the logging file
FIO_RemoveFile(CARD_DRIVE "ML/audioreg.txt");
reg_file = FIO_CreateFile(CARD_DRIVE "ML/audioreg.txt" );
#endif
msleep(500);
audio_monitoring_update();
while(1)
{
// will be unlocked by the property handler
int rc = take_semaphore( gain.sem, recording && MVR_FRAME_NUMBER < 30 ? 100 : 1000 );
if(gui_state != GUISTATE_PLAYMENU || (audio_monitoring && AUDIO_MONITORING_HEADPHONES_CONNECTED)) {
audio_configure( rc == 0 ); // force it if we got the semaphore
}
}
}
#ifndef CONFIG_600D
TASK_OVERRIDE( sounddev_task, my_sounddev_task );
#endif
#if 0
/** Replace the audio level task with our own.
*
* This task runs when the sound device is activated to keep track of
* the average audio level and translate it to dB. Nothing ever seems
* to activate it, so it is commented out for now.
*/
static void
my_audio_level_task( void )
{
//const uint32_t * const thresholds = (void*) 0xFFC60ABC;
DebugMsg( DM_AUDIO, 3, "!!!!! %s: Replaced Canon task %x", __func__, audio_level_task );
audio_in.gain = -40;
audio_in.sample_count = 0;
audio_in.max_sample = 0;
audio_in.sem_interval = create_named_semaphore( 0, 1 );
audio_in.sem_task = create_named_semaphore( 0, 0 );
while(1)
{
DebugMsg( DM_AUDIO, 3, "%s: sleeping init=%d",
__func__,
audio_in.initialized
);
if( take_semaphore( audio_in.sem_interval, 0 ) & 1 )
{
//DebugAssert( "!IS_ERROR", "SoundDevice sem_interval", 0x82 );
break;
}
if( take_semaphore( audio_in.sem_task, 0 ) & 1 )
{
//DebugAssert( "!IS_ERROR", SoundDevice", 0x83 );
break;
}
DebugMsg( DM_AUDIO, 3, "%s: awake init=%d\n", __func__, audio_in.initialized );
if( !audio_in.initialized )
{
DebugMsg( DM_AUDIO, 3, "**** %s: agc=%d/%d wind=%d volume=%d",
__func__,
audio_in.agc_on,
audio_in.last_agc_on,
audio_in.windcut,
audio_in.volume
);
audio_set_filter_off();
if( audio_in.last_agc_on == 1
&& audio_in.agc_on == 0
)
audio_set_alc_off();
audio_in.last_agc_on = audio_in.agc_on;
audio_set_windcut( audio_in.windcut );
audio_set_sampling_param( 44100, 0x10, 1 );
audio_set_volume_in(
audio_in.agc_on,
audio_in.volume
);
if( audio_in.agc_on == 1 )
audio_set_alc_on();
audio_in.initialized = 1;
audio_in.gain = -39;
audio_in.sample_count = 0;
}
if( audio_in.asif_started == 0 )
{
DebugMsg( DM_AUDIO, 3, "%s: Starting asif observer", __func__ );
audio_start_asif_observer();
audio_in.asif_started = 1;
}
//uint32_t level = audio_read_level(0);
give_semaphore( audio_in.sem_task );
// Never adjust it!
//set_audio_agc();
//if( file != (void*) 0xFFFFFFFF )
//FIO_WriteFile( file, &level, sizeof(level) );
// audio_interval_wakeup will unlock our semaphore
oneshot_timer( 0x200, audio_interval_unlock, audio_interval_unlock, 0 );
}
DebugMsg( DM_AUDIO, 3, "!!!!! %s task exited????", __func__ );
}
TASK_OVERRIDE( audio_level_task, my_audio_level_task );
#endif
static void volume_display()
{
int mgain_db = mgain_index2gain(mgain);
NotifyBox(2000, "Volume: %d + (%d,%d) dB", mgain_db, dgain_l, dgain_r);
}
void volume_up()
{
int mgain_db = mgain_index2gain(mgain);
if (mgain_db < 32)
audio_mgain_toggle(&mgain, 0);
else
{
if( MAX(dgain_l, dgain_r) + 6 <= 40 )
{
audio_dgain_toggle(&dgain_l, 0);
audio_dgain_toggle(&dgain_r, 0);
}
}
volume_display();
}
void volume_down()
{
int mgain_db = mgain_index2gain(mgain);
if( MIN(dgain_l, dgain_r) > 0 )
{
audio_dgain_toggle_reverse(&dgain_l, 0);
audio_dgain_toggle_reverse(&dgain_r, 0);
}
else if (mgain_db > 0)
audio_mgain_toggle_reverse(&mgain, 0);
volume_display();
}
static void out_volume_display()
{
//int mgain_db = mgain_index2gain(mgain);
NotifyBox(2000, "Out Volume: %d dB", 2 * lovl);
}
void out_volume_up()
{
int* p = (int*) &lovl;
*p = COERCE(*p + 1, 0, 3);
out_volume_display();
}
void out_volume_down()
{
int* p = (int*) &lovl;
*p = COERCE(*p - 1, 0, 3);
out_volume_display();
}
void input_toggle()
{
audio_input_toggle(&input_choice, 1);
NotifyBox(2000, "Input: %s", get_audio_input_string());
}
static void audio_menus_init()
{
#if defined(CONFIG_550D) || defined(CONFIG_60D) || defined(CONFIG_500D) || defined(CONFIG_5D2)
menu_add( "Audio", audio_menus, COUNT(audio_menus) );
#else
menu_add( "Display", audio_menus, 1 );
#endif
}
/* Dump audio for 600D
#ifdef CONFIG_600D
void audio_reg_dump_600D()
{
static char log_filename[100];
int log_number = 0;
for (log_number = 0; log_number < 100; log_number++)
{
snprintf(log_filename, sizeof(log_filename), CARD_DRIVE "ML/audio%02d.LOG", log_number);
unsigned size;
if( FIO_GetFileSize( log_filename, &size ) != 0 ) break;
if (size == 0) break;
}
FIO_RemoveFile(log_filename);
FILE* f = FIO_CreateFile(log_filename);
int output = 0;
for( int addr = 0 ; addr < 0x100 ; addr++ )
{
const uint16_t reg = audio_ic_read(addr << 8);
my_fprintf(f, "%02x %02x\n", addr, reg);
}
FIO_CloseFile(f);
}
#endif
*/