BGM
チャンネルA
チャンネルB
SE(効果音)
チャンネルC
.ファイル構成
│ main.c
│ Makefile
│ notes.c
│ notes.h
│ pitches.h
│ psg.c
│ psg.h
│
└─.vscode
c_cpp_properties.json
launch.json
tasks.json
main.c
#include <stdio.h>
#include "notes.h"
#include "psg.h"
void on_complete(uint8_t id);
void main() {
printf("[right]: play se\n");
printf("[left]: stop se\n");
printf("[up]: play bgm\n");
printf("[down]: stop bgm\n");
while (1) {
uint16_t key = getk();
if (key == 28) {
// [->]
play_se(p_se_1, BASE_BPM * 2, 15, 0, 1, on_complete);
}
if (key == 29) {
// [<-]
stop_se();
}
if (key == 30) {
// [up]
play_bgm(p_bgm_1, BASE_BPM, 15, -1, 0, NULL);
}
if (key == 31) {
stop_bgm();
}
}
}
void on_complete(uint8_t id) { printf("%d ", id); }
Makefile.
# コンパイラとターゲット設定
ZCC = zcc +msx
TARGET = main.rom
# ソースとオブジェクト
SRCS = main.c psg.c notes.c
OBJS = $(SRCS:.c=.o)
# デフォルトターゲット
all: $(TARGET)
%.o: %.c
$(ZCC) -c $< -o $@
# ROM生成
$(TARGET): $(OBJS)
$(ZCC) -subtype=rom -create-app $(OBJS) -o $(TARGET)
# クリーンアップ
clean:
del /Q $(OBJS) $(TARGET) *.bin
notes.c
#include "notes.h"
uint16_t bgm_1_a[] = {
// 1
PIT_C4, 8, PIT_C4, 8, PIT_C4, 8, PIT_E4, 8, PIT_A4, 8, PIT_A4, 8, PIT_A4, 8,
PIT_NONE, 8,
// 2
PIT_D4, 8, PIT_D4, 8, PIT_D4, 8, PIT_F4, 8, PIT_G4, 8, PIT_G4, 8, PIT_G4, 8,
PIT_NONE, 8,
// 3
PIT_NONE, 8, PIT_G4, 8, PIT_G4, 8, PIT_GS4, 8, PIT_A4, 8, PIT_A4, 8, PIT_A4,
8, PIT_GS4, 8,
// 4
PIT_G4, 4, PIT_B4, 4, PIT_C5, 4, PIT_NONE, 4,
// 5
PIT_NONE, 8, PIT_G4, 8, PIT_G4, 8, PIT_F4, 8, PIT_E4, 8, PIT_E4, 8, PIT_E4,
8, PIT_F4, 8,
// 6
PIT_G4, 8, PIT_G4, 8, PIT_G4, 8, PIT_G4, 8, PIT_C4, 8, PIT_NONE, 4,
// 7
PIT_NONE, 8, PIT_F4, 8, PIT_F4, 8, PIT_E4, 8, PIT_D4, 8, PIT_D4, 8, PIT_D4,
8, PIT_D4, 8,
// 8
PIT_D4, 8, PIT_A4, 8, PIT_A4, 8, PIT_GS4, 8, PIT_G4, 8, PIT_NONE, 4,
// 9
PIT_A4, 8, PIT_A4, 8, PIT_A4, 8, PIT_B4, 8, PIT_C5, 8, PIT_C5, 8, PIT_C5, 4,
// 10
PIT_B4, 8, PIT_B4, 8, PIT_B4, 8, PIT_B4, 8, PIT_A4, 4, PIT_NONE, 4,
// 11
PIT_NONE, 8, PIT_G4, 8, PIT_G4, 8, PIT_GS4, 8, PIT_A4, 8, PIT_A4, 8, PIT_A4,
8, PIT_GS4, 8,
// 12
PIT_G4, 4, PIT_B4, 4, PIT_C5, 4, PIT_NONE, 4,
// 13
PIT_C4, 8, PIT_C4, 8, PIT_C4, 8, PIT_E4, 8, PIT_A4, 8, PIT_A4, 8, PIT_A4, 8,
PIT_NONE, 8,
// 14
PIT_D4, 8, PIT_D4, 8, PIT_D4, 8, PIT_F4, 8, PIT_G4, 8, PIT_G4, 8, PIT_G4, 8,
PIT_NONE, 8,
// 15
PIT_NONE, 8, PIT_G4, 8, PIT_G4, 8, PIT_GS4, 8, PIT_A4, 8, PIT_A4, 8, PIT_A4,
8, PIT_GS4, 8,
// 16
PIT_G4, 4, PIT_B4, 4, PIT_C5, 4, PIT_NONE, 4,
// 終端データ
0, 0};
uint16_t bgm_1_b[] = {
// 1
PIT_C3, 4, PIT_C3, 4, PIT_A3, 4, PIT_A3, 4,
// 2
PIT_D3, 4, PIT_D3, 4, PIT_G3, 4, PIT_G3, 4,
// 3
PIT_G3, 4, PIT_G3, 4, PIT_G3, 4, PIT_G3, 4,
// 4
PIT_G3, 4, PIT_D3, 4, PIT_C3, 4, PIT_NONE, 4,
// 5
PIT_C3, 2, PIT_C3, 2,
// 6
PIT_C3, 2, PIT_C3, 2,
// 7
PIT_D3, 2, PIT_D3, 2,
// 8
PIT_D3, 2, PIT_G3, 2,
// 9
PIT_F3, 2, PIT_F3, 2,
// 10
PIT_E3, 2, PIT_A3, 2,
// 11
PIT_G3, 2, PIT_G3, 2,
// 12
PIT_G3, 4, PIT_D3, 4, PIT_C3, 4, PIT_NONE, 4,
// 13
PIT_C3, 4, PIT_C3, 4, PIT_A3, 4, PIT_A3, 4,
// 14
PIT_D3, 4, PIT_D3, 4, PIT_G3, 4, PIT_G3, 4,
// 15
PIT_G3, 4, PIT_G3, 4, PIT_G3, 4, PIT_G3, 4,
// 16
PIT_G3, 4, PIT_D3, 4, PIT_C3, 4, PIT_NONE, 4,
// 終端データ
0, 0};
uint16_t* p_bgm_1[2] = {bgm_1_a, bgm_1_b};
// se
uint16_t se_1[] = {PIT_C5, 32, PIT_D5, 32, PIT_E5, 32, PIT_F5, 32, PIT_G5, 32,
PIT_A5, 32, PIT_B5, 32,
// 終端データ
0, 0};
uint16_t* p_se_1 = se_1;
notes.h
#ifndef MUSIC_DATA_H
#define MUSIC_DATA_H
#include "pitches.h"
#include "stdint.h"
// 音符 構造体
typedef struct {
uint16_t pitch; // 周波数(音程)
uint8_t note_value; // 音符種類 (1: 全音符, 2: 2分音符, 4: 4分音符, ...)
} Note;
extern uint16_t* p_bgm_1[2];
extern uint16_t* p_se_1;
#endif // MUSIC_DATA_H
pitches.h
#define PIT_NONE 0
// #define PIT_B0 31
// #define PIT_C1 33
// #define PIT_CS1 35
// #define PIT_D1 37
// #define PIT_DS1 39
// #define PIT_E1 41
// #define PIT_F1 44
// #define PIT_FS1 46
// #define PIT_G1 49
// #define PIT_GS1 52
// #define PIT_A1 55
// #define PIT_AS1 58
// #define PIT_B1 62
// #define PIT_C2 65
// #define PIT_CS2 69
// #define PIT_D2 73
// #define PIT_DS2 78
// #define PIT_E2 82
// #define PIT_F2 87
// #define PIT_FS2 93
// #define PIT_G2 98
// #define PIT_GS2 104
// #define PIT_A2 110
// #define PIT_AS2 117
// #define PIT_B2 123
#define PIT_C3 131
#define PIT_CS3 139
#define PIT_D3 147
#define PIT_DS3 156
#define PIT_E3 165
#define PIT_F3 175
#define PIT_FS3 185
#define PIT_G3 196
#define PIT_GS3 208
#define PIT_A3 220
#define PIT_AS3 233
#define PIT_B3 247
#define PIT_C4 262
#define PIT_CS4 277
#define PIT_D4 294
#define PIT_DS4 311
#define PIT_E4 330
#define PIT_F4 349
#define PIT_FS4 370
#define PIT_G4 392
#define PIT_GS4 415
#define PIT_A4 440
#define PIT_AS4 466
#define PIT_B4 494
#define PIT_C5 523
#define PIT_CS5 554
#define PIT_D5 587
#define PIT_DS5 622
#define PIT_E5 659
#define PIT_F5 698
#define PIT_FS5 740
#define PIT_G5 784
#define PIT_GS5 831
#define PIT_A5 880
#define PIT_AS5 932
#define PIT_B5 988
// #define PIT_C6 1047
// #define PIT_CS6 1109
// #define PIT_D6 1175
// #define PIT_DS6 1245
// #define PIT_E6 1319
// #define PIT_F6 1397
// #define PIT_FS6 1480
// #define PIT_G6 1568
// #define PIT_GS6 1661
// #define PIT_A6 1760
// #define PIT_AS6 1865
// #define PIT_B6 1976
// #define PIT_C7 2093
// #define PIT_CS7 2217
// #define PIT_D7 2349
// #define PIT_DS7 2489
// #define PIT_E7 2637
// #define PIT_F7 2794
// #define PIT_FS7 2960
// #define PIT_G7 3136
// #define PIT_GS7 3322
// #define PIT_A7 3520
// #define PIT_AS7 3729
// #define PIT_B7 3951
// #define PIT_C8 4186
// #define PIT_CS8 4435
// #define PIT_D8 4699
// #define PIT_DS8 4978
psg.c
#include "psg.h"
#include <stdio.h>
uint8_t psg_regs[][3] = {{REG_CH_A_LOW, REG_CH_A_HIGH, REG_CH_A_VOLUME},
{REG_CH_B_LOW, REG_CH_B_HIGH, REG_CH_B_VOLUME},
{REG_CH_C_LOW, REG_CH_C_HIGH, REG_CH_C_VOLUME}};
uint8_t is_interrupt = 0;
uint16_t* notes[3];
uint16_t* notes_copy[3];
uint32_t base_duration[3];
uint16_t note_duration[3];
uint8_t is_done[3];
int8_t bgm_repeat; // -1: 無限繰り返し, 0: 繰り返し0回, 1: 繰り返し1回, ...
int8_t se_repeat;
uint8_t is_bgm_enable = 0;
uint8_t is_se_enable = 0;
uint8_t bgm_id;
uint8_t se_id;
void (*bgm_callback)(uint8_t id);
void (*se_callback)(uint8_t id);
void play_bgm(uint16_t* pointer[2], uint16_t bpm, uint8_t volume,
uint8_t repeat, uint16_t id, void (*callback)(uint8_t id)) {
is_bgm_enable = 0;
bpm = bpm > 0 ? bpm : BASE_BPM;
for (uint8_t ch = 0; ch < 2; ch++) {
notes[ch] = pointer[ch];
notes_copy[ch] = pointer[ch];
base_duration[ch] = BASE_DURATION * BASE_BPM / bpm;
note_duration[ch] = 0;
set_volume(ch, volume);
is_done[ch] = 0;
}
bgm_repeat = repeat;
bgm_id = id;
bgm_callback = callback;
if (!is_interrupt) {
start_interrupt();
}
is_bgm_enable = 1;
}
void stop_bgm() {
bgm_repeat = 0;
is_bgm_enable = 0;
for (uint8_t ch = 0; ch < 2; ch++) {
set_pitch(ch, PIT_NONE);
note_duration[ch] = 0;
is_done[ch] = 1;
}
}
void play_se(uint16_t* pointer, uint16_t bpm, uint8_t volume, uint8_t repeat,
uint16_t id, void (*callback)(uint8_t id)) {
is_se_enable = 0;
bpm = bpm > 0 ? bpm : BASE_BPM;
notes[CHANNEL_C] = pointer;
notes_copy[CHANNEL_C] = pointer;
base_duration[CHANNEL_C] = BASE_DURATION * BASE_BPM / bpm;
note_duration[CHANNEL_C] = 0;
set_volume(CHANNEL_C, volume);
is_done[CHANNEL_C] = 0;
se_repeat = repeat;
se_id = id;
se_callback = callback;
if (!is_interrupt) {
start_interrupt();
}
is_se_enable = 1;
}
void stop_se() {
se_repeat = 0;
is_se_enable = 0;
set_pitch(CHANNEL_C, PIT_NONE);
note_duration[CHANNEL_C] = 0;
is_done[CHANNEL_C] = 1;
}
// 音符設定
void set_note() {
for (uint8_t ch = 0; ch < 3; ch++) {
if (is_done[ch]) {
continue;
}
if (!is_bgm_enable && ch < CHANNEL_C) {
continue;
}
if (!is_se_enable && ch == CHANNEL_C) {
continue;
}
if (note_duration[ch] > 0) {
note_duration[ch]--;
continue;
}
// 音程
uint16_t pitch = *notes[ch];
set_pitch(ch, pitch);
// 音符の長さ
uint8_t note_value = *(notes[ch] + 1);
note_duration[ch] = note_value > 0 ? (base_duration[ch] / note_value) : 0;
if (pitch == 0 && note_value == 0) {
is_done[ch] = 1;
} else {
notes[ch] += 2;
}
}
check_loop();
}
void check_loop() {
// bgm
uint8_t bgm_done = is_done[CHANNEL_A] && is_done[CHANNEL_B];
if (is_bgm_enable && bgm_done) {
if (bgm_repeat == 0) {
is_bgm_enable = 0;
if (bgm_callback != NULL) {
// コールバック実行
bgm_callback(bgm_id);
}
} else {
if (bgm_repeat > 0) {
// 繰り返し
bgm_repeat--;
}
for (uint8_t ch = 0; ch < 2; ch++) {
notes[ch] = notes_copy[ch];
note_duration[ch] = 0;
is_done[ch] = 0;
}
}
}
// se
uint8_t se_done = is_done[CHANNEL_C];
if (is_se_enable && se_done) {
if (se_repeat == 0) {
is_se_enable = 0;
if (se_callback != NULL) {
// コールバック実行
se_callback(se_id);
}
} else {
// 繰り返し
if (se_repeat > 0) {
se_repeat--;
}
notes[CHANNEL_C] = notes_copy[CHANNEL_C];
note_duration[CHANNEL_C] = 0;
is_done[CHANNEL_C] = 0;
}
}
if (!is_bgm_enable && !is_se_enable) {
stop_interrupt();
}
}
void set_pitch(uint8_t channel, uint16_t pitch) {
// PSGに周波数設定 クロック 1.789773 MHz
unsigned int n = pitch > 0 ? (1789773 / (16 * pitch)) : 0;
// チャンネルA
__outp(PORT_PSG_REG, psg_regs[channel][0]); // 下位8ビット
__outp(PORT_PSG_DATA, n & 0xFF);
__outp(PORT_PSG_REG, psg_regs[channel][1]); // 上位4ビット
__outp(PORT_PSG_DATA, (n >> 8) & 0x0F);
}
// 音量設定
void set_volume(uint8_t channel, uint8_t volume) {
__outp(PORT_PSG_REG, psg_regs[channel][2]); // ボリューム
__outp(PORT_PSG_DATA, volume);
}
// 割り込みが発生したときに実行する関数のアドレスを設定
void start_interrupt() {
is_interrupt = 1;
uint16_t addr = set_note; // この関数を定期的に実行する
__asm__("di"); // 割り込み無効
volatile unsigned char* vec =
(volatile unsigned char*)WORK_H_TIMI; // フック アドレス
vec[0] = 0xC3; // JP命令
vec[1] = (unsigned char)(addr & 0xFF); // 下位アドレス
vec[2] = (unsigned char)((addr >> 8) & 0xFF); // 上位アドレス
__asm__("ei");
}
void stop_interrupt() {
__asm__("di"); // 割り込み無効
volatile unsigned char* vec =
(volatile unsigned char*)WORK_H_TIMI; // フック アドレス
vec[0] = 0xC9; // RET命令
vec[1] = 0xC9;
vec[2] = 0xC9;
__asm__("ei"); // 割り込み有効化
is_interrupt = 0;
}
psg.h
#ifndef PSG_H
#define PSG_H
#include <stdint.h>
#include "pitches.h"
#define PORT_PSG_REG 0xA0 // PSGポート
#define PORT_PSG_DATA 0xA1 // PSGポート
#define REG_CH_A_LOW 0 // チャンネルA 音程分周比 下位8ビット レジスタ番号
#define REG_CH_A_HIGH 1 // チャンネルA 音程分周比 上位4ビット レジスタ番号
#define REG_CH_B_LOW 2 // チャンネルB 音程分周比 下位8ビット レジスタ番号
#define REG_CH_B_HIGH 3 // チャンネルB 音程分周比 上位4ビット レジスタ番号
#define REG_CH_C_LOW 4 // チャンネルC 音程分周比 下位8ビット レジスタ番号
#define REG_CH_C_HIGH 5 // チャンネルC 音程分周比 上位4ビット レジスタ番号
#define REG_CH_A_VOLUME 8 // チャンネルA ボリューム
#define REG_CH_B_VOLUME 9 // チャンネルB ボリューム
#define REG_CH_C_VOLUME 10 // チャンネルC ボリューム
#define WORK_H_TIMI 0xFD9F // フック アドレス
#define BASE_BPM 120
#define BASE_DURATION 120 // 全音符の長さ
#define CHANNEL_A 0
#define CHANNEL_B 1
#define CHANNEL_C 2
void play_bgm(uint16_t* pointer[2], uint16_t bpm, uint8_t volume,
uint8_t repeat, uint16_t id, void (*callback)(uint8_t id));
void stop_bgm();
void play_se(uint16_t* pointer, uint16_t bpm, uint8_t volume, uint8_t repeat,
uint16_t id, void (*callback)(uint8_t id));
void stop_se();
void start_interrupt();
void stop_interrupt();
void set_note();
void set_pitch(uint8_t channel, uint16_t pitch);
void set_volume(uint8_t channel, uint8_t volume);
void check_loop();
#endif
.vscode/c_cpp_properties.json
{
"configurations": [
{
"name": "z88dk-msx",
"includePath": [
"${workspaceFolder}/**",
"C:/z88dk/include"
],
"defines": [],
"compilerPath": "C:/z88dk/bin/zcc.exe",
"cStandard": "c89",
"intelliSenseMode": "${default}"
}
],
"version": 4
}
.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run MSX ROM in Emulator",
"type": "cppdbg",
"request": "launch",
"program": "C:/openmsx-21.0-windows-vc-x64-bin/openmsx.exe",
"args": [
"${workspaceFolder}/${fileBasenameNoExtension}.rom"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "",
"preLaunchTask": "Build MSX ROM"
}
]
}
.vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Build MSX ROM",
"type": "shell",
"command": "mingw32-make",
"args": [
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": []
},
{
"label": "make clean",
"type": "shell",
"command": "mingw32-make",
"args": [
"clean"
],
}
]
}