Skip to content

Commit 94e4911

Browse files
committed
Add Double Trigger feature
Double Trigger can be configured to - 'Off' For the previous behaviour - 'Threshold' (with a separate set of thresholds) For triggering both sides if a pad is hit harder than the double trigger threshold - 'Always' For always triggering both sides if a pad would trigger normally
1 parent f2bd8e6 commit 94e4911

File tree

9 files changed

+334
-69
lines changed

9 files changed

+334
-69
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Few things which you probably want to change more regularly can be changed using
5555
- LED brightness
5656
- Trigger thresholds
5757
- Hold Time
58+
- Double Trigger Mode and Thresholds
5859
- Enter BOOTSEL mode for firmware flashing
5960

6061
Those settings are persisted to flash memory if you choose 'Save' when exiting the Menu and will survive power cycles.
@@ -67,6 +68,14 @@ The debounce delay also implicitly serves as the hold time of the input after a
6768

6869
If you notice dropped inputs even if the controller signals a hit on the LED/Display, try to increase this value.
6970

71+
### Double Trigger (Large Notes)
72+
73+
Home versions of Taiko no Tatsujin give higher scores for large notes when both sides are hit simultaneously. In contrast, arcade versions will only need a normal hit (or sometimes a harder hit). To emulate this behavior, the following modes are offered:
74+
75+
- **Off**: Hit both sides to score large notes.
76+
- **Threshold**: Automatically trigger both sides if a hit is stronger than the configured double hit threshold.
77+
- **Always**: A hit on one side will always trigger the other side as well.
78+
7079
### PS4 Authentication
7180

7281
The PS4 needs a controller to sign a cryptographic challenge every few seconds, otherwise it will stop working after around 8 minutes after plugging in. For the Taiko no Tatsujin games this is somewhat bearable, since you can re-plug the controller before starting each song to avoid running into the timeout during gameplay. Still, this is annoying.

include/GlobalConfiguration.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ const Peripherals::Drum::Config drum_config = {
3737
10, // Don Right
3838
5, // Ka Right
3939
},
40+
41+
// Double Trigger mode
42+
Peripherals::Drum::Config::DoubleTriggerMode::Off,
43+
// Double Trigger thresholds
44+
{
45+
2000, // Don Left
46+
1500, // Ka Left
47+
2000, // Don Right
48+
1500, // Ka Right
49+
},
4050
25, // Debounce delay in milliseconds
4151
500, // Roll Counter Timeout in Milliseconds
4252

include/peripherals/Drum.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,17 @@ class Drum {
4646
uint8_t spi_level_shifter_enable_pin;
4747
};
4848

49+
enum class DoubleTriggerMode {
50+
Off,
51+
Threshold,
52+
Always,
53+
};
54+
4955
Thresholds trigger_thresholds;
56+
57+
DoubleTriggerMode double_trigger_mode;
58+
Thresholds double_trigger_thresholds;
59+
5060
uint16_t debounce_delay_ms;
5161

5262
uint32_t roll_counter_timeout_ms;
@@ -117,7 +127,10 @@ class Drum {
117127
void updateInputState(Utils::InputState &input_state);
118128

119129
void setDebounceDelay(const uint16_t delay);
120-
void setThresholds(const Config::Thresholds &thresholds);
130+
void setTriggerThresholds(const Config::Thresholds &thresholds);
131+
132+
void setDoubleTriggerMode(const Config::DoubleTriggerMode mode);
133+
void setDoubleThresholds(const Config::Thresholds &thresholds);
121134
};
122135

123136
} // namespace Doncon::Peripherals

