/* TinyMOD Written by Tammo "kb" Hinrichs in 2007 This source code is hereby placed into the public domain. Use, distribute, modify, misappropriate and generally abuse it as you wish. Giving credits would be nice of course. This player includes an Amiga Paula chip "emulation" that faithfully recreates how it sounds when a sample is resampled using a master clock of 3.5 MHz. Yes, rendering at this rate and downsampling to the usual 48KHz takes quite a bit of CPU. Feel free to replace this part with some conventional mixing routines if authenticity isn't your goal and you really need Protracker MOD support for any other reason... The code should be pretty portable, all OS/platform dependent stuff is at the top. Code for testing is at the bottom. You'll need some kind of sound output that calls back the player providing it a stereo interleaved single float buffer to write into (0dB=1.0). Changelog: 2024-03-09: (Jason Bou-Samra) * changes to main() routine to make executable from Linux command line interface * modularised paula and modplayer classes into seperate files * added sound output using port audio * sprinkled comments throughout source code, and general source formatting * created makefile 2007-12-07: (Tammo "kb" Hinrichs) * fixed 40x and 4x0 vibrato effects (jogeir - tiny tunes) * fixed pattern loop (olof gustafsson - pinball illusions) * fixed fine volslide down (olof gustafsson - pinball illusions) * included some external header files * cleanups 2007-12-06: (Tammo "kb" Hinrichs) * first "release". Note to self: Don't post stuff on pouet.net when drunk. */ // TO COMPILE: g++ -o tinymod `pkg-config --libs alsa` tinymod.cpp -lm -L . -l:libportaudio.a // =================== system dependent stuff starts here ===================== // Multicharacter literal #pragma GCC diagnostic ignored "-Wmultichar" #pragma intrinsic(memset, sqrt, sin, cos, atan, powf) // memory set, square root, SINe, COSine, Arc TAngent, power of // ### defines #define __stdcall #define __cdecl #define NUM_SECONDS (1000) #define SAMPLE_RATE (96000) #define FRAMES_PER_BUFFER (0x10000) #define cls() printf("\033[H\033[J") // control chars to clear screen // ### includes #include // fixed size integer types (part of c standard library) #include // basic mathematical operations (part of c standard library) - mostly floating point #include // standard I/O library #include // string handling (part of c standard library) #include // (unix standard) #include // (file control options) - used by open() #include // needed by stat function (get file status) #include // defines set of integeral types #include // defines set of C standard I/O types #include "portaudio.h" // port audio #include "types.h" // various type definitions #include "paula.h" // Amiga "Paula" sound hardware emulator #include "modplayer.h" // MOD play routine // ## declerations void error(PaError err1); // declare port audio error handling function // *********************************************** // ** handle Command Line Interface (CLI) stuff ** // *********************************************** int __cdecl main (int argc, const char **argv) // main start { // freopen("warning.log", "w", stderr); // surpress console messages const char* filename = argv[1]; // only one argument and command if (argc != 2){ // if less than 2 arguments passed on command line, display usage printf("Usage: tinymod [|OPTION]\n\ tinymod --help for help\n"); return 1; } // display usage, then exit if(!strcmp(filename, "--about")) { printf("TinyMOD\n\n An Amiga MOD file player that tries to replicate the authentic sound\n\ charateristics of an Amiga via software emulation of the Paula chip.\n\ "); return 0; } // display about, then exit if(!strcmp (filename, "--help")) { printf("Usage: tinymod [|OPTION]\n\n\ OPTIONS\n\ --about displays about message\n\ --help displays this help message\n"); return 0; } // display help, then exit // ********************** // ** load MOD file ** // ********************** static sU8 mod[4 * 1024 * 1024]; // 4MB unsigned character array to hold MOD file FILE* fh = fopen(filename, "rb"); if (!fh) { perror("fopen"); exit(EXIT_FAILURE); } struct stat sb; if (stat(filename, &sb) == -1) { perror("stat"); exit(EXIT_FAILURE); } fread(mod, sb.st_size, 1, fh); fclose(fh); // ********************** // ** port audio setup ** // ********************** PaStreamParameters outputParameters; PaStream *stream; PaError err; float buffer[FRAMES_PER_BUFFER][2]; // stereo output buffer int left_phase = 0; int right_phase = 0; int left_inc = 1; int right_inc = 1; // higher pitch so we can distinguish left and right. int i, j, k; // for indexing int bufferCount; static const int BUFFERLEN = 0x10000; // buffer length sInt nwrite = 0x10000; // number of samples sF32 mixbuffer[BUFFERLEN]; // sample buffer err = Pa_Initialize(); // initialize if( err != paNoError ) error(err); outputParameters.device = Pa_GetDefaultOutputDevice(); // default output device if (outputParameters.device == paNoDevice) { fprintf(stderr,"Error: No default output device.\n"); error(err); } outputParameters.channelCount = 2; // stereo output outputParameters.sampleFormat = paFloat32; // 32 bit floating point output outputParameters.suggestedLatency = 0.050; // Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = NULL; err = Pa_OpenStream( &stream, NULL, // no input &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, // we won't output out of range samples so don't bother clipping them NULL, // no callback, use blocking API NULL ); // no callback, so no callback userData if( err != paNoError ) error(err); err = Pa_StartStream( stream ); if( err != paNoError ) error(err); // ********************* // ** play setup/loop ** // ********************* Paula P; // instantiate paula ModPlayer player (&P, mod); // instantiate paramaterised constructior ModPlayer cls(); // clear screen printf("TinyMOD\n"); printf ("currently playing: %s\n", player.Name); // display details of MOD being played printf("Playing for %d seconds.\n", NUM_SECONDS ); printf("^C to stop\n"); // printf("PortAudio: SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); bufferCount = ((NUM_SECONDS * SAMPLE_RATE) / FRAMES_PER_BUFFER); // determine buffer loads for( i=0; i < bufferCount; i++ ) // countdown buffers { player.RenderProxy(&player, mixbuffer, nwrite/2); // MOD player for( j=0; j < FRAMES_PER_BUFFER; j++ ) // copy samples to buffer { buffer[j][0] = mixbuffer[left_phase]; // left buffer[j][1] = mixbuffer[right_phase]; // right left_phase += left_inc; right_phase += right_inc; if( left_phase >= FRAMES_PER_BUFFER ) left_phase -= FRAMES_PER_BUFFER; if( right_phase >= FRAMES_PER_BUFFER ) right_phase -= FRAMES_PER_BUFFER; } // left_phase = right_phase = 0; err = Pa_WriteStream( stream, buffer, FRAMES_PER_BUFFER ); // transfer buffer to stream if( err != paNoError ) error(err); } // keep going // ************************* // ** port audio shutdown ** // ************************* err = Pa_StopStream( stream ); if( err != paNoError ) error(err); // ++left_inc; // ++right_inc; Pa_Sleep( 1000 ); // mainly for test purposes so we can hear sound err = Pa_CloseStream( stream ); if( err != paNoError ) error(err); Pa_Terminate(); printf("Bye bye!.\n"); return err; // that's all folks, all done } // ********************** // ** hidden message ** // ********************** char message[] = { "toto, all the way\n" }; const char* text = "09/03/2024 jason bou-samra, 07/12/2007 Tammo \"kb\" Hinrichs\n"; // ******************************* // ** error handling routine ** // ******************************* void error(PaError err1) { fprintf( stderr, "An error occured while using the portaudio stream\n" ); fprintf( stderr, "Error number: %d\n", err1 ); fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err1 ) ); // Print more information about the error. if( err1 == paUnanticipatedHostError ) { const PaHostErrorInfo *hostErrorInfo = Pa_GetLastHostErrorInfo(); fprintf( stderr, "Host API error = #%ld, hostApiType = %d\n", hostErrorInfo->errorCode, hostErrorInfo->hostApiType ); fprintf( stderr, "Host API error = %s\n", hostErrorInfo->errorText ); } Pa_Terminate(); exit(1); }