Browse Source

add wav file conversion and playback

Thomas B 1 month ago
parent
commit
17b6fddfb2
11 changed files with 269 additions and 20 deletions
  1. 18
    6
      Makefile
  2. 2
    0
      README.md
  3. BIN
      data/sfx_shoot.wav
  4. 2
    1
      src/game.c
  5. 3
    2
      src/main.c
  6. 103
    0
      src/sample.c
  7. 26
    0
      src/sample.h
  8. 5
    8
      src/sound.c
  9. 0
    1
      src/sound.h
  10. 5
    2
      src/timer.c
  11. 105
    0
      util/cvtsample.py

+ 18
- 6
Makefile View File

@@ -32,10 +32,17 @@ SRCS += $(GIT)
32 32
 
33 33
 OBJS := $(SRCS:%.c=$(BUILD_DIR)/%.o)
34 34
 
35
-ASSETS := $(wildcard $(DATA_DIR)/*.png)
36
-SPRITES := $(ASSETS:%.png=$(BUILD_DIR)/%.c)
35
+IMAGES := $(wildcard $(DATA_DIR)/*.png)
36
+SPRITES := $(IMAGES:%.png=$(BUILD_DIR)/%.c)
37 37
 OBJS += $(SPRITES:%.c=%.o)
38 38
 
39
+WAVES := $(wildcard $(DATA_DIR)/*.wav)
40
+SOUNDS := $(WAVES:%.wav=$(BUILD_DIR)/%.c)
41
+OBJS += $(SOUNDS:%.c=%.o)
42
+
43
+ASSETS := $(SPRITES)
44
+ASSETS += $(SOUNDS)
45
+
39 46
 LCC := $(GBDK_HOME)/bin/lcc
40 47
 PNGA := $(GBDK_HOME)/bin/png2asset
41 48
 ROMU := $(GBDK_HOME)/bin/romusage
@@ -104,9 +111,14 @@ bgb_run: $(BUILD_DIR)/$(BIN)
104 111
 	@$(BGB_EMU) $(BGB_EMUFLAGS)
105 112
 
106 113
 flash: $(BIN)
107
-	@echo Flasing $<
114
+	@echo Flashing $<
108 115
 	@$(FLASHER) $(FLASHFLAGS) $<
109 116
 
117
+$(BUILD_DIR)/$(DATA_DIR)/%.c $(BUILD_DIR)/$(DATA_DIR)/%.h: $(DATA_DIR)/%.wav
118
+	@mkdir -p $(@D)
119
+	@echo Converting sound $<
120
+	@util/cvtsample.py $< "(None)" GBDK $(BUILD_DIR)/$(DATA_DIR)
121
+
110 122
 $(BUILD_DIR)/$(DATA_DIR)/%.c $(BUILD_DIR)/$(DATA_DIR)/%.h: $(DATA_DIR)/%.png
111 123
 	@mkdir -p $(@D)
112 124
 	$(eval SPRFLAG = $(shell echo "$<" | sed -n 's/.*_spr\([0-9]\+\).*/\-sw \1 \-sh \1/p'))
@@ -130,18 +142,18 @@ $(BUILD_DIR)/$(DATA_DIR)/%.c $(BUILD_DIR)/$(DATA_DIR)/%.h: $(DATA_DIR)/%.png
130 142
 		$(PNGA) $< -o $@ -spr8x8                                                        \
131 143
 	)))))
132 144
 
133
-$(BUILD_DIR)/%.o: %.c $(SPRITES)
145
+$(BUILD_DIR)/%.o: %.c $(ASSETS)
134 146
 	@mkdir -p $(@D)
135 147
 	@echo Compiling Code $<
136 148
 	$(eval BAFLAG = $(shell echo "$<" | sed -n 's/.*\.ba\([0-9]\+\).*/\-Wf-ba\1/p'))
137 149
 	@$(LCC) $(LCCFLAGS) $(BAFLAG) -c -o $@ $<
138 150
 
139
-$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.c $(SPRITES)
151
+$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.c $(ASSETS)
140 152
 	@mkdir -p $(@D)
141 153
 	@echo Compiling Asset $<
142 154
 	@$(LCC) $(LCCFLAGS) -c -o $@ $<
143 155
 
144
-$(BUILD_DIR)/%.o: %.s $(SPRITES)
156
+$(BUILD_DIR)/%.o: %.s $(ASSETS)
145 157
 	@mkdir -p $(@D)
146 158
 	@echo Assembling $<
147 159
 	@$(LCC) $(LCCFLAGS) -c -o $@ $<

+ 2
- 0
README.md View File

