diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | src/gba/gba.h | 8 | ||||
-rw-r--r-- | src/gba/interrupts.s | 4 | ||||
-rw-r--r-- | src/sequencer.c | 155 | ||||
-rw-r--r-- | src/settings.c | 5 | ||||
-rw-r--r-- | src/settings.h | 2 |
6 files changed, 144 insertions, 32 deletions
@@ -27,7 +27,7 @@ INC_FLAGS := $(addprefix -I,$(INC_DIRS)) | |||
27 | INC_FLAGS += -I$(LIBGBA_SRC) | 27 | INC_FLAGS += -I$(LIBGBA_SRC) |
28 | 28 | ||
29 | # Output library names and executables. | 29 | # Output library names and executables. |
30 | TARGET := STEPPER-v1.8 | 30 | TARGET := STEPPER-v1.9-dev-2 |
31 | ELF := $(BUILD_DIR)/$(TARGET).elf | 31 | ELF := $(BUILD_DIR)/$(TARGET).elf |
32 | BIN := $(BUILD_DIR)/$(TARGET).gba | 32 | BIN := $(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. |
535 | void irq_init(void); | 535 | void 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). | ||
539 | static inline void | ||
540 | irs_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 | 25 | INLINE void | |
26 | void | 26 | gate_set(u8 sc, u8 so) { |
27 | gate_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 | |||
38 | void | ||
39 | gate_off(void) { | ||
40 | TIMER_CTRL_3 = 0; | ||
41 | gate_set(0, 0); | ||
42 | } | ||
43 | |||
44 | void | ||
45 | gate_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 | |||
74 | int pulse_count = 0; | ||
75 | int pulse_value = 0; | ||
76 | int gate_status = 0; | ||
77 | |||
78 | void | ||
79 | send_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 | ||
39 | void | 113 | void |
40 | gate_on(void) { | 114 | send_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 | ||
56 | u8 | 131 | u8 |
@@ -475,22 +550,37 @@ env_start: | |||
475 | 550 | ||
476 | void | 551 | void |
477 | sequencer_tick(void) { | 552 | sequencer_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 | ||
685 | void | 775 | void |
686 | reset_sequencer(void) { | 776 | reset_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); | |||
17 | void reset_sequencer(void); | 17 | void reset_sequencer(void); |
18 | void toggle_playing(void); | 18 | void toggle_playing(void); |
19 | void stop_playing(void); | 19 | void stop_playing(void); |
20 | void gate_set(u8, u8); | ||
20 | 21 | ||
21 | static int sync_ticks = 0; | 22 | static 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)", |