include/utils/Menu.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,21 @@ class Menu {
2424
Bootsel,
2525

2626
DrumDebounceDelay,
27+
DrumTriggerThresholds,
28+
DrumDoubleTrigger,
29+
2730
DrumTriggerThresholdKaLeft,
2831
DrumTriggerThresholdDonLeft,
2932
DrumTriggerThresholdDonRight,
3033
DrumTriggerThresholdKaRight,
3134

35+
DrumDoubleTriggerThresholds,
36+
37+
DrumDoubleTriggerThresholdKaLeft,
38+
DrumDoubleTriggerThresholdDonLeft,
39+
DrumDoubleTriggerThresholdDonRight,
40+
DrumDoubleTriggerThresholdKaRight,
41+
3242
LedBrightness,
3343
LedEnablePlayerColor,
3444

@@ -61,22 +71,40 @@ class Menu {
6171
GotoPageBootsel,
6272

6373
GotoPageDrumDebounceDelay,
74+
GotoPageDrumDoubleTrigger,
75+
GotoPageDrumTriggerThresholds,
76+
GotoPageDrumDoubleTriggerThresholds,
77+
6478
GotoPageDrumTriggerThresholdKaLeft,
6579
GotoPageDrumTriggerThresholdDonLeft,
6680
GotoPageDrumTriggerThresholdDonRight,
6781
GotoPageDrumTriggerThresholdKaRight,
6882

83+
GotoPageDrumDoubleTriggerThresholdKaLeft,
84+
GotoPageDrumDoubleTriggerThresholdDonLeft,
85+
GotoPageDrumDoubleTriggerThresholdDonRight,
86+
GotoPageDrumDoubleTriggerThresholdKaRight,
87+
6988
GotoPageLedBrightness,
7089
GotoPageLedEnablePlayerColor,
7190

7291
SetUsbMode,
7392

7493
SetDrumDebounceDelay,
94+
95+
SetDoubleTriggerOff,
96+
SetDoubleTriggerAlways,
97+
7598
SetDrumTriggerThresholdKaLeft,
7699
SetDrumTriggerThresholdDonLeft,
77100
SetDrumTriggerThresholdDonRight,
78101
SetDrumTriggerThresholdKaRight,
79102

103+
SetDrumDoubleTriggerThresholdKaLeft,
104+
SetDrumDoubleTriggerThresholdDonLeft,
105+
SetDrumDoubleTriggerThresholdDonRight,
106+
SetDrumDoubleTriggerThresholdKaRight,
107+
80108
SetLedBrightness,
81109
SetLedEnablePlayerColor,
82110

include/utils/SettingsStore.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ class SettingsStore {
2323
uint8_t led_brightness;
2424
bool led_enable_player_color;
2525
uint16_t debounce_delay;
26+
Peripherals::Drum::Config::DoubleTriggerMode double_trigger_mode;
27+
Peripherals::Drum::Config::Thresholds double_trigger_thresholds;
2628

2729
uint8_t _padding[m_store_size - sizeof(uint8_t) - sizeof(usb_mode_t) -
2830
sizeof(Peripherals::Drum::Config::Thresholds) - sizeof(uint8_t) - sizeof(bool) -
29-
sizeof(uint16_t)];
31+
sizeof(uint16_t) - sizeof(Peripherals::Drum::Config::DoubleTriggerMode) -
32+
sizeof(Peripherals::Drum::Config::Thresholds)];
3033
};
3134
static_assert(sizeof(Storecache) == m_store_size);
3235

@@ -53,6 +56,12 @@ class SettingsStore {
5356
void setTriggerThresholds(const Peripherals::Drum::Config::Thresholds &thresholds);
5457
Peripherals::Drum::Config::Thresholds getTriggerThresholds();
5558

59+
void setDoubleTriggerMode(const Peripherals::Drum::Config::DoubleTriggerMode &mode);
60+
Peripherals::Drum::Config::DoubleTriggerMode getDoubleTriggerMode();
61+
62+
void setDoubleTriggerThresholds(const Peripherals::Drum::Config::Thresholds &thresholds);
63+
Peripherals::Drum::Config::Thresholds getDoubleTriggerThresholds();
64+
5665
void setLedBrightness(const uint8_t brightness);
5766
uint8_t getLedBrightness();
5867

src/main.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ int main() {
167167
queue_add_blocking(&control_queue, &ctrl_message);
168168

169169
drum.setDebounceDelay(settings_store->getDebounceDelay());
170-
drum.setThresholds(settings_store->getTriggerThresholds());
170+
drum.setTriggerThresholds(settings_store->getTriggerThresholds());
171+
drum.setDoubleTriggerMode(settings_store->getDoubleTriggerMode());
172+
drum.setDoubleThresholds(settings_store->getDoubleTriggerThresholds());
171173
};
172174

173175
readSettings();

src/peripherals/Drum.cpp

Lines changed: 84 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -150,80 +150,96 @@ void Drum::updateRollCounter(Utils::InputState &input_state) {
150150
}
151151

152152
void Drum::updateDigitalInputState(Utils::InputState &input_state, const std::map<Drum::Id, uint16_t> &raw_values) {
153-
154-
std::map<Drum::Id, uint16_t> filtered_raw_values;
155-
156-
// First zero everything below its threshold.
157-
const auto value_if_above_threshold = [](const auto &values, const auto &thresholds, Id target) {
158-
const auto get_threshold = [&](const Id target) {
159-
switch (target) {
160-
case Id::DON_LEFT:
161-
return thresholds.don_left;
162-
case Id::DON_RIGHT:
163-
return thresholds.don_right;
164-
case Id::KA_LEFT:
165-
return thresholds.ka_left;
166-
case Id::KA_RIGHT:
167-
return thresholds.ka_right;
168-
}
169-
assert(false);
170-
return (uint16_t)0;
153+
const auto resolve_twin_pads = [&](Id left, Id right) {
154+
const auto is_over_threshold = [&raw_values](const Id target, const auto &thresholds) {
155+
const auto get_threshold = [&thresholds](const Id target) {
156+
switch (target) {
157+
case Id::DON_LEFT:
158+
return thresholds.don_left;
159+
case Id::DON_RIGHT:
160+
return thresholds.don_right;
161+
case Id::KA_LEFT:
162+
return thresholds.ka_left;
163+
case Id::KA_RIGHT:
164+
return thresholds.ka_right;
165+
}
166+
assert(false);
167+
return (uint16_t)0;
168+
};
169+
return (raw_values.at(target) > get_threshold(target));
171170
};
172-
return (values.at(target) > get_threshold(target)) ? values.at(target) : (uint16_t)0;
173-
};
174171

175-
for (const auto &entry : raw_values) {
176-
filtered_raw_values.insert(
177-
{entry.first, value_if_above_threshold(raw_values, m_config.trigger_thresholds, entry.first)});
178-
}
172+
const auto resolve_single_trigger = [&]() {
173+
if (!is_over_threshold(left, m_config.trigger_thresholds) &&
174+
!is_over_threshold(right, m_config.trigger_thresholds)) {
179175

180-
// Only DON or KA can be active at a time, zero the lesser
181-
if (std::max(filtered_raw_values.at(Id::DON_LEFT), filtered_raw_values.at(Id::DON_RIGHT)) >
182-
std::max(filtered_raw_values.at(Id::KA_LEFT), filtered_raw_values.at(Id::KA_RIGHT))) {
176+
m_pads.at(left).setState(false, m_config.debounce_delay_ms);
177+
m_pads.at(right).setState(false, m_config.debounce_delay_ms);
178+
return;
179+
}
183180

184-
filtered_raw_values.at(Id::KA_LEFT) = 0;
185-
filtered_raw_values.at(Id::KA_RIGHT) = 0;
186-
} else {
187-
filtered_raw_values.at(Id::DON_LEFT) = 0;
188-
filtered_raw_values.at(Id::DON_RIGHT) = 0;
189-
}
181+
// Trigger twin pad if within 50% of hit strength to allow
182+
// simultaneous hits while still rejecting unintended double hits.
183+
if (raw_values.at(left) > raw_values.at(right)) {
184+
m_pads.at(left).setState(true, m_config.debounce_delay_ms);
190185

191-
// Check same same with regard to current debounce state
192-
if (m_pads.at(Id::DON_LEFT).getState() || m_pads.at(Id::DON_RIGHT).getState()) {
193-
filtered_raw_values.at(Id::KA_LEFT) = 0;
194-
filtered_raw_values.at(Id::KA_RIGHT) = 0;
195-
} else if (m_pads.at(Id::KA_LEFT).getState() || m_pads.at(Id::KA_RIGHT).getState()) {
196-
filtered_raw_values.at(Id::DON_LEFT) = 0;
197-
filtered_raw_values.at(Id::DON_RIGHT) = 0;
198-
}
186+
if (raw_values.at(right) > (raw_values.at(left) >> 1)) {
187+
m_pads.at(right).setState(true, m_config.debounce_delay_ms);
188+
} else {
189+
m_pads.at(right).setState(false, m_config.debounce_delay_ms);
190+
}
191+
} else {
192+
m_pads.at(right).setState(true, m_config.debounce_delay_ms);
199193

200-
// Zero values which are not within +/- 50% of their twin pad
201-
const auto zero_if_not_within_twin = [](auto &values, Id a, Id b) {
202-
if (values.at(a) == 0 || values.at(b) == 0) {
203-
return;
204-
}
194+
if (raw_values.at(left) > (raw_values.at(right) >> 1)) {
195+
m_pads.at(left).setState(true, m_config.debounce_delay_ms);
196+
} else {
197+
m_pads.at(left).setState(false, m_config.debounce_delay_ms);
198+
}
199+
}
200+
};
201+
202+
switch (m_config.double_trigger_mode) {
203+
case Config::DoubleTriggerMode::Off:
204+
resolve_single_trigger();
205+
break;
206+
case Config::DoubleTriggerMode::Threshold:
207+
if (is_over_threshold(left, m_config.double_trigger_thresholds) ||
208+
is_over_threshold(right, m_config.double_trigger_thresholds)) {
205209

206-
if (values.at(a) > values.at(b)) {
207-
if (values.at(b) < (values.at(a) >> 1)) {
208-
values.at(b) = 0;
210+
m_pads.at(left).setState(true, m_config.debounce_delay_ms);
211+
m_pads.at(right).setState(true, m_config.debounce_delay_ms);
212+
} else {
213+
resolve_single_trigger();
209214
}
210-
} else {
211-
if (values.at(a) < (values.at(b) >> 1)) {
212-
values.at(a) = 0;
215+
break;
216+
case Config::DoubleTriggerMode::Always:
217+
if (is_over_threshold(left, m_config.trigger_thresholds) ||
218+
is_over_threshold(right, m_config.trigger_thresholds)) {
219+
220+
m_pads.at(left).setState(true, m_config.debounce_delay_ms);
221+
m_pads.at(right).setState(true, m_config.debounce_delay_ms);
222+
} else {
223+
m_pads.at(left).setState(false, m_config.debounce_delay_ms);
224+
m_pads.at(right).setState(false, m_config.debounce_delay_ms);
213225
}
226+
break;
214227
}
215228
};
216229

217-
zero_if_not_within_twin(filtered_raw_values, Id::DON_LEFT, Id::DON_RIGHT);
218-
zero_if_not_within_twin(filtered_raw_values, Id::KA_LEFT, Id::KA_RIGHT);
230+
// Either DON or KA can be active at a time
231+
if (std::max(raw_values.at(Id::DON_LEFT), raw_values.at(Id::DON_RIGHT)) >
232+
std::max(raw_values.at(Id::KA_LEFT), raw_values.at(Id::KA_RIGHT))) {
219233

220-
// All values != 0 are already over their threshold.
221-
for (const auto &entry : filtered_raw_values) {
222-
if (entry.second != 0) {
223-
m_pads.at(entry.first).setState(true, m_config.debounce_delay_ms);
224-
} else {
225-
m_pads.at(entry.first).setState(false, m_config.debounce_delay_ms);
226-
}
234+
resolve_twin_pads(Id::DON_LEFT, Id::DON_RIGHT);
235+
236+
m_pads.at(Id::KA_LEFT).setState(false, m_config.debounce_delay_ms);
237+
m_pads.at(Id::KA_RIGHT).setState(false, m_config.debounce_delay_ms);
238+
} else {
239+
resolve_twin_pads(Id::KA_LEFT, Id::KA_RIGHT);
240+
241+
m_pads.at(Id::DON_LEFT).setState(false, m_config.debounce_delay_ms);
242+
m_pads.at(Id::DON_RIGHT).setState(false, m_config.debounce_delay_ms);
227243
}
228244

229245
input_state.drum.don_left.triggered = m_pads.at(Id::DON_LEFT).getState();
@@ -298,6 +314,12 @@ void Drum::updateInputState(Utils::InputState &input_state) {
298314

299315
void Drum::setDebounceDelay(const uint16_t delay) { m_config.debounce_delay_ms = delay; }
300316

301-
void Drum::setThresholds(const Config::Thresholds &thresholds) { m_config.trigger_thresholds = thresholds; }
317+
void Drum::setTriggerThresholds(const Config::Thresholds &thresholds) { m_config.trigger_thresholds = thresholds; }
318+
319+
void Drum::setDoubleTriggerMode(const Config::DoubleTriggerMode mode) { m_config.double_trigger_mode = mode; }
320+
321+
void Drum::setDoubleThresholds(const Config::Thresholds &thresholds) {
322+
m_config.double_trigger_thresholds = thresholds;
323+
}
302324

303325
} // namespace Doncon::Peripherals

0 commit comments

Comments
 (0)