@@ -88,6 +88,8 @@ The files `sgb_border.c` and `sgb_border.h` are copied directly from their `sgb_
88 88
 
89 89
     See <http://www.gnu.org/licenses/>.
90 90
 
91
+The `util/cvtsample.py` script is based on a [GBDK example](https://github.com/gbdk-2020/gbdk-2020/blob/develop/gbdk-lib/examples/gb/wav_sample/utils/cvtsample.py).
92
+
91 93
 The included cartridge label graphic in `artwork/cart_label.xcf` is based on the ['Cartridge-Label-Templates' by Dinierto](https://github.com/Dinierto/Cartridge-Label-Templates) licensed as CC0.
92 94
 
93 95
 The included cartridge graphic in `artwork/cartridge.xcf` is based on the ['Front-End-Assets' by Duimon](https://github.com/Duimon/Front-End-Assets).

BIN
data/sfx_shoot.wav View File


+ 2
- 1
src/game.c View File

@@ -28,6 +28,7 @@
28 28
 #include "sound.h"
29 29
 #include "input.h"
30 30
 #include "main.h"
31
+#include "sample.h"
31 32
 #include "game.h"
32 33
 
33 34
 #define BAR_OFFSET_X (4 - 80)
@@ -315,7 +316,7 @@ int32_t game(void) NONBANKED {
315 316
             }
316 317
 
317 318
             if (ret == OBJ_ADDED) {
318
-                snd_shot();
319
+                sample_play_shoot();
319 320
 
320 321
                 if (score > 0) {
321 322
                     score--;

+ 3
- 2
src/main.c View File

@@ -34,6 +34,7 @@
34 34
 #include "sgb_border.h"
35 35
 #include "border_sgb.h"
36 36
 #include "timer.h"
37
+#include "sample.h"
37 38
 #include "main.h"
38 39
 
39 40
 #ifdef DEBUG
@@ -150,7 +151,7 @@ static void splash_anim(uint8_t *hiwater) NONBANKED {
150 151
             spr_draw(SPR_SHIP, FLIP_NONE, -4, -42 - 1, 4, hiwater);
151 152
             if (frame == 0) {
152 153
                 obj_add(SPR_SHOT, SHIP_OFF, -42, SHOT_SPEED, 0);
153
-                snd_shot();
154
+                sample_play_shoot();
154 155
             }
155 156
             break;
156 157
 
@@ -182,7 +183,7 @@ static void splash_anim(uint8_t *hiwater) NONBANKED {
182 183
             spr_draw(SPR_SHIP, FLIP_X, 4, -42, 4, hiwater);
183 184
             if (frame == 0) {
184 185
                 obj_add(SPR_SHOT, -SHIP_OFF, -42, -SHOT_SPEED, 0);
185
-                snd_shot();
186
+                sample_play_shoot();
186 187
             }
187 188
             break;
188 189
     }

+ 103
- 0
src/sample.c View File

@@ -0,0 +1,103 @@
1
+/*
2
+ * sample.c
3
+ * Duality
4
+ *
5
+ * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
6
+ *
7
+ * Based on examples from gbdk-2020:
8
+ * https://github.com/gbdk-2020/gbdk-2020/tree/develop/gbdk-lib/examples/gb/wav_sample
9
+ *
10
+ * And the docs for the DMG APU:
11
+ * https://gbdev.io/pandocs/Audio_Registers.html
12
+ *
13
+ * This program is free software: you can redistribute it and/or modify
14
+ * it under the terms of the GNU General Public License as published by
15
+ * the Free Software Foundation, either version 3 of the License, or
16
+ * (at your option) any later version.
17
+ *
18
+ * This program is distributed in the hope that it will be useful,
19
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
+ * GNU General Public License for more details.
22
+ *
23
+ * See <http://www.gnu.org/licenses/>.
24
+ */
25
+
26
+#include "sfx_shoot.h"
27
+#include "sample.h"
28
+
29
+static volatile uint8_t play_bank = 1;
30
+static volatile const uint8_t *play_sample = 0;
31
+static volatile uint16_t play_length = 0;
32
+
33
+void sample_play_shoot(void) NONBANKED {
34
+    CRITICAL {
35
+        play_bank = BANK(sfx_shoot);
36
+        play_sample = sfx_shoot;
37
+        play_length = sfx_shoot_SIZE >> 4;
38
+    }
39
+}
40
+
41
+void sample_isr(void) NONBANKED NAKED {
42
+    __asm
43
+    ld hl, #_play_length    ; something left to play?
44
+    ld a, (hl+)
45
+    or (hl)
46
+    ret z
47
+
48
+    ld hl, #_play_sample
49
+    ld a, (hl+)
50
+    ld h, (hl)
51
+    ld l, a                 ; HL = current position inside the sample
52
+
53
+    ; load new waveform
54
+    ld a, (#__current_bank) ; save bank and switch
55
+    ld e, a
56
+    ld a, (#_play_bank)
57
+    ld (_rROMB0), a
58
+
59
+    ldh a, (_NR51_REG)
60
+    ld c, a
61
+    and #0b10111011
62
+    ldh (_NR51_REG), a
63
+
64
+    xor a
65
+    ldh (_NR30_REG), a
66
+
67
+    .irp ofs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
68
+    ld a, (hl+)
69
+    ldh (__AUD3WAVERAM+ofs), a
70
+    .endm
71
+
72
+    ld a, #0x80
73
+    ldh (_NR30_REG), a
74
+    ld a, #0xFE             ; length of wave
75
+    ldh (_NR31_REG), a
76
+    ld a, #0x20             ; volume
77
+    ldh (_NR32_REG), a
78
+    xor a                   ; low freq bits are zero
79
+    ldh (_NR33_REG), a
80
+    ld a, #0xC7             ; start; no loop; high freq bits are 111
81
+    ldh (_NR34_REG), a
82
+
83
+    ld a, c
84
+    ldh (_NR51_REG), a
85
+
86
+    ld a, e                 ; restore bank
87
+    ld (_rROMB0), a
88
+
89
+    ld a, l                 ; save current position
90
+    ld (#_play_sample), a
91
+    ld a, h
92
+    ld (#_play_sample+1), a
93
+
94
+    ld hl, #_play_length    ; decrement length variable
95
+    ld a, (hl)
96
+    sub #1
97
+    ld (hl+), a
98
+    ld a, (hl)
99
+    sbc #0
100
+    ld (hl), a
101
+    ret
102
+    __endasm;
103
+}

+ 26
- 0
src/sample.h View File

@@ -0,0 +1,26 @@
1
+/*
2
+ * sample.h
3
+ * Duality
4
+ *
5
+ * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
6
+ *
7
+ * This program is free software: you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation, either version 3 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * See <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+#ifndef __SAMPLE_H__
21
+#define __SAMPLE_H__
22
+
23
+void sample_play_shoot(void);
24
+void sample_isr(void);
25
+
26
+#endif // __SAMPLE_H__

+ 5
- 8
src/sound.c View File

@@ -6,6 +6,7 @@
6 6
  *
7 7
  * Based on examples from gbdk-2020:
8 8
  * https://github.com/gbdk-2020/gbdk-2020/blob/develop/gbdk-lib/examples/gb/sound/sound.c
9
+ * https://github.com/gbdk-2020/gbdk-2020/tree/develop/gbdk-lib/examples/gb/wav_sample
9 10
  *
10 11
  * And the docs for the DMG APU:
11 12
  * https://gbdev.io/pandocs/Audio_Registers.html
@@ -53,7 +54,7 @@ static void play_note(enum notes note) NONBANKED {
53 54
         END_ROM_BANK();
54 55
 
55 56
         NR11_REG = 0x80 | 0x3F; // 50% duty, shortest initial length
56
-        NR12_REG = 0xF0; // max volume, no change
57
+        NR12_REG = 0x70; // half volume, no change
57 58
         NR13_REG = freq & 0xFF; // given frequency
58 59
         NR14_REG = 0x80 | ((freq >> 8) & 0x07); // trigger, upper freq bits
59 60
     } else {
@@ -173,14 +174,10 @@ void snd_play(void) NONBANKED {
173 174
     END_ROM_BANK();
174 175
 }
175 176
 
176
-void snd_shot(void) BANKED {
177
-    NR41_REG = 0x2F; // length timer, higher value is shorter time (up to 0x3F)
178
-    NR42_REG = 0xF0; // initially full volume, no volume changes over time
179
-    NR43_REG = 0x11; // frequency distribution
180
-    NR44_REG = 0xC0; // trigger and enable length
181
-}
182
-
183 177
 void snd_explode(void) BANKED {
178
+    // TODO use something more elaborate than noise
179
+    //return;
180
+
184 181
     NR41_REG = 0x00; // length timer, higher value is shorter time (up to 0x3F)
185 182
     NR42_REG = 0xF1; // initially full volume, then fade sound out
186 183
     NR43_REG = 0x46; // frequency distribution

+ 0
- 1
src/sound.h View File

@@ -73,7 +73,6 @@ void snd_game_music(void) BANKED;
73 73
 void snd_gameover_music(void) BANKED;
74 74
 void snd_play(void);
75 75
 
76
-void snd_shot(void) BANKED;
77 76
 void snd_explode(void) BANKED;
78 77
 
79 78
 BANKREF_EXTERN(sound)

+ 5
- 2
src/timer.c View File

@@ -17,12 +17,15 @@
17 17
  * See <http://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
+#include "sample.h"
20 21
 #include "timer.h"
21
-#include "gb/gb.h"
22 22
 
23
-static volatile uint16_t count;
23
+static volatile uint16_t count = 0;
24 24
 
25 25
 static void timer_isr(void) NONBANKED {
26
+    if ((count & 0x03) == 0) {
27
+        sample_isr();
28
+    }
26 29
     count++;
27 30
 }
28 31
 

+ 105
- 0
util/cvtsample.py View File

@@ -0,0 +1,105 @@
1
+#!/usr/bin/env python3
2
+
3
+# based on the cvtsample.py included in the GBDK example:
4
+# https://github.com/gbdk-2020/gbdk-2020/blob/develop/gbdk-lib/examples/gb/wav_sample/utils/cvtsample.py
5
+
6
+import sys
7
+import wave
8
+import os
9
+
10
+sGBDK = """//AUTOGENERATED FILE FROM cvtsample
11
+#include <stdint.h>
12
+#include <gbdk/platform.h>
13
+BANKREF({:s})
14
+"""
15
+
16
+hGBDK = """//AUTOGENERATED FILE FROM cvtsample
17
+#ifndef WAVE_SOUND_{:s}_H
18
+#define WAVE_SOUND_{:s}_H
19
+#include <stdint.h>
20
+#include <gbdk/platform.h>
21
+
22
+#define {:s}_SIZE 0x{:x}
23
+extern const uint8_t {:s}[];
24
+
25
+BANKREF_EXTERN({:s})
26
+#endif
27
+"""
28
+
29
+def main(argv=None):
30
+    argv = argv or sys.argv
31
+    if len(argv) < 2:
32
+        sys.stderr.write("cvtsample.py: no filename; try cvtsample.py --help\n")
33
+        sys.exit(1)
34
+
35
+    infilename = argv[1]
36
+    ident = argv[2] if len(argv) > 2 else None
37
+    fmt = argv[3].upper() if len(argv) > 3 else "C"
38
+    outdir = argv[4] if len(argv) > 4 else os.getcwd()
39
+
40
+    if infilename in ('--help', '-h'):
41
+        print("usage: cvtsample.py SOURCE [IDENTIFIER] [FMT] [OUTDIR]")
42
+        return
43
+
44
+    if ident == "(None)":
45
+        ident = None
46
+    if ident == None:
47
+        ident = os.path.splitext(os.path.basename(infilename))[0]
48
+
49
+    if fmt == "C":
50
+        sHDR = "const UINT8 {:s}[] = {{\n"
51
+        sFOOT = "};\n"
52
+        sEMIT = "0x{:x}"
53
+        sNEW = ",\n"
54
+        sNONEW = ","
55
+    elif fmt == "GBDK":
56
+        outheader = os.path.join(outdir, f"{ident}.h")
57
+        outsource = os.path.join(outdir, f"{ident}.c")
58
+        sHDR = "const uint8_t {:s}[] = {{\n"
59
+        sFOOT = "};\n"
60
+        sEMIT = "0x{:x}"
61
+        sNEW = ",\n"
62
+        sNONEW = ","
63
+    elif fmt == "ASM":
64
+        sHDR = "{:s}::"
65
+        sFOOT = ""
66
+        sEMIT = "${:x}"
67
+        sNEW = "\n"
68
+        sNONEW = ","
69
+
70
+    with wave.open(infilename, mode="rb") as f:
71
+        p = f.getparams()        
72
+        if (p.nchannels == 1) and (p.sampwidth == 1) and (p.framerate >= 8000) and (p.framerate <= 8192) and (p.comptype == 'NONE'):
73
+            data = f.readframes(p.nframes)
74
+            c = 0
75
+            cnt = 0;
76
+            flag = False
77
+
78
+            s = sHDR.format(ident)
79
+            for i in range(len(data) - len(data) % 32):
80
+                c = ((c << 4) | (data[i] >> 4)) & 0xFF
81
+                if flag:
82
+                    s += sEMIT.format(c)
83
+                    cnt += 1
84
+                    s += sNEW if (cnt % 16 == 0) else sNONEW
85
+                        
86
+                flag = not flag
87
+            s += sFOOT
88
+
89
+            if fmt == "GBDK":
90
+                with open(outsource, "w") as o:
91
+                    o.write(sGBDK.format(ident))
92
+                    o.write(s)
93
+                with open(outheader, "w") as o:
94
+                    o.write(hGBDK.format(ident, ident, ident, cnt, ident, ident))
95
+            else:
96
+                sys.stdout.write(s)
97
+                sys.stdout.flush()
98
+        else:
99
+            sys.stderr.write("ERROR: Invalid wav file format\n")
100
+            sys.stderr.write("Requires - nChannels: 1, sampWidth: 1, rate: 8000 - 8192, compType: NONE\n")
101
+            sys.stderr.write("Found    - nChannels: %d, sampWidth: %d, rate: %d, compType: %s\n" % (p.nchannels, p.sampwidth, p.framerate, p.comptype))
102
+            sys.stderr.flush()
103
+
104
+if __name__=='__main__':
105
+    main()

Loading…
Cancel
Save