Optimized bank and voice loading (not ready yet).

pull/4/head
Holger Wirtz 6 years ago
parent c10258cafd
commit d4fe310fff
  1. 51
      MicroDexed.ino
  2. 24
      UI.cpp
  3. 5
      UI.h
  4. 2
      config.h
  5. 289
      dexed_sysex.cpp
  6. 12
      dexed_sysex.h

@ -20,7 +20,6 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation, along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include <Audio.h> #include <Audio.h>
@ -82,12 +81,15 @@ uint32_t overload = 0;
uint32_t peak = 0; uint32_t peak = 0;
uint16_t render_time_max = 0; uint16_t render_time_max = 0;
uint8_t bank = 0; uint8_t bank = 0;
uint8_t max_loaded_banks=0;
uint8_t voice = 0; uint8_t voice = 0;
float vol = VOLUME; float vol = VOLUME;
float vol_right = 1.0; float vol_right = 1.0;
float vol_left = 1.0; float vol_left = 1.0;
char bank_name[11]; char bank_name[BANK_NAME_LEN];
char voice_name[11]; char voice_name[VOICE_NAME_LEN];
char bank_names[MAX_BANKS][BANK_NAME_LEN];
char voice_names[MAX_VOICES][VOICE_NAME_LEN];
#ifdef MASTER_KEY_MIDI #ifdef MASTER_KEY_MIDI
bool master_key_enabled = false; bool master_key_enabled = false;
#endif #endif
@ -180,8 +182,33 @@ void setup()
Serial.println(F("SD card found.")); Serial.println(F("SD card found."));
sd_card_available = true; sd_card_available = true;
// read all bank names
max_loaded_banks=get_bank_names();
strip_extension(bank_names[bank],bank_name);
// read all voice name for actual bank
get_voice_names_from_bank(bank);
#ifdef DEBUG
Serial.print(F("Bank ["));
Serial.print(bank_names[bank]);
Serial.print(F("/"));
Serial.print(bank_name);
Serial.println(F("]"));
for (uint8_t n = 0; n < MAX_VOICES; n++)
{
if (n < 10)
Serial.print(F(" "));
Serial.print(F(" "));
Serial.print(n, DEC);
Serial.print(F("["));
Serial.print(voice_names[n]);
Serial.println(F("]"));
}
#endif
// load default SYSEX data // load default SYSEX data
load_sysex(bank, voice); load_sysex(bank, voice);
}
#ifdef I2C_DISPLAY #ifdef I2C_DISPLAY
enc[0].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS)); enc[0].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS));
enc_val[0] = enc[0].read(); enc_val[0] = enc[0].read();
@ -190,7 +217,6 @@ void setup()
but[0].update(); but[0].update();
but[1].update(); but[1].update();
#endif #endif
}
#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) #if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC)
// Initialize processor and memory measurements // Initialize processor and memory measurements
@ -383,7 +409,7 @@ bool handle_master_key(uint8_t data)
#ifdef I2C_DISPLAY #ifdef I2C_DISPLAY
lcd.show(1, 0, 2, voice + 1); lcd.show(1, 0, 2, voice + 1);
lcd.show(1, 2, 1, " "); lcd.show(1, 2, 1, " ");
lcd.show(1, 3, 10, voice_name); lcd.show(1, 3, 10, voice_names[voice]);
#endif #endif
} }
#ifdef DEBUG #ifdef DEBUG
@ -414,8 +440,9 @@ bool handle_master_key(uint8_t data)
Serial.println(bank, DEC); Serial.println(bank, DEC);
#endif #endif
#ifdef I2C_DISPLAY #ifdef I2C_DISPLAY
if (get_bank_voice_name(bank, voice)) if (get_voice_names_from_bank(bank))
{ {
strip_extension(bank_names[bank],bank_name);
lcd.show(0, 0, 2, bank + 1); lcd.show(0, 0, 2, bank + 1);
lcd.show(0, 2, 1, " "); lcd.show(0, 2, 1, " ");
lcd.show(0, 3, 10, bank_name); lcd.show(0, 3, 10, bank_name);
@ -588,12 +615,12 @@ void set_volume(float v, float vr, float vl)
#endif #endif
#ifdef TEENSY_AUDIO_BOARD #ifdef TEENSY_AUDIO_BOARD
//sgtl5000_1.dacVolume(log(vol * vol_left)+1, log(vol * vol_right)+1); //sgtl5000_1.dacVolume(vol * vol_left, vol * vol_right);
sgtl5000_1.dacVolume(1 - pow((1 - vol * vol_left), 2.7), (1 - pow((1 - vol * vol_right), 2.7))); sgtl5000_1.dacVolume(pow(vol * vol_left, 0.2), pow(vol * vol_right, 0.2));
#else #else
volume_master.gain(1 - pow(1 - lvol, 2.7)); volume_master.gain(pow(lvol, 0.2));
volume_r.gain(1 - pow(1 - vr, 2.7)); volume_r.gain(pow(vr, 0.2));
volume_l.gain(1 - pow(1 - vl, 2.7)); volume_l.gain(pow(vl, 0.2));
#endif #endif
} }
@ -772,7 +799,7 @@ void show_cpu_and_mem_usage(void)
void show_patch(void) void show_patch(void)
{ {
uint8_t i; uint8_t i;
char voicename[11]; char voicename[VOICE_NAME_LEN];
memset(voicename, 0, sizeof(voicename)); memset(voicename, 0, sizeof(voicename));
for (i = 0; i < 6; i++) for (i = 0; i < 6; i++)

@ -109,33 +109,33 @@ void handle_ui(void)
case UI_MAIN_BANK: case UI_MAIN_BANK:
if (enc[i].read() <= 0) if (enc[i].read() <= 0)
enc[i].write(0); enc[i].write(0);
else if (enc[i].read() >= MAX_BANKS) else if (enc[i].read() > max_loaded_banks-1)
enc[i].write(MAX_BANKS); enc[i].write(max_loaded_banks-1);
bank = enc[i].read(); bank = enc[i].read();
if (!get_bank_voice_name(bank, voice)) if (!get_voice_names_from_bank(bank))
{ {
bank--; bank--;
enc[i].write(bank); enc[i].write(bank);
get_bank_voice_name(bank, voice); get_voice_names_from_bank(bank);
} }
break; break;
case UI_MAIN_VOICE: case UI_MAIN_VOICE:
if (enc[i].read() <= 0) if (enc[i].read() <= 0)
enc[i].write(0); enc[i].write(0);
else if (enc[i].read() >= MAX_VOICES) else if (enc[i].read() > MAX_VOICES-1)
enc[i].write(MAX_VOICES); enc[i].write(MAX_VOICES-1);
voice = enc[i].read(); voice = enc[i].read();
break; break;
case UI_MAIN_VOICE_SELECTED: case UI_MAIN_VOICE_SELECTED:
ui_main_state = UI_MAIN_VOICE; ui_main_state = UI_MAIN_VOICE;
if (enc[i].read() <= 0) if (enc[i].read() <= 0)
enc[i].write(0); enc[i].write(0);
else if (enc[i].read() >= MAX_VOICES) else if (enc[i].read() >= MAX_VOICES-1)
enc[i].write(MAX_VOICES); enc[i].write(MAX_VOICES);
voice = enc[i].read(); voice = enc[i].read();
break; break;
} }
get_bank_voice_name(bank, voice); get_voice_names_from_bank(bank);
ui_show_main(); ui_show_main();
break; break;
} }
@ -157,6 +157,8 @@ void ui_show_main(void)
lcd.clear(); lcd.clear();
lcd.show(0, 0, 2, bank + 1); lcd.show(0, 0, 2, bank + 1);
lcd.show(0, 2, 1, " "); lcd.show(0, 2, 1, " ");
strip_extension(bank_names[bank], bank_name);
if (ui_main_state == UI_MAIN_BANK) if (ui_main_state == UI_MAIN_BANK)
{ {
lcd.show(0, 2, 1, "["); lcd.show(0, 2, 1, "[");
@ -171,17 +173,17 @@ void ui_show_main(void)
if (ui_main_state == UI_MAIN_VOICE) if (ui_main_state == UI_MAIN_VOICE)
{ {
lcd.show(1, 2, 1, "<"); lcd.show(1, 2, 1, "<");
lcd.show(1, 3, 10, voice_name); lcd.show(1, 3, 10, voice_names[voice]);
lcd.show(1, 14, 1, ">"); lcd.show(1, 14, 1, ">");
} }
else if (ui_main_state == UI_MAIN_VOICE_SELECTED) else if (ui_main_state == UI_MAIN_VOICE_SELECTED)
{ {
lcd.show(1, 2, 1, "["); lcd.show(1, 2, 1, "[");
lcd.show(1, 3, 10, voice_name); lcd.show(1, 3, 10, voice_names[voice]);
lcd.show(1, 14, 1, "]"); lcd.show(1, 14, 1, "]");
} }
else else
lcd.show(1, 3, 10, voice_name); lcd.show(1, 3, 10, voice_names[voice]);
} }
void ui_show_volume(void) void ui_show_volume(void)

