Refactor: modularize code into separate files with enhanced documentation

- Split paula.cpp and modplayer.cpp into separate compilation units
- Created config.h for centralized configuration constants
- Created types.h for type definitions and utility functions
- Added comprehensive comments throughout all files
- Improved code organization with clear section dividers
- Maintained all original functionality and logic
This commit is contained in:
Jason
2026-04-24 19:36:30 +10:00
parent b84677a01e
commit 1f17cf04b4
9 changed files with 1660 additions and 42 deletions
+282
View File
@@ -0,0 +1,282 @@
// =================== TinyMOD Player - Main Entry Point ===================
// TinyMOD: An Amiga MOD file player with authentic Paula chip emulation
// Supports playback of Protracker MOD files with full effect support
//
// Authors:
// Tammo "kb" Hinrichs - Original Paula emulator (2007)
// Jason Bou-samra - Refactoring and PortAudio integration (2024)
//
// This program is released into the public domain.
// Use, distribute, modify as you wish.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <portaudio.h>
#include "types.h"
#include "config.h"
#include "paula.h"
#include "modplayer.h"
// =================== Audio Configuration Constants ===================
const int SAMPLE_RATE_INTERNAL = 96000; // Internal Paula emulation rate
const int SAMPLE_RATE_OUTPUT = 48000; // Output audio sample rate
// =================== Utility Functions ===================
// Load MOD file from disk into memory
// Returns pointer to allocated memory containing file data
// Sets file_size to the number of bytes read
sU8 *load_mod_file(const char *filename, size_t &file_size)
{
// Open file for reading in binary mode
FILE *fh = fopen(filename, "rb");
if (!fh)
{
perror("fopen");
return NULL;
}
// Get file size using stat()
struct stat sb;
if (stat(filename, &sb) == -1)
{
perror("stat");
fclose(fh);
return NULL;
}
file_size = sb.st_size;
// Allocate memory for file (4MB max)
if (file_size > 4 * 1024 * 1024)
{
fprintf(stderr, "Error: MOD file too large (max 4MB)\n");
fclose(fh);
return NULL;
}
sU8 *mod = (sU8 *)malloc(file_size);
if (!mod)
{
perror("malloc");
fclose(fh);
return NULL;
}
// Read file into memory
if (fread(mod, file_size, 1, fh) != 1)
{
perror("fread");
free(mod);
fclose(fh);
return NULL;
}
fclose(fh);
return mod;
}
// PortAudio error handler
// Prints error information and terminates program
void handle_pa_error(PaError err)
{
if (err == paNoError)
return;
fprintf(stderr, "PortAudio Error: %s\n", Pa_GetErrorText(err));
// Print additional host API error information if available
if (err == paUnanticipatedHostError)
{
const PaHostErrorInfo *hostErrorInfo = Pa_GetLastHostErrorInfo();
fprintf(stderr, "Host API Error: #%ld\n", hostErrorInfo->errorCode);
fprintf(stderr, "Host API: %d\n", hostErrorInfo->hostApiType);
fprintf(stderr, "Details: %s\n", hostErrorInfo->errorText);
}
Pa_Terminate();
exit(1);
}
// Print usage information
void print_usage(const char *program_name)
{
printf("Usage: %s [<mod file>|OPTION]\n\n", program_name);
printf("OPTIONS:\n");
printf(" --about Display about message\n");
printf(" --help Display this help message\n");
}
// Print about information
void print_about()
{
printf("TinyMOD - Amiga MOD File Player\n\n");
printf("An Amiga MOD file player that replicates the authentic sound\n");
printf("characteristics of an Amiga via Paula chip emulation.\n\n");
printf("Authors:\n");
printf(" Tammo \"kb\" Hinrichs - Paula emulator (2007)\n");
printf(" Jason Bou-samra - Refactoring and integration (2024)\n\n");
printf("Released into the public domain.\n");
}
// =================== Main Program ===================
int main(int argc, const char **argv)
{
// === Check Command Line Arguments ===
if (argc != 2)
{
print_usage(argv[0]);
return 1;
}
const char *filename = argv[1];
// Handle --about option
if (!strcmp(filename, "--about"))
{
print_about();
return 0;
}
// Handle --help option
if (!strcmp(filename, "--help"))
{
print_usage(argv[0]);
return 0;
}
// === Load MOD File ===
printf("Loading MOD file: %s\n", filename);
size_t mod_size = 0;
sU8 *mod_data = load_mod_file(filename, mod_size);
if (!mod_data)
{
fprintf(stderr, "Error: Failed to load MOD file\n");
return 1;
}
printf("Loaded %zu bytes\n", mod_size);
// === Initialize PortAudio ===
printf("Initializing PortAudio...\n");
PaError err = Pa_Initialize();
if (err != paNoError)
handle_pa_error(err);
// === Configure Output Stream ===
PaStreamParameters outputParameters;
outputParameters.device = Pa_GetDefaultOutputDevice();
if (outputParameters.device == paNoDevice)
{
fprintf(stderr, "Error: No default output device found\n");
Pa_Terminate();
free(mod_data);
return 1;
}
outputParameters.channelCount = 2; // Stereo output
outputParameters.sampleFormat = paFloat32; // 32-bit float samples
outputParameters.suggestedLatency =
Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
// === Open Audio Stream ===
PaStream *stream;
err = Pa_OpenStream(
&stream,
NULL, // No input
&outputParameters,
SAMPLE_RATE_OUTPUT, // Output sample rate
FRAMES_PER_BUFFER, // Frames per buffer
paClipOff, // Don't clip output
NULL, // No callback
NULL); // No user data
if (err != paNoError)
handle_pa_error(err);
// === Start Audio Stream ===
err = Pa_StartStream(stream);
if (err != paNoError)
handle_pa_error(err);
// === Initialize MOD Player and Paula Emulator ===
Paula paula; // Create Paula emulator instance
ModPlayer player(&paula, mod_data); // Create MOD player with MOD file
// === Display Playback Information ===
cls(); // Clear screen
printf("TinyMOD - Amiga MOD File Player\n");
printf("================================\n\n");
printf("Currently playing: %s\n", player.Name);
printf("Duration: %d seconds\n", NUM_SECONDS);
printf("Sample rate: %d Hz (Paula: %d Hz)\n", SAMPLE_RATE_OUTPUT, SAMPLE_RATE_INTERNAL);
printf("\nPress Ctrl+C to stop\n\n");
// === Calculate Playback Parameters ===
sInt nwrite = FRAMES_PER_BUFFER / 2; // Samples per buffer
sInt buffer_count = (NUM_SECONDS * SAMPLE_RATE_OUTPUT) / FRAMES_PER_BUFFER;
// === Allocate Audio Buffers ===
sF32 *mixbuffer = (sF32 *)malloc(nwrite * 2 * sizeof(sF32));
if (!mixbuffer)
{
fprintf(stderr, "Error: Failed to allocate audio buffer\n");
Pa_CloseStream(stream);
Pa_Terminate();
free(mod_data);
return 1;
}
// === Main Playback Loop ===
printf("Playing...\n");
for (int i = 0; i < buffer_count; i++)
{
// Render MOD file audio
player.RenderProxy(&player, mixbuffer, nwrite);
// Write audio to stream
err = Pa_WriteStream(stream, mixbuffer, nwrite);
if (err != paNoError)
{
fprintf(stderr, "Warning: Write error - %s\n", Pa_GetErrorText(err));
}
// Print progress
if ((i + 1) % 10 == 0)
{
printf(".");
fflush(stdout);
}
}
printf("\n\n");
// === Shutdown Audio ===
err = Pa_StopStream(stream);
if (err != paNoError)
handle_pa_error(err);
// Allow stream to finish draining
Pa_Sleep(1000);
err = Pa_CloseStream(stream);
if (err != paNoError)
handle_pa_error(err);
Pa_Terminate();
// === Cleanup ===
free(mixbuffer);
free(mod_data);
printf("Playback complete. Goodbye!\n");
return 0;
}