2026年時点の4kintroの作り方の一例。
今日AkibaExecutableParty行ってきて、Sointuすげえなってなったのもあり、
現状で作ることになる4kb introの開発手順を思いついて作ってみた。
できるだけコンパイル周りを明確にしておくのが目的。
目的
準備が簡単なデモの作り方を確認する。
色々お試しできる環境が必要。かつ簡単な単位で生成していき、自動化ができるようにする。
IDE依存で作る場合、コンパイラオプションが山のように付くためどれが必要かが分かりづらくなる。最近だと最適化やセキュリティチェック用のオプションも多くなってきており、気軽にコンパイルができないため、そういうのをなくした単純コンパイルが可能な環境を用意しておく。
音楽
これでソングをソースコードとして吐き出す。
名前.asm、名前.h、名前.incが出力されていたら成功。
ヘッダのディレクティブは32bit環境の記述を前提としているので、基本的には32bit環境でコンパイルするのが想定の模様。
sointu-compile -arch 386 -os windows song.yml
ウィンドウ周り
適当に書く。
今回のコードは、終了してもプロセス自体は死なないようで、タスクマネージャから終了してください。
本来だとこういうの作っちゃあかんけど、テストなのでご勘弁を。
ちなみにstdcallにしている理由は、Sointuの生成されたヘッダ部分に「__stdcall」と書いてあるため。
つまりSointuに合わせて環境調整したともいえる。
main.c
#include <windows.h>
#include <mmsystem.h>
#include <mmreg.h>
#ifdef __cplusplus
extern "C" {
#endif
// Sointuが1回で全楽曲をレンダリングする関数
void __stdcall su_render_song(void* buffer);
#ifdef __cplusplus
}
#endif
#define SAMPLE_RATE 44100
#define CHANNELS 2
// 🎵 楽曲の長さに応じてここを設定(例: 44100Hz * 2ch * 180秒 ≒ 15,876,000)
#define SU_LENGTH_IN_SAMPLES (SAMPLE_RATE * CHANNELS * 180)
// 巨大なバッファですが、グローバル変数(BSS領域)に置くことでexeサイズは肥大化しません
float audio_buffer[SU_LENGTH_IN_SAMPLES];
HWAVEOUT hWaveOut;
WAVEHDR waveHdr;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
if (msg == WM_DESTROY) { ExitProcess(0); }
return DefWindowProc(hwnd, msg, wp, lp);
}
void __cdecl EntryPoint() {
// 1. 先にSointuで「楽曲全体の波形」をすべて計算する
su_render_song(audio_buffer);
// 2. ウィンドウ生成
HWND hwnd = CreateWindowA("edit", "Sointu Demo", WS_POPUP | WS_VISIBLE,
0, 0, 640, 480, NULL, NULL, NULL, NULL);
HDC hdc = GetDC(hwnd);
// 3. オーディオデバイスの初期化
WAVEFORMATEX wfx = {
.wFormatTag = WAVE_FORMAT_IEEE_FLOAT,
.nChannels = CHANNELS,
.nSamplesPerSec = SAMPLE_RATE,
.wBitsPerSample = 32,
.nBlockAlign = (CHANNELS * 32) / 8,
.nAvgBytesPerSec = SAMPLE_RATE * ((CHANNELS * 32) / 8),
.cbSize = 0
};
waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL);
waveHdr.lpData = (LPSTR)audio_buffer;
waveHdr.dwBufferLength = sizeof(audio_buffer);
waveHdr.dwFlags = 0;
waveOutPrepareHeader(hWaveOut, &waveHdr, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, &waveHdr, sizeof(WAVEHDR)); // これでバックグラウンドで最後まで自動再生されます
// 5. グラフィック&メインループ
int frame = 0;
while (1) {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
HBRUSH brush = CreateSolidBrush(RGB((frame) % 255, 50, 100));
RECT rect = {0, 0, 640, 480};
FillRect(hdc, &rect, brush);
DeleteObject(brush);
frame++;
Sleep(16); // 約60fps
}
}
コンパイル・オブジェクトコード生成
まずソングからオブジェクトコードを生成する。
ツールはNASMを使う。32bit環境を想定してコマンドを入力しておく。
64bit環境はヘッダにも書いてないので、基本的に考えない。
必要があれば自分で追加して
netwide-assembler/nasm: A cross-platform x86 assembler with an Intel-like syntax
nasm -f win32 song.asm -o song.obj
次にmain.cのコンパイルを行う。これはVisualStudioのコンパイラを使う。
VisualStudioをインストールしたあとで、フォルダ等を読み込んだコマンドプロンプト経由で行うのが簡単。
今回は32bit環境、x86 NativeTools Command Promptという方を利用した。
cl.exe /c /O1 /GS- main.c /Fomain.obj
最後にCrinklerでリンクする。
Crinklerも32bit/64bitが存在するので確認しておくこと。
(リリースにはどっちのバージョンも封入されてる)
crinkler.exe /ENTRY:EntryPoint /SUBSYSTEM:WINDOWS main.obj winmm.lib user32.lib gdi32.lib kernel32.lib /OUT:demo.exe
これで4kbの実行ファイルが出力できる。
ちなみに音楽いれると1.6kbだった。音楽入れないと600byteを切る。
自分が4kb intro作った頃と比べて、だいぶとんでもない事になってる。
うまく動かないときは、Sointu側のアセンブリコード、ないしはmain関数を修正することになるので注意ね。
