aboutsummaryrefslogtreecommitdiff
path: root/utils/midiplay
diff options
context:
space:
mode:
Diffstat (limited to 'utils/midiplay')
-rw-r--r--utils/midiplay/Makefile12
-rw-r--r--utils/midiplay/Makefile.win3212
-rw-r--r--utils/midiplay/adlmidiplay.cpp338
-rwxr-xr-xutils/midiplay/wave_writer.c170
-rwxr-xr-xutils/midiplay/wave_writer.h21
5 files changed, 553 insertions, 0 deletions
diff --git a/utils/midiplay/Makefile b/utils/midiplay/Makefile
new file mode 100644
index 0000000..f2512b4
--- /dev/null
+++ b/utils/midiplay/Makefile
@@ -0,0 +1,12 @@
+all: midiplay
+
+midiplay: adlmidiplay.o wave_writer.o
+ g++ $^ -Wl,-rpath='$$ORIGIN' -L../../bin -ladlmidi -lSDL2 -o ../../bin/adlmidiplay
+ rm *.o
+
+adlmidiplay.o: adlmidiplay.cpp
+ g++ -c $^ -I.. -o adlmidiplay.o
+
+wave_writer.o: wave_writer.c
+ gcc -c $^ -I.. -o wave_writer.o
+
diff --git a/utils/midiplay/Makefile.win32 b/utils/midiplay/Makefile.win32
new file mode 100644
index 0000000..05e192f
--- /dev/null
+++ b/utils/midiplay/Makefile.win32
@@ -0,0 +1,12 @@
+all: midiplay
+
+midiplay: adlmidiplay.o wave_writer.o
+ g++ $^ -L../../bin -ladlmidi -lSDL2 -o ../../bin/adlmidiplay
+ rm *.o
+
+adlmidiplay.o: adlmidiplay.cpp
+ g++ -c $^ -I.. -o adlmidiplay.o
+
+wave_writer.o: wave_writer.c
+ gcc -c $^ -I.. -o wave_writer.o
+
diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp
new file mode 100644
index 0000000..7c8766d
--- /dev/null
+++ b/utils/midiplay/adlmidiplay.cpp
@@ -0,0 +1,338 @@
+
+#include <vector>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <deque>
+#include <signal.h>
+#define SDL_MAIN_HANDLED
+#include <SDL2/SDL.h>
+
+#include <adlmidi.h>
+
+#include "wave_writer.h"
+
+class MutexType
+{
+ SDL_mutex *mut;
+public:
+ MutexType() : mut(SDL_CreateMutex()) { }
+ ~MutexType()
+ {
+ SDL_DestroyMutex(mut);
+ }
+ void Lock()
+ {
+ SDL_mutexP(mut);
+ }
+ void Unlock()
+ {
+ SDL_mutexV(mut);
+ }
+};
+
+
+static std::deque<short> AudioBuffer;
+static MutexType AudioBuffer_lock;
+
+static void SDL_AudioCallbackX(void *, Uint8 *stream, int len)
+{
+ SDL_LockAudio();
+ short *target = (short *) stream;
+ AudioBuffer_lock.Lock();
+ /*if(len != AudioBuffer.size())
+ fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples",
+ len/4, (unsigned) AudioBuffer.size()/2);*/
+ unsigned ate = (unsigned)len / 2; // number of shorts
+ if(ate > AudioBuffer.size())
+ ate = (unsigned)AudioBuffer.size();
+ for(unsigned a = 0; a < ate; ++a)
+ {
+ target[a] = AudioBuffer[a];
+ }
+ AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin() + ate);
+ AudioBuffer_lock.Unlock();
+ SDL_UnlockAudio();
+}
+
+static bool is_number(const std::string &s)
+{
+ std::string::const_iterator it = s.begin();
+ while(it != s.end() && std::isdigit(*it)) ++it;
+ return !s.empty() && it == s.end();
+}
+
+static void printError(const char *err)
+{
+ std::fprintf(stderr, "\nERROR: %s\n\n", err);
+ std::fflush(stderr);
+}
+
+static int stop = 0;
+static void sighandler(int dum)
+{
+ if((dum == SIGINT)
+ || (dum == SIGTERM)
+ #ifndef _WIN32
+ || (dum == SIGHUP)
+ #endif
+ )
+ stop = 1;
+}
+
+int main(int argc, char **argv)
+{
+ if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h")
+ {
+ std::printf(
+ "Usage: adlmidi <midifilename> [ <options> ] [ <bank> [ <numcards> [ <numfourops>] ] ]\n"
+ " -p Enables adlib percussion instrument mode\n"
+ " -t Enables tremolo amplification mode\n"
+ " -v Enables vibrato amplification mode\n"
+ " -s Enables scaling of modulator volumes\n"
+ " -nl Quit without looping\n"
+ " -w Write WAV file rather than playing\n"
+ "\n"
+ "Where <bank> - number of embeeded bank or filepath to custom WOPL bank file\n"
+ "\n"
+ "Note: To create WOPL bank files use OPL Bank Editor you can get here: \n"
+ "https://github.com/Wohlstand/OPL3BankEditor\n"
+ "\n"
+ );
+
+ int banksCount = adl_getBanksCount();
+ const char *const *banknames = adl_getBankNames();
+
+ if(banksCount > 0)
+ {
+ std::printf(" Available embedded banks by number:\n\n");
+
+ for(int a = 0; a < banksCount; ++a)
+ std::printf("%10s%2u = %s\n", a ? "" : "Banks:", a, banknames[a]);
+
+ std::printf(
+ "\n"
+ " Use banks 2-5 to play Descent \"q\" soundtracks.\n"
+ " Look up the relevant bank number from descent.sng.\n"
+ "\n"
+ " The fourth parameter can be used to specify the number\n"
+ " of four-op channels to use. Each four-op channel eats\n"
+ " the room of two regular channels. Use as many as required.\n"
+ " The Doom & Hexen sets require one or two, while\n"
+ " Miles four-op set requires the maximum of numcards*6.\n"
+ "\n"
+ );
+ }
+ else
+ {
+ std::printf(" This build of libADLMIDI has no embedded banks!\n\n");
+ }
+
+ return 0;
+ }
+
+ //const unsigned MaxSamplesAtTime = 512; // 512=dbopl limitation
+ // How long is SDL buffer, in seconds?
+ // The smaller the value, the more often SDL_AudioCallBack()
+ // is called.
+ const double AudioBufferLength = 0.08;
+ // How much do WE buffer, in seconds? The smaller the value,
+ // the more prone to sound chopping we are.
+ const double OurHeadRoomLength = 0.1;
+ // The lag between visual content and audio content equals
+ // the sum of these two buffers.
+ SDL_AudioSpec spec;
+ SDL_AudioSpec obtained;
+
+ spec.freq = 44100;
+ spec.format = AUDIO_S16SYS;
+ spec.channels = 2;
+ spec.samples = Uint16((double)spec.freq * AudioBufferLength);
+ spec.callback = SDL_AudioCallbackX;
+
+ ADL_MIDIPlayer *myDevice;
+ myDevice = adl_init(44100);
+ if(myDevice == NULL)
+ {
+ printError("Failed to init MIDI device!\n");
+ return 1;
+ }
+
+ bool recordWave = false;
+
+ adl_setLoopEnabled(myDevice, 1);
+
+ while(argc > 2)
+ {
+ bool had_option = false;
+
+ if(!std::strcmp("-p", argv[2]))
+ adl_setPercMode(myDevice, 1);
+ else if(!std::strcmp("-v", argv[2]))
+ adl_setHVibrato(myDevice, 1);
+ else if(!std::strcmp("-w", argv[2]))
+ {
+ recordWave = true;
+ adl_setLoopEnabled(myDevice, 0);//Disable loop while record WAV
+ }
+ else if(!std::strcmp("-t", argv[2]))
+ adl_setHTremolo(myDevice, 1);
+ else if(!std::strcmp("-nl", argv[2]))
+ adl_setLoopEnabled(myDevice, 0);
+ else if(!std::strcmp("-s", argv[2]))
+ adl_setScaleModulators(myDevice, 1);
+ else break;
+
+ std::copy(argv + (had_option ? 4 : 3), argv + argc,
+ argv + 2);
+ argc -= (had_option ? 2 : 1);
+ }
+
+ if(!recordWave)
+ {
+ // Set up SDL
+ if(SDL_OpenAudio(&spec, &obtained) < 0)
+ {
+ std::fprintf(stderr, "\nERROR: Couldn't open audio: %s\n\n", SDL_GetError());
+ std::fflush(stderr);
+ //return 1;
+ }
+ if(spec.samples != obtained.samples)
+ {
+ std::fprintf(stderr, "Wanted (samples=%u,rate=%u,channels=%u); obtained (samples=%u,rate=%u,channels=%u)\n",
+ spec.samples, spec.freq, spec.channels,
+ obtained.samples, obtained.freq, obtained.channels);
+ std::fflush(stderr);
+ }
+ }
+
+ if(argc >= 3)
+ {
+ if(is_number(argv[2]))
+ {
+ int bankno = std::atoi(argv[2]);
+ if(adl_setBank(myDevice, bankno) != 0)
+ {
+ printError(adl_errorString());
+ return 1;
+ }
+ }
+ else
+ {
+ std::fprintf(stdout, "Loading custom bank file %s...", argv[2]);
+ std::fflush(stdout);
+ if(adl_openBankFile(myDevice, argv[2]) != 0)
+ {
+ std::fprintf(stdout, "FAILED!\n");
+ std::fflush(stdout);
+ printError(adl_errorString());
+ return 1;
+ }
+ std::fprintf(stdout, "OK!\n");
+ std::fflush(stdout);
+ }
+ }
+
+ if(argc >= 4)
+ {
+ if(adl_setNumCards(myDevice, std::atoi(argv[3])) != 0)
+ {
+ printError(adl_errorString());
+ return 1;
+ }
+ std::fprintf(stdout, "Number of cards %s\n", argv[3]);
+ }
+ else
+ {
+ // 4 chips by default
+ if(adl_setNumCards(myDevice, 4) != 0)
+ {
+ printError(adl_errorString());
+ return 1;
+ }
+ }
+
+ if(argc >= 5)
+ {
+ if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0)
+ {
+ printError(adl_errorString());
+ return 1;
+ }
+ std::fprintf(stdout, "Number of four-ops %s\n", argv[4]);
+ }
+
+ if(adl_openFile(myDevice, argv[1]) != 0)
+ {
+ printError(adl_errorString());
+ return 2;
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ #ifndef _WIN32
+ signal(SIGHUP, sighandler);
+ #endif
+
+ if(!recordWave)
+ {
+ SDL_PauseAudio(0);
+
+ while(!stop)
+ {
+ short buff[4096];
+ size_t got = (size_t)adl_play(myDevice, 4096, buff);
+ if(got <= 0)
+ break;
+
+ AudioBuffer_lock.Lock();
+ size_t pos = AudioBuffer.size();
+ AudioBuffer.resize(pos + got);
+ for(size_t p = 0; p < got; ++p)
+ AudioBuffer[pos + p] = buff[p];
+ AudioBuffer_lock.Unlock();
+
+ const SDL_AudioSpec &spec = obtained;
+ while(AudioBuffer.size() > spec.samples + (spec.freq * 2) * OurHeadRoomLength)
+ {
+ SDL_Delay(1);
+ }
+ }
+
+ SDL_CloseAudio();
+ }
+ else
+ {
+ std::string wave_out = std::string(argv[1]) + ".wav";
+ std::fprintf(stdout, "Recording WAV file %s...\n", wave_out.c_str());
+ std::fflush(stdout);
+
+ if(wave_open(spec.freq, wave_out.c_str()) == 0)
+ {
+ wave_enable_stereo();
+ while(!stop)
+ {
+ short buff[4096];
+ size_t got = (size_t)adl_play(myDevice, 4096, buff);
+ if(got <= 0)
+ break;
+ wave_write(buff, (long)got);
+ }
+ wave_close();
+
+ std::fprintf(stdout, "Completed!\n");
+ std::fflush(stdout);
+ }
+ else
+ {
+ adl_close(myDevice);
+ return 1;
+ }
+ }
+
+ adl_close(myDevice);
+
+ return 0;
+}
+
diff --git a/utils/midiplay/wave_writer.c b/utils/midiplay/wave_writer.c
new file mode 100755
index 0000000..0bfaf68
--- /dev/null
+++ b/utils/midiplay/wave_writer.c
@@ -0,0 +1,170 @@
+/* snes_spc 0.9.0. http://www.slack.net/~ant/ */
+
+#include "wave_writer.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+/* Copyright (C) 2003-2007 Shay Green. This module is free software; you
+can redistribute it and/or modify it under the terms of the GNU Lesser
+General Public License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version. This
+module is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+enum { buf_size = 32768 * 2 };
+enum { header_size = 0x2C };
+
+typedef short sample_t;
+
+static unsigned char* buf;
+static FILE* file;
+static long sample_count_;
+static long sample_rate_;
+static long buf_pos;
+static int chan_count;
+
+static void exit_with_error( const char* str )
+{
+ fprintf(stderr, "WAVE Writer Error: %s\n", str );
+ fflush(stderr);
+}
+
+int wave_open( long sample_rate, const char* filename )
+{
+ sample_count_ = 0;
+ sample_rate_ = sample_rate;
+ buf_pos = header_size;
+ chan_count = 1;
+
+ buf = (unsigned char*) malloc( buf_size * sizeof *buf );
+ if ( !buf )
+ {
+ exit_with_error( "Out of memory" );
+ return -1;
+ }
+
+#ifndef _WIN32
+ file = fopen( filename, "wb" );
+#else
+ wchar_t widePath[MAX_PATH];
+ int size = MultiByteToWideChar(CP_UTF8, 0, filename, strlen(filename), widePath, MAX_PATH);
+ widePath[size] = '\0';
+ file = _wfopen( widePath, L"wb" );
+#endif
+ if (!file)
+ {
+ exit_with_error( "Couldn't open WAVE file for writing" );
+ return -1;
+ }
+
+ setvbuf( file, 0, _IOFBF, 32 * 1024L );
+ return 0;
+}
+
+void wave_enable_stereo( void )
+{
+ chan_count = 2;
+}
+
+static void flush_()
+{
+ if ( buf_pos && !fwrite( buf, (size_t)buf_pos, 1, file ) )
+ exit_with_error( "Couldn't write WAVE data" );
+ buf_pos = 0;
+}
+
+void wave_write( short const* in, long remain )
+{
+ sample_count_ += remain;
+ while ( remain )
+ {
+ if ( buf_pos >= buf_size )
+ flush_();
+
+ {
+ unsigned char* p = &buf [buf_pos];
+ long n = (buf_size - (unsigned long)buf_pos) / sizeof (sample_t);
+ if ( n > remain )
+ n = remain;
+ remain -= n;
+
+ /* convert to LSB first format */
+ while ( n-- )
+ {
+ int s = *in++;
+ *p++ = (unsigned char) s & (0x00FF);
+ *p++ = (unsigned char) (s >> 8) & (0x00FF);
+ }
+
+ buf_pos = p - buf;
+ assert( buf_pos <= buf_size );
+ }
+ }
+}
+
+long wave_sample_count( void )
+{
+ return sample_count_;
+}
+
+static void set_le32( void* p, unsigned long n )
+{
+ ((unsigned char*) p) [0] = (unsigned char) n & (0xFF);
+ ((unsigned char*) p) [1] = (unsigned char) (n >> 8) & (0xFF);
+ ((unsigned char*) p) [2] = (unsigned char) (n >> 16) & (0xFF);
+ ((unsigned char*) p) [3] = (unsigned char) (n >> 24) & (0xFF);
+}
+
+void wave_close( void )
+{
+ if ( file )
+ {
+ /* generate header */
+ unsigned char h [header_size] =
+ {
+ 'R','I','F','F',
+ 0,0,0,0, /* length of rest of file */
+ 'W','A','V','E',
+ 'f','m','t',' ',
+ 0x10,0,0,0, /* size of fmt chunk */
+ 1,0, /* uncompressed format */
+ 0,0, /* channel count */
+ 0,0,0,0, /* sample rate */
+ 0,0,0,0, /* bytes per second */
+ 0,0, /* bytes per sample frame */
+ 16,0, /* bits per sample */
+ 'd','a','t','a',
+ 0,0,0,0, /* size of sample data */
+ /* ... */ /* sample data */
+ };
+ long ds = sample_count_ * (long)sizeof (sample_t);
+ int frame_size = chan_count * (long)sizeof (sample_t);
+
+ set_le32( h + 0x04, header_size - 8 + ds );
+ h [0x16] = (unsigned char)chan_count;
+ set_le32( h + 0x18, (unsigned long)sample_rate_ );
+ set_le32( h + 0x1C, (unsigned long)sample_rate_ * (unsigned long)frame_size );
+ h [0x20] = (unsigned char)frame_size;
+ set_le32( h + 0x28, (unsigned long)ds );
+
+ flush_();
+
+ /* write header */
+ fseek( file, 0, SEEK_SET );
+ fwrite( h, header_size, 1, file );
+ fclose( file );
+ file = 0;
+ free( buf );
+ buf = 0;
+ }
+}
diff --git a/utils/midiplay/wave_writer.h b/utils/midiplay/wave_writer.h
new file mode 100755
index 0000000..6d49718
--- /dev/null
+++ b/utils/midiplay/wave_writer.h
@@ -0,0 +1,21 @@
+/* WAVE sound file writer for recording 16-bit output during program development */
+
+#pragma once
+#ifndef WAVE_WRITER_H
+#define WAVE_WRITER_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+int wave_open( long sample_rate, const char* filename );
+void wave_enable_stereo( void );
+void wave_write( short const* in, long count );
+long wave_sample_count( void );
+void wave_close( void );
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif