aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--src/gba/gba.h8
-rw-r--r--src/gba/interrupts.s4
-rw-r--r--src/sequencer.c155
-rw-r--r--src/settings.c5
-rw-r--r--src/settings.h2
6 files changed, 144 insertions, 32 deletions
diff --git a/Makefile b/Makefile
index e464b75..0e5cf83 100644
--- a/Makefile
+++ b/Makefile
@@ -27,7 +27,7 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS))
27INC_FLAGS += -I$(LIBGBA_SRC) 27INC_FLAGS += -I$(LIBGBA_SRC)
28 28
29# Output library names and executables. 29# Output library names and executables.
30TARGET := STEPPER-v1.8 30TARGET := STEPPER-v1.9-dev-2
31ELF := $(BUILD_DIR)/$(TARGET).elf 31ELF := $(BUILD_DIR)/$(TARGET).elf
32BIN := $(BUILD_DIR)/$(TARGET).gba 32BIN := $(BUILD_DIR)/$(TARGET).gba
33 33
diff --git a/src/gba/gba.h b/src/gba/gba.h
index 3f629fd..1bb3afd 100644
--- a/src/gba/gba.h
+++ b/src/gba/gba.h
@@ -534,6 +534,14 @@ void irs_set(IrqIndex idx, IrsFunc func);
534// assembly and enable interrupts. 534// assembly and enable interrupts.
535void irq_init(void); 535void irq_init(void);
536 536
537// Only should be used inside an IrsFunc to enable it being interrupted by the
538// given interrupt (nested IRS).
539static inline void
540irs_interruptible(IrqIndex idx) {
541 IRQ_ENABLE |= (1 << idx);
542 IRQ_CTRL = 1;
543}
544
537// 545//
538// BIOS function declarations. 546// BIOS function declarations.
539// 547//
diff --git a/src/gba/interrupts.s b/src/gba/interrupts.s
index 67b9fe9..1d3e001 100644
--- a/src/gba/interrupts.s
+++ b/src/gba/interrupts.s
@@ -67,6 +67,10 @@ irs_main_handle_irs:
67 bx r2 67 bx r2
68 ldmfd sp!, {lr} 68 ldmfd sp!, {lr}
69 69
70 @ Clear IRQ enable again just in case it was enabled from within the IRS.
71 mov r3, #0 @ r3 = 0
72 strh r3, [ip, #8] @ *(ip + 0x8) = r3
73
70 @ Set CPU to irq mode 74 @ Set CPU to irq mode
71 mrs r3, cpsr 75 mrs r3, cpsr
72 bic r3, r3, #0xDF 76 bic r3, r3, #0xDF
diff --git a/src/sequencer.c b/src/sequencer.c
index 9efd506..64e0e49 100644
--- a/src/sequencer.c
+++ b/src/sequencer.c
@@ -22,35 +22,110 @@ clear_pattern(size_t idx) {
22 redraw_bpm = true; 22 redraw_bpm = true;
23 redraw_params = true; 23 redraw_params = true;
24} 24}
25 25INLINE void
26void 26gate_set(u8 sc, u8 so) {
27gate_off(void) {
28 SIO_MODE = SIO_MODE_GP 27 SIO_MODE = SIO_MODE_GP
29 | SIO_SC_OUT(1) 28 | SIO_SC_OUT(1)
30 | SIO_SD_OUT(1)
31 | SIO_SI_OUT(0)
32 | SIO_SO_OUT(1) 29 | SIO_SO_OUT(1)
33 | SIO_SC(0) 30 | SIO_SD_OUT(0)
31 | SIO_SI_OUT(0)
32 | SIO_SC(sc)
33 | SIO_SO(so)
34 | SIO_SD(0) 34 | SIO_SD(0)
35 | SIO_SO(0); 35 | SIO_SI(0);
36}
37
38void
39gate_off(void) {
40 TIMER_CTRL_3 = 0;
41 gate_set(0, 0);
42}
43
44void
45gate_on(SyncSetting sync_mode) {
46 // SYNC24 NOTES
47 // (from https://e-rm.de/data/E-RM_report_HowToDinSync_10_14_EN.pdf)
48 //
49 // "Moreover, a duty cycle of 50% doesn't seem to be necessary, all tested
50 // machines were able to sync properly to clock ticks with a positive width
51 // of 5 ms to up to 300 BPM" @ 24bpq
52 //
53 // Since we want to use a 96bpq timer, we need to go 4 times faster (1.25ms
54 // per gate), though we could tailor the timing to the selected sync mode
55 // for less lenient machines.
56 gate_off();
57 gate_set(1, 1);
58 int n_ticks = 0;
59 switch (sync_mode) {
60 case SYNC_OUT_LINK_96BPQ: { n_ticks = -80 / 4; } break;
61 case SYNC_OUT_LINK_48BPQ: { n_ticks = -80 / 2; } break;
62 case SYNC_OUT_LINK_24BPQ: { n_ticks = -80; } break;
63 case SYNC_OUT_LINK_12BPQ: { n_ticks = -80 * 2; } break;
64 case SYNC_OUT_LINK_6BPQ: { n_ticks = -80 * 4; } break;
65 case SYNC_OUT_LINK_4BPQ: { n_ticks = -80 * 6; } break;
66 case SYNC_OUT_LINK_2BPQ: { n_ticks = -80 * 12; } break;
67 default: break;
68 }
69 irs_set(IRQ_TIMER_3, gate_off);
70 TIMER_DATA_3 = n_ticks;
71 TIMER_CTRL_3 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3;
72}
73
74int pulse_count = 0;
75int pulse_value = 0;
76int gate_status = 0;
77
78void
79send_pulse(void) {
80 // LSDJ expects a pulse train on the SC pin (active low). For the song
81 // start, the first pulse indicates the starting row, for example, for row
82 // 0x06:
83 //
84 // CLK (SC) --_-_-_-_-_-_-_-_----------------_-_-_-_-_-_-_-_----------------
85 // OUT (SO) --__________----_________________-------------------------------
86 //
87 // Additionally, in LSDJ sync mode, at the start of each bar, SO should send
88 // the 0x01 byte (active low).
89 //
90 // CLK (SC) --_-_-_-_-_-_-_-_----------------_-_-_-_-_-_-_-_----------------
91 // OUT (SO) --____________--_________________-------------------------------
92 //
93 // The width of the pulse is approximately 915-916us (122us per pulse at 50%
94 // duty cycle of 61us). This sync method is based on sync24, so it should
95 // receive 6 pulses each 1/16th step (96 pulses in a 16 step sequence).
36 TIMER_CTRL_3 = 0; 96 TIMER_CTRL_3 = 0;
97 gate_set(gate_status, (pulse_value >> (7 - pulse_count)) & 0x1);
98 gate_status ^= 1;
99 if (gate_status == 0) {
100 pulse_count++;
101 }
102 if (pulse_count <= 7) {
103 int n_ticks = -9; // 122/2 = 61us; 61 / 3.8 = 16
104 irs_set(IRQ_TIMER_3, send_pulse);
105 TIMER_DATA_3 = n_ticks;
106 TIMER_CTRL_3 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_1;
107 } else {
108 pulse_value = 0;
109 pulse_count = 0;
110 }
37} 111}
38 112
39void 113void
40gate_on(void) { 114send_lsdj_pulse(u8 out_byte) {
41 gate_off(); 115 // Reset timer.
42 SIO_MODE = SIO_MODE_GP 116 TIMER_DATA_3 = 0;
43 | SIO_SC_OUT(1) 117
44 | SIO_SD_OUT(1) 118 // Set starting values.
45 | SIO_SI_OUT(0) 119 gate_set(1, 1);
46 | SIO_SO_OUT(1) 120 pulse_value = out_byte;
47 | SIO_SC(1) 121 pulse_count = 0;
48 | SIO_SD(0) 122 gate_status = 0;
49 | SIO_SO(1); 123
50 int n_ticks = -244181 / 600; 124 // Start pulse message.
51 irs_set(IRQ_TIMER_3, gate_off); 125 int n_ticks = -9; // 122/2 = 61us; 61 / 3.8 = 16
126 irs_set(IRQ_TIMER_3, send_pulse);
52 TIMER_DATA_3 = n_ticks; 127 TIMER_DATA_3 = n_ticks;
53 TIMER_CTRL_3 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_3; 128 TIMER_CTRL_3 = TIMER_CTRL_IRQ | TIMER_CTRL_ENABLE | TIMER_CTRL_FREQ_1;
54} 129}
55 130
56u8 131u8
@@ -475,22 +550,37 @@ env_start:
475 550
476void 551void
477sequencer_tick(void) { 552sequencer_tick(void) {
553 irs_interruptible(IRQ_VBLANK); // Audio mix.
554 irs_interruptible(IRQ_TIMER_1); // Audio click sync.
555 irs_interruptible(IRQ_TIMER_3); // Link cable sync out.
556 irs_interruptible(IRQ_SERIAL); // Linc cable sync in.
478 switch (settings.sync) { 557 switch (settings.sync) {
479 case SYNC_OUT_LINK_96BPQ: { gate_on(); } break; 558 case SYNC_OUT_LINK_96BPQ: { gate_on(SYNC_OUT_LINK_96BPQ); } break;
480 case SYNC_OUT_LINK_48BPQ: { if (sync_ticks++ % 2 == 0) { gate_on(); } } break; 559 case SYNC_OUT_LINK_48BPQ: { if (sync_ticks++ % 2 == 0) { gate_on(SYNC_OUT_LINK_48BPQ); } } break;
481 case SYNC_OUT_LINK_24BPQ: { if (sync_ticks++ % 4 == 0) { gate_on(); } } break; 560 case SYNC_OUT_LINK_24BPQ: { if (sync_ticks++ % 4 == 0) { gate_on(SYNC_OUT_LINK_24BPQ); } } break;
482 case SYNC_OUT_LINK_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(); } } break; 561 case SYNC_OUT_LINK_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(SYNC_OUT_LINK_12BPQ); } } break;
483 case SYNC_OUT_LINK_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(); } } break; 562 case SYNC_OUT_LINK_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(SYNC_OUT_LINK_6BPQ); } } break;
484 case SYNC_OUT_LINK_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(); } } break; 563 case SYNC_OUT_LINK_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(SYNC_OUT_LINK_4BPQ); } } break;
485 case SYNC_OUT_LINK_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(); } } break; 564 case SYNC_OUT_LINK_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(SYNC_OUT_LINK_2BPQ); } } break;
486 case SYNC_OUT_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { audio_sync_click = true; } } break; 565 case SYNC_OUT_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { audio_sync_click = true; } } break;
487 case SYNC_OUT_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { audio_sync_click = true; } } break; 566 case SYNC_OUT_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { audio_sync_click = true; } } break;
488 case SYNC_OUT_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { audio_sync_click = true; } } break; 567 case SYNC_OUT_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { audio_sync_click = true; } } break;
489 case SYNC_OUT_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { audio_sync_click = true; } } break; 568 case SYNC_OUT_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { audio_sync_click = true; } } break;
490 case SYNC_OUT_LINK_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(); audio_sync_click = true; } } break; 569 case SYNC_OUT_LINK_AUDIO_12BPQ: { if (sync_ticks++ % 8 == 0) { gate_on(SYNC_OUT_LINK_12BPQ); audio_sync_click = true; } } break;
491 case SYNC_OUT_LINK_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(); audio_sync_click = true; } } break; 570 case SYNC_OUT_LINK_AUDIO_6BPQ: { if (sync_ticks++ % 16 == 0) { gate_on(SYNC_OUT_LINK_6BPQ); audio_sync_click = true; } } break;
492 case SYNC_OUT_LINK_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(); audio_sync_click = true; } } break; 571 case SYNC_OUT_LINK_AUDIO_4BPQ: { if (sync_ticks++ % 24 == 0) { gate_on(SYNC_OUT_LINK_4BPQ); audio_sync_click = true; } } break;
493 case SYNC_OUT_LINK_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(); audio_sync_click = true; } } break; 572 case SYNC_OUT_LINK_AUDIO_2BPQ: { if (sync_ticks++ % 48 == 0) { gate_on(SYNC_OUT_LINK_2BPQ); audio_sync_click = true; } } break;
573 case SYNC_OUT_LSDJ: {
574 if (sync_ticks++ % 4 == 0) {
575 u8 val = 0xff;
576 if ((sync_ticks - 1) == 0) {
577 val = current_bank * 8 + current_pattern;
578 } else if ((sync_ticks - 1) % 384 == 0) {
579 val = 253;
580 }
581 send_lsdj_pulse(val);
582 }
583 } break;
494 default: break; 584 default: break;
495 } 585 }
496 if (nseq_ticks++ == 0) { 586 if (nseq_ticks++ == 0) {
@@ -684,6 +774,7 @@ stop_sound(void) {
684 774
685void 775void
686reset_sequencer(void) { 776reset_sequencer(void) {
777 TIMER_CTRL_3 = 0;
687 step_counter = 0; 778 step_counter = 0;
688 nseq_ticks = 0; 779 nseq_ticks = 0;
689 sync_ticks = 0; 780 sync_ticks = 0;
@@ -1984,4 +2075,6 @@ sequencer_init(void) {
1984 SOUND_STATUS = SOUND_ENABLE; 2075 SOUND_STATUS = SOUND_ENABLE;
1985 init_dsound(); 2076 init_dsound();
1986 set_audio_settings(); 2077 set_audio_settings();
2078 // TODO: Set the gate to expected value depending on sync mode selected.
2079 // Also applies to the reset_sequencer function.
1987} 2080}
diff --git a/src/settings.c b/src/settings.c
index 8b4c3b1..e6f07f4 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -17,6 +17,7 @@ void sync_in_4(void);
17void reset_sequencer(void); 17void reset_sequencer(void);
18void toggle_playing(void); 18void toggle_playing(void);
19void stop_playing(void); 19void stop_playing(void);
20void gate_set(u8, u8);
20 21
21static int sync_ticks = 0; 22static int sync_ticks = 0;
22 23
@@ -65,6 +66,10 @@ set_audio_settings(void) {
65 | SOUND_DSOUND_RATIO_A 66 | SOUND_DSOUND_RATIO_A
66 | SOUND_DSOUND_LEFT_A 67 | SOUND_DSOUND_LEFT_A
67 | SOUND_DSOUND_RESET_A; 68 | SOUND_DSOUND_RESET_A;
69 gate_set(0, 0);
70 } break;
71 case SYNC_OUT_LSDJ: {
72 gate_set(1, 1);
68 } break; 73 } break;
69 default: { 74 default: {
70 SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1 75 SOUND_DMG_MASTER = sound_volume(SOUND_SQUARE1
diff --git a/src/settings.h b/src/settings.h
index bbfdfd2..73da88f 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -28,6 +28,7 @@ typedef enum SyncSetting {
28 SYNC_OUT_LINK_AUDIO_6BPQ, 28 SYNC_OUT_LINK_AUDIO_6BPQ,
29 SYNC_OUT_LINK_AUDIO_4BPQ, 29 SYNC_OUT_LINK_AUDIO_4BPQ,
30 SYNC_OUT_LINK_AUDIO_2BPQ, 30 SYNC_OUT_LINK_AUDIO_2BPQ,
31 SYNC_OUT_LSDJ,
31 SYNC_IN_LINK_96BPQ, 32 SYNC_IN_LINK_96BPQ,
32 SYNC_IN_LINK_48BPQ, 33 SYNC_IN_LINK_48BPQ,
33 SYNC_IN_LINK_24BPQ, 34 SYNC_IN_LINK_24BPQ,
@@ -54,6 +55,7 @@ char * sync_setting_str[] = {
54 "LINK+AUDIO OUT (6BPQ)", 55 "LINK+AUDIO OUT (6BPQ)",
55 "LINK+AUDIO OUT (4BPQ)", 56 "LINK+AUDIO OUT (4BPQ)",
56 "LINK+AUDIO OUT (2BPQ)", 57 "LINK+AUDIO OUT (2BPQ)",
58 "LSDJ OUT",
57 "LINK IN (96BPQ)", 59 "LINK IN (96BPQ)",
58 "LINK IN (48BPQ)", 60 "LINK IN (48BPQ)",
59 "LINK IN (24BPQ)", 61 "LINK IN (24BPQ)",