Drum config is now a JSON file.

pull/58/head
Holger Wirtz 3 years ago
parent 4b46764311
commit f55cc2b1db
  1. 7
      MicroDexed.ino
  2. 27
      UI.hpp
  3. 96
      addon/SD/drm/CFGDrums.json
  4. 3
      config.h
  5. 210
      drums.cpp
  6. 9
      drums.h

@ -325,6 +325,10 @@ int16_t delayline[NUM_DEXED][MOD_DELAY_SAMPLE_BUFFER];
#endif #endif
#if NUM_DRUMS > 0 #if NUM_DRUMS > 0
uint8_t num_drums = 0;
drum_config_t drum_config[DRUM_MAX_INSTRUMENTS];
uint8_t drum_counter;
uint8_t drum_type[DRUM_MAX_INSTRUMENTS];
extern void sequencer(void); extern void sequencer(void);
#endif #endif
@ -754,9 +758,10 @@ void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity)
Serial.print(drum_counter, DEC); Serial.print(drum_counter, DEC);
Serial.print(F("]: ")); Serial.print(F("]: "));
Serial.println(note_name); Serial.println(note_name);
Serial.println(inNumber);
#endif #endif
for (uint8_t d = 0; d < NUM_DRUMCONFIG; d++) for (uint8_t d = 0; d < num_drums; d++)
{ {
if (inNumber == drum_config[d].midinote) if (inNumber == drum_config[d].midinote)
{ {

@ -37,9 +37,6 @@
#include "effect_freeverbf.h" #include "effect_freeverbf.h"
#endif #endif
#include "synth_dexed.h" #include "synth_dexed.h"
#if NUM_DRUMS > 0
#include "drums.h"
#endif
#include <LCDMenuLib2.h> #include <LCDMenuLib2.h>
#include <MD_REncoder.h> #include <MD_REncoder.h>
@ -84,6 +81,8 @@ extern uint8_t seq_chain_active_chainstep; //for editor
extern uint8_t seq_chain_active_step; extern uint8_t seq_chain_active_step;
#if NUM_DRUMS > 0 #if NUM_DRUMS > 0
#include "drums.h" #include "drums.h"
extern drum_config_t drum_config[DRUM_MAX_INSTRUMENTS];
extern uint8_t num_drums;
#endif #endif
#ifdef DISPLAY_LCD_SPI #ifdef DISPLAY_LCD_SPI
@ -3551,7 +3550,7 @@ const char* seq_find_shortname(uint8_t sstep)
bool found = false; bool found = false;
if (seq_active_track < 3) { if (seq_active_track < 3) {
for (uint8_t d = 0; d < NUM_DRUMCONFIG - 1; d++) for (uint8_t d = 0; d < num_drums - 1; d++)
{ {
if (seq_data[ seq_patternchain[seq_chain_active_step][seq_active_track] ][sstep] == drum_config[d].midinote) if (seq_data[ seq_patternchain[seq_chain_active_step][seq_active_track] ][sstep] == drum_config[d].midinote)
{ {
@ -3575,7 +3574,7 @@ const char* seq_find_shortname(uint8_t sstep)
//{ //{
// //
// if (track < 3) { // if (track < 3) {
// for (uint8_t d = 0; d < NUM_DRUMCONFIG - 1; d++) // for (uint8_t d = 0; d < num_drums - 1; d++)
// { // {
// if (seq_data[track][seq_step_timer] == drum_config[d].midinote) // if (seq_data[track][seq_step_timer] == drum_config[d].midinote)
// { // {
@ -3589,7 +3588,7 @@ const char* seq_find_shortname(uint8_t sstep)
void UI_func_sequencer(uint8_t param) void UI_func_sequencer(uint8_t param)
{ {
char displayname[7]={0,0,0,0,0,0}; char displayname[7] = {0, 0, 0, 0, 0, 0};
if (LCDML.FUNC_setup()) // ****** SETUP ********* if (LCDML.FUNC_setup()) // ****** SETUP *********
{ {
@ -3635,9 +3634,9 @@ void UI_func_sequencer(uint8_t param)
if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up()))
{ {
if (LCDML.BT_checkDown()) if (LCDML.BT_checkDown())
activesample = constrain(activesample + 1, 0, NUM_DRUMCONFIG + 2 ); activesample = constrain(activesample + 1, 0, num_drums + 2 );
else if (LCDML.BT_checkUp()) else if (LCDML.BT_checkUp())
activesample = constrain(activesample - 1, 0, NUM_DRUMCONFIG + 2 ); activesample = constrain(activesample - 1, 0, num_drums + 2 );
} }
} else if (seq_active_function == 2) { } else if (seq_active_function == 2) {
if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up()))
@ -3656,7 +3655,7 @@ void UI_func_sequencer(uint8_t param)
seq_active_function = 0; seq_active_function = 0;
} else if ( seq_menu == 0 && seq_active_function == 0) } else if ( seq_menu == 0 && seq_active_function == 0)
{ {
if (activesample == NUM_DRUMCONFIG + 2) { if (activesample == num_drums + 2) {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
memset(seq_data[i], 0, sizeof(seq_data[i])); memset(seq_data[i], 0, sizeof(seq_data[i]));
} }
@ -3665,7 +3664,7 @@ void UI_func_sequencer(uint8_t param)
lcd.print(seq_find_shortname(i) ); lcd.print(seq_find_shortname(i) );
} }
} else if (activesample == NUM_DRUMCONFIG + 1) { } else if (activesample == num_drums + 1) {
memset(seq_data[seq_active_track], 0, sizeof(seq_data[seq_active_track])); memset(seq_data[seq_active_track], 0, sizeof(seq_data[seq_active_track]));
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
lcd.setCursor(i, 1); lcd.setCursor(i, 1);
@ -3720,17 +3719,17 @@ void UI_func_sequencer(uint8_t param)
lcd.setCursor(0, 0); lcd.setCursor(0, 0);
lcd.print("["); lcd.print("[");
if (activesample < NUM_DRUMCONFIG) { if (activesample < num_drums) {
lcd.setCursor(1, 0); lcd.setCursor(1, 0);
strncpy(displayname, drum_config[activesample].filename + 5, 6); strncpy(displayname, drum_config[activesample].filename + 5, 6);
lcd.print(displayname); lcd.print(displayname);
} else if (activesample == NUM_DRUMCONFIG) { } else if (activesample == num_drums) {
lcd.setCursor(1, 0); lcd.setCursor(1, 0);
lcd.print("EMPTY "); lcd.print("EMPTY ");
} else if (activesample == NUM_DRUMCONFIG + 1) { } else if (activesample == num_drums + 1) {
lcd.setCursor(1, 0); lcd.setCursor(1, 0);
lcd.print("ClrTrk"); lcd.print("ClrTrk");
} else if (activesample == NUM_DRUMCONFIG + 2) { } else if (activesample == num_drums + 2) {
lcd.setCursor(1, 0); lcd.setCursor(1, 0);
lcd.print("ClrAll"); lcd.print("ClrAll");
} }

@ -2,140 +2,140 @@
"drums": { "drums": {
"bd01": { "bd01": {
"drum_class": "DRUM_BASS", "drum_class": "DRUM_BASS",
"midinote": "MIDI_C3", "midinote": "C3",
"shortname": "B", "shortname": "B",
"pan": 0.0, "pan": 0,
"vol_max": 0.8, "vol_max": 0.8,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"cp02": { "cp02": {
"drum_class": "DRUM_HANDCLAP", "drum_class": "DRUM_HANDCLAP",
"midinote": "MIDI_CIS3", "midinote": "C#3",
"shortname": "C", "shortname": "C",
"pan": -0.4, "pan": -0.4,
"vol_max": 0.6, "vol_max": 0.6,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.4 "reverb_send": 0.4
}, },
"sd15": { "sd15": {
"drum_class": "DRUM_SNARE", "drum_class": "DRUM_SNARE",
"midinote": "MIDI_D3", "midinote": "D3",
"shortname": "S", "shortname": "S",
"pan": 0.2, "pan": 0.2,
"vol_max": 0.6, "vol_max": 0.6,
"vol_min": 0.2, "vol_min": 0.2,
"reverb_send": 0.0 "reverb_send": 0
}, },
"hh01": { "hh01": {
"drum_class": "DRUM_HIHAT", "drum_class": "DRUM_HIHAT",
"midinote": "MIDI_FIS3", "midinote": "F#3",
"shortname": "h", "shortname": "h",
"pan": 0.8, "pan": 0.8,
"vol_max": 0.2, "vol_max": 0.2,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"hh02": { "hh02": {
"drum_class": "DRUM_HIHAT", "drum_class": "DRUM_HIHAT",
"midinote": "MIDI_GIS3", "midinote": "G#3",
"shortname": "h", "shortname": "h",
"pan": 0.8, "pan": 0.8,
"vol_max": 0.2, "vol_max": 0.2,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"oh02": { "oh02": {
"drum_class": "DRUM_HIHAT", "drum_class": "DRUM_HIHAT",
"midinote": "MIDI_AIS3", "midinote": "A#3",
"shortname": "H", "shortname": "H",
"pan": 0.8, "pan": 0.8,
"vol_max": 0.2, "vol_max": 0.2,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"lt01": { "lt01": {
"drum_class": "DRUM_LOWTOM", "drum_class": "DRUM_LOWTOM",
"midinote": "MIDI_G3", "midinote": "G3",
"shortname": "T", "shortname": "T",
"pan": -0.7, "pan": -0.7,
"vol_max": 0.8, "vol_max": 0.8,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"ht01": { "ht01": {
"drum_class": "DRUM_HIGHTOM", "drum_class": "DRUM_HIGHTOM",
"midinote": "MIDI_A3", "midinote": "A3",
"shortname": "T", "shortname": "T",
"pan": -0.5, "pan": -0.5,
"vol_max": 0.8, "vol_max": 0.8,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"rd01": { "rd01": {
"drum_class": "DRUM_RIDE", "drum_class": "DRUM_RIDE",
"midinote": "MIDI_CIS4", "midinote": "C#4",
"shortname": "R", "shortname": "R",
"pan": -0.6, "pan": -0.6,
"vol_max": 0.3, "vol_max": 0.3,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"rd02": { "rd02": {
"drum_class": "DRUM_RIDE", "drum_class": "DRUM_RIDE",
"midinote": "MIDI_DIS4", "midinote": "D#4",
"shortname": "R", "shortname": "R",
"pan": -0.6, "pan": -0.6,
"vol_max": 0.3, "vol_max": 0.3,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"PHKick1": { "PHKick1": {
"drum_class": "DRUM_BASS", "drum_class": "DRUM_BASS",
"midinote": "MIDI_C5", "midinote": "C5",
"shortname": "B", "shortname": "B",
"pan": 0.0, "pan": 0,
"vol_max": 0.9, "vol_max": 0.9,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"808Clap1": { "808Clap1": {
"drum_class": "DRUM_HANDCLAP", "drum_class": "DRUM_HANDCLAP",
"midinote": "MIDI_DIS5", "midinote": "D#5",
"filename": "808Clap1.raw", "filename": "808Clap1.raw",
"shortname": "C", "shortname": "C",
"pan": 0.0, "pan": 0,
"vol_max": 0.9, "vol_max": 0.9,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.4 "reverb_send": 0.4
}, },
"808RimS1": { "808RimS1": {
"drum_class": "DRUM_SNARE", "drum_class": "DRUM_SNARE",
"midinote": "MIDI_CIS5", "midinote": "C#5",
"shortname": "R", "shortname": "R",
"pan": -0.3, "pan": -0.3,
"vol_max": 0.5, "vol_max": 0.5,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"808HHCL1": { "808HHCL1": {
"drum_class": "DRUM_HIHAT", "drum_class": "DRUM_HIHAT",
"midinote": "MIDI_FIS5", "midinote": "F#5",
"shortname": "H", "shortname": "H",
"pan": 0.4, "pan": 0.4,
"vol_max": 0.6, "vol_max": 0.6,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}, },
"EMPTY": { "EMPTY": {
"drum_class": "DRUM_NONE", "drum_class": "DRUM_NONE",
"midinote": "MIDI_NONE", "midinote": "NONE",
"filename": "EMPTY", "filename": "EMPTY",
"shortname": "-", "shortname": "-",
"pan": 0.0, "pan": 0,
"vol_max": 0.0, "vol_max": 0,
"vol_min": 0.0, "vol_min": 0,
"reverb_send": 0.0 "reverb_send": 0
}
} }
}
} }

@ -112,6 +112,7 @@
// NUMBER OF SAMPLEDRUMS // NUMBER OF SAMPLEDRUMS
#define NUM_DRUMS 8 #define NUM_DRUMS 8
#define DRUM_MIDI_CHANNEL 1 #define DRUM_MIDI_CHANNEL 1
#define DRUM_MAX_INSTRUMENTS 24
// CHORUS parameters // CHORUS parameters
#define MOD_DELAY_SAMPLE_BUFFER int32_t(TIME_MS2SAMPLES(20.0)) // 20.0 ms delay buffer. #define MOD_DELAY_SAMPLE_BUFFER int32_t(TIME_MS2SAMPLES(20.0)) // 20.0 ms delay buffer.
@ -611,7 +612,7 @@
#define EQ_TREBLE_DEFAULT 0 #define EQ_TREBLE_DEFAULT 0
// Buffer for load/save configuration as JSON // Buffer for load/save configuration as JSON
#define JSON_BUFFER 3072 #define JSON_BUFFER 2048
// Internal configuration structure // Internal configuration structure
typedef struct dexed_s { typedef struct dexed_s {

@ -29,11 +29,15 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <Audio.h> #include <Audio.h>
#include "config.h" #include "config.h"
#include "drums.h"
extern uint8_t num_drums;
extern drum_config_t drum_config[DRUM_MAX_INSTRUMENTS];
void read_drum_config(void) void read_drum_config(void)
{ {
File json; File json;
DynamicJsonDocument drums_json(4096);
AudioNoInterrupts(); AudioNoInterrupts();
if (SD.exists("/DRM/CFGDrums.json")) if (SD.exists("/DRM/CFGDrums.json"))
@ -44,18 +48,210 @@ void read_drum_config(void)
json = SD.open("/DRM/CFGDrums.json"); json = SD.open("/DRM/CFGDrums.json");
if (json) if (json)
{ {
DynamicJsonDocument drums_json(5000);
deserializeJson(drums_json, json); deserializeJson(drums_json, json);
json.close();
num_drums = drums_json.size();
#ifdef DEBUG #ifdef DEBUG
Serial.println("Drums:");
serializeJsonPretty(drums_json, Serial);
Serial.println();
Serial.print(F("Drum Objects: ")); Serial.print(F("Drum Objects: "));
Serial.println(drums_json["drums"].size()); Serial.println(num_drums);
if (num_drums > DRUM_MAX_INSTRUMENTS)
Serial.println(F("*** Maximum number of drum samples exceeded! ***"));
//Serial.println(F("Drum Configuration:"));
//serializeJsonPretty(drums_json, Serial);
//Serial.println();
#endif #endif
json.close(); num_drums = constrain(num_drums, 0, DRUM_MAX_INSTRUMENTS);
} }
} }
AudioInterrupts(); AudioInterrupts();
uint8_t drum = 0;
JsonObject root = drums_json["drums"].as<JsonObject>();
for (JsonPair kv : root)
{
sprintf(drum_config[drum].filename, "/drm/%s.wav", kv.key().c_str());
if (!SD.exists(drum_config[drum].filename))
{
#ifdef DEBUG
Serial.print(F("Cannot find WAV file ["));
Serial.print(drum_config[drum].filename);
Serial.println(F("]"));
#endif
continue;
}
JsonObject drum_root = drums_json["drums"][kv.key().c_str()].as<JsonObject>();
for (JsonPair drum_kv : drum_root)
{
if (strcmp(drum_kv.key().c_str(), "drum_class") == 0)
{
if (drum_kv.value().is<const char*>())
{
if (strcmp(drum_kv.value().as<const char*>(), "DRUM_NONE") == 0)
drum_config[drum].drum_class = DRUM_NONE;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_BASS") == 0)
drum_config[drum].drum_class = DRUM_BASS;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_SNARE") == 0)
drum_config[drum].drum_class = DRUM_SNARE;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_HIHAT") == 0)
drum_config[drum].drum_class = DRUM_HIHAT;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_HANDCLAP") == 0)
drum_config[drum].drum_class = DRUM_HANDCLAP;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_RIDE") == 0)
drum_config[drum].drum_class = DRUM_RIDE;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_CHRASH") == 0)
drum_config[drum].drum_class = DRUM_CHRASH;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_LOWTOM") == 0)
drum_config[drum].drum_class = DRUM_LOWTOM;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_MIDTOM") == 0)
drum_config[drum].drum_class = DRUM_MIDTOM;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_HIGHTOM") == 0)
drum_config[drum].drum_class = DRUM_HIGHTOM;
else if (strcmp(drum_kv.value().as<const char*>(), "DRUM_PERCUSSION") == 0)
drum_config[drum].drum_class = DRUM_PERCUSSION;
}
}
else if (strcmp(drum_kv.key().c_str(), "midinote") == 0)
{
if (drum_kv.value().is<const char*>())
{
uint8_t note_number = 0;
uint8_t oct = 0;
char midi_note[4];
strcpy(midi_note, drum_kv.value().as<const char*>());
switch (midi_note[0])
{
case 'A':
if (midi_note[1] == '#')
{
note_number = 1;
oct = midi_note[2] - 48;
}
else
{
note_number = 0;
oct = midi_note[1] - 48;
}
break;
case 'B':
note_number = 2;
oct = midi_note[1] - 48;
break;
case 'C':
if (midi_note[1] == '#')
{
note_number = 4;
oct = midi_note[2] - 48;
}
else
{
note_number = 3;
oct = midi_note[1] - 48;
}
break;
case 'D':
if (midi_note[1] == '#')
{
note_number = 6;
oct = midi_note[2] - 48;
}
else
{
note_number = 5;
oct = midi_note[1] - 48;
}
break;
case 'E':
note_number = 7;
oct = midi_note[1] - 48;
break;
case 'F':
if (midi_note[1] == '#')
{
note_number = 9;
oct = midi_note[2] - 48;
}
else
{
note_number = 8;
oct = midi_note[1] - 48;
}
break;
case 'G':
if (midi_note[1] == '#')
{
note_number = 11;
oct = midi_note[2] - 48;
}
else
{
note_number = 10;
oct = midi_note[1] - 48;
}
break;
}
drum_config[drum].midinote = note_number + 9 + (oct * 12);
}
}
else if (strcmp(drum_kv.key().c_str(), "shortname") == 0)
{
memset(drum_config[drum].shortname, 0, 2);
if (drum_kv.value().is<const char*>())
drum_config[drum].shortname[0] = drum_kv.value().as<const char*>()[0];
}
else if (strcmp(drum_kv.key().c_str(), "pan") == 0)
{
if (drum_kv.value().is<float>())
drum_config[drum].pan = drum_kv.value().as<float>();
if (drum_kv.value().is<int>())
drum_config[drum].pan = float(drum_kv.value().as<int>());
}
else if (strcmp(drum_kv.key().c_str(), "vol_max") == 0)
{
if (drum_kv.value().is<float>())
drum_config[drum].vol_max = drum_kv.value().as<float>();
if (drum_kv.value().is<int>())
drum_config[drum].vol_max = float(drum_kv.value().as<int>());
}
else if (strcmp(drum_kv.key().c_str(), "vol_min") == 0)
{
if (drum_kv.value().is<float>())
drum_config[drum].vol_min = drum_kv.value().as<float>();
if (drum_kv.value().is<int>())
drum_config[drum].vol_min = float(drum_kv.value().as<int>());
}
else if (strcmp(drum_kv.key().c_str(), "reverb_send") == 0)
{
if (drum_kv.value().is<float>())
drum_config[drum].reverb_send = drum_kv.value().as<float>();
if (drum_kv.value().is<int>())
drum_config[drum].reverb_send = float(drum_kv.value().as<int>());
}
}
#ifdef DEBUG
Serial.print(F("[Drum "));
Serial.print(drum, DEC);
Serial.println(F("]"));
Serial.print(F("drum_class="));
Serial.println(drum_config[drum].drum_class, DEC);
Serial.print(F("midinote="));
Serial.println(drum_config[drum].midinote, DEC);
Serial.print(F("filename="));
Serial.println(drum_config[drum].filename);
Serial.print(F("shortname="));
Serial.println(drum_config[drum].shortname);
Serial.print(F("pan="));
Serial.println(drum_config[drum].pan);
Serial.print(F("vol_max="));
Serial.println(drum_config[drum].vol_max);
Serial.print(F("vol_min="));
Serial.println(drum_config[drum].vol_min);
Serial.print(F("reverb_send="));
Serial.println(drum_config[drum].reverb_send);
#endif
drum++;
}
} }

@ -46,10 +46,8 @@ typedef struct drum_config_s {
enum {DRUM_NONE, DRUM_BASS, DRUM_SNARE, DRUM_HIHAT, DRUM_HANDCLAP, DRUM_RIDE, DRUM_CHRASH, DRUM_LOWTOM, DRUM_MIDTOM, DRUM_HIGHTOM, DRUM_PERCUSSION}; enum {DRUM_NONE, DRUM_BASS, DRUM_SNARE, DRUM_HIHAT, DRUM_HANDCLAP, DRUM_RIDE, DRUM_CHRASH, DRUM_LOWTOM, DRUM_MIDTOM, DRUM_HIGHTOM, DRUM_PERCUSSION};
uint8_t drum_counter; //drum_config_t drum_config[DRUM_MAX_INSTRUMENTS];
uint8_t drum_type[NUM_DRUMS]; /* = {
#define NUM_DRUMCONFIG 15
drum_config_t drum_config[NUM_DRUMCONFIG] = {
{ {
DRUM_BASS, DRUM_BASS,
MIDI_C3, MIDI_C3,
@ -200,5 +198,6 @@ drum_config_t drum_config[NUM_DRUMCONFIG] = {
0.0, 0.0,
0.0 0.0
} }
}; };
*/
#endif #endif

Loading…
Cancel
Save