@ -40,9 +40,10 @@ extern float vol_left;
extern float vol_right; extern float vol_right;
extern LiquidCrystalPlus_I2C lcd; extern LiquidCrystalPlus_I2C lcd;
extern uint8_t bank; extern uint8_t bank;
extern uint8_t max_loaded_banks;
extern uint8_t voice; extern uint8_t voice;
extern char bank_name[11]; extern char bank_name[BANK_NAME_LEN];
extern char voice_name[11]; extern char voice_name[VOICE_NAME_LEN];
extern uint8_t ui_state; extern uint8_t ui_state;
extern uint8_t ui_main_state; extern uint8_t ui_main_state;
extern void update_eeprom_checksum(void); extern void update_eeprom_checksum(void);

@ -49,6 +49,8 @@
#define SAMPLE_RATE 44100 #define SAMPLE_RATE 44100
#define MAX_BANKS 99 #define MAX_BANKS 99
#define MAX_VOICES 32 // voices per bank #define MAX_VOICES 32 // voices per bank
#define BANK_NAME_LEN 13 // FAT12 filenames (plus '\0')
#define VOICE_NAME_LEN 11 // 10 (plus '\0')
#if !defined(__MK66FX1M0__) // check for Teensy-3.6 #if !defined(__MK66FX1M0__) // check for Teensy-3.6
#define MAX_NOTES 11 // No? #define MAX_NOTES 11 // No?

