Compare commits
8 Commits
8bb89a5f4b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| acd028846c | |||
| ed625e75fc | |||
| b60dcc0e68 | |||
| 909d589609 | |||
| 330f5e6ceb | |||
| 87dacd869b | |||
| 64f467d6ac | |||
| 84cafe2387 |
@ -2,7 +2,7 @@
|
|||||||
* @file Gravity.ino
|
* @file Gravity.ino
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version v2.0.0 - June 2025 awonak - Full rewrite
|
* @version v2.0.0 - August 2025 awonak - Full rewrite
|
||||||
* @version v1.0 - August 2023 Oleksiy H - Initial release
|
* @version v1.0 - August 2023 Oleksiy H - Initial release
|
||||||
* @date 2025-07-04
|
* @date 2025-07-04
|
||||||
*
|
*
|
||||||
@ -227,8 +227,16 @@ void HandleEncoderPressed() {
|
|||||||
case PARAM_MAIN_LOAD_DATA:
|
case PARAM_MAIN_LOAD_DATA:
|
||||||
if (app.selected_sub_param < StateManager::MAX_SAVE_SLOTS) {
|
if (app.selected_sub_param < StateManager::MAX_SAVE_SLOTS) {
|
||||||
app.selected_save_slot = app.selected_sub_param;
|
app.selected_save_slot = app.selected_sub_param;
|
||||||
|
// Load pattern data into app state.
|
||||||
stateManager.loadData(app, app.selected_save_slot);
|
stateManager.loadData(app, app.selected_save_slot);
|
||||||
InitGravity(app);
|
// Load global performance settings if they have changed.
|
||||||
|
if (gravity.clock.Tempo() != app.tempo) {
|
||||||
|
gravity.clock.SetTempo(app.tempo);
|
||||||
|
}
|
||||||
|
// Load global settings only clock is not active.
|
||||||
|
if (gravity.clock.IsPaused()) {
|
||||||
|
InitGravity(app);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PARAM_MAIN_FACTORY_RESET:
|
case PARAM_MAIN_FACTORY_RESET:
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file app_state.h
|
* @file app_state.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file channel.h
|
* @file channel.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file display.h
|
* @file display.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file euclidean.h
|
* @file euclidean.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file save_state.cpp
|
* @file save_state.cpp
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
@ -33,54 +33,66 @@ const int StateManager::EEPROM_DATA_START_ADDR = sizeof(StateManager::Metadata);
|
|||||||
StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {}
|
StateManager::StateManager() : _isDirty(false), _lastChangeTime(0) {}
|
||||||
|
|
||||||
bool StateManager::initialize(AppState& app) {
|
bool StateManager::initialize(AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
|
bool success = false;
|
||||||
if (_isDataValid()) {
|
if (_isDataValid()) {
|
||||||
// Load global settings.
|
// Load global settings.
|
||||||
_loadMetadata(app);
|
_loadMetadata(app);
|
||||||
// Load app data from the transient slot.
|
// Load app data from the transient slot.
|
||||||
_loadState(app, TRANSIENT_SLOT);
|
_loadState(app, TRANSIENT_SLOT);
|
||||||
return true;
|
success = true;
|
||||||
}
|
}
|
||||||
// EEPROM does not contain save data for this firmware & version.
|
// EEPROM does not contain save data for this firmware & version.
|
||||||
else {
|
else {
|
||||||
// Erase EEPROM and initialize state. Save default pattern to all save slots.
|
// Erase EEPROM and initialize state. Save default pattern to all save slots.
|
||||||
factoryReset(app);
|
factoryReset(app);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
interrupts();
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StateManager::loadData(AppState& app, byte slot_index) {
|
bool StateManager::loadData(AppState& app, byte slot_index) {
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (slot_index >= MAX_SAVE_SLOTS + 1) return false;
|
if (slot_index >= MAX_SAVE_SLOTS + 1) return false;
|
||||||
|
|
||||||
|
noInterrupts();
|
||||||
|
|
||||||
// Load the state data from the specified EEPROM slot and update the app state save slot.
|
// Load the state data from the specified EEPROM slot and update the app state save slot.
|
||||||
_loadState(app, slot_index);
|
_loadState(app, slot_index);
|
||||||
app.selected_save_slot = slot_index;
|
app.selected_save_slot = slot_index;
|
||||||
// Persist this change in the global metadata.
|
// Persist this change in the global metadata on next update.
|
||||||
_saveMetadata(app);
|
_isDirty = true;
|
||||||
|
|
||||||
|
interrupts();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save app state to user specified save slot.
|
// Save app state to user specified save slot.
|
||||||
void StateManager::saveData(const AppState& app) {
|
void StateManager::saveData(const AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
_saveState(app, app.selected_save_slot);
|
_saveState(app, app.selected_save_slot);
|
||||||
_saveMetadata(app);
|
_saveMetadata(app);
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save transient state if it has changed and enough time has passed since last save.
|
// Save transient state if it has changed and enough time has passed since last save.
|
||||||
void StateManager::update(const AppState& app) {
|
void StateManager::update(const AppState& app) {
|
||||||
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
|
if (_isDirty && (millis() - _lastChangeTime > SAVE_DELAY_MS)) {
|
||||||
|
noInterrupts();
|
||||||
_saveState(app, TRANSIENT_SLOT);
|
_saveState(app, TRANSIENT_SLOT);
|
||||||
_saveMetadata(app);
|
_saveMetadata(app);
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::reset(AppState& app) {
|
void StateManager::reset(AppState& app) {
|
||||||
|
noInterrupts();
|
||||||
|
|
||||||
AppState default_app;
|
AppState default_app;
|
||||||
app.tempo = default_app.tempo;
|
app.tempo = default_app.tempo;
|
||||||
app.selected_param = default_app.selected_param;
|
app.selected_param = default_app.selected_param;
|
||||||
@ -98,6 +110,7 @@ void StateManager::reset(AppState& app) {
|
|||||||
_loadMetadata(app);
|
_loadMetadata(app);
|
||||||
|
|
||||||
_isDirty = false;
|
_isDirty = false;
|
||||||
|
interrupts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::markDirty() {
|
void StateManager::markDirty() {
|
||||||
@ -134,7 +147,6 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
|
|||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
if (app.selected_save_slot >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
noInterrupts();
|
|
||||||
static EepromData save_data;
|
static EepromData save_data;
|
||||||
|
|
||||||
save_data.tempo = app.tempo;
|
save_data.tempo = app.tempo;
|
||||||
@ -165,14 +177,12 @@ void StateManager::_saveState(const AppState& app, byte slot_index) {
|
|||||||
|
|
||||||
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
||||||
EEPROM.put(address, save_data);
|
EEPROM.put(address, save_data);
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_loadState(AppState& app, byte slot_index) {
|
void StateManager::_loadState(AppState& app, byte slot_index) {
|
||||||
// Check if slot_index is within max range + 1 for transient.
|
// Check if slot_index is within max range + 1 for transient.
|
||||||
if (slot_index >= MAX_SAVE_SLOTS + 1) return;
|
if (slot_index >= MAX_SAVE_SLOTS + 1) return;
|
||||||
|
|
||||||
noInterrupts();
|
|
||||||
static EepromData load_data;
|
static EepromData load_data;
|
||||||
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
int address = EEPROM_DATA_START_ADDR + (slot_index * sizeof(EepromData));
|
||||||
EEPROM.get(address, load_data);
|
EEPROM.get(address, load_data);
|
||||||
@ -200,11 +210,9 @@ void StateManager::_loadState(AppState& app, byte slot_index) {
|
|||||||
ch.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest));
|
ch.setCv1Dest(static_cast<CvDestination>(saved_ch_state.cv1_dest));
|
||||||
ch.setCv2Dest(static_cast<CvDestination>(saved_ch_state.cv2_dest));
|
ch.setCv2Dest(static_cast<CvDestination>(saved_ch_state.cv2_dest));
|
||||||
}
|
}
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_saveMetadata(const AppState& app) {
|
void StateManager::_saveMetadata(const AppState& app) {
|
||||||
noInterrupts();
|
|
||||||
Metadata current_meta;
|
Metadata current_meta;
|
||||||
strcpy(current_meta.sketch_name, SKETCH_NAME);
|
strcpy(current_meta.sketch_name, SKETCH_NAME);
|
||||||
strcpy(current_meta.version, SEMANTIC_VERSION);
|
strcpy(current_meta.version, SEMANTIC_VERSION);
|
||||||
@ -215,14 +223,12 @@ void StateManager::_saveMetadata(const AppState& app) {
|
|||||||
current_meta.rotate_display = app.rotate_display;
|
current_meta.rotate_display = app.rotate_display;
|
||||||
|
|
||||||
EEPROM.put(METADATA_START_ADDR, current_meta);
|
EEPROM.put(METADATA_START_ADDR, current_meta);
|
||||||
interrupts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateManager::_loadMetadata(AppState& app) {
|
void StateManager::_loadMetadata(AppState& app) {
|
||||||
noInterrupts();
|
|
||||||
Metadata metadata;
|
Metadata metadata;
|
||||||
EEPROM.get(METADATA_START_ADDR, metadata);
|
EEPROM.get(METADATA_START_ADDR, metadata);
|
||||||
app.selected_save_slot = metadata.selected_save_slot;
|
app.selected_save_slot = metadata.selected_save_slot;
|
||||||
app.encoder_reversed = metadata.encoder_reversed;
|
app.encoder_reversed = metadata.encoder_reversed;
|
||||||
interrupts();
|
app.rotate_display = metadata.rotate_display;
|
||||||
}
|
}
|
||||||
@ -2,8 +2,8 @@
|
|||||||
* @file save_state.h
|
* @file save_state.h
|
||||||
* @author Adam Wonak (https://github.com/awonak/)
|
* @author Adam Wonak (https://github.com/awonak/)
|
||||||
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
* @brief Alt firmware version of Gravity by Sitka Instruments.
|
||||||
* @version 2.0.1
|
* @version 2.0.0
|
||||||
* @date 2025-07-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
@ -52,8 +52,8 @@ class StateManager {
|
|||||||
|
|
||||||
// This struct holds the data that identifies the firmware version.
|
// This struct holds the data that identifies the firmware version.
|
||||||
struct Metadata {
|
struct Metadata {
|
||||||
char sketch_name[12];
|
char sketch_name[16];
|
||||||
char version[5];
|
char version[16];
|
||||||
// Additional global/hardware settings
|
// Additional global/hardware settings
|
||||||
byte selected_save_slot;
|
byte selected_save_slot;
|
||||||
bool encoder_reversed;
|
bool encoder_reversed;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name=libGravity
|
name=libGravity
|
||||||
version=2.0.0
|
version=2.0.1
|
||||||
author=Adam Wonak
|
author=Adam Wonak
|
||||||
maintainer=awonak <github.com/awonak>
|
maintainer=awonak <github.com/awonak>
|
||||||
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
sentence=Hardware abstraction library for Sitka Instruments Gravity eurorack module
|
||||||
@ -7,4 +7,4 @@ category=Other
|
|||||||
license=MIT
|
license=MIT
|
||||||
url=https://github.com/awonak/libGravity
|
url=https://github.com/awonak/libGravity
|
||||||
architectures=avr
|
architectures=avr
|
||||||
depends=uClock,RotaryEncoder,U8g2
|
depends=uClock,RotaryEncoder,U8g2,NeoHWSerial
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file analog_input.h
|
* @file analog_input.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with analog inputs.
|
* @brief Class for interacting with analog inputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-05-23
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file button.h
|
* @file button.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Wrapper class for interacting with trigger / gate inputs.
|
* @brief Wrapper class for interacting with trigger / gate inputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-20
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file clock.h
|
* @file clock.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Wrapper Class for clock timing functions.
|
* @brief Wrapper Class for clock timing functions.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-05-04
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file digital_output.h
|
* @file digital_output.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with trigger / gate outputs.
|
* @brief Class for interacting with trigger / gate outputs.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-17
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file encoder_dir.h
|
* @file encoder_dir.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Class for interacting with encoders.
|
* @brief Class for interacting with encoders.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file libGravity.cpp
|
* @file libGravity.cpp
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file libGravity.h
|
* @file libGravity.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
* @brief Library for building custom scripts for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
* @file peripherials.h
|
* @file peripherials.h
|
||||||
* @author Adam Wonak (https://github.com/awonak)
|
* @author Adam Wonak (https://github.com/awonak)
|
||||||
* @brief Arduino pin definitions for the Sitka Instruments Gravity module.
|
* @brief Arduino pin definitions for the Sitka Instruments Gravity module.
|
||||||
* @version 0.1
|
* @version 2.0.0
|
||||||
* @date 2025-04-19
|
* @date 2025-08-17
|
||||||
*
|
*
|
||||||
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
* @copyright MIT - (c) 2025 - Adam Wonak - adam.wonak@gmail.com
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user