0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

z88dk MSX BGM SE 再生 割り込み 非同期

Last updated at Posted at 2025-11-27
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"
            ],
        }
    ]
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?