@ -32,201 +32,294 @@
#include "config.h" #include "config.h"
#include "UI.h" #include "UI.h"
bool get_bank_voice_name(uint8_t b, uint8_t v) void create_sysex_filename(uint8_t b, char* sysex_file_name)
{ {
File root; // init and set name for actual bank
memset(sysex_file_name, 0, 4 + VOICE_NAME_LEN);
sysex_file_name[0] = '/';
itoa(b, &sysex_file_name[1], 10);
strcat(sysex_file_name, "/");
strcat(sysex_file_name, bank_names[b]);
#ifdef DEBUG
Serial.print(F("Created sysex_filename from bank "));
Serial.print(b, DEC);
Serial.print(F(" and name "));
Serial.print(bank_names[b]);
Serial.print(F(": ["));
Serial.print(sysex_file_name);
Serial.println(F("]"));
#endif
}
void strip_extension(char* s, char* target)
{
char tmp[BANK_NAME_LEN];
char* token;
strcpy(tmp,s);
token = strtok(tmp, ".");
if (token == NULL)
strcpy(target, "*ERROR*");
else
strcpy(target, token);
}
bool get_voice_names_from_bank(uint8_t b)
{
File sysex;
uint8_t voice_counter = 0;
int32_t bulk_checksum_calc = 0;
int8_t bulk_checksum;
b %= MAX_BANKS; b %= MAX_BANKS;
// erase all data for voice names
memset(voice_names, 0, MAX_VOICES * VOICE_NAME_LEN);
if (sd_card_available) if (sd_card_available)
{ {
char bankdir[4]; char sysex_file_name[4 + VOICE_NAME_LEN];
memset(bankdir, 0, sizeof(bankdir)); // init and set name for actual bank
bankdir[0] = '/'; create_sysex_filename(b, sysex_file_name);
itoa(b, &bankdir[1], 10);
root = SD.open(bankdir); #ifdef DEBUG
if (!root) Serial.print(F("Reading voice names for bank ["));
Serial.print(sysex_file_name);
Serial.println(F("]"));
#endif
// try to open bank directory
sysex = SD.open(sysex_file_name);
if (!sysex)
return (false);
if (sysex.size() != 4104) // check sysex size
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: Cannot open main dir from SD.")); Serial.println(F("E : SysEx file size wrong."));
#endif #endif
return (false); return (false);
} }
while (42 == 42) if (sysex.read() != 0xf0) // check sysex start-byte
{ {
File entry = root.openNextFile(); #ifdef DEBUG
if (!entry) Serial.println(F("E : SysEx start byte not found."));
#endif
return (false);
}
if (sysex.read() != 0x43) // check sysex vendor is Yamaha
{ {
// No more files #ifdef DEBUG
break; Serial.println(F("E : SysEx vendor not Yamaha."));
#endif
return (false);
} }
else sysex.seek(4103);
if (sysex.read() != 0xf7) // check sysex end-byte
{ {
if (!entry.isDirectory()) #ifdef DEBUG
Serial.println(F("E : SysEx end byte not found."));
#endif
return (false);
}
sysex.seek(3);
if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices)
{ {
char *token; #ifdef DEBUG
uint8_t data[128]; Serial.println(F("E : SysEx type not 32 voices."));
#endif
return (false);
}
sysex.seek(4102); // Bulk checksum
bulk_checksum = sysex.read();
if (get_sysex_voice(bankdir, entry, v, data)) sysex.seek(6); // start of bulk data
strncpy(voice_name, (char*)&data[118], 10);
else
strcpy(voice_name, "*ERROR*");
token = strtok(entry.name(), "."); for (uint16_t n = 0; n < 4096; n++)
if (token == NULL) {
strcpy(bank_name, "*ERROR*"); uint8_t d = sysex.read();
else
strcpy(bank_name, token);
return (true); if ((n % 128) >= 118 && (n % 128) < 128) // found the start of the voicename
{
voice_names[voice_counter][(n % 128) - 118] = d;
} }
if (n % 128 == 127)
voice_counter++;
bulk_checksum_calc -= d;
} }
bulk_checksum_calc &= 0x7f;
#ifdef DEBUG
Serial.print(F("Bulk checksum : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" [0x"));
Serial.print(bulk_checksum, HEX);
Serial.println(F("]"));
#endif
if (bulk_checksum_calc != bulk_checksum)
{
#ifdef DEBUG
Serial.print(F("E : Bulk checksum mismatch : 0x"));
Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" != 0x"));
Serial.println(bulk_checksum, HEX);
#endif
return (false);
} }
} }
else
return (false); return (false);
} }
bool load_sysex(uint8_t b, uint8_t v) uint8_t get_bank_names(void)
{ {
File root; File root;
bool found = false; uint8_t bank_counter = 0;
v %= MAX_VOICES; // erase all data for bank names
b %= MAX_BANKS; memset(bank_names, 0, MAX_BANKS * BANK_NAME_LEN);
if (sd_card_available) if (sd_card_available)
{ {
char bankdir[4]; char bankdir[4];
do
{
// init and set name for actual bank directory
memset(bankdir, 0, sizeof(bankdir)); memset(bankdir, 0, sizeof(bankdir));
bankdir[0] = '/'; bankdir[0] = '/';
itoa(b, &bankdir[1], 10); itoa(bank_counter, &bankdir[1], 10);
// try to open directory
root = SD.open(bankdir); root = SD.open(bankdir);
if (!root) if (!root)
break;
// read filenames
File entry = root.openNextFile();
if (!entry.isDirectory())
{ {
strcpy(bank_names[bank_counter], entry.name());
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: Cannot open main dir from SD.")); Serial.print(F("Found bank ["));
Serial.print(bank_names[bank_counter]);
Serial.println(F("]"));
#endif #endif
return (false); bank_counter++;
} }
while (42 == 42) } while (root);
{
File entry = root.openNextFile(); return (bank_counter);
if (!entry)
{
// No more files
break;
} }
else else
return (bank_counter);
}
bool load_sysex(uint8_t b, uint8_t v)
{
bool found = false;
v %= MAX_VOICES;
b %= MAX_BANKS;
if (sd_card_available)
{ {
if (!entry.isDirectory()) File sysex;
{ char sysex_file_name[4 + VOICE_NAME_LEN];
uint8_t data[128]; uint8_t data[128];
found = true;
if (get_sysex_voice(bankdir, entry, v, data)) create_sysex_filename(b, sysex_file_name);
sysex = SD.open(sysex_file_name);
if (!sysex)
{
#ifdef DEBUG
Serial.print(F("E : Cannot open "));
Serial.print(sysex_file_name);
Serial.println(F("from SD."));
#endif
return (false);
}
if (get_sysex_voice(sysex, v, data))
{ {
#ifdef DEBUG #ifdef DEBUG
char n[11]; char n[11];
strncpy(n, (char*)&data[118], 10); strncpy(n, (char*)&data[118], 10);
Serial.print("Loading sysex "); Serial.print("Loading sysex ");
Serial.print(bankdir); Serial.print(sysex_file_name);
Serial.print("/");
Serial.print(entry.name());
Serial.print(F(" [")); Serial.print(F(" ["));
Serial.print(n); Serial.print(voice_names[v]);
Serial.println(F("]")); Serial.println(F(" ["));
#endif #endif
char *token;
token = strtok(entry.name(), ".");
if (token != NULL)
strcpy(bank_name, token);
else
strcpy(bank_name, "*ERROR*");
return (dexed->loadSysexVoice(data)); return (dexed->loadSysexVoice(data));
} }
else else
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: Cannot load voice data")); Serial.println(F("E : Cannot load voice data"));
#endif #endif
entry.close();
break;
} }
}
}
}
#ifdef DEBUG #ifdef DEBUG
if (found == false) if (found == false)
Serial.println(F("E: File not found.")); Serial.println(F("E : File not found."));
#endif #endif
return (false); return (false);
} }
bool get_sysex_voice(char* dir, File sysex, uint8_t voice_number, uint8_t* data) bool get_sysex_voice(File sysex, uint8_t voice_number, uint8_t* data)
{ {
File file;
uint16_t n; uint16_t n;
int32_t bulk_checksum_calc = 0; int32_t bulk_checksum_calc = 0;
int8_t bulk_checksum; int8_t bulk_checksum;
char sysex_file[20];
strcpy(sysex_file, dir);
strcat(sysex_file, "/");
strcat(sysex_file, sysex.name());
if (sysex.size() != 4104) // check sysex size if (sysex.size() != 4104) // check sysex size
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: SysEx file size wrong.")); Serial.println(F("E : SysEx file size wrong."));
#endif #endif
return (false); return (false);
} }
if (file = SD.open(sysex_file)) if (sysex.read() != 0xf0) // check sysex start-byte
{
if (file.read() != 0xf0) // check sysex start-byte
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: SysEx start byte not found.")); Serial.println(F("E : SysEx start byte not found."));
#endif #endif
return (false); return (false);
} }
if (file.read() != 0x43) // check sysex vendor is Yamaha if (sysex.read() != 0x43) // check sysex vendor is Yamaha
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: SysEx vendor not Yamaha.")); Serial.println(F("E : SysEx vendor not Yamaha."));
#endif #endif
return (false); return (false);
} }
file.seek(4103); sysex.seek(4103);
if (file.read() != 0xf7) // check sysex end-byte if (sysex.read() != 0xf7) // check sysex end-byte
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: SysEx end byte not found.")); Serial.println(F("E : SysEx end byte not found."));
#endif #endif
return (false); return (false);
} }
file.seek(3); sysex.seek(3);
if (file.read() != 0x09) // check for sysex type (0x09=32 voices) if (sysex.read() != 0x09) // check for sysex type (0x09=32 voices)
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.println(F("E: SysEx type not 32 voices.")); Serial.println(F("E : SysEx type not 32 voices."));
#endif #endif
return (false); return (false);
} }
file.seek(4102); // Bulk checksum sysex.seek(4102); // Bulk checksum
bulk_checksum = file.read(); bulk_checksum = sysex.read();
file.seek(6); // start of bulk data sysex.seek(6); // start of bulk data
for (n = 0; n < 4096; n++) for (n = 0; n < 4096; n++)
{ {
uint8_t d = file.read(); uint8_t d = sysex.read();
if (n >= voice_number * 128 && n < (voice_number + 1) * 128) if (n >= voice_number * 128 && n < (voice_number + 1) * 128)
{ {
data[n - (voice_number * 128)] = d; data[n - (voice_number * 128)] = d;
@ -236,7 +329,7 @@ bool get_sysex_voice(char* dir, File sysex, uint8_t voice_number, uint8_t* data)
bulk_checksum_calc &= 0x7f; bulk_checksum_calc &= 0x7f;
#ifdef DEBUG #ifdef DEBUG
Serial.print(F("Bulk checksum: 0x")); Serial.print(F("Bulk checksum : 0x"));
Serial.print(bulk_checksum_calc, HEX); Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" [0x")); Serial.print(F(" [0x"));
Serial.print(bulk_checksum, HEX); Serial.print(bulk_checksum, HEX);
@ -246,21 +339,13 @@ bool get_sysex_voice(char* dir, File sysex, uint8_t voice_number, uint8_t* data)
if (bulk_checksum_calc != bulk_checksum) if (bulk_checksum_calc != bulk_checksum)
{ {
#ifdef DEBUG #ifdef DEBUG
Serial.print(F("E: Bulk checksum mismatch: 0x")); Serial.print(F("E : Bulk checksum mismatch : 0x"));
Serial.print(bulk_checksum_calc, HEX); Serial.print(bulk_checksum_calc, HEX);
Serial.print(F(" != 0x")); Serial.print(F(" != 0x"));
Serial.println(bulk_checksum, HEX); Serial.println(bulk_checksum, HEX);
#endif #endif
return (false); return (false);
} }
}
#ifdef DEBUG
else
{
Serial.print(F("Cannot open "));
Serial.println(sysex.name());
}
#endif
render_time_max = 0; render_time_max = 0;

@ -33,13 +33,19 @@ extern Dexed* dexed;
extern uint16_t render_time_max; extern uint16_t render_time_max;
extern uint8_t bank; extern uint8_t bank;
extern uint8_t voice; extern uint8_t voice;
extern char bank_name[11]; extern char bank_name[BANK_NAME_LEN];
extern char voice_name[11]; extern char voice_name[VOICE_NAME_LEN];
extern char bank_names[MAX_BANKS][BANK_NAME_LEN];
extern char voice_names[MAX_VOICES][VOICE_NAME_LEN];
extern uint8_t ui_state; extern uint8_t ui_state;
extern uint8_t ui_main_state; extern uint8_t ui_main_state;
void create_sysex_filename(uint8_t b, char* sysex_file_name);
void strip_extension(char* s, char *target);
bool get_voice_names_from_bank(uint8_t b);
uint8_t get_bank_names(void);
bool get_bank_voice_name(uint8_t b, uint8_t v); bool get_bank_voice_name(uint8_t b, uint8_t v);
bool load_sysex(uint8_t b, uint8_t v); bool load_sysex(uint8_t b, uint8_t v);
bool get_sysex_voice(char* dir, File sysex, uint8_t voice_number, uint8_t* data); bool get_sysex_voice(File sysex, uint8_t voice_number, uint8_t* data);
#endif #endif

Loading…
Cancel
Save