add SDL_mixer

This commit is contained in:
Sven Balzer
2025-04-03 03:50:59 +02:00
parent 90d167857e
commit 14728d17f5
739 changed files with 194492 additions and 1 deletions
+99
View File
@@ -0,0 +1,99 @@
SDL3_mixer_0.0.0 {
global:
Mix_AllocateChannels;
Mix_ChannelFinished;
Mix_CloseAudio;
Mix_EachSoundFont;
Mix_ExpireChannel;
Mix_FadeInChannel;
Mix_FadeInChannelTimed;
Mix_FadeInMusic;
Mix_FadeInMusicPos;
Mix_FadeOutChannel;
Mix_FadeOutGroup;
Mix_FadeOutMusic;
Mix_FadingChannel;
Mix_FadingMusic;
Mix_FreeChunk;
Mix_FreeMusic;
Mix_GetChunk;
Mix_GetChunkDecoder;
Mix_GetMusicAlbumTag;
Mix_GetMusicArtistTag;
Mix_GetMusicCopyrightTag;
Mix_GetMusicDecoder;
Mix_GetMusicHookData;
Mix_GetMusicLoopEndTime;
Mix_GetMusicLoopLengthTime;
Mix_GetMusicLoopStartTime;
Mix_GetMusicPosition;
Mix_GetMusicTitle;
Mix_GetMusicTitleTag;
Mix_GetMusicType;
Mix_GetMusicVolume;
Mix_GetNumChunkDecoders;
Mix_GetNumMusicDecoders;
Mix_GetNumTracks;
Mix_GetSoundFonts;
Mix_GetTimidityCfg;
Mix_GroupAvailable;
Mix_GroupChannel;
Mix_GroupChannels;
Mix_GroupCount;
Mix_GroupNewer;
Mix_GroupOldest;
Mix_HaltChannel;
Mix_HaltGroup;
Mix_HaltMusic;
Mix_HasChunkDecoder;
Mix_HasMusicDecoder;
Mix_HookMusic;
Mix_HookMusicFinished;
Mix_Init;
Mix_LoadMUS;
Mix_LoadMUSType_IO;
Mix_LoadMUS_IO;
Mix_LoadWAV;
Mix_LoadWAV_IO;
Mix_MasterVolume;
Mix_ModMusicJumpToOrder;
Mix_MusicDuration;
Mix_OpenAudio;
Mix_Pause;
Mix_PauseGroup;
Mix_PauseAudio;
Mix_PauseMusic;
Mix_Paused;
Mix_PausedMusic;
Mix_PlayChannel;
Mix_PlayChannelTimed;
Mix_PlayMusic;
Mix_Playing;
Mix_PlayingMusic;
Mix_QuerySpec;
Mix_QuickLoad_RAW;
Mix_QuickLoad_WAV;
Mix_Quit;
Mix_RegisterEffect;
Mix_ReserveChannels;
Mix_Resume;
Mix_ResumeGroup;
Mix_ResumeMusic;
Mix_RewindMusic;
Mix_SetDistance;
Mix_SetMusicPosition;
Mix_SetPanning;
Mix_SetPosition;
Mix_SetPostMix;
Mix_SetReverseStereo;
Mix_SetSoundFonts;
Mix_SetTimidityCfg;
Mix_StartTrack;
Mix_UnregisterAllEffects;
Mix_UnregisterEffect;
Mix_Version;
Mix_Volume;
Mix_VolumeChunk;
Mix_VolumeMusic;
local: *;
};
+47
View File
@@ -0,0 +1,47 @@
This software is available as a choice of the following licenses. Choose
whichever you prefer.
===============================================================================
ALTERNATIVE 1 - Public Domain (www.unlicense.org)
===============================================================================
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
===============================================================================
ALTERNATIVE 2 - MIT No Attribution
===============================================================================
Copyright 2020 David Reid
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,23 @@
<h4 align="center">Public domain, single file audio decoding libraries for C and C++.</h4>
<p align="center">
<a href="https://discord.gg/9vpqbjU"><img src="https://img.shields.io/discord/712952679415939085?label=discord&logo=discord&style=flat-square" alt="discord"></a>
</p>
All development of released libraries happens on the master branch. There may exist some decoder-specific branches for work in progress. Check tags for the latest version of a particular library.
Library | Description
----------------------------------------------- | -----------
[dr_flac](dr_flac.h) | FLAC audio decoder.
[dr_mp3](dr_mp3.h) | MP3 audio decoder. Based off [minimp3](https://github.com/lieff/minimp3).
[dr_wav](dr_wav.h) | WAV audio loader and writer.
# Other Libraries
Below are some of my other libraries you may be interested in.
Library | Description
------------------------------------------------- | -----------
[miniaudio](https://github.com/mackron/miniaudio) | A public domain, single file library for audio playback and recording.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+283
View File
@@ -0,0 +1,283 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode an AIFF file into a waveform.
It's pretty straightforward once you get going. The only
externally-callable function is Mix_LoadAIFF_IO(), which is meant to
act as identically to SDL_LoadWAV_IO() as possible.
This file by Torbjörn Andersson (torbjorn.andersson@eurotime.se)
8SVX file support added by Marc Le Douarain (mavati@club-internet.fr)
in december 2002.
*/
#include <SDL3/SDL_endian.h>
#include <SDL3_mixer/SDL_mixer.h>
#include "load_aiff.h"
/*********************************************/
/* Define values for AIFF (IFF audio) format */
/*********************************************/
#define FORM 0x4d524f46 /* "FORM" */
#define AIFF 0x46464941 /* "AIFF" */
#define SSND 0x444e5353 /* "SSND" */
#define COMM 0x4d4d4f43 /* "COMM" */
#define _8SVX 0x58565338 /* "8SVX" */
#define VHDR 0x52444856 /* "VHDR" */
#define BODY 0x59444F42 /* "BODY" */
/* This function was taken from libsndfile. I don't pretend to fully
* understand it.
*/
static Uint32 SANE_to_Uint32 (Uint8 *sanebuf)
{
/* Is the frequency outside of what we can represent with Uint32? */
if ((sanebuf[0] & 0x80) || (sanebuf[0] <= 0x3F) || (sanebuf[0] > 0x40)
|| (sanebuf[0] == 0x40 && sanebuf[1] > 0x1C))
return 0;
return ((sanebuf[2] << 23) | (sanebuf[3] << 15) | (sanebuf[4] << 7)
| (sanebuf[5] >> 1)) >> (29 - sanebuf[1]);
}
/* This function is based on SDL_LoadWAV_IO(). */
SDL_AudioSpec *Mix_LoadAIFF_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
{
int found_SSND;
int found_COMM;
int found_VHDR;
int found_BODY;
bool was_error = true;
Sint64 start = 0;
Uint32 chunk_type;
Uint32 chunk_length;
Sint64 next_chunk;
/* AIFF magic header */
Uint32 FORMchunk;
Uint32 AIFFmagic;
/* SSND chunk */
Uint32 offset;
Uint32 blocksize;
/* COMM format chunk */
Uint16 channels = 0;
Uint32 numsamples = 0;
Uint16 samplesize = 0;
Uint8 sane_freq[10];
Uint32 frequency = 0;
/* VHDR chunk */
Uint16 frequency16 = 0;
/* Sanity checks */
if (audio_buf) {
*audio_buf = NULL;
}
if (!src) {
SDL_InvalidParamError("src");
goto done;
}
if (!spec) {
SDL_InvalidParamError("spec");
goto done;
}
if (!audio_buf) {
SDL_InvalidParamError("audio_buf");
goto done;
}
if (!audio_len) {
SDL_InvalidParamError("audio_len");
goto done;
}
if (!SDL_ReadU32LE(src, &FORMchunk) ||
!SDL_ReadU32BE(src, &chunk_length)) {
goto done;
}
if (chunk_length == AIFF) { /* The FORMchunk has already been read */
AIFFmagic = chunk_length;
chunk_length = FORMchunk;
FORMchunk = FORM;
} else {
if (!SDL_ReadU32LE(src, &AIFFmagic)) {
goto done;
}
}
if ((FORMchunk != FORM) || ((AIFFmagic != AIFF) && (AIFFmagic != _8SVX))) {
SDL_SetError("Unrecognized file type (not AIFF nor 8SVX)");
goto done;
}
/* TODO: Better santity-checking. */
found_SSND = 0;
found_COMM = 0;
found_VHDR = 0;
found_BODY = 0;
do {
if (!SDL_ReadU32LE(src, &chunk_type) ||
!SDL_ReadU32BE(src, &chunk_length)) {
goto done;
}
next_chunk = SDL_TellIO(src) + chunk_length;
/* Paranoia to avoid infinite loops */
if (chunk_length == 0) {
break;
}
switch (chunk_type) {
case SSND:
found_SSND = 1;
if (!SDL_ReadU32BE(src, &offset) ||
!SDL_ReadU32BE(src, &blocksize)) {
goto done;
}
start = SDL_TellIO(src) + offset;
(void)blocksize; /* unused. */
break;
case COMM:
found_COMM = 1;
if (!SDL_ReadU16BE(src, &channels) ||
!SDL_ReadU32BE(src, &numsamples) ||
!SDL_ReadU16BE(src, &samplesize)) {
goto done;
}
if (SDL_ReadIO(src, sane_freq, sizeof(sane_freq)) != sizeof(sane_freq)) {
SDL_SetError("Bad AIFF sample frequency");
goto done;
}
frequency = SANE_to_Uint32(sane_freq);
if (frequency == 0) {
SDL_SetError("Bad AIFF sample frequency");
goto done;
}
break;
case VHDR:
found_VHDR = 1;
if (!SDL_ReadU32BE(src, NULL) ||
!SDL_ReadU32BE(src, NULL) ||
!SDL_ReadU32BE(src, NULL) ||
!SDL_ReadU16BE(src, &frequency16)) {
goto done;
}
channels = 1;
samplesize = 8;
frequency = frequency16;
break;
case BODY:
found_BODY = 1;
numsamples = chunk_length;
start = SDL_TellIO(src);
break;
default:
break;
}
/* a 0 pad byte can be stored for any odd-length chunk */
if (chunk_length&1) {
next_chunk++;
}
} while ((((AIFFmagic == AIFF) && (!found_SSND || !found_COMM))
|| ((AIFFmagic == _8SVX) && (!found_VHDR || !found_BODY)))
&& SDL_SeekIO(src, next_chunk, SDL_IO_SEEK_SET) != 1);
if ((AIFFmagic == AIFF) && !found_SSND) {
SDL_SetError("Bad AIFF (no SSND chunk)");
goto done;
}
if ((AIFFmagic == AIFF) && !found_COMM) {
SDL_SetError("Bad AIFF (no COMM chunk)");
goto done;
}
if ((AIFFmagic == _8SVX) && !found_VHDR) {
SDL_SetError("Bad 8SVX (no VHDR chunk)");
goto done;
}
if ((AIFFmagic == _8SVX) && !found_BODY) {
SDL_SetError("Bad 8SVX (no BODY chunk)");
goto done;
}
/* Decode the audio data format */
SDL_zerop(spec);
spec->freq = frequency;
switch (samplesize) {
case 8:
spec->format = SDL_AUDIO_S8;
break;
case 16:
spec->format = SDL_AUDIO_S16BE;
break;
default:
SDL_SetError("Unsupported AIFF samplesize");
goto done;
}
spec->channels = (Uint8) channels;
*audio_len = channels * numsamples * (samplesize / 8);
*audio_buf = (Uint8 *)SDL_malloc(*audio_len);
if (*audio_buf == NULL) {
goto done;
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
if (SDL_ReadIO(src, *audio_buf, *audio_len) != *audio_len) {
SDL_SetError("Unable to read audio data");
goto done;
}
/* Don't return a buffer that isn't a multiple of samplesize */
*audio_len &= ~((samplesize / 8) - 1);
was_error = false;
done:
if (closeio && src) {
SDL_CloseIO(src);
}
if (was_error) {
if (audio_buf && *audio_buf) {
SDL_free(*audio_buf);
*audio_buf = NULL;
}
if (audio_len) {
*audio_len = 0;
}
spec = NULL;
}
return spec;
}
/* vi: set ts=4 sw=4 expandtab: */
+33
View File
@@ -0,0 +1,33 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode an AIFF file into a waveform.
It's pretty straightforward once you get going. The only
externally-callable function is Mix_LoadAIFF_IO(), which is meant to
act as identically to SDL_LoadWAV_IO() as possible.
This file by Torbjörn Andersson (torbjorn.andersson@eurotime.se)
*/
/* Don't call this directly; use Mix_LoadWAV_IO() for now. */
SDL_AudioSpec *Mix_LoadAIFF_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len);
/* vi: set ts=4 sw=4 expandtab: */
+245
View File
@@ -0,0 +1,245 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode a file in any format supported by
libsndfile. The only externally-callable function is Mix_LoadSndFile_IO(),
which is meant to act as identically to SDL_LoadWAV_IO() as possible.
This file by Fabian Greffrath (fabian@greffrath.com).
*/
#include <SDL3/SDL_audio.h>
#include "load_sndfile.h"
#ifdef LOAD_SNDFILE
#include <SDL3/SDL_loadso.h>
#include <sndfile.h>
static SNDFILE* (*SF_sf_open_virtual) (SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data);
static int (*SF_sf_close) (SNDFILE *sndfile);
static sf_count_t (*SF_sf_readf_short) (SNDFILE *sndfile, short *ptr, sf_count_t frames);
static const char* (*SF_sf_strerror) (SNDFILE *sndfile);
static int SNDFILE_loaded;
static void *SNDFILE_lib;
static int SNDFILE_init (void)
{
if (SNDFILE_loaded == 0) {
#ifdef SNDFILE_DYNAMIC
SNDFILE_lib = SDL_LoadObject(SNDFILE_DYNAMIC);
if (SNDFILE_lib == NULL) {
return -1;
}
/* *INDENT-OFF* */ /* clang-format off */
SF_sf_open_virtual = (SNDFILE* (*)(SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data))SDL_LoadFunction(SNDFILE_lib, "sf_open_virtual");
SF_sf_close = (int (*)(SNDFILE *sndfile))SDL_LoadFunction(SNDFILE_lib, "sf_close");
SF_sf_readf_short = (sf_count_t(*)(SNDFILE *sndfile, short *ptr, sf_count_t frames))SDL_LoadFunction(SNDFILE_lib, "sf_readf_short");
SF_sf_strerror = (const char* (*)(SNDFILE *sndfile))SDL_LoadFunction(SNDFILE_lib, "sf_strerror");
/* *INDENT-ON* */ /* clang-format on */
if (SF_sf_open_virtual == NULL || SF_sf_close == NULL ||
SF_sf_readf_short == NULL || SF_sf_strerror == NULL) {
SDL_UnloadObject(SNDFILE_lib);
SNDFILE_lib = NULL;
return -1;
}
#else
SF_sf_open_virtual = sf_open_virtual;
SF_sf_close = sf_close;
SF_sf_readf_short = sf_readf_short;
SF_sf_strerror = sf_strerror;
#endif
SNDFILE_loaded = 1;
}
return 0;
}
void SNDFILE_uninit (void)
{
if (SNDFILE_lib != NULL) {
SDL_UnloadObject(SNDFILE_lib);
SNDFILE_lib = NULL;
SF_sf_open_virtual = NULL;
SF_sf_close = NULL;
SF_sf_readf_short = NULL;
SF_sf_strerror = NULL;
SNDFILE_loaded = 0;
}
}
static sf_count_t sfvio_size(void *user_data)
{
SDL_IOStream *io = user_data;
return SDL_GetIOSize(io);
}
static sf_count_t sfvio_seek(sf_count_t offset, int whence, void *user_data)
{
SDL_IOStream *io = user_data;
return SDL_SeekIO(io, offset, whence);
}
static sf_count_t sfvio_read(void *ptr, sf_count_t count, void *user_data)
{
SDL_IOStream *io = user_data;
return SDL_ReadIO(io, ptr, count);
}
static sf_count_t sfvio_tell(void *user_data)
{
SDL_IOStream *io = user_data;
return SDL_TellIO(io);
}
SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
{
bool was_error = true;
SNDFILE *sndfile = NULL;
SF_INFO sfinfo;
SF_VIRTUAL_IO sfvio = {
sfvio_size,
sfvio_seek,
sfvio_read,
NULL,
sfvio_tell
};
Uint32 len;
short *buf = NULL;
/* Sanity checks */
if (audio_buf) {
*audio_buf = NULL;
}
if (!src) {
SDL_InvalidParamError("src");
goto done;
}
if (!spec) {
SDL_InvalidParamError("spec");
goto done;
}
if (!audio_buf) {
SDL_InvalidParamError("audio_buf");
goto done;
}
if (!audio_len) {
SDL_InvalidParamError("audio_len");
goto done;
}
if (SNDFILE_loaded == 0) {
if (SNDFILE_init() != 0) {
goto done;
}
}
SDL_memset(&sfinfo, 0, sizeof(sfinfo));
sndfile = SF_sf_open_virtual(&sfvio, SFM_READ, &sfinfo, src);
if (sndfile == NULL) {
SDL_SetError("sf_open_virtual: %s", SF_sf_strerror(sndfile));
goto done;
}
if (sfinfo.frames <= 0) {
SDL_SetError("Invalid number of frames: %ld", (long)sfinfo.frames);
goto done;
}
if (sfinfo.channels <= 0) {
SDL_SetError("Invalid number of channels: %d", sfinfo.channels);
goto done;
}
len = sfinfo.frames * sfinfo.channels * sizeof(short);
buf = SDL_malloc(len);
if (buf == NULL) {
goto done;
}
if (SF_sf_readf_short(sndfile, buf, sfinfo.frames) < sfinfo.frames) {
SDL_free(buf);
SDL_SetError("sf_readf_short: %s", SF_sf_strerror(sndfile));
goto done;
}
SDL_zerop(spec);
spec->channels = sfinfo.channels;
spec->freq = sfinfo.samplerate;
spec->format = SDL_AUDIO_S16;
*audio_buf = (Uint8 *)buf;
*audio_len = len;
was_error = false;
done:
if (sndfile) {
SF_sf_close(sndfile);
}
if (closeio && src) {
SDL_CloseIO(src);
}
if (was_error) {
if (audio_buf && *audio_buf) {
SDL_free(*audio_buf);
*audio_buf = NULL;
}
if (audio_len) {
*audio_len = 0;
}
spec = NULL;
}
return spec;
}
#else
SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
{
(void) src;
(void) closeio;
(void) spec;
(void) audio_buf;
(void) audio_len;
return NULL;
}
void SNDFILE_uninit (void)
{
/* no-op */
}
#endif
/* vi: set ts=4 sw=4 expandtab: */
+41
View File
@@ -0,0 +1,41 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode a file in any format supported by
libsndfile. The only externally-callable function is Mix_LoadSndFile_IO(),
which is meant to act as identically to SDL_LoadWAV_IO() as possible.
This file by Fabian Greffrath (fabian@greffrath.com).
*/
#ifndef LOAD_SNDFILE_H
#define LOAD_SNDFILE_H
#include <SDL3_mixer/SDL_mixer.h>
/* Don't call this directly; use Mix_LoadWAV_IO() for now. */
SDL_AudioSpec *Mix_LoadSndFile_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len);
void SNDFILE_uninit (void);
#endif
/* vi: set ts=4 sw=4 expandtab: */
+486
View File
@@ -0,0 +1,486 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode a Creative Labs VOC file into a
waveform. It's pretty straightforward once you get going. The only
externally-callable function is Mix_LoadVOC_IO(), which is meant to
act as identically to SDL_LoadWAV_IO() as possible.
This file by Ryan C. Gordon (icculus@icculus.org).
Heavily borrowed from sox v12.17.1's voc.c.
(http://www.freshmeat.net/projects/sox/)
*/
#include <SDL3_mixer/SDL_mixer.h>
#include "load_voc.h"
/* Private data for VOC file */
typedef struct vocstuff {
Uint32 rest; /* bytes remaining in current block */
Uint32 rate; /* rate code (byte) of this chunk */
int silent; /* sound or silence? */
Uint32 srate; /* rate code (byte) of silence */
Uint32 blockseek; /* start of current output block */
Uint32 samples; /* number of samples output */
Uint32 size; /* word length of data */
Uint8 channels; /* number of sound channels */
int has_extended; /* Has an extended block been read? */
} vs_t;
/* Size field */
/* SJB: note that the 1st 3 are sometimes used as sizeof(type) */
#define ST_SIZE_BYTE 1
#define ST_SIZE_8BIT 1
#define ST_SIZE_WORD 2
#define ST_SIZE_16BIT 2
#define ST_SIZE_DWORD 4
#define ST_SIZE_32BIT 4
#define ST_SIZE_FLOAT 5
#define ST_SIZE_DOUBLE 6
#define ST_SIZE_IEEE 7 /* IEEE 80-bit floats. */
/* Style field */
#define ST_ENCODING_UNSIGNED 1 /* unsigned linear: Sound Blaster */
#define ST_ENCODING_SIGN2 2 /* signed linear 2's comp: Mac */
#define ST_ENCODING_ULAW 3 /* U-law signed logs: US telephony, SPARC */
#define ST_ENCODING_ALAW 4 /* A-law signed logs: non-US telephony */
#define ST_ENCODING_ADPCM 5 /* Compressed PCM */
#define ST_ENCODING_IMA_ADPCM 6 /* Compressed PCM */
#define ST_ENCODING_GSM 7 /* GSM 6.10 33-byte frame lossy compression */
#define VOC_TERM 0
#define VOC_DATA 1
#define VOC_CONT 2
#define VOC_SILENCE 3
#define VOC_MARKER 4
#define VOC_TEXT 5
#define VOC_LOOP 6
#define VOC_LOOPEND 7
#define VOC_EXTENDED 8
#define VOC_DATA_16 9
#define VOC_BAD_RATE ~((Uint32)0)
static int voc_check_header(SDL_IOStream *src)
{
/* VOC magic header */
Uint8 signature[20]; /* "Creative Voice File\032" */
Uint16 datablockofs;
SDL_SeekIO(src, 0, SDL_IO_SEEK_SET);
if (SDL_ReadIO(src, signature, sizeof(signature)) != sizeof(signature)) {
return 0;
}
if (SDL_memcmp(signature, "Creative Voice File\032", sizeof(signature)) != 0) {
SDL_SetError("Unrecognized file type (not VOC)");
return 0;
}
/* get the offset where the first datablock is located */
if (SDL_ReadIO(src, &datablockofs, sizeof(Uint16)) != sizeof(Uint16)) {
return 0;
}
datablockofs = SDL_Swap16LE(datablockofs);
if (SDL_SeekIO(src, datablockofs, SDL_IO_SEEK_SET) != datablockofs) {
return 0;
}
return 1; /* success! */
} /* voc_check_header */
/* Read next block header, save info, leave position at start of data */
static int voc_get_block(SDL_IOStream *src, vs_t *v, SDL_AudioSpec *spec)
{
Uint8 bits24[3];
Uint8 uc, block;
Uint32 sblen;
Uint16 new_rate_short;
Uint32 new_rate_long;
Uint8 trash[6];
Uint16 period;
unsigned int i;
v->silent = 0;
while (v->rest == 0) {
if (SDL_ReadIO(src, &block, sizeof(block)) != sizeof(block)) {
return 1; /* assume that's the end of the file. */
}
if (block == VOC_TERM) {
return 1;
}
if (SDL_ReadIO(src, bits24, sizeof(bits24)) != sizeof(bits24)) {
return 1; /* assume that's the end of the file. */
}
/* Size is an 24-bit value. Ugh. */
sblen = (Uint32)((bits24[0]) | (bits24[1] << 8) | (bits24[2] << 16));
switch(block) {
case VOC_DATA:
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
/* When DATA block preceeded by an EXTENDED */
/* block, the DATA blocks rate value is invalid */
if (!v->has_extended) {
if (uc == 0) {
SDL_SetError("VOC Sample rate is zero?");
return 0;
}
if ((v->rate != VOC_BAD_RATE) && (uc != v->rate)) {
SDL_SetError("VOC sample rate codes differ");
return 0;
}
v->rate = uc;
spec->freq = (Uint16)(1000000.0/(256 - v->rate));
v->channels = 1;
}
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
if (uc != 0) {
SDL_SetError("VOC decoder only interprets 8-bit data");
return 0;
}
v->has_extended = 0;
v->rest = sblen - 2;
v->size = ST_SIZE_BYTE;
return 1;
case VOC_DATA_16:
if (SDL_ReadIO(src, &new_rate_long, sizeof(new_rate_long)) != sizeof(new_rate_long)) {
return 0;
}
new_rate_long = SDL_Swap32LE(new_rate_long);
if (new_rate_long == 0) {
SDL_SetError("VOC Sample rate is zero?");
return 0;
}
if ((v->rate != VOC_BAD_RATE) && (new_rate_long != v->rate)) {
SDL_SetError("VOC sample rate codes differ");
return 0;
}
v->rate = new_rate_long;
spec->freq = (int)new_rate_long;
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
switch (uc) {
case 8: v->size = ST_SIZE_BYTE; break;
case 16: v->size = ST_SIZE_WORD; break;
default:
SDL_SetError("VOC with unknown data size");
return 0;
}
if (SDL_ReadIO(src, &v->channels, sizeof(Uint8)) != sizeof(Uint8)) {
return 0;
}
if (SDL_ReadIO(src, trash, 6) != 6) {
return 0;
}
v->rest = sblen - 12;
return 1;
case VOC_CONT:
v->rest = sblen;
return 1;
case VOC_SILENCE:
if (SDL_ReadIO(src, &period, sizeof(period)) != sizeof(period)) {
return 0;
}
period = SDL_Swap16LE(period);
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
if (uc == 0) {
SDL_SetError("VOC silence sample rate is zero");
return 0;
}
/*
* Some silence-packed files have gratuitously
* different sample rate codes in silence.
* Adjust period.
*/
if ((v->rate != VOC_BAD_RATE) && (uc != v->rate))
period = (Uint16)((period * (256 - uc))/(256 - v->rate));
else
v->rate = uc;
v->rest = period;
v->silent = 1;
return 1;
case VOC_LOOP:
case VOC_LOOPEND:
for (i = 0; i < sblen; i++) { /* skip repeat loops. */
if (SDL_ReadIO(src, trash, sizeof(Uint8)) != sizeof(Uint8)) {
return 0;
}
}
break;
case VOC_EXTENDED:
/* An Extended block is followed by a data block */
/* Set this byte so we know to use the rate */
/* value from the extended block and not the */
/* data block. */
v->has_extended = 1;
if (SDL_ReadIO(src, &new_rate_short, sizeof(new_rate_short)) != sizeof(new_rate_short)) {
return 0;
}
new_rate_short = SDL_Swap16LE(new_rate_short);
if (new_rate_short == 0) {
SDL_SetError("VOC sample rate is zero");
return 0;
}
if ((v->rate != VOC_BAD_RATE) && (new_rate_short != v->rate)) {
SDL_SetError("VOC sample rate codes differ");
return 0;
}
v->rate = new_rate_short;
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
if (uc != 0) {
SDL_SetError("VOC decoder only interprets 8-bit data");
return 0;
}
if (SDL_ReadIO(src, &uc, sizeof(uc)) != sizeof(uc)) {
return 0;
}
if (uc) /* Stereo */
spec->channels = 2;
/* VOC_EXTENDED may be read before spec->channels inited: */
else spec->channels = 1;
/* Needed number of channels before finishing
compute for rate */
spec->freq = (256000000L / (65536L - v->rate)) / spec->channels;
/* An extended block must be followed by a data */
/* block to be valid so loop back to top so it */
/* can be grabed. */
continue;
case VOC_MARKER:
if (SDL_ReadIO(src, trash, 2) != 2) {
return 0;
}
/* fallthrough */
default: /* text block or other krapola. */
for (i = 0; i < sblen; i++) {
if (SDL_ReadIO(src, trash, sizeof(Uint8)) != sizeof(Uint8)) {
return 0;
}
}
if (block == VOC_TEXT) {
continue; /* get next block */
}
}
}
return 1;
}
static Uint32 voc_read(SDL_IOStream *src, vs_t *v, Uint8 *buf, SDL_AudioSpec *spec)
{
Sint64 done = 0;
Uint8 silence = 0x80;
if (v->rest == 0) {
if (!voc_get_block(src, v, spec)) {
return 0;
}
}
if (v->rest == 0) {
return 0;
}
if (v->silent) {
if (v->size == ST_SIZE_WORD) {
silence = 0x00;
}
/* Fill in silence */
SDL_memset(buf, silence, v->rest);
done = v->rest;
v->rest = 0;
}
else {
done = SDL_ReadIO(src, buf, v->rest);
if (done <= 0) {
return 0;
}
v->rest = (Uint32)(v->rest - done);
if (v->size == ST_SIZE_WORD) {
#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
Uint16 *samples = (Uint16 *)buf;
for (; v->rest > 0; v->rest -= 2) {
*samples = SDL_Swap16LE(*samples);
samples++;
}
#endif
done >>= 1;
}
}
return (Uint32)done;
} /* voc_read */
/* don't call this directly; use Mix_LoadWAV_IO() for now. */
SDL_AudioSpec *Mix_LoadVOC_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len)
{
bool was_error = true;
vs_t v;
int samplesize;
Uint8 *fillptr;
void *ptr;
/* Sanity checks */
if (audio_buf) {
*audio_buf = NULL;
}
if (!src) {
SDL_InvalidParamError("src");
goto done;
}
if (!spec) {
SDL_InvalidParamError("spec");
goto done;
}
if (!audio_buf) {
SDL_InvalidParamError("audio_buf");
goto done;
}
if (!audio_len) {
SDL_InvalidParamError("audio_len");
goto done;
}
if (!voc_check_header(src)) {
goto done;
}
SDL_zero(v);
v.rate = VOC_BAD_RATE;
v.rest = 0;
v.has_extended = 0;
SDL_zerop(spec);
if (!voc_get_block(src, &v, spec)) {
goto done;
}
if (v.rate == VOC_BAD_RATE) {
SDL_SetError("VOC data had no sound!");
goto done;
}
if (v.size == 0) {
SDL_SetError("VOC data had invalid word size!");
goto done;
}
spec->format = ((v.size == ST_SIZE_WORD) ? SDL_AUDIO_S16 : SDL_AUDIO_U8);
if (spec->channels == 0) {
spec->channels = v.channels;
}
*audio_len = v.rest;
*audio_buf = (v.rest == 0) ? NULL : SDL_malloc(v.rest);
if (*audio_buf == NULL) {
goto done;
}
fillptr = *audio_buf;
while (voc_read(src, &v, fillptr, spec)) {
if (!voc_get_block(src, &v, spec)) {
goto done;
}
*audio_len += v.rest;
ptr = SDL_realloc(*audio_buf, *audio_len);
if (ptr == NULL) {
goto done;
}
*audio_buf = ptr;
fillptr = ((Uint8 *) ptr) + (*audio_len - v.rest);
}
/* Don't return a buffer that isn't a multiple of samplesize */
samplesize = ((spec->format & 0xFF)/8)*spec->channels;
*audio_len &= (Uint32) ~(samplesize-1);
was_error = false;
done:
if (closeio && src) {
SDL_CloseIO(src);
}
if (was_error) {
if (audio_buf && *audio_buf) {
SDL_free(*audio_buf);
*audio_buf = NULL;
}
if (audio_len) {
*audio_len = 0;
}
spec = NULL;
}
return spec;
} /* Mix_LoadVOC_IO */
/* end of load_voc.c ... */
/* vi: set ts=4 sw=4 expandtab: */
+36
View File
@@ -0,0 +1,36 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This is the source needed to decode a Creative Labs VOC file into a
waveform. It's pretty straightforward once you get going. The only
externally-callable function is Mix_LoadVOC_IO(), which is meant to
act as identically to SDL_LoadWAV_IO() as possible.
This file by Ryan C. Gordon (icculus@icculus.org).
Heavily borrowed from sox v12.17.1's voc.c.
(http://www.freshmeat.net/projects/sox/)
*/
/* Don't call this directly; use Mix_LoadWAV_IO() for now. */
SDL_AudioSpec *Mix_LoadVOC_IO (SDL_IOStream *src, bool closeio,
SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len);
/* vi: set ts=4 sw=4 expandtab: */
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file provides utility functions to work with MP3 files including reading of tags. */
#ifndef MIX_MP3UTILS_H
#define MIX_MP3UTILS_H
#include "music.h"
#define ENABLE_ALL_MP3_TAGS
#if defined(MUSIC_WAV) || defined(ENABLE_ALL_MP3_TAGS)
#define ENABLE_ID3V2_TAG
struct mp3file_t {
SDL_IOStream *src;
Sint64 start, length, pos;
};
#endif
#ifdef ENABLE_ALL_MP3_TAGS
extern int mp3_read_tags(Mix_MusicMetaTags *out_tags, struct mp3file_t *fil, bool keep_id3v2);
#endif /* ENABLE_ALL_MP3_TAGS */
#ifdef ENABLE_ID3V2_TAG
extern int read_id3v2_from_mem(Mix_MusicMetaTags *out_tags, Uint8 *data, size_t length);
#endif
#ifdef ENABLE_ALL_MP3_TAGS
extern int MP3_IOinit(struct mp3file_t *fil, SDL_IOStream *src);
extern size_t MP3_IOread(struct mp3file_t *fil, void *ptr, size_t size, size_t maxnum);
extern Sint64 MP3_IOseek(struct mp3file_t *fil, Sint64 offset, int whence);
extern Sint64 MP3_IOtell(struct mp3file_t *fil);
#endif /* ENABLE_ALL_MP3_TAGS */
#endif /* MIX_MP3UTILS_H */
/* vi: set ts=4 sw=4 expandtab: */
+421
View File
@@ -0,0 +1,421 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_FLAC_DRFLAC
#include "music_drflac.h"
#include "mp3utils.h"
#include "../utils.h"
#include <SDL3/SDL.h>
#define DR_FLAC_IMPLEMENTATION
#if defined(__GNUC__) && (__GNUC__ >= 4) && \
!(defined(_WIN32) || defined(__EMX__))
#define DRFLAC_API __attribute__((visibility("hidden")))
#elif defined(__APPLE__)
#define DRFLAC_API __private_extern__
#else
#define DRFLAC_API /* just in case.. */
#endif
#define DR_FLAC_NO_STDIO
#define DRFLAC_ASSERT(expression)
#define DRFLAC_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz))
#define DRFLAC_MOVE_MEMORY(dst, src, sz) SDL_memmove((dst), (src), (sz))
#define DRFLAC_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz))
#define DRFLAC_MALLOC(sz) SDL_malloc((sz))
#define DRFLAC_REALLOC(p, sz) SDL_realloc((p), (sz))
#define DRFLAC_FREE(p) SDL_free((p))
#include "dr_libs/dr_flac.h"
typedef struct {
struct mp3file_t file;
drflac *dec;
int play_count;
bool closeio;
int volume;
int status;
int sample_rate;
int channels;
SDL_AudioStream *stream;
drflac_int16 *buffer;
int buffer_size;
int loop;
bool loop_flag;
Sint64 loop_start;
Sint64 loop_end;
Sint64 loop_len;
Mix_MusicMetaTags tags;
} DRFLAC_Music;
static size_t DRFLAC_ReadCB(void *context, void *buf, size_t size)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
return MP3_IOread(&music->file, buf, 1, size);
}
static drflac_bool32 DRFLAC_SeekCB(void *context, int offset, drflac_seek_origin origin)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
int whence = (origin == drflac_seek_origin_start) ? SDL_IO_SEEK_SET : SDL_IO_SEEK_CUR;
if (MP3_IOseek(&music->file, offset, whence) < 0) {
return DRFLAC_FALSE;
}
return DRFLAC_TRUE;
}
static void DRFLAC_MetaCB(void *context, drflac_metadata *metadata)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
if (metadata->type == DRFLAC_METADATA_BLOCK_TYPE_STREAMINFO) {
music->sample_rate = metadata->data.streaminfo.sampleRate;
music->channels = metadata->data.streaminfo.channels;
} else if (metadata->type == DRFLAC_METADATA_BLOCK_TYPE_VORBIS_COMMENT) {
drflac_uint32 i;
char *param, *argument, *value;
bool is_loop_length = false;
const char *pRunningData = (const char *)metadata->data.vorbis_comment.pComments;
for (i = 0; i < metadata->data.vorbis_comment.commentCount; ++i) {
drflac_uint32 commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4;
param = (char *)SDL_malloc(commentLength + 1);
if (param) {
SDL_memcpy(param, pRunningData, commentLength);
param[commentLength] = '\0';
argument = param;
value = SDL_strchr(param, '=');
if (value == NULL) {
value = param + SDL_strlen(param);
} else {
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
music->loop_start = _Mix_ParseTime(value, music->sample_rate);
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
music->loop_len = SDL_strtoll(value, NULL, 10);
is_loop_length = true;
} else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
music->loop_end = _Mix_ParseTime(value, music->sample_rate);
is_loop_length = false;
} else if (SDL_strcasecmp(argument, "TITLE") == 0) {
meta_tags_set(&music->tags, MIX_META_TITLE, value);
} else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
meta_tags_set(&music->tags, MIX_META_ARTIST, value);
} else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
meta_tags_set(&music->tags, MIX_META_ALBUM, value);
} else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
}
SDL_free(param);
}
pRunningData += commentLength;
}
if (is_loop_length) {
music->loop_end = music->loop_start + music->loop_len;
} else {
music->loop_len = music->loop_end - music->loop_start;
}
/* Ignore invalid loop tag */
if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
music->loop_start = 0;
music->loop_len = 0;
music->loop_end = 0;
}
}
}
static int DRFLAC_Seek(void *context, double position);
static void *DRFLAC_CreateFromIO(SDL_IOStream *src, bool closeio)
{
DRFLAC_Music *music;
SDL_AudioSpec srcspec;
music = (DRFLAC_Music *)SDL_calloc(1, sizeof(DRFLAC_Music));
if (!music) {
return NULL;
}
music->volume = MIX_MAX_VOLUME;
if (MP3_IOinit(&music->file, src) < 0) {
SDL_free(music);
return NULL;
}
meta_tags_init(&music->tags);
music->dec = drflac_open_with_metadata(DRFLAC_ReadCB, DRFLAC_SeekCB, DRFLAC_MetaCB, music, NULL);
if (!music->dec) {
SDL_free(music);
SDL_SetError("music_drflac: corrupt flac file (bad stream).");
return NULL;
}
/* We should have channels and sample rate set up here */
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = music->channels;
srcspec.freq = music->sample_rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
drflac_close(music->dec);
SDL_free(music);
return NULL;
}
music->buffer_size = 4096/*music_spec.samples*/ * sizeof(drflac_int16) * music->channels;
music->buffer = (drflac_int16*)SDL_calloc(1, music->buffer_size);
if (!music->buffer) {
drflac_close(music->dec);
SDL_free(music);
return NULL;
}
/* loop_start, loop_end and loop_len get set by metadata callback if tags
* are present in metadata.
*/
if ((music->loop_end > 0) && (music->loop_end <= (Sint64)music->dec->totalPCMFrameCount) &&
(music->loop_start < music->loop_end)) {
music->loop = 1;
}
music->closeio = closeio;
return music;
}
static void DRFLAC_SetVolume(void *context, int volume)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
music->volume = volume;
}
static int DRFLAC_GetVolume(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
return music->volume;
}
/* Starts the playback. */
static int DRFLAC_Play(void *context, int play_count)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
music->play_count = play_count;
return DRFLAC_Seek(music, 0.0);
}
static void DRFLAC_Stop(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
SDL_ClearAudioStream(music->stream);
}
static int DRFLAC_GetSome(void *context, void *data, int bytes, bool *done)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
int filled;
drflac_uint64 amount;
if (music->stream) {
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
if (music->loop_flag) {
if (!drflac_seek_to_pcm_frame(music->dec, music->loop_start)) {
SDL_SetError("drflac_seek_to_pcm_frame() failed");
return -1;
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
music->play_count = play_count;
music->loop_flag = false;
}
}
amount = drflac_read_pcm_frames_s16(music->dec, 4096/*music_spec.samples*/, music->buffer);
if (amount > 0) {
if (music->loop && (music->play_count != 1) &&
((Sint64)music->dec->currentPCMFrame >= music->loop_end)) {
amount -= (music->dec->currentPCMFrame - music->loop_end);
music->loop_flag = true;
}
if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount * sizeof(drflac_int16) * music->channels)) {
return -1;
}
} else {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (DRFLAC_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int DRFLAC_GetAudio(void *context, void *data, int bytes)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, DRFLAC_GetSome);
}
static int DRFLAC_Seek(void *context, double position)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
drflac_uint64 destpos = (drflac_uint64)(position * music->sample_rate);
drflac_seek_to_pcm_frame(music->dec, destpos);
return 0;
}
static double DRFLAC_Tell(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
return (double)music->dec->currentPCMFrame / music->sample_rate;
}
static double DRFLAC_Duration(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
drflac_uint64 samples = music->dec->totalPCMFrameCount;
return (double)samples / music->sample_rate;
}
static double DRFLAC_LoopStart(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
if (music->loop > 0) {
return (double)music->loop_start / music->sample_rate;
}
return -1.0;
}
static double DRFLAC_LoopEnd(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
if (music->loop > 0) {
return (double)music->loop_end / music->sample_rate;
}
return -1.0;
}
static double DRFLAC_LoopLength(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
if (music->loop > 0) {
return (double)music->loop_len / music->sample_rate;
}
return -1.0;
}
static const char* DRFLAC_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
static void DRFLAC_Delete(void *context)
{
DRFLAC_Music *music = (DRFLAC_Music *)context;
drflac_close(music->dec);
meta_tags_clear(&music->tags);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->file.src);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_DRFLAC =
{
"DRFLAC",
MIX_MUSIC_DRFLAC,
MUS_FLAC,
false,
false,
NULL, /* Load */
NULL, /* Open */
DRFLAC_CreateFromIO,
NULL, /* CreateFromFile */
DRFLAC_SetVolume,
DRFLAC_GetVolume,
DRFLAC_Play,
NULL, /* IsPlaying */
DRFLAC_GetAudio,
NULL, /* Jump */
DRFLAC_Seek,
DRFLAC_Tell,
DRFLAC_Duration,
DRFLAC_LoopStart,
DRFLAC_LoopEnd,
DRFLAC_LoopLength,
DRFLAC_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
DRFLAC_Stop,
DRFLAC_Delete,
NULL, /* Close */
NULL /* Unload */
};
#endif /* MUSIC_FLAC_DRFLAC */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing FLAC files with dr_flac */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_DRFLAC;
/* vi: set ts=4 sw=4 expandtab: */
+314
View File
@@ -0,0 +1,314 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_MP3_DRMP3
#include "music_drmp3.h"
#include "mp3utils.h"
#include <SDL3/SDL.h>
#define DR_MP3_IMPLEMENTATION
#if defined(__GNUC__) && (__GNUC__ >= 4) && \
!(defined(_WIN32) || defined(__EMX__))
#define DRMP3_API __attribute__((visibility("hidden")))
#elif defined(__APPLE__)
#define DRMP3_API __private_extern__
#else
#define DRMP3_API /* just in case.. */
#endif
#define DR_MP3_NO_STDIO
#define DRMP3_ASSERT(expression)
#define DRMP3_COPY_MEMORY(dst, src, sz) SDL_memcpy((dst), (src), (sz))
#define DRMP3_MOVE_MEMORY(dst, src, sz) SDL_memmove((dst), (src), (sz))
#define DRMP3_ZERO_MEMORY(p, sz) SDL_memset((p), 0, (sz))
#define DRMP3_MALLOC(sz) SDL_malloc((sz))
#define DRMP3_REALLOC(p, sz) SDL_realloc((p), (sz))
#define DRMP3_FREE(p) SDL_free((p))
#include "dr_libs/dr_mp3.h"
typedef struct {
struct mp3file_t file;
drmp3 dec;
int play_count;
bool closeio;
int volume;
int status;
SDL_AudioStream *stream;
drmp3_int16 *buffer;
int buffer_size;
int channels;
Mix_MusicMetaTags tags;
} DRMP3_Music;
static size_t DRMP3_ReadCB(void *context, void *buf, size_t size)
{
DRMP3_Music *music = (DRMP3_Music *)context;
return MP3_IOread(&music->file, buf, 1, size);
}
static drmp3_bool32 DRMP3_SeekCB(void *context, int offset, drmp3_seek_origin origin)
{
DRMP3_Music *music = (DRMP3_Music *)context;
int whence;
switch (origin) {
case drmp3_seek_origin_start:
whence = SDL_IO_SEEK_SET;
break;
case drmp3_seek_origin_current:
whence = SDL_IO_SEEK_CUR;
break;
case drmp3_seek_origin_end:
whence = SDL_IO_SEEK_END;
break;
default:
return DRMP3_FALSE;
}
if (MP3_IOseek(&music->file, offset, whence) < 0) {
return DRMP3_FALSE;
}
return DRMP3_TRUE;
}
static drmp3_bool32 DRMP3_TellCB(void *context, drmp3_int64 *pos)
{
DRMP3_Music *music = (DRMP3_Music *)context;
*pos = MP3_IOtell(&music->file);
return (*pos < 0) ? DRMP3_FALSE : DRMP3_TRUE;
}
static int DRMP3_Seek(void *context, double position);
static void *DRMP3_CreateFromIO(SDL_IOStream *src, bool closeio)
{
DRMP3_Music *music;
SDL_AudioSpec file_spec;
music = (DRMP3_Music *)SDL_calloc(1, sizeof(DRMP3_Music));
if (!music) {
return NULL;
}
music->volume = MIX_MAX_VOLUME;
if (MP3_IOinit(&music->file, src) < 0) {
SDL_free(music);
return NULL;
}
meta_tags_init(&music->tags);
if (mp3_read_tags(&music->tags, &music->file, false) < 0) {
SDL_free(music);
SDL_SetError("music_drmp3: corrupt mp3 file (bad tags).");
return NULL;
}
MP3_IOseek(&music->file, 0, SDL_IO_SEEK_SET);
if (!drmp3_init(&music->dec, DRMP3_ReadCB, DRMP3_SeekCB, DRMP3_TellCB, NULL, music, NULL)) {
SDL_free(music);
SDL_SetError("music_drmp3: corrupt mp3 file (bad stream).");
return NULL;
}
SDL_zero(file_spec);
file_spec.format = SDL_AUDIO_S16;
file_spec.channels = (Uint8)music->dec.channels;
file_spec.freq = (int)music->dec.sampleRate;
music->stream = SDL_CreateAudioStream(&file_spec, &music_spec);
if (!music->stream) {
drmp3_uninit(&music->dec);
SDL_free(music);
return NULL;
}
music->channels = music->dec.channels;
music->buffer_size = 4096/*music_spec.samples*/ * sizeof(drmp3_int16) * music->channels;
music->buffer = (drmp3_int16*)SDL_calloc(1, music->buffer_size);
if (!music->buffer) {
drmp3_uninit(&music->dec);
SDL_free(music);
return NULL;
}
music->closeio = closeio;
return music;
}
static void DRMP3_SetVolume(void *context, int volume)
{
DRMP3_Music *music = (DRMP3_Music *)context;
music->volume = volume;
}
static int DRMP3_GetVolume(void *context)
{
DRMP3_Music *music = (DRMP3_Music *)context;
return music->volume;
}
/* Starts the playback. */
static int DRMP3_Play(void *context, int play_count)
{
DRMP3_Music *music = (DRMP3_Music *)context;
music->play_count = play_count;
return DRMP3_Seek(music, 0.0);
}
static void DRMP3_Stop(void *context)
{
DRMP3_Music *music = (DRMP3_Music *)context;
SDL_ClearAudioStream(music->stream);
}
static int DRMP3_GetSome(void *context, void *data, int bytes, bool *done)
{
DRMP3_Music *music = (DRMP3_Music *)context;
int filled;
drmp3_uint64 amount;
if (music->stream) {
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
amount = drmp3_read_pcm_frames_s16(&music->dec, 4096/*music_spec.samples*/, music->buffer);
if (amount > 0) {
if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount * sizeof(drmp3_int16) * music->channels)) {
return -1;
}
} else {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (DRMP3_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int DRMP3_GetAudio(void *context, void *data, int bytes)
{
DRMP3_Music *music = (DRMP3_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, DRMP3_GetSome);
}
static int DRMP3_Seek(void *context, double position)
{
DRMP3_Music *music = (DRMP3_Music *)context;
drmp3_uint64 destpos = (drmp3_uint64)(position * music->dec.sampleRate);
drmp3_seek_to_pcm_frame(&music->dec, destpos);
return 0;
}
static double DRMP3_Tell(void *context)
{
DRMP3_Music *music = (DRMP3_Music *)context;
return (double)music->dec.currentPCMFrame / music->dec.sampleRate;
}
static double DRMP3_Duration(void *context)
{
DRMP3_Music *music = (DRMP3_Music *)context;
drmp3_uint64 samples = drmp3_get_pcm_frame_count(&music->dec);
return (double)samples / music->dec.sampleRate;
}
static const char* DRMP3_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
DRMP3_Music *music = (DRMP3_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
static void DRMP3_Delete(void *context)
{
DRMP3_Music *music = (DRMP3_Music *)context;
drmp3_uninit(&music->dec);
meta_tags_clear(&music->tags);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->file.src);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_DRMP3 =
{
"DRMP3",
MIX_MUSIC_DRMP3,
MUS_MP3,
false,
false,
NULL, /* Load */
NULL, /* Open */
DRMP3_CreateFromIO,
NULL, /* CreateFromFile */
DRMP3_SetVolume,
DRMP3_GetVolume,
DRMP3_Play,
NULL, /* IsPlaying */
DRMP3_GetAudio,
NULL, /* Jump */
DRMP3_Seek,
DRMP3_Tell,
DRMP3_Duration,
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
DRMP3_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
DRMP3_Stop,
DRMP3_Delete,
NULL, /* Close */
NULL /* Unload */
};
#endif /* MUSIC_MP3_DRMP3 */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MP3 files with dr_mp3 */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_DRMP3;
/* vi: set ts=4 sw=4 expandtab: */
+819
View File
@@ -0,0 +1,819 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This file is used to support SDL_LoadMUS playback of FLAC files.
~ Austen Dicken (admin@cvpcs.org)
*/
#ifdef MUSIC_FLAC_LIBFLAC
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_assert.h>
#include "music_flac.h"
#include "utils.h"
#include <FLAC/stream_decoder.h>
typedef struct {
int loaded;
void *handle;
FLAC__StreamDecoder *(*FLAC__stream_decoder_new)(void);
void (*FLAC__stream_decoder_delete)(FLAC__StreamDecoder *decoder);
FLAC__StreamDecoderInitStatus (*FLAC__stream_decoder_init_stream)(
FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderReadCallback read_callback,
FLAC__StreamDecoderSeekCallback seek_callback,
FLAC__StreamDecoderTellCallback tell_callback,
FLAC__StreamDecoderLengthCallback length_callback,
FLAC__StreamDecoderEofCallback eof_callback,
FLAC__StreamDecoderWriteCallback write_callback,
FLAC__StreamDecoderMetadataCallback metadata_callback,
FLAC__StreamDecoderErrorCallback error_callback,
void *client_data);
FLAC__StreamDecoderInitStatus (*FLAC__stream_decoder_init_ogg_stream)(
FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderReadCallback read_callback,
FLAC__StreamDecoderSeekCallback seek_callback,
FLAC__StreamDecoderTellCallback tell_callback,
FLAC__StreamDecoderLengthCallback length_callback,
FLAC__StreamDecoderEofCallback eof_callback,
FLAC__StreamDecoderWriteCallback write_callback,
FLAC__StreamDecoderMetadataCallback metadata_callback,
FLAC__StreamDecoderErrorCallback error_callback,
void *client_data);
FLAC__bool (*FLAC__stream_decoder_finish)(FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_flush)(FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_process_single)(
FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_process_until_end_of_metadata)(
FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_process_until_end_of_stream)(
FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_seek_absolute)(
FLAC__StreamDecoder *decoder,
FLAC__uint64 sample);
FLAC__StreamDecoderState (*FLAC__stream_decoder_get_state)(
const FLAC__StreamDecoder *decoder);
FLAC__uint64 (*FLAC__stream_decoder_get_total_samples)(
const FLAC__StreamDecoder *decoder);
FLAC__bool (*FLAC__stream_decoder_set_metadata_respond)(
FLAC__StreamDecoder *decoder,
FLAC__MetadataType type);
} flac_loader;
static flac_loader flac;
#ifdef FLAC_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
flac.FUNC = (SIG) SDL_LoadFunction(flac.handle, #FUNC); \
if (flac.FUNC == NULL) { SDL_UnloadObject(flac.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
flac.FUNC = FUNC; \
if (flac.FUNC == NULL) { SDL_SetError("Missing FLAC.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int FLAC_Load(void)
{
if (flac.loaded == 0) {
#ifdef FLAC_DYNAMIC
flac.handle = SDL_LoadObject(FLAC_DYNAMIC);
if (flac.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(FLAC__stream_decoder_new, FLAC__StreamDecoder *(*)(void))
FUNCTION_LOADER(FLAC__stream_decoder_delete, void (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_init_stream, FLAC__StreamDecoderInitStatus (*)(
FLAC__StreamDecoder *,
FLAC__StreamDecoderReadCallback,
FLAC__StreamDecoderSeekCallback,
FLAC__StreamDecoderTellCallback,
FLAC__StreamDecoderLengthCallback,
FLAC__StreamDecoderEofCallback,
FLAC__StreamDecoderWriteCallback,
FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback,
void *))
FUNCTION_LOADER(FLAC__stream_decoder_init_ogg_stream, FLAC__StreamDecoderInitStatus (*)(
FLAC__StreamDecoder *,
FLAC__StreamDecoderReadCallback,
FLAC__StreamDecoderSeekCallback,
FLAC__StreamDecoderTellCallback,
FLAC__StreamDecoderLengthCallback,
FLAC__StreamDecoderEofCallback,
FLAC__StreamDecoderWriteCallback,
FLAC__StreamDecoderMetadataCallback,
FLAC__StreamDecoderErrorCallback,
void *))
FUNCTION_LOADER(FLAC__stream_decoder_finish, FLAC__bool (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_flush, FLAC__bool (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_process_single, FLAC__bool (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_process_until_end_of_metadata, FLAC__bool (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_process_until_end_of_stream, FLAC__bool (*)(FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_seek_absolute, FLAC__bool (*)(FLAC__StreamDecoder *, FLAC__uint64))
FUNCTION_LOADER(FLAC__stream_decoder_get_state, FLAC__StreamDecoderState (*)(const FLAC__StreamDecoder *decoder))
FUNCTION_LOADER(FLAC__stream_decoder_get_total_samples,
FLAC__uint64 (*)(const FLAC__StreamDecoder *))
FUNCTION_LOADER(FLAC__stream_decoder_set_metadata_respond,
FLAC__bool (*)(FLAC__StreamDecoder *,
FLAC__MetadataType))
}
++flac.loaded;
return 0;
}
static void FLAC_Unload(void)
{
if (flac.loaded == 0) {
return;
}
if (flac.loaded == 1) {
#ifdef FLAC_DYNAMIC
SDL_UnloadObject(flac.handle);
#endif
}
--flac.loaded;
}
typedef struct {
int volume;
int play_count;
FLAC__StreamDecoder *flac_decoder;
unsigned sample_rate;
unsigned channels;
unsigned bits_per_sample;
SDL_IOStream *src;
bool closeio;
SDL_AudioStream *stream;
int loop;
FLAC__int64 pcm_pos;
FLAC__int64 full_length;
bool loop_flag;
FLAC__int64 loop_start;
FLAC__int64 loop_end;
FLAC__int64 loop_len;
Mix_MusicMetaTags tags;
} FLAC_Music;
static int FLAC_Seek(void *context, double position);
static FLAC__StreamDecoderReadStatus flac_read_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__byte buffer[],
size_t *bytes,
void *client_data)
{
FLAC_Music *data = (FLAC_Music*)client_data;
(void)decoder;
/* make sure there is something to be reading */
if (*bytes > 0) {
SDL_IOStatus status;
*bytes = SDL_ReadIO(data->src, buffer, *bytes);
status = SDL_GetIOStatus(data->src);
if (status == SDL_IO_STATUS_READY ||
status == SDL_IO_STATUS_NOT_READY) {
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
} else if (status == SDL_IO_STATUS_EOF) {
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
} else {
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
} else {
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
}
static FLAC__StreamDecoderSeekStatus flac_seek_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__uint64 absolute_byte_offset,
void *client_data)
{
FLAC_Music *data = (FLAC_Music*)client_data;
(void)decoder;
if (SDL_SeekIO(data->src, (Sint64)absolute_byte_offset, SDL_IO_SEEK_SET) < 0) {
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
} else {
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
}
static FLAC__StreamDecoderTellStatus flac_tell_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__uint64 *absolute_byte_offset,
void *client_data)
{
FLAC_Music *data = (FLAC_Music*)client_data;
Sint64 pos = SDL_TellIO(data->src);
(void)decoder;
if (pos < 0) {
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
} else {
*absolute_byte_offset = (FLAC__uint64)pos;
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
}
static FLAC__StreamDecoderLengthStatus flac_length_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__uint64 *stream_length,
void *client_data)
{
FLAC_Music *data = (FLAC_Music*)client_data;
Sint64 pos = SDL_TellIO(data->src);
Sint64 length = SDL_SeekIO(data->src, 0, SDL_IO_SEEK_END);
(void)decoder;
if (SDL_SeekIO(data->src, pos, SDL_IO_SEEK_SET) != pos || length < 0) {
/* there was an error attempting to return the stream to the original
* position, or the length was invalid. */
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
} else {
*stream_length = (FLAC__uint64)length;
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
}
static FLAC__bool flac_eof_music_cb(
const FLAC__StreamDecoder *decoder,
void *client_data)
{
FLAC_Music *data = (FLAC_Music*)client_data;
Sint64 pos = SDL_TellIO(data->src);
Sint64 end = SDL_SeekIO(data->src, 0, SDL_IO_SEEK_END);
(void)decoder;
/* was the original position equal to the end (a.k.a. the seek didn't move)? */
if (pos == end) {
/* must be EOF */
return true;
} else {
/* not EOF, return to the original position */
SDL_SeekIO(data->src, pos, SDL_IO_SEEK_SET);
return false;
}
}
static FLAC__StreamDecoderWriteStatus flac_write_music_cb(
const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame,
const FLAC__int32 *const buffer[],
void *client_data)
{
FLAC_Music *music = (FLAC_Music *)client_data;
Sint16 *data;
unsigned int i, j, channels;
int shift_amount = 0, amount;
(void)decoder;
if (!music->stream) {
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
switch (music->bits_per_sample) {
case 16:
shift_amount = 0;
break;
case 20:
shift_amount = 4;
break;
case 24:
shift_amount = 8;
break;
default:
SDL_SetError("FLAC decoder doesn't support %d bits_per_sample", music->bits_per_sample);
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (music->channels == 3) {
/* We'll just drop the center channel for now */
channels = 2;
} else {
channels = music->channels;
}
data = SDL_stack_alloc(Sint16, (frame->header.blocksize * channels));
if (!data) {
SDL_SetError("Couldn't allocate %d bytes stack memory", (int)(frame->header.blocksize * channels * sizeof(*data)));
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (music->channels == 3) {
Sint16 *dst = data;
for (i = 0; i < frame->header.blocksize; ++i) {
Sint16 FL = (Sint16)(buffer[0][i] >> shift_amount);
Sint16 FR = (Sint16)(buffer[1][i] >> shift_amount);
Sint16 FCmix = (Sint16)((buffer[2][i] >> shift_amount) * 0.5f);
int sample;
sample = (FL + FCmix);
if (sample > SDL_MAX_SINT16) {
*dst = SDL_MAX_SINT16;
} else if (sample < SDL_MIN_SINT16) {
*dst = SDL_MIN_SINT16;
} else {
*dst = (Sint16)sample;
}
++dst;
sample = (FR + FCmix);
if (sample > SDL_MAX_SINT16) {
*dst = SDL_MAX_SINT16;
} else if (sample < SDL_MIN_SINT16) {
*dst = SDL_MIN_SINT16;
} else {
*dst = (Sint16)sample;
}
++dst;
}
} else {
for (i = 0; i < channels; ++i) {
Sint16 *dst = data + i;
for (j = 0; j < frame->header.blocksize; ++j) {
*dst = (Sint16)(buffer[i][j] >> shift_amount);
dst += channels;
}
}
}
amount = (int)(frame->header.blocksize * channels * sizeof(*data));
music->pcm_pos += (FLAC__int64) frame->header.blocksize;
if (music->loop && (music->play_count != 1) &&
(music->pcm_pos >= music->loop_end)) {
amount -= (music->pcm_pos - music->loop_end) * channels * (int)sizeof(*data);
music->loop_flag = true;
}
SDL_PutAudioStreamData(music->stream, data, amount);
SDL_stack_free(data);
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
static void flac_metadata_music_cb(
const FLAC__StreamDecoder *decoder,
const FLAC__StreamMetadata *metadata,
void *client_data)
{
FLAC_Music *music = (FLAC_Music *)client_data;
const FLAC__StreamMetadata_VorbisComment *vc;
int channels;
unsigned rate;
char *param, *argument, *value;
bool is_loop_length = false;
(void)decoder;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
SDL_AudioSpec srcspec;
music->sample_rate = metadata->data.stream_info.sample_rate;
music->channels = metadata->data.stream_info.channels;
music->bits_per_sample = metadata->data.stream_info.bits_per_sample;
/*printf("FLAC: Sample rate = %d, channels = %d, bits_per_sample = %d\n", music->sample_rate, music->channels, music->bits_per_sample);*/
/* SDL's channel mapping and FLAC channel mapping are the same,
except for 3 channels: SDL is FL FR LFE and FLAC is FL FR FC
*/
if (music->channels == 3) {
channels = 2;
} else {
channels = (int)music->channels;
}
/* We check for NULL stream later when we get data */
SDL_assert(!music->stream);
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = channels;
srcspec.freq = (int)music->sample_rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
} else if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
FLAC__uint32 i;
vc = &metadata->data.vorbis_comment;
rate = music->sample_rate;
for (i = 0; i < vc->num_comments; ++i) {
param = SDL_strdup((const char *) vc->comments[i].entry);
argument = param;
value = SDL_strchr(param, '=');
if (value == NULL) {
value = param + SDL_strlen(param);
} else {
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
music->loop_start = _Mix_ParseTime(value, rate);
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
music->loop_len = SDL_strtoll(value, NULL, 10);
is_loop_length = true;
} else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
music->loop_end = _Mix_ParseTime(value, rate);
is_loop_length = false;
} else if (SDL_strcasecmp(argument, "TITLE") == 0) {
meta_tags_set(&music->tags, MIX_META_TITLE, value);
} else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
meta_tags_set(&music->tags, MIX_META_ARTIST, value);
} else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
meta_tags_set(&music->tags, MIX_META_ALBUM, value);
} else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
}
SDL_free(param);
}
if (is_loop_length) {
music->loop_end = music->loop_start + music->loop_len;
} else {
music->loop_len = music->loop_end - music->loop_start;
}
/* Ignore invalid loop tag */
if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
music->loop_start = 0;
music->loop_len = 0;
music->loop_end = 0;
}
}
}
static void flac_error_music_cb(
const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status,
void *client_data)
{
(void)decoder;
(void)client_data;
/* print an SDL error based on the error status */
switch (status) {
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
SDL_SetError("Error processing the FLAC file [LOST_SYNC].");
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
SDL_SetError("Error processing the FLAC file [BAD_HEADER].");
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
SDL_SetError("Error processing the FLAC file [CRC_MISMATCH].");
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM:
SDL_SetError("Error processing the FLAC file [UNPARSEABLE].");
break;
default:
SDL_SetError("Error processing the FLAC file [UNKNOWN].");
break;
}
}
/* Load an FLAC stream from an SDL_IOStream object */
static void *FLAC_CreateFromIO(SDL_IOStream *src, bool closeio)
{
FLAC_Music *music;
int init_stage = 0;
int was_error = 1;
FLAC__int64 full_length;
int is_ogg_flac;
Uint8 magic[4];
if (SDL_ReadIO(src, magic, 4) != 4) {
SDL_SetError("Couldn't read first 4 bytes of audio data");
return NULL;
}
SDL_SeekIO(src, -4, SDL_IO_SEEK_CUR);
is_ogg_flac = (SDL_memcmp(magic, "OggS", 4) == 0);
music = (FLAC_Music *)SDL_calloc(1, sizeof(*music));
if (!music) {
return NULL;
}
music->src = src;
music->volume = MIX_MAX_VOLUME;
music->flac_decoder = flac.FLAC__stream_decoder_new();
if (music->flac_decoder) {
FLAC__StreamDecoderInitStatus ret;
init_stage++; /* stage 1! */
flac.FLAC__stream_decoder_set_metadata_respond(music->flac_decoder,
FLAC__METADATA_TYPE_VORBIS_COMMENT);
if (is_ogg_flac) {
ret = flac.FLAC__stream_decoder_init_ogg_stream(
music->flac_decoder,
flac_read_music_cb, flac_seek_music_cb,
flac_tell_music_cb, flac_length_music_cb,
flac_eof_music_cb, flac_write_music_cb,
flac_metadata_music_cb, flac_error_music_cb,
music);
} else {
ret = flac.FLAC__stream_decoder_init_stream(
music->flac_decoder,
flac_read_music_cb, flac_seek_music_cb,
flac_tell_music_cb, flac_length_music_cb,
flac_eof_music_cb, flac_write_music_cb,
flac_metadata_music_cb, flac_error_music_cb,
music);
}
if (ret == FLAC__STREAM_DECODER_INIT_STATUS_OK) {
init_stage++; /* stage 2! */
if (flac.FLAC__stream_decoder_process_until_end_of_metadata(music->flac_decoder)) {
was_error = 0;
} else {
SDL_SetError("FLAC__stream_decoder_process_until_end_of_metadata() failed");
}
} else {
SDL_SetError("FLAC__stream_decoder_init_stream() failed");
}
} else {
SDL_SetError("FLAC__stream_decoder_new() failed");
}
if (was_error) {
switch (init_stage) {
case 2:
flac.FLAC__stream_decoder_finish(music->flac_decoder); /* fallthrough */
case 1:
flac.FLAC__stream_decoder_delete(music->flac_decoder); /* fallthrough */
case 0:
SDL_free(music);
break;
}
return NULL;
}
/* loop_start, loop_end and loop_len get set by metadata callback if tags
* are present in metadata.
*/
full_length = (FLAC__int64) flac.FLAC__stream_decoder_get_total_samples(music->flac_decoder);
if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
(music->loop_start < music->loop_end)) {
music->loop = 1;
}
music->full_length = full_length;
music->closeio = closeio;
return music;
}
static const char* FLAC_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
FLAC_Music *music = (FLAC_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Set the volume for an FLAC stream */
static void FLAC_SetVolume(void *context, int volume)
{
FLAC_Music *music = (FLAC_Music *)context;
music->volume = volume;
}
/* Get the volume for an FLAC stream */
static int FLAC_GetVolume(void *context)
{
FLAC_Music *music = (FLAC_Music *)context;
return music->volume;
}
/* Start playback of a given FLAC stream */
static int FLAC_Play(void *context, int play_count)
{
FLAC_Music *music = (FLAC_Music *)context;
music->play_count = play_count;
return FLAC_Seek(music, 0.0);
}
static void FLAC_Stop(void *context)
{
FLAC_Music *music = (FLAC_Music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Read some FLAC stream data and convert it for output */
static int FLAC_GetSome(void *context, void *data, int bytes, bool *done)
{
FLAC_Music *music = (FLAC_Music *)context;
int filled;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
if (!flac.FLAC__stream_decoder_process_single(music->flac_decoder)) {
SDL_SetError("FLAC__stream_decoder_process_single() failed");
return -1;
}
if (music->loop_flag) {
music->pcm_pos = music->loop_start;
if (flac.FLAC__stream_decoder_seek_absolute(music->flac_decoder, (FLAC__uint64)music->loop_start) ==
FLAC__STREAM_DECODER_SEEK_ERROR) {
flac.FLAC__stream_decoder_flush(music->flac_decoder);
SDL_SetError("FLAC__stream_decoder_seek_absolute() failed");
return -1;
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
music->play_count = play_count;
music->loop_flag = false;
}
}
if (flac.FLAC__stream_decoder_get_state(music->flac_decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (FLAC_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
/* Play some of a stream previously started with FLAC_play() */
static int FLAC_GetAudio(void *context, void *data, int bytes)
{
FLAC_Music *music = (FLAC_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, FLAC_GetSome);
}
/* Jump (seek) to a given position (position is in seconds) */
static int FLAC_Seek(void *context, double position)
{
FLAC_Music *music = (FLAC_Music *)context;
FLAC__uint64 seek_sample = (FLAC__uint64) (music->sample_rate * position);
SDL_ClearAudioStream(music->stream);
music->pcm_pos = (FLAC__int64) seek_sample;
if (!flac.FLAC__stream_decoder_seek_absolute(music->flac_decoder, seek_sample)) {
if (flac.FLAC__stream_decoder_get_state(music->flac_decoder) == FLAC__STREAM_DECODER_SEEK_ERROR) {
flac.FLAC__stream_decoder_flush(music->flac_decoder);
}
SDL_SetError("Seeking of FLAC stream failed: libFLAC seek failed.");
return -1;
}
return 0;
}
static double FLAC_Tell(void *context)
{
FLAC_Music *music = (FLAC_Music *)context;
return (double)music->pcm_pos / music->sample_rate;
}
/* Return music duration in seconds */
static double FLAC_Duration(void *context)
{
FLAC_Music *music = (FLAC_Music *)context;
return (double)music->full_length / music->sample_rate;
}
static double FLAC_LoopStart(void *music_p)
{
FLAC_Music *music = (FLAC_Music *)music_p;
if (music->loop > 0) {
return (double)music->loop_start / music->sample_rate;
}
return -1.0;
}
static double FLAC_LoopEnd(void *music_p)
{
FLAC_Music *music = (FLAC_Music *)music_p;
if (music->loop > 0) {
return (double)music->loop_end / music->sample_rate;
}
return -1.0;
}
static double FLAC_LoopLength(void *music_p)
{
FLAC_Music *music = (FLAC_Music *)music_p;
if (music->loop > 0) {
return (double)music->loop_len / music->sample_rate;
}
return -1.0;
}
/* Close the given FLAC_Music object */
static void FLAC_Delete(void *context)
{
FLAC_Music *music = (FLAC_Music *)context;
if (music) {
meta_tags_clear(&music->tags);
if (music->flac_decoder) {
flac.FLAC__stream_decoder_finish(music->flac_decoder);
flac.FLAC__stream_decoder_delete(music->flac_decoder);
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->closeio) {
SDL_CloseIO(music->src);
}
SDL_free(music);
}
}
Mix_MusicInterface Mix_MusicInterface_FLAC =
{
"FLAC",
MIX_MUSIC_FLAC,
MUS_FLAC,
false,
false,
FLAC_Load,
NULL, /* Open */
FLAC_CreateFromIO,
NULL, /* CreateFromFile */
FLAC_SetVolume,
FLAC_GetVolume,
FLAC_Play,
NULL, /* IsPlaying */
FLAC_GetAudio,
NULL, /* Jump */
FLAC_Seek,
FLAC_Tell,
FLAC_Duration,
FLAC_LoopStart,
FLAC_LoopEnd,
FLAC_LoopLength,
FLAC_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
FLAC_Stop, /* Stop */
FLAC_Delete,
NULL, /* Close */
FLAC_Unload
};
#endif /* MUSIC_FLAC_LIBFLAC */
/* vi: set ts=4 sw=4 expandtab: */
+26
View File
@@ -0,0 +1,26 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_FLAC;
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,403 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
James Le Cuirot
chewi@aura-online.co.uk
*/
#ifdef MUSIC_MID_FLUIDSYNTH
#include <SDL3/SDL.h>
#include "music_fluidsynth.h"
#include <fluidsynth.h>
typedef struct {
int loaded;
void *handle;
#if (FLUIDSYNTH_VERSION_MAJOR >= 2)
void (*delete_fluid_player)(fluid_player_t*);
void (*delete_fluid_synth)(fluid_synth_t*);
int (*fluid_player_seek)(fluid_player_t *, int);
#else
int (*delete_fluid_player)(fluid_player_t*);
int (*delete_fluid_synth)(fluid_synth_t*);
#endif
void (*delete_fluid_settings)(fluid_settings_t*);
int (*fluid_player_add)(fluid_player_t*, const char*);
int (*fluid_player_add_mem)(fluid_player_t*, const void*, size_t);
int (*fluid_player_get_status)(fluid_player_t*);
int (*fluid_player_play)(fluid_player_t*);
int (*fluid_player_set_loop)(fluid_player_t*, int);
int (*fluid_player_stop)(fluid_player_t*);
int (*fluid_settings_setnum)(fluid_settings_t*, const char*, double);
int (*fluid_settings_getnum)(fluid_settings_t*, const char*, double*);
fluid_settings_t* (*fluid_synth_get_settings)(fluid_synth_t*);
void (*fluid_synth_set_gain)(fluid_synth_t*, float);
int (*fluid_synth_sfload)(fluid_synth_t*, const char*, int);
int (*fluid_synth_write_s16)(fluid_synth_t*, int, void*, int, int, void*, int, int);
int (*fluid_synth_write_float)(fluid_synth_t*, int, void*, int, int, void*, int, int);
fluid_player_t* (*new_fluid_player)(fluid_synth_t*);
fluid_settings_t* (*new_fluid_settings)(void);
fluid_synth_t* (*new_fluid_synth)(fluid_settings_t*);
} fluidsynth_loader;
static fluidsynth_loader fluidsynth;
#ifdef FLUIDSYNTH_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
fluidsynth.FUNC = (SIG) SDL_LoadFunction(fluidsynth.handle, #FUNC); \
if (fluidsynth.FUNC == NULL) { SDL_UnloadObject(fluidsynth.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
fluidsynth.FUNC = FUNC;
#endif
static int FLUIDSYNTH_Load()
{
if (fluidsynth.loaded == 0) {
#ifdef FLUIDSYNTH_DYNAMIC
fluidsynth.handle = SDL_LoadObject(FLUIDSYNTH_DYNAMIC);
if (fluidsynth.handle == NULL) {
return -1;
}
#endif
#if (FLUIDSYNTH_VERSION_MAJOR >= 2)
FUNCTION_LOADER(delete_fluid_player, void (*)(fluid_player_t*))
FUNCTION_LOADER(delete_fluid_synth, void (*)(fluid_synth_t*))
FUNCTION_LOADER(fluid_player_seek, int (*)(fluid_player_t *, int))
#else
FUNCTION_LOADER(delete_fluid_player, int (*)(fluid_player_t*))
FUNCTION_LOADER(delete_fluid_synth, int (*)(fluid_synth_t*))
#endif
FUNCTION_LOADER(delete_fluid_settings, void (*)(fluid_settings_t*))
FUNCTION_LOADER(fluid_player_add, int (*)(fluid_player_t*, const char*))
FUNCTION_LOADER(fluid_player_add_mem, int (*)(fluid_player_t*, const void*, size_t))
FUNCTION_LOADER(fluid_player_get_status, int (*)(fluid_player_t*))
FUNCTION_LOADER(fluid_player_play, int (*)(fluid_player_t*))
FUNCTION_LOADER(fluid_player_set_loop, int (*)(fluid_player_t*, int))
FUNCTION_LOADER(fluid_player_stop, int (*)(fluid_player_t*))
FUNCTION_LOADER(fluid_settings_setnum, int (*)(fluid_settings_t*, const char*, double))
FUNCTION_LOADER(fluid_settings_getnum, int (*)(fluid_settings_t*, const char*, double*))
FUNCTION_LOADER(fluid_synth_get_settings, fluid_settings_t* (*)(fluid_synth_t*))
FUNCTION_LOADER(fluid_synth_set_gain, void (*)(fluid_synth_t*, float))
FUNCTION_LOADER(fluid_synth_sfload, int(*)(fluid_synth_t*, const char*, int))
FUNCTION_LOADER(fluid_synth_write_s16, int(*)(fluid_synth_t*, int, void*, int, int, void*, int, int))
FUNCTION_LOADER(fluid_synth_write_float, int(*)(fluid_synth_t*, int, void*, int, int, void*, int, int))
FUNCTION_LOADER(new_fluid_player, fluid_player_t* (*)(fluid_synth_t*))
FUNCTION_LOADER(new_fluid_settings, fluid_settings_t* (*)(void))
FUNCTION_LOADER(new_fluid_synth, fluid_synth_t* (*)(fluid_settings_t*))
}
++fluidsynth.loaded;
return 0;
}
static void FLUIDSYNTH_Unload()
{
if (fluidsynth.loaded == 0) {
return;
}
if (fluidsynth.loaded == 1) {
#ifdef FLUIDSYNTH_DYNAMIC
SDL_UnloadObject(fluidsynth.handle);
#endif
}
--fluidsynth.loaded;
}
typedef struct {
fluid_synth_t *synth;
fluid_settings_t *settings;
fluid_player_t *player;
int (*synth_write)(fluid_synth_t*, int, void*, int, int, void*, int, int);
SDL_AudioStream *stream;
void *buffer;
int buffer_size;
int volume;
bool is_paused;
} FLUIDSYNTH_Music;
static void FLUIDSYNTH_Delete(void *context);
static bool SDLCALL fluidsynth_check_soundfont(const char *path, void *data)
{
SDL_IOStream *io = SDL_IOFromFile(path, "rb");
(void)data;
if (io) {
SDL_CloseIO(io);
return true;
} else {
SDL_SetError("Failed to access the SoundFont %s", path);
return false;
}
}
static bool SDLCALL fluidsynth_load_soundfont(const char *path, void *data)
{
/* If this fails, it's too late to try Timidity so pray that at least one works. */
fluidsynth.fluid_synth_sfload((fluid_synth_t*) data, path, 1);
return true;
}
static int FLUIDSYNTH_Open(const SDL_AudioSpec *spec)
{
(void)spec;
if (!Mix_EachSoundFont(fluidsynth_check_soundfont, NULL)) {
return -1;
}
return 0;
}
static FLUIDSYNTH_Music *FLUIDSYNTH_LoadMusic(void *data)
{
SDL_AudioSpec srcspec;
SDL_IOStream *src = (SDL_IOStream *)data;
FLUIDSYNTH_Music *music;
double samplerate; /* as set by the lib. */
const Uint8 channels = 2;
int src_format = SDL_AUDIO_S16;
void *io_mem;
size_t io_size;
int ret;
if (!(music = SDL_calloc(1, sizeof(FLUIDSYNTH_Music)))) {
return NULL;
}
music->volume = MIX_MAX_VOLUME;
music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * channels;
music->synth_write = fluidsynth.fluid_synth_write_s16;
if (music_spec.format & 0x0020) { /* 32 bit. */
src_format = SDL_AUDIO_F32;
music->buffer_size <<= 1;
music->synth_write = fluidsynth.fluid_synth_write_float;
}
if (!(music->buffer = SDL_malloc((size_t)music->buffer_size))) {
goto fail;
}
if (!(music->settings = fluidsynth.new_fluid_settings())) {
SDL_SetError("Failed to create FluidSynth settings");
goto fail;
}
fluidsynth.fluid_settings_setnum(music->settings, "synth.sample-rate", (double) music_spec.freq);
fluidsynth.fluid_settings_getnum(music->settings, "synth.sample-rate", &samplerate);
if (!(music->synth = fluidsynth.new_fluid_synth(music->settings))) {
SDL_SetError("Failed to create FluidSynth synthesizer");
goto fail;
}
if (!Mix_EachSoundFont(fluidsynth_load_soundfont, music->synth)) {
goto fail;
}
if (!(music->player = fluidsynth.new_fluid_player(music->synth))) {
SDL_SetError("Failed to create FluidSynth player");
goto fail;
}
io_mem = SDL_LoadFile_IO(src, &io_size, false);
if (!io_mem) {
goto fail;
}
ret = fluidsynth.fluid_player_add_mem(music->player, io_mem, io_size);
SDL_free(io_mem);
if (ret != FLUID_OK) {
SDL_SetError("FluidSynth failed to load in-memory song");
goto fail;
}
SDL_zero(srcspec);
srcspec.format = src_format;
srcspec.channels = channels;
srcspec.freq = (int) samplerate;
if (!(music->stream = SDL_CreateAudioStream(&srcspec, &music_spec))) {
goto fail;
}
return music;
fail:
FLUIDSYNTH_Delete(music);
return NULL;
}
static void *FLUIDSYNTH_CreateFromIO(SDL_IOStream *src, bool closeio)
{
FLUIDSYNTH_Music *music;
music = FLUIDSYNTH_LoadMusic(src);
if (music && closeio) {
SDL_CloseIO(src);
}
return music;
}
static void FLUIDSYNTH_SetVolume(void *context, int volume)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
/* FluidSynth's default gain is 0.2. Make 1.0 the maximum gain value to avoid sound overload. */
music->volume = volume;
fluidsynth.fluid_synth_set_gain(music->synth, volume * 1.0f / MIX_MAX_VOLUME);
}
static int FLUIDSYNTH_GetVolume(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
return music->volume;
}
static int FLUIDSYNTH_Play(void *context, int play_count)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
fluidsynth.fluid_player_set_loop(music->player, play_count);
#if (FLUIDSYNTH_VERSION_MAJOR >= 2)
fluidsynth.fluid_player_seek(music->player, 0);
#endif
fluidsynth.fluid_player_play(music->player);
music->is_paused = false;
return 0;
}
static void FLUIDSYNTH_Resume(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
fluidsynth.fluid_player_play(music->player);
music->is_paused = false;
}
static bool FLUIDSYNTH_IsPlaying(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
return music->is_paused || fluidsynth.fluid_player_get_status(music->player) == FLUID_PLAYER_PLAYING ? true : false;
}
static int FLUIDSYNTH_GetSome(void *context, void *data, int bytes, bool *done)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
int filled;
(void)done;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (music->synth_write(music->synth, 4096/*music_spec.samples*/, music->buffer, 0, 2, music->buffer, 1, 2) != FLUID_OK) {
SDL_SetError("Error generating FluidSynth audio");
return -1;
}
if (!SDL_PutAudioStreamData(music->stream, music->buffer, music->buffer_size)) {
return -1;
}
return 0;
}
static int FLUIDSYNTH_GetAudio(void *context, void *data, int bytes)
{
return music_pcm_getaudio(context, data, bytes, MIX_MAX_VOLUME, FLUIDSYNTH_GetSome);
}
static void FLUIDSYNTH_Stop(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
fluidsynth.fluid_player_stop(music->player);
#if (FLUIDSYNTH_VERSION_MAJOR >= 2)
fluidsynth.fluid_player_seek(music->player, 0);
#endif
music->is_paused = false;
}
static void FLUIDSYNTH_Pause(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
fluidsynth.fluid_player_stop(music->player);
music->is_paused = true;
}
static void FLUIDSYNTH_Delete(void *context)
{
FLUIDSYNTH_Music *music = (FLUIDSYNTH_Music *)context;
if (music->player) {
fluidsynth.delete_fluid_player(music->player);
}
if (music->synth) {
fluidsynth.delete_fluid_synth(music->synth);
}
if (music->settings) {
fluidsynth.delete_fluid_settings(music->settings);
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_FLUIDSYNTH =
{
"FLUIDSYNTH",
MIX_MUSIC_FLUIDSYNTH,
MUS_MID,
false,
false,
FLUIDSYNTH_Load,
FLUIDSYNTH_Open,
FLUIDSYNTH_CreateFromIO,
NULL, /* CreateFromFile */
FLUIDSYNTH_SetVolume,
FLUIDSYNTH_GetVolume,
FLUIDSYNTH_Play,
FLUIDSYNTH_IsPlaying,
FLUIDSYNTH_GetAudio,
NULL, /* Jump */
NULL, /* Seek */
NULL, /* Tell */
NULL, /* Duration */
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
NULL, /* GetNumTracks */
NULL, /* StartTrack */
FLUIDSYNTH_Pause,
FLUIDSYNTH_Resume,
FLUIDSYNTH_Stop,
FLUIDSYNTH_Delete,
NULL, /* Close */
FLUIDSYNTH_Unload
};
#endif /* MUSIC_MID_FLUIDSYNTH */
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,31 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
James Le Cuirot
chewi@aura-online.co.uk
*/
/* This file supports playing MIDI files with FluidSynth */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_FLUIDSYNTH;
/* vi: set ts=4 sw=4 expandtab: */
+446
View File
@@ -0,0 +1,446 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_GME
#include <SDL3/SDL_loadso.h>
#include "music_gme.h"
#include <gme/gme.h>
typedef struct {
int loaded;
void *handle;
gme_err_t (*gme_open_data)(void const* data, long size, Music_Emu** out, int sample_rate);
int (*gme_track_count)(Music_Emu const*);
gme_err_t (*gme_start_track)(Music_Emu*, int index);
int (*gme_track_ended)(Music_Emu const*);
void (*gme_set_tempo)(Music_Emu*, double tempo);
int (*gme_voice_count)(Music_Emu const*);
void (*gme_mute_voice)(Music_Emu*, int index, int mute);
void (*gme_set_fade)(Music_Emu*, int start_msec);
void (*gme_set_autoload_playback_limit)(Music_Emu*, int do_autoload_limit);
gme_err_t (*gme_track_info)(Music_Emu const*, gme_info_t** out, int track);
void (*gme_free_info)(gme_info_t*);
gme_err_t (*gme_seek)(Music_Emu*, int msec);
int (*gme_tell)(Music_Emu const*);
gme_err_t (*gme_play)(Music_Emu*, int count, short out[]);
void (*gme_delete)(Music_Emu*);
} gme_loader;
static gme_loader gme;
#ifdef GME_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
gme.FUNC = (SIG) SDL_LoadFunction(gme.handle, #FUNC); \
if (gme.FUNC == NULL) { SDL_UnloadObject(gme.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
gme.FUNC = FUNC; \
if (gme.FUNC == NULL) { SDL_SetError("Missing gme.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int GME_Load(void)
{
if (gme.loaded == 0) {
#ifdef GME_DYNAMIC
gme.handle = SDL_LoadObject(GME_DYNAMIC);
if (gme.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(gme_open_data, gme_err_t (*)(void const*,long,Music_Emu**,int))
FUNCTION_LOADER(gme_track_count, int (*)(Music_Emu const*))
FUNCTION_LOADER(gme_start_track, gme_err_t (*)( Music_Emu*,int))
FUNCTION_LOADER(gme_track_ended, int (*)( Music_Emu const*))
FUNCTION_LOADER(gme_set_tempo, void (*)(Music_Emu*,double))
FUNCTION_LOADER(gme_voice_count, int (*)(Music_Emu const*))
FUNCTION_LOADER(gme_mute_voice, void (*)(Music_Emu*,int,int))
FUNCTION_LOADER(gme_set_fade, void (*)(Music_Emu*,int))
FUNCTION_LOADER(gme_track_info, gme_err_t (*)(Music_Emu const*, gme_info_t**, int))
FUNCTION_LOADER(gme_free_info, void (*)(gme_info_t*))
FUNCTION_LOADER(gme_seek, gme_err_t (*)(Music_Emu*,int))
FUNCTION_LOADER(gme_tell, int (*)(Music_Emu const*))
FUNCTION_LOADER(gme_play, gme_err_t (*)(Music_Emu*, int, short[]))
FUNCTION_LOADER(gme_delete, void (*)(Music_Emu*))
#if defined(GME_DYNAMIC)
gme.gme_set_autoload_playback_limit = (void (*)(Music_Emu*,int)) SDL_LoadFunction(gme.handle, "gme_set_autoload_playback_limit");
if (!gme.gme_set_autoload_playback_limit) {
SDL_ClearError(); /* gme_set_autoload_playback_limit is optional. */
}
#elif (GME_VERSION >= 0x000603)
gme.gme_set_autoload_playback_limit = gme_set_autoload_playback_limit;
#else
gme.gme_set_autoload_playback_limit = NULL;
#endif
}
++gme.loaded;
return 0;
}
static void GME_Unload(void)
{
if (gme.loaded == 0) {
return;
}
if (gme.loaded == 1) {
#ifdef GME_DYNAMIC
SDL_UnloadObject(gme.handle);
#endif
}
--gme.loaded;
}
/* This file supports Game Music Emulator music streams */
typedef struct
{
int play_count;
Music_Emu* game_emu;
bool closeio;
bool has_track_length;
int track_length;
int intro_length;
int loop_length;
int volume;
double tempo;
double gain;
SDL_AudioStream *stream;
void *buffer;
size_t buffer_size;
Mix_MusicMetaTags tags;
} GME_Music;
static void GME_Delete(void *context);
/* Set the volume for a GME stream */
static void GME_SetVolume(void *music_p, int volume)
{
GME_Music *music = (GME_Music*)music_p;
double v = SDL_floor(((double)volume * music->gain) + 0.5);
music->volume = (int)v;
}
/* Get the volume for a GME stream */
static int GME_GetVolume(void *music_p)
{
GME_Music *music = (GME_Music*)music_p;
double v = SDL_floor(((double)(music->volume) / music->gain) + 0.5);
return (int)v;
}
static int initialize_from_track_info(GME_Music *music, int track)
{
gme_info_t *musInfo;
bool has_loop_length = true;
const char *err;
err = gme.gme_track_info(music->game_emu, &musInfo, track);
if (err != 0) {
SDL_SetError("GME: %s", err);
return -1;
}
music->track_length = musInfo->length;
music->intro_length = musInfo->intro_length;
music->loop_length = musInfo->loop_length;
music->has_track_length = true;
if (music->track_length <= 0 ) {
music->track_length = (int)(2.5 * 60 * 1000);
music->has_track_length = false;
}
if (music->intro_length < 0 ) {
music->intro_length = 0;
}
if (music->loop_length <= 0 ) {
if (music->track_length > 0) {
music->loop_length = music->track_length;
} else {
music->loop_length = (int)(2.5 * 60 * 1000);
}
has_loop_length = false;
}
if (!music->has_track_length && has_loop_length) {
music->track_length = music->intro_length + music->loop_length;
music->has_track_length = true;
}
meta_tags_set(&music->tags, MIX_META_TITLE, musInfo->song);
meta_tags_set(&music->tags, MIX_META_ARTIST, musInfo->author);
meta_tags_set(&music->tags, MIX_META_ALBUM, musInfo->game);
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, musInfo->copyright);
gme.gme_free_info(musInfo);
return 0;
}
static void *GME_CreateFromIO(struct SDL_IOStream *src, bool closeio)
{
SDL_AudioSpec srcspec;
void *mem = 0;
size_t size;
GME_Music *music;
const char *err;
if (src == NULL) {
SDL_SetError("GME: Empty source given");
return NULL;
}
music = (GME_Music *)SDL_calloc(1, sizeof(GME_Music));
music->tempo = 1.0;
music->gain = 1.0;
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = 2;
srcspec.freq = music_spec.freq;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
GME_Delete(music);
return NULL;
}
music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * 2/*channels*/ * music_spec.channels;
music->buffer = SDL_malloc(music->buffer_size);
if (!music->buffer) {
GME_Delete(music);
return NULL;
}
SDL_SeekIO(src, 0, SDL_IO_SEEK_SET);
mem = SDL_LoadFile_IO(src, &size, false);
if (mem) {
err = gme.gme_open_data(mem, (long)size, &music->game_emu, music_spec.freq);
SDL_free(mem);
if (err != 0) {
GME_Delete(music);
SDL_SetError("GME: %s", err);
return NULL;
}
} else {
GME_Delete(music);
return NULL;
}
/* Set this flag BEFORE calling the gme_start_track() to fix an inability to loop forever */
if (gme.gme_set_autoload_playback_limit) {
gme.gme_set_autoload_playback_limit(music->game_emu, 0);
}
err = gme.gme_start_track(music->game_emu, 0);
if (err != 0) {
GME_Delete(music);
SDL_SetError("GME: %s", err);
return NULL;
}
gme.gme_set_tempo(music->game_emu, music->tempo);
music->volume = MIX_MAX_VOLUME;
meta_tags_init(&music->tags);
if (initialize_from_track_info(music, 0) < 0) {
GME_Delete(music);
return NULL;
}
music->closeio = closeio;
return music;
}
/* Start playback of a given Game Music Emulators stream */
static int GME_Play(void *music_p, int play_count)
{
GME_Music *music = (GME_Music*)music_p;
int fade_start;
if (music) {
SDL_ClearAudioStream(music->stream);
music->play_count = play_count;
fade_start = play_count > 0 ? music->intro_length + (music->loop_length * play_count) : -1;
/* libgme >= 0.6.4 has gme_set_fade_msecs(),
* but gme_set_fade() sets msecs to 8000 by
* default and we are OK with that. */
gme.gme_set_fade(music->game_emu, fade_start);
gme.gme_seek(music->game_emu, 0);
}
return 0;
}
static int GME_GetSome(void *context, void *data, int bytes, bool *done)
{
GME_Music *music = (GME_Music*)context;
int filled;
const char *err = NULL;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (gme.gme_track_ended(music->game_emu)) {
/* All done */
*done = true;
return 0;
}
err = gme.gme_play(music->game_emu, (int)(music->buffer_size / 2), (short*)music->buffer);
if (err != NULL) {
SDL_SetError("GME: %s", err);
return 0;
}
if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)music->buffer_size)) {
return -1;
}
return 0;
}
/* Play some of a stream previously started with GME_Play() */
static int GME_PlayAudio(void *music_p, void *data, int bytes)
{
GME_Music *music = (GME_Music*)music_p;
return music_pcm_getaudio(music_p, data, bytes, music->volume, GME_GetSome);
}
/* Close the given Game Music Emulators stream */
static void GME_Delete(void *context)
{
GME_Music *music = (GME_Music*)context;
if (music) {
meta_tags_clear(&music->tags);
if (music->game_emu && music->closeio) {
gme.gme_delete(music->game_emu);
music->game_emu = NULL;
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
SDL_free(music);
}
}
// TODO: this should accept a track number, not assume the current track!
static const char* GME_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
GME_Music *music = (GME_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Jump (seek) to a given position (time is in seconds) */
static int GME_Seek(void *music_p, double time)
{
GME_Music *music = (GME_Music*)music_p;
gme.gme_seek(music->game_emu, (int)(SDL_floor((time * 1000.0) + 0.5)));
return 0;
}
static double GME_Tell(void *music_p)
{
GME_Music *music = (GME_Music*)music_p;
return (double)(gme.gme_tell(music->game_emu)) / 1000.0;
}
static double GME_Duration(void *music_p)
{
GME_Music *music = (GME_Music*)music_p;
if (music->has_track_length) {
return (double)(music->track_length) / 1000.0;
}
return -1.0;
}
static int GME_GetNumTracks(void *music_p)
{
GME_Music *music = (GME_Music *)music_p;
return gme.gme_track_count(music->game_emu);
}
static int GME_StartTrack(void *music_p, int track)
{
GME_Music *music = (GME_Music *)music_p;
const char *err;
if ((track < 0) || (track >= gme.gme_track_count(music->game_emu))) {
track = gme.gme_track_count(music->game_emu) - 1;
}
err = gme.gme_start_track(music->game_emu, track);
if (err != 0) {
SDL_SetError("GME: %s", err);
return -1;
}
GME_Play(music, music->play_count);
return initialize_from_track_info(music, track);
}
Mix_MusicInterface Mix_MusicInterface_GME =
{
"GME",
MIX_MUSIC_GME,
MUS_GME,
false,
false,
GME_Load,
NULL, /* Open */
GME_CreateFromIO,
NULL, /* CreateFromFile */
GME_SetVolume,
GME_GetVolume,
GME_Play,
NULL, /* IsPlaying */
GME_PlayAudio,
NULL, /* Jump */
GME_Seek,
GME_Tell,
GME_Duration,
NULL,
NULL,
NULL,
GME_GetMetaTag,
GME_GetNumTracks,
GME_StartTrack,
NULL, /* Pause */
NULL, /* Resume */
NULL, /* Stop */
GME_Delete,
NULL, /* Close */
GME_Unload
};
#endif /* MUSIC_GME */
+26
View File
@@ -0,0 +1,26 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing chiptune files with libGME */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_GME;
+565
View File
@@ -0,0 +1,565 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MP3 files with mpg123 */
#ifdef MUSIC_MP3_MPG123
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_assert.h>
#include "music_mpg123.h"
#include "mp3utils.h"
#include <stdio.h> /* For SEEK_SET */
#define MPG123_NO_LARGENAME /* disable the _FILE_OFFSET_BITS suffixes. */
#ifdef MPG123_HEADER
#include MPG123_HEADER
#else
#include <mpg123.h>
#endif
#ifdef _MSC_VER
typedef ptrdiff_t MIX_SSIZE_T;
#else
typedef ssize_t MIX_SSIZE_T;
#endif
typedef struct {
int loaded;
void *handle;
int (*mpg123_close)(mpg123_handle *mh);
void (*mpg123_delete)(mpg123_handle *mh);
void (*mpg123_exit)(void);
int (*mpg123_format)( mpg123_handle *mh, long rate, int channels, int encodings );
int (*mpg123_format_none)(mpg123_handle *mh);
int (*mpg123_getformat)( mpg123_handle *mh, long *rate, int *channels, int *encoding );
int (*mpg123_init)(void);
mpg123_handle *(*mpg123_new)(const char* decoder, int *error);
int (*mpg123_open_handle)(mpg123_handle *mh, void *iohandle);
const char* (*mpg123_plain_strerror)(int errcode);
void (*mpg123_rates)(const long **list, size_t *number);
#if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */
int (*mpg123_read)(mpg123_handle *mh, void *outmemory, size_t outmemsize, size_t *done );
#else
int (*mpg123_read)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done );
#endif
int (*mpg123_replace_reader_handle)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) );
off_t (*mpg123_seek)( mpg123_handle *mh, off_t sampleoff, int whence );
off_t (*mpg123_tell)( mpg123_handle *mh);
off_t (*mpg123_length)(mpg123_handle *mh);
const char* (*mpg123_strerror)(mpg123_handle *mh);
} mpg123_loader;
static mpg123_loader mpg123;
#ifdef MPG123_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
mpg123.FUNC = (SIG) SDL_LoadFunction(mpg123.handle, #FUNC); \
if (mpg123.FUNC == NULL) { SDL_UnloadObject(mpg123.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
mpg123.FUNC = FUNC; \
if (mpg123.FUNC == NULL) { SDL_SetError("Missing mpg123.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int MPG123_Load(void)
{
if (mpg123.loaded == 0) {
#ifdef MPG123_DYNAMIC
mpg123.handle = SDL_LoadObject(MPG123_DYNAMIC);
if (mpg123.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(mpg123_close, int (*)(mpg123_handle *mh))
FUNCTION_LOADER(mpg123_delete, void (*)(mpg123_handle *mh))
FUNCTION_LOADER(mpg123_exit, void (*)(void))
FUNCTION_LOADER(mpg123_format, int (*)( mpg123_handle *mh, long rate, int channels, int encodings ))
FUNCTION_LOADER(mpg123_format_none, int (*)(mpg123_handle *mh))
FUNCTION_LOADER(mpg123_getformat, int (*)( mpg123_handle *mh, long *rate, int *channels, int *encoding ))
FUNCTION_LOADER(mpg123_init, int (*)(void))
FUNCTION_LOADER(mpg123_new, mpg123_handle *(*)(const char* decoder, int *error))
FUNCTION_LOADER(mpg123_open_handle, int (*)(mpg123_handle *mh, void *iohandle))
FUNCTION_LOADER(mpg123_plain_strerror, const char* (*)(int errcode))
FUNCTION_LOADER(mpg123_rates, void (*)(const long **list, size_t *number))
#if (MPG123_API_VERSION >= 45) /* api (but not abi) change as of mpg123-1.26.0 */
FUNCTION_LOADER(mpg123_read, int (*)(mpg123_handle *mh, void *outmemory, size_t outmemsize, size_t *done ))
#else
FUNCTION_LOADER(mpg123_read, int (*)(mpg123_handle *mh, unsigned char *outmemory, size_t outmemsize, size_t *done ))
#endif
FUNCTION_LOADER(mpg123_replace_reader_handle, int (*)( mpg123_handle *mh, MIX_SSIZE_T (*r_read) (void *, void *, size_t), off_t (*r_lseek)(void *, off_t, int), void (*cleanup)(void*) ))
FUNCTION_LOADER(mpg123_seek, off_t (*)( mpg123_handle *mh, off_t sampleoff, int whence ))
FUNCTION_LOADER(mpg123_tell, off_t (*)( mpg123_handle *mh))
FUNCTION_LOADER(mpg123_length, off_t (*)(mpg123_handle *mh))
FUNCTION_LOADER(mpg123_strerror, const char* (*)(mpg123_handle *mh))
}
++mpg123.loaded;
return 0;
}
static void MPG123_Unload(void)
{
if (mpg123.loaded == 0) {
return;
}
if (mpg123.loaded == 1) {
#ifdef MPG123_DYNAMIC
SDL_UnloadObject(mpg123.handle);
#endif
}
--mpg123.loaded;
}
typedef struct
{
struct mp3file_t mp3file;
int play_count;
bool closeio;
int volume;
mpg123_handle* handle;
SDL_AudioStream *stream;
unsigned char *buffer;
size_t buffer_size;
long sample_rate;
off_t total_length;
Mix_MusicMetaTags tags;
} MPG123_Music;
static int MPG123_Seek(void *context, double secs);
static void MPG123_Delete(void *context);
static int mpg123_format_to_sdl(int fmt)
{
switch (fmt) {
case MPG123_ENC_SIGNED_8: return SDL_AUDIO_S8;
case MPG123_ENC_UNSIGNED_8: return SDL_AUDIO_U8;
case MPG123_ENC_SIGNED_16: return SDL_AUDIO_S16;
case MPG123_ENC_SIGNED_32: return SDL_AUDIO_S32;
case MPG123_ENC_FLOAT_32: return SDL_AUDIO_F32;
default: return -1;
}
}
/*#define DEBUG_MPG123*/
#ifdef DEBUG_MPG123
static const char *mpg123_format_str(int fmt)
{
switch (fmt) {
#define f(x) case x: return #x;
f(MPG123_ENC_UNSIGNED_8)
f(MPG123_ENC_SIGNED_8)
f(MPG123_ENC_SIGNED_16)
f(MPG123_ENC_SIGNED_32)
f(MPG123_ENC_FLOAT_32)
#undef f
}
return "unknown";
}
#endif
static char const* mpg_err(mpg123_handle* mpg, int result)
{
char const* err = "unknown error";
if (mpg && result == MPG123_ERR) {
err = mpg123.mpg123_strerror(mpg);
} else {
err = mpg123.mpg123_plain_strerror(result);
}
return err;
}
/* we're gonna override mpg123's I/O with these wrappers for SDL_IOStream */
static MIX_SSIZE_T IO_read(void* p, void* dst, size_t n)
{
struct mp3file_t *mp3file = (struct mp3file_t *)p;
MIX_SSIZE_T r = (MIX_SSIZE_T)MP3_IOread(mp3file, dst, 1, n);
if (!r && SDL_GetIOStatus(mp3file->src) != SDL_IO_STATUS_EOF) {
return -1;
}
return r < 0 ? -1 : r;
}
static off_t IO_seek(void* p, off_t offset, int whence)
{
return (off_t)MP3_IOseek((struct mp3file_t *)p, (Sint64)offset, whence);
}
static void IO_cleanup(void* p)
{
(void)p;
/* do nothing, we will free the file later */
}
static int MPG123_Open(const SDL_AudioSpec *spec)
{
(void)spec;
if (mpg123.mpg123_init() != MPG123_OK) {
SDL_SetError("mpg123_init() failed");
return -1;
}
return 0;
}
static void *MPG123_CreateFromIO(SDL_IOStream *src, bool closeio)
{
SDL_AudioSpec srcspec;
MPG123_Music *music;
int result, format, channels, encoding;
long rate;
const long *rates;
size_t i, num_rates;
music = (MPG123_Music*)SDL_calloc(1, sizeof(*music));
if (!music) {
return NULL;
}
music->volume = MIX_MAX_VOLUME;
if (MP3_IOinit(&music->mp3file, src) < 0) {
SDL_free(music);
return NULL;
}
meta_tags_init(&music->tags);
if (mp3_read_tags(&music->tags, &music->mp3file, true) < 0) {
SDL_free(music);
SDL_SetError("music_mpg123: corrupt mp3 file (bad tags.)");
return NULL;
}
/* Just assume 16-bit 2 channel audio for now */
music->buffer_size = 4096/*music_spec.samples*/ * sizeof(Sint16) * 2;
music->buffer = (unsigned char *)SDL_malloc(music->buffer_size);
if (!music->buffer) {
MPG123_Delete(music);
return NULL;
}
music->handle = mpg123.mpg123_new(0, &result);
if (result != MPG123_OK) {
MPG123_Delete(music);
SDL_SetError("mpg123_new failed");
return NULL;
}
result = mpg123.mpg123_replace_reader_handle(
music->handle,
IO_read, IO_seek, IO_cleanup
);
if (result != MPG123_OK) {
SDL_SetError("mpg123_replace_reader_handle: %s", mpg_err(music->handle, result));
MPG123_Delete(music);
return NULL;
}
result = mpg123.mpg123_format_none(music->handle);
if (result != MPG123_OK) {
SDL_SetError("mpg123_format_none: %s", mpg_err(music->handle, result));
MPG123_Delete(music);
return NULL;
}
mpg123.mpg123_rates(&rates, &num_rates);
for (i = 0; i < num_rates; ++i) {
const int channels = (MPG123_MONO|MPG123_STEREO);
const int formats = (MPG123_ENC_SIGNED_8 |
MPG123_ENC_UNSIGNED_8 |
MPG123_ENC_SIGNED_16 |
MPG123_ENC_SIGNED_32 |
MPG123_ENC_FLOAT_32);
mpg123.mpg123_format(music->handle, rates[i], channels, formats);
}
result = mpg123.mpg123_open_handle(music->handle, &music->mp3file);
if (result != MPG123_OK) {
SDL_SetError("mpg123_open_handle: %s", mpg_err(music->handle, result));
MPG123_Delete(music);
return NULL;
}
result = mpg123.mpg123_getformat(music->handle, &rate, &channels, &encoding);
if (result != MPG123_OK) {
SDL_SetError("mpg123_getformat: %s", mpg_err(music->handle, result));
MPG123_Delete(music);
return NULL;
}
#ifdef DEBUG_MPG123
printf("MPG123 format: %s, channels: %d, rate: %ld\n",
mpg123_format_str(encoding), channels, rate);
#endif
format = mpg123_format_to_sdl(encoding);
SDL_assert(format != -1);
music->sample_rate = rate;
SDL_zero(srcspec);
srcspec.format = (SDL_AudioFormat)format;
srcspec.channels = channels;
srcspec.freq = (int)rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
MPG123_Delete(music);
return NULL;
}
music->total_length = mpg123.mpg123_length(music->handle);
music->closeio = closeio;
return music;
}
static void MPG123_SetVolume(void *context, int volume)
{
MPG123_Music *music = (MPG123_Music *)context;
music->volume = volume;
}
static int MPG123_GetVolume(void *context)
{
MPG123_Music *music = (MPG123_Music *)context;
return music->volume;
}
static int MPG123_Play(void *context, int play_count)
{
MPG123_Music *music = (MPG123_Music *)context;
music->play_count = play_count;
return MPG123_Seek(music, 0.0);
}
static void MPG123_Stop(void *context)
{
MPG123_Music *music = (MPG123_Music *)context;
SDL_ClearAudioStream(music->stream);
}
/* read some mp3 stream data and convert it for output */
static int MPG123_GetSome(void *context, void *data, int bytes, bool *done)
{
SDL_AudioSpec srcspec;
MPG123_Music *music = (MPG123_Music *)context;
int filled, result;
size_t amount = 0;
long rate;
int channels, encoding, format;
if (music->stream) {
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
result = mpg123.mpg123_read(music->handle, music->buffer, music->buffer_size, &amount);
switch (result) {
case MPG123_OK:
if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount)) {
return -1;
}
break;
case MPG123_NEW_FORMAT:
result = mpg123.mpg123_getformat(music->handle, &rate, &channels, &encoding);
if (result != MPG123_OK) {
SDL_SetError("mpg123_getformat: %s", mpg_err(music->handle, result));
return -1;
}
#ifdef DEBUG_MPG123
printf("MPG123 format: %s, channels: %d, rate: %ld\n",
mpg123_format_str(encoding), channels, rate);
#endif
format = mpg123_format_to_sdl(encoding);
SDL_assert(format != -1);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
SDL_zero(srcspec);
srcspec.format = (SDL_AudioFormat)format;
srcspec.channels = channels;
srcspec.freq = (int)rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
return -1;
}
music->sample_rate = rate;
break;
case MPG123_DONE:
if (amount > 0) {
if (!SDL_PutAudioStreamData(music->stream, music->buffer, (int)amount)) {
return -1;
}
break;
}
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (MPG123_Play(music, play_count) < 0) {
return -1;
}
}
break;
default:
SDL_SetError("mpg123_read: %s", mpg_err(music->handle, result));
return -1;
}
return 0;
}
static int MPG123_GetAudio(void *context, void *data, int bytes)
{
MPG123_Music *music = (MPG123_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, MPG123_GetSome);
}
static int MPG123_Seek(void *context, double secs)
{
MPG123_Music *music = (MPG123_Music *)context;
off_t offset = (off_t)(music->sample_rate * secs);
if ((offset = mpg123.mpg123_seek(music->handle, offset, SEEK_SET)) < 0) {
SDL_SetError("mpg123_seek: %s", mpg_err(music->handle, (int)-offset));
return -1;
}
return 0;
}
static double MPG123_Tell(void *context)
{
MPG123_Music *music = (MPG123_Music *)context;
off_t offset = 0;
if (!music->sample_rate) {
return 0.0;
}
if ((offset = mpg123.mpg123_tell(music->handle)) < 0) {
SDL_SetError("mpg123_tell: %s", mpg_err(music->handle, (int)-offset));
return -1.0;
}
return (double)offset / music->sample_rate;
}
/* Return music duration in seconds */
static double MPG123_Duration(void *context)
{
MPG123_Music *music = (MPG123_Music *)context;
if (music->total_length < 0) {
return -1.0;
}
return (double)music->total_length / music->sample_rate;
}
static const char* MPG123_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
MPG123_Music *music = (MPG123_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
static void MPG123_Delete(void *context)
{
MPG123_Music *music = (MPG123_Music *)context;
meta_tags_clear(&music->tags);
if (music->handle) {
mpg123.mpg123_close(music->handle);
mpg123.mpg123_delete(music->handle);
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->mp3file.src);
}
SDL_free(music);
}
static void MPG123_Close(void)
{
mpg123.mpg123_exit();
}
Mix_MusicInterface Mix_MusicInterface_MPG123 =
{
"MPG123",
MIX_MUSIC_MPG123,
MUS_MP3,
false,
false,
MPG123_Load,
MPG123_Open,
MPG123_CreateFromIO,
NULL, /* CreateFromFile */
MPG123_SetVolume,
MPG123_GetVolume,
MPG123_Play,
NULL, /* IsPlaying */
MPG123_GetAudio,
NULL, /* Jump */
MPG123_Seek,
MPG123_Tell,
MPG123_Duration,
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
MPG123_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
MPG123_Stop,
MPG123_Delete,
MPG123_Close,
MPG123_Unload
};
#endif /* MUSIC_MP3_MPG123 */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MP3 files with mpg123 */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_MPG123;
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,123 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_MID_NATIVE
/* This file supports playing MIDI files with OS APIs */
#include "music_nativemidi.h"
#include "native_midi/native_midi.h"
static void *NATIVEMIDI_CreateFromIO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *music = native_midi_loadsong_IO(src, closeio);
if (!music) {
SDL_SetError("%s", native_midi_error());
}
return music;
}
static int NATIVEMIDI_Play(void *context, int play_count)
{
NativeMidiSong *music = (NativeMidiSong *)context;
int loops = play_count;
if (loops > 0) {
--loops;
}
native_midi_start(music, loops);
return 0;
}
static void NATIVEMIDI_SetVolume(void *context, int volume)
{
(void)context;
native_midi_setvolume(volume);
}
static bool NATIVEMIDI_IsPlaying(void *context)
{
(void)context;
return native_midi_active();
}
static void NATIVEMIDI_Pause(void *context)
{
(void)context;
native_midi_pause();
}
static void NATIVEMIDI_Resume(void *context)
{
(void)context;
native_midi_resume();
}
static void NATIVEMIDI_Stop(void *context)
{
(void)context;
native_midi_stop();
}
static void NATIVEMIDI_Delete(void *context)
{
NativeMidiSong *music = (NativeMidiSong *)context;
native_midi_freesong(music);
}
Mix_MusicInterface Mix_MusicInterface_NATIVEMIDI =
{
"NATIVEMIDI",
MIX_MUSIC_NATIVEMIDI,
MUS_MID,
false,
false,
NULL, /* Load */
NULL, /* Open */
NATIVEMIDI_CreateFromIO,
NULL, /* CreateFromFile */
NATIVEMIDI_SetVolume,
NULL, /* GetVolume */
NATIVEMIDI_Play,
NATIVEMIDI_IsPlaying,
NULL, /* GetAudio */
NULL, /* Jump */
NULL, /* Seek */
NULL, /* Tell */
NULL, /* Duration */
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NATIVEMIDI_Pause,
NATIVEMIDI_Resume,
NATIVEMIDI_Stop,
NATIVEMIDI_Delete,
NULL, /* Close */
NULL /* Unload */
};
#endif /* MUSIC_MID_NATIVE */
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MIDI files with OS APIs */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_NATIVEMIDI;
/* vi: set ts=4 sw=4 expandtab: */
+571
View File
@@ -0,0 +1,571 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#if defined(MUSIC_OGG) && !defined(OGG_USE_STB)
/* This file supports Ogg Vorbis music streams */
#include <SDL3/SDL_loadso.h>
#include "music_ogg.h"
#include "utils.h"
#define OV_EXCLUDE_STATIC_CALLBACKS
#if defined(OGG_HEADER)
#include OGG_HEADER
#elif defined(OGG_USE_TREMOR)
#include <tremor/ivorbisfile.h>
#else
#include <vorbis/vorbisfile.h>
#endif
typedef struct {
int loaded;
void *handle;
int (*ov_clear)(OggVorbis_File *vf);
vorbis_info *(*ov_info)(OggVorbis_File *vf,int link);
vorbis_comment *(*ov_comment)(OggVorbis_File *vf,int link);
int (*ov_open_callbacks)(void *datasource, OggVorbis_File *vf, const char *initial, long ibytes, ov_callbacks callbacks);
ogg_int64_t (*ov_pcm_total)(OggVorbis_File *vf,int i);
#ifdef OGG_USE_TREMOR
long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int *bitstream);
int (*ov_time_seek)(OggVorbis_File *vf,ogg_int64_t pos);
ogg_int64_t (*ov_time_tell)(OggVorbis_File *vf);
ogg_int64_t (*ov_time_total)(OggVorbis_File *vf, int i);
#else
long (*ov_read)(OggVorbis_File *vf,char *buffer,int length, int bigendianp,int word,int sgned,int *bitstream);
int (*ov_time_seek)(OggVorbis_File *vf,double pos);
double (*ov_time_tell)(OggVorbis_File *vf);
double (*ov_time_total)(OggVorbis_File *vf, int i);
#endif
int (*ov_pcm_seek)(OggVorbis_File *vf, ogg_int64_t pos);
ogg_int64_t (*ov_pcm_tell)(OggVorbis_File *vf);
} vorbis_loader;
static vorbis_loader vorbis;
#ifdef OGG_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
vorbis.FUNC = (SIG) SDL_LoadFunction(vorbis.handle, #FUNC); \
if (vorbis.FUNC == NULL) { SDL_UnloadObject(vorbis.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
vorbis.FUNC = FUNC; \
if (vorbis.FUNC == NULL) { SDL_SetError("Missing vorbis.framework or tremor.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int OGG_Load(void)
{
if (vorbis.loaded == 0) {
#ifdef OGG_DYNAMIC
vorbis.handle = SDL_LoadObject(OGG_DYNAMIC);
if (vorbis.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(ov_clear, int (*)(OggVorbis_File *))
FUNCTION_LOADER(ov_info, vorbis_info *(*)(OggVorbis_File *,int))
FUNCTION_LOADER(ov_comment, vorbis_comment *(*)(OggVorbis_File *,int))
FUNCTION_LOADER(ov_open_callbacks, int (*)(void *,OggVorbis_File *,const char *,long,ov_callbacks))
FUNCTION_LOADER(ov_pcm_total, ogg_int64_t (*)(OggVorbis_File *,int))
#ifdef OGG_USE_TREMOR
FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int *))
FUNCTION_LOADER(ov_time_seek, int (*)(OggVorbis_File *,ogg_int64_t))
FUNCTION_LOADER(ov_time_tell, ogg_int64_t (*)(OggVorbis_File *))
FUNCTION_LOADER(ov_time_total, ogg_int64_t (*)(OggVorbis_File *, int))
#else
FUNCTION_LOADER(ov_read, long (*)(OggVorbis_File *,char *,int,int,int,int,int *))
FUNCTION_LOADER(ov_time_seek, int (*)(OggVorbis_File *,double))
FUNCTION_LOADER(ov_time_tell, double (*)(OggVorbis_File *))
FUNCTION_LOADER(ov_time_total, double (*)(OggVorbis_File *, int))
#endif
FUNCTION_LOADER(ov_pcm_seek, int (*)(OggVorbis_File *,ogg_int64_t))
FUNCTION_LOADER(ov_pcm_tell, ogg_int64_t (*)(OggVorbis_File *))
}
++vorbis.loaded;
return 0;
}
static void OGG_Unload(void)
{
if (vorbis.loaded == 0) {
return;
}
if (vorbis.loaded == 1) {
#ifdef OGG_DYNAMIC
SDL_UnloadObject(vorbis.handle);
#endif
}
--vorbis.loaded;
}
typedef struct {
SDL_IOStream *src;
bool closeio;
int play_count;
int volume;
OggVorbis_File vf;
vorbis_info vi;
int section;
SDL_AudioStream *stream;
char *buffer;
int buffer_size;
int loop;
ogg_int64_t loop_start;
ogg_int64_t loop_end;
ogg_int64_t loop_len;
Mix_MusicMetaTags tags;
} OGG_music;
static int set_ov_error(const char *function, int error)
{
#define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break;
switch (error) {
HANDLE_ERROR_CASE(OV_FALSE)
HANDLE_ERROR_CASE(OV_EOF)
HANDLE_ERROR_CASE(OV_HOLE)
HANDLE_ERROR_CASE(OV_EREAD)
HANDLE_ERROR_CASE(OV_EFAULT)
HANDLE_ERROR_CASE(OV_EIMPL)
HANDLE_ERROR_CASE(OV_EINVAL)
HANDLE_ERROR_CASE(OV_ENOTVORBIS)
HANDLE_ERROR_CASE(OV_EBADHEADER)
HANDLE_ERROR_CASE(OV_EVERSION)
HANDLE_ERROR_CASE(OV_ENOTAUDIO)
HANDLE_ERROR_CASE(OV_EBADPACKET)
HANDLE_ERROR_CASE(OV_EBADLINK)
HANDLE_ERROR_CASE(OV_ENOSEEK)
default:
SDL_SetError("%s: unknown error %d\n", function, error);
break;
}
return -1;
}
static size_t sdl_read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
{
if (size > 0 && nmemb > 0) {
return SDL_ReadIO((SDL_IOStream*)datasource, ptr, size * nmemb) / size;
}
return 0;
}
static int sdl_seek_func(void *datasource, ogg_int64_t offset, int whence)
{
return (SDL_SeekIO((SDL_IOStream*)datasource, offset, whence) < 0)? -1 : 0;
}
static long sdl_tell_func(void *datasource)
{
return (long) SDL_TellIO((SDL_IOStream*)datasource);
}
static int sdl_close_func(void *datasource)
{
(void)datasource;
return 0;
}
static int OGG_Seek(void *context, double time);
static void OGG_Delete(void *context);
static int OGG_UpdateSection(OGG_music *music)
{
SDL_AudioSpec srcspec;
vorbis_info *vi;
vi = vorbis.ov_info(&music->vf, -1);
if (!vi) {
SDL_SetError("ov_info returned NULL");
return -1;
}
if (vi->channels == music->vi.channels && vi->rate == music->vi.rate) {
return 0;
}
SDL_memcpy(&music->vi, vi, sizeof(*vi));
if (music->buffer) {
SDL_free(music->buffer);
music->buffer = NULL;
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
music->stream = NULL;
}
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = vi->channels;
srcspec.freq = (int)vi->rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
return -1;
}
music->buffer_size = 4096/*music_spec.samples*/ * (int)sizeof(Sint16) * vi->channels;
music->buffer = (char *)SDL_malloc((size_t)music->buffer_size);
if (!music->buffer) {
return -1;
}
return 0;
}
/* Load an OGG stream from an SDL_IOStream object */
static void *OGG_CreateFromIO(SDL_IOStream *src, bool closeio)
{
OGG_music *music;
ov_callbacks callbacks;
vorbis_comment *vc;
long rate;
ogg_int64_t full_length;
bool is_loop_length = false;
int i;
music = (OGG_music *)SDL_calloc(1, sizeof *music);
if (!music) {
return NULL;
}
music->src = src;
music->volume = MIX_MAX_VOLUME;
music->section = -1;
callbacks.read_func = sdl_read_func;
callbacks.seek_func = sdl_seek_func;
callbacks.close_func = sdl_close_func;
callbacks.tell_func = sdl_tell_func;
if (vorbis.ov_open_callbacks(src, &music->vf, NULL, 0, callbacks) < 0) {
SDL_SetError("Not an Ogg Vorbis audio stream");
SDL_free(music);
return NULL;
}
if (OGG_UpdateSection(music) < 0) {
OGG_Delete(music);
return NULL;
}
rate = music->vi.rate;
vc = vorbis.ov_comment(&music->vf, -1);
if (vc != NULL) {
for (i = 0; i < vc->comments; i++) {
char *param = SDL_strdup(vc->user_comments[i]);
char *argument = param;
char *value = SDL_strchr(param, '=');
if (value == NULL) {
value = param + SDL_strlen(param);
} else {
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
music->loop_start = _Mix_ParseTime(value, rate);
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
music->loop_len = SDL_strtoll(value, NULL, 10);
is_loop_length = true;
} else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
music->loop_end = _Mix_ParseTime(value, rate);
is_loop_length = false;
} else if (SDL_strcasecmp(argument, "TITLE") == 0) {
meta_tags_set(&music->tags, MIX_META_TITLE, value);
} else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
meta_tags_set(&music->tags, MIX_META_ARTIST, value);
} else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
meta_tags_set(&music->tags, MIX_META_ALBUM, value);
} else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
}
SDL_free(param);
}
if (is_loop_length) {
music->loop_end = music->loop_start + music->loop_len;
} else {
music->loop_len = music->loop_end - music->loop_start;
}
/* Ignore invalid loop tag */
if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
music->loop_start = 0;
music->loop_len = 0;
music->loop_end = 0;
}
}
full_length = vorbis.ov_pcm_total(&music->vf, -1);
if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
(music->loop_start < music->loop_end)) {
music->loop = 1;
}
music->closeio = closeio;
return music;
}
static const char* OGG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
OGG_music *music = (OGG_music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Set the volume for an OGG stream */
static void OGG_SetVolume(void *context, int volume)
{
OGG_music *music = (OGG_music *)context;
music->volume = volume;
}
/* Get the volume for an OGG stream */
static int OGG_GetVolume(void *context)
{
OGG_music *music = (OGG_music *)context;
return music->volume;
}
/* Start playback of a given OGG stream */
static int OGG_Play(void *context, int play_count)
{
OGG_music *music = (OGG_music *)context;
music->play_count = play_count;
return OGG_Seek(music, 0.0);
}
static void OGG_Stop(void *context)
{
OGG_music *music = (OGG_music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Play some of a stream previously started with OGG_play() */
static int OGG_GetSome(void *context, void *data, int bytes, bool *done)
{
OGG_music *music = (OGG_music *)context;
bool looped = false;
int filled, amount, result;
int section;
ogg_int64_t pcmPos;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
section = music->section;
#ifdef OGG_USE_TREMOR
amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, &section);
#else
amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, &section);
#endif
if (amount < 0) {
return set_ov_error("ov_read", amount);
}
if (section != music->section) {
music->section = section;
if (OGG_UpdateSection(music) < 0) {
return -1;
}
}
pcmPos = vorbis.ov_pcm_tell(&music->vf);
if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) {
amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(Sint16);
result = vorbis.ov_pcm_seek(&music->vf, music->loop_start);
if (result < 0) {
return set_ov_error("ov_pcm_seek", result);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
music->play_count = play_count;
}
looped = true;
}
if (amount > 0) {
if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) {
return -1;
}
} else if (!looped) {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (OGG_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int OGG_GetAudio(void *context, void *data, int bytes)
{
OGG_music *music = (OGG_music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, OGG_GetSome);
}
/* Jump (seek) to a given position (time is in seconds) */
static int OGG_Seek(void *context, double time)
{
OGG_music *music = (OGG_music *)context;
int result;
#ifdef OGG_USE_TREMOR
result = vorbis.ov_time_seek(&music->vf, (ogg_int64_t)(time * 1000.0));
#else
result = vorbis.ov_time_seek(&music->vf, time);
#endif
if (result < 0) {
return set_ov_error("ov_time_seek", result);
}
return 0;
}
static double OGG_Tell(void *context)
{
OGG_music *music = (OGG_music *)context;
#ifdef OGG_USE_TREMOR
return vorbis.ov_time_tell(&music->vf) / 1000.0;
#else
return vorbis.ov_time_tell(&music->vf);
#endif
}
/* Return music duration in seconds */
static double OGG_Duration(void *context)
{
OGG_music *music = (OGG_music *)context;
#ifdef OGG_USE_TREMOR
return vorbis.ov_time_total(&music->vf, -1) / 1000.0;
#else
return vorbis.ov_time_total(&music->vf, -1);
#endif
}
static double OGG_LoopStart(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_start / music->vi.rate;
}
return -1.0;
}
static double OGG_LoopEnd(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_end / music->vi.rate;
}
return -1.0;
}
static double OGG_LoopLength(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_len / music->vi.rate;
}
return -1.0;
}
/* Close the given OGG stream */
static void OGG_Delete(void *context)
{
OGG_music *music = (OGG_music *)context;
meta_tags_clear(&music->tags);
vorbis.ov_clear(&music->vf);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->src);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_OGG =
{
"OGG",
MIX_MUSIC_OGG,
MUS_OGG,
false,
false,
OGG_Load,
NULL, /* Open */
OGG_CreateFromIO,
NULL, /* CreateFromFile */
OGG_SetVolume,
OGG_GetVolume,
OGG_Play,
NULL, /* IsPlaying */
OGG_GetAudio,
NULL, /* Jump */
OGG_Seek,
OGG_Tell,
OGG_Duration,
OGG_LoopStart,
OGG_LoopEnd,
OGG_LoopLength,
OGG_GetMetaTag, /* GetMetaTag */
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OGG_Stop,
OGG_Delete,
NULL, /* Close */
OGG_Unload
};
#endif /* MUSIC_OGG */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports Ogg Vorbis music streams */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_OGG;
/* vi: set ts=4 sw=4 expandtab: */
+495
View File
@@ -0,0 +1,495 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#if defined(MUSIC_OGG) && defined(OGG_USE_STB)
/* This file supports Ogg Vorbis music streams using a modified stb_vorbis module */
#include "music_ogg.h"
#include "utils.h"
#include <SDL3/SDL_assert.h>
#define STB_VORBIS_SDL 1 /* for SDL_mixer-specific stuff. */
#define STB_VORBIS_NO_STDIO 1
#define STB_VORBIS_NO_CRT 1
#define STB_VORBIS_NO_PUSHDATA_API 1
#define STB_VORBIS_MAX_CHANNELS 8 /* For 7.1 surround sound */
#define STB_FORCEINLINE SDL_FORCE_INLINE
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define STB_VORBIS_BIG_ENDIAN 1
#endif
#define STBV_CDECL SDLCALL /* for SDL_qsort() */
#ifdef assert
#undef assert
#endif
#ifdef memset
#undef memset
#endif
#ifdef memcpy
#undef memcpy
#endif
#define assert SDL_assert
#define memset SDL_memset
#define memcmp SDL_memcmp
#define memcpy SDL_memcpy
#define qsort SDL_qsort
#define malloc SDL_malloc
#define realloc SDL_realloc
#define free SDL_free
#define pow SDL_pow
#define floor SDL_floor
#define ldexp(v, e) SDL_scalbn((v), (e))
#define abs(x) SDL_abs(x)
#define cos(x) SDL_cos(x)
#define sin(x) SDL_sin(x)
#define log(x) SDL_log(x)
#define exp(x) SDL_exp(x)
#include "stb_vorbis/stb_vorbis.h"
typedef struct {
SDL_IOStream *src;
bool closeio;
int play_count;
int volume;
stb_vorbis *vf;
stb_vorbis_info vi;
int section;
SDL_AudioStream *stream;
char *buffer;
int buffer_size;
int loop;
Sint64 loop_start;
Sint64 loop_end;
Sint64 loop_len;
Sint64 full_length;
Mix_MusicMetaTags tags;
} OGG_music;
static int set_ov_error(const char *function, int error)
{
#define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break;
switch (error) {
HANDLE_ERROR_CASE(VORBIS_need_more_data)
HANDLE_ERROR_CASE(VORBIS_invalid_api_mixing)
HANDLE_ERROR_CASE(VORBIS_outofmem)
HANDLE_ERROR_CASE(VORBIS_feature_not_supported)
HANDLE_ERROR_CASE(VORBIS_too_many_channels)
HANDLE_ERROR_CASE(VORBIS_file_open_failure)
HANDLE_ERROR_CASE(VORBIS_seek_without_length)
HANDLE_ERROR_CASE(VORBIS_unexpected_eof)
HANDLE_ERROR_CASE(VORBIS_seek_invalid)
HANDLE_ERROR_CASE(VORBIS_invalid_setup)
HANDLE_ERROR_CASE(VORBIS_invalid_stream)
HANDLE_ERROR_CASE(VORBIS_missing_capture_pattern)
HANDLE_ERROR_CASE(VORBIS_invalid_stream_structure_version)
HANDLE_ERROR_CASE(VORBIS_continued_packet_flag_invalid)
HANDLE_ERROR_CASE(VORBIS_incorrect_stream_serial_number)
HANDLE_ERROR_CASE(VORBIS_invalid_first_page)
HANDLE_ERROR_CASE(VORBIS_bad_packet_type)
HANDLE_ERROR_CASE(VORBIS_cant_find_last_page)
HANDLE_ERROR_CASE(VORBIS_seek_failed)
HANDLE_ERROR_CASE(VORBIS_ogg_skeleton_not_supported)
default:
SDL_SetError("%s: unknown error %d\n", function, error);
break;
}
return -1;
}
static int OGG_Seek(void *context, double time);
static void OGG_Delete(void *context);
static int OGG_UpdateSection(OGG_music *music)
{
stb_vorbis_info vi;
SDL_AudioSpec srcspec;
vi = stb_vorbis_get_info(music->vf);
if (vi.channels == music->vi.channels && vi.sample_rate == music->vi.sample_rate) {
return 0;
}
SDL_memcpy(&music->vi, &vi, sizeof(vi));
if (music->buffer) {
SDL_free(music->buffer);
music->buffer = NULL;
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
music->stream = NULL;
}
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_F32;
srcspec.channels = vi.channels;
srcspec.freq = (int)vi.sample_rate;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
return -1;
}
music->buffer_size = 4096/*music_spec.samples*/ * (int)sizeof(float) * vi.channels;
if (music->buffer_size <= 0) {
return -1;
}
music->buffer = (char *)SDL_malloc((size_t)music->buffer_size);
if (!music->buffer) {
return -1;
}
return 0;
}
/* Load an OGG stream from an SDL_IOStream object */
static void *OGG_CreateFromIO(SDL_IOStream *src, bool closeio)
{
OGG_music *music;
stb_vorbis_comment vc;
long rate;
bool is_loop_length = false;
int i, error;
music = (OGG_music *)SDL_calloc(1, sizeof *music);
if (!music) {
return NULL;
}
music->src = src;
music->volume = MIX_MAX_VOLUME;
music->section = -1;
music->vf = stb_vorbis_open_io(src, 0, &error, NULL);
if (music->vf == NULL) {
set_ov_error("stb_vorbis_open_io", error);
SDL_free(music);
return NULL;
}
if (OGG_UpdateSection(music) < 0) {
OGG_Delete(music);
return NULL;
}
music->vi = stb_vorbis_get_info(music->vf);
if ((int)music->vi.sample_rate <= 0) {
SDL_SetError("Invalid sample rate value");
OGG_Delete(music);
return NULL;
}
rate = music->vi.sample_rate;
music->full_length = stb_vorbis_stream_length_in_samples(music->vf);
if (music->full_length <= 0) {
SDL_SetError("No samples in ogg/vorbis stream.");
OGG_Delete(music);
return NULL;
}
vc = stb_vorbis_get_comment(music->vf);
if (vc.comment_list != NULL) {
for (i = 0; i < vc.comment_list_length; i++) {
char *param = SDL_strdup(vc.comment_list[i]);
char *argument = param;
char *value = SDL_strchr(param, '=');
if (value == NULL) {
value = param + SDL_strlen(param);
} else {
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
music->loop_start = _Mix_ParseTime(value, rate);
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
music->loop_len = SDL_strtoll(value, NULL, 10);
is_loop_length = true;
} else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
music->loop_end = _Mix_ParseTime(value, rate);
is_loop_length = false;
} else if (SDL_strcasecmp(argument, "TITLE") == 0) {
meta_tags_set(&music->tags, MIX_META_TITLE, value);
} else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
meta_tags_set(&music->tags, MIX_META_ARTIST, value);
} else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
meta_tags_set(&music->tags, MIX_META_ALBUM, value);
} else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
}
SDL_free(param);
}
if (is_loop_length) {
music->loop_end = music->loop_start + music->loop_len;
} else {
music->loop_len = music->loop_end - music->loop_start;
}
/* Ignore invalid loop tag */
if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
music->loop_start = 0;
music->loop_len = 0;
music->loop_end = 0;
}
}
if ((music->loop_end > 0) && (music->loop_end <= music->full_length) &&
(music->loop_start < music->loop_end)) {
music->loop = 1;
}
OGG_Seek(music, 0.0);
music->closeio = closeio;
return music;
}
static const char* OGG_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
OGG_music *music = (OGG_music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Set the volume for an OGG stream */
static void OGG_SetVolume(void *context, int volume)
{
OGG_music *music = (OGG_music *)context;
music->volume = volume;
}
/* Get the volume for an OGG stream */
static int OGG_GetVolume(void *context)
{
OGG_music *music = (OGG_music *)context;
return music->volume;
}
/* Start playback of a given OGG stream */
static int OGG_Play(void *context, int play_count)
{
OGG_music *music = (OGG_music *)context;
music->play_count = play_count;
return OGG_Seek(music, 0.0);
}
static void OGG_Stop(void *context)
{
OGG_music *music = (OGG_music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Play some of a stream previously started with OGG_play() */
static int OGG_GetSome(void *context, void *data, int bytes, bool *done)
{
OGG_music *music = (OGG_music *)context;
bool looped = false;
int filled, amount, result;
int section;
Sint64 pcmPos;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
section = music->section;
amount = stb_vorbis_get_samples_float_interleaved(music->vf,
music->vi.channels,
(float *)music->buffer,
4096/*music_spec.samples*/ * music->vi.channels);
amount *= music->vi.channels * sizeof(float);
if (section != music->section) {
music->section = section;
if (OGG_UpdateSection(music) < 0) {
return -1;
}
}
pcmPos = stb_vorbis_get_playback_sample_offset(music->vf);
if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) {
amount -= (int)((pcmPos - music->loop_end) * music->vi.channels) * (int)sizeof(float);
result = stb_vorbis_seek(music->vf, (Uint32)music->loop_start);
if (!result) {
return set_ov_error("stb_vorbis_seek", stb_vorbis_get_error(music->vf));
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
music->play_count = play_count;
}
looped = true;
}
if (amount > 0) {
if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) {
return -1;
}
} else if (!looped) {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (OGG_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int OGG_GetAudio(void *context, void *data, int bytes)
{
OGG_music *music = (OGG_music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, OGG_GetSome);
}
/* Jump (seek) to a given position (time is in seconds) */
static int OGG_Seek(void *context, double time)
{
OGG_music *music = (OGG_music *)context;
int result;
result = stb_vorbis_seek(music->vf, (unsigned int)(time * music->vi.sample_rate));
if (!result) {
return set_ov_error("stb_vorbis_seek", stb_vorbis_get_error(music->vf));
}
return 0;
}
static double OGG_Tell(void *context)
{
OGG_music *music = (OGG_music *)context;
return (double)stb_vorbis_get_playback_sample_offset(music->vf) / music->vi.sample_rate;
}
/* Return music duration in seconds */
static double OGG_Duration(void *context)
{
OGG_music *music = (OGG_music *)context;
return (double)music->full_length / music->vi.sample_rate;
}
static double OGG_LoopStart(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_start / music->vi.sample_rate;
}
return -1.0;
}
static double OGG_LoopEnd(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_end / music->vi.sample_rate;
}
return -1.0;
}
static double OGG_LoopLength(void *music_p)
{
OGG_music *music = (OGG_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_len / music->vi.sample_rate;
}
return -1.0;
}
/* Close the given OGG stream */
static void OGG_Delete(void *context)
{
OGG_music *music = (OGG_music *)context;
meta_tags_clear(&music->tags);
stb_vorbis_close(music->vf);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->src);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_OGG =
{
"OGG",
MIX_MUSIC_OGG,
MUS_OGG,
false,
false,
NULL, /* Load */
NULL, /* Open */
OGG_CreateFromIO,
NULL, /* CreateFromFile */
OGG_SetVolume,
OGG_GetVolume,
OGG_Play,
NULL, /* IsPlaying */
OGG_GetAudio,
NULL, /* Jump */
OGG_Seek,
OGG_Tell,
OGG_Duration,
OGG_LoopStart,
OGG_LoopEnd,
OGG_LoopLength,
OGG_GetMetaTag, /* GetMetaTag */
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OGG_Stop,
OGG_Delete,
NULL, /* Close */
NULL /* Unload */
};
#endif /* MUSIC_OGG */
/* vi: set ts=4 sw=4 expandtab: */
+535
View File
@@ -0,0 +1,535 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_OPUS
/* This file supports Ogg Opus music streams */
#include <SDL3/SDL_loadso.h>
#include "music_opus.h"
#include "utils.h"
#ifdef OPUSFILE_HEADER
#include OPUSFILE_HEADER
#else
#include <opus/opusfile.h>
#endif
typedef struct {
int loaded;
void *handle;
const OpusTags *(*op_tags)(const OggOpusFile *,int);
OggOpusFile *(*op_open_callbacks)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *);
void (*op_free)(OggOpusFile *);
const OpusHead *(*op_head)(const OggOpusFile *,int);
int (*op_seekable)(const OggOpusFile *);
int (*op_read)(OggOpusFile *, opus_int16 *,int,int *);
int (*op_pcm_seek)(OggOpusFile *,ogg_int64_t);
ogg_int64_t (*op_pcm_tell)(const OggOpusFile *);
ogg_int64_t (*op_pcm_total)(const OggOpusFile *, int);
} opus_loader;
static opus_loader opus;
#ifdef OPUS_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
opus.FUNC = (SIG) SDL_LoadFunction(opus.handle, #FUNC); \
if (opus.FUNC == NULL) { SDL_UnloadObject(opus.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
opus.FUNC = FUNC; \
if (opus.FUNC == NULL) { SDL_SetError("Missing opus.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int OPUS_Load(void)
{
if (opus.loaded == 0) {
#ifdef OPUS_DYNAMIC
opus.handle = SDL_LoadObject(OPUS_DYNAMIC);
if (opus.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(op_open_callbacks, OggOpusFile *(*)(void *,const OpusFileCallbacks *,const unsigned char *,size_t,int *))
FUNCTION_LOADER(op_tags, const OpusTags *(*)(const OggOpusFile *,int))
FUNCTION_LOADER(op_free, void (*)(OggOpusFile *))
FUNCTION_LOADER(op_head, const OpusHead *(*)(const OggOpusFile *,int))
FUNCTION_LOADER(op_seekable, int (*)(const OggOpusFile *))
FUNCTION_LOADER(op_read, int (*)(OggOpusFile *, opus_int16 *,int,int *))
FUNCTION_LOADER(op_pcm_seek, int (*)(OggOpusFile *,ogg_int64_t))
FUNCTION_LOADER(op_pcm_tell, ogg_int64_t (*)(const OggOpusFile *))
FUNCTION_LOADER(op_pcm_total, ogg_int64_t (*)(const OggOpusFile *, int))
}
++opus.loaded;
return 0;
}
static void OPUS_Unload(void)
{
if (opus.loaded == 0) {
return;
}
if (opus.loaded == 1) {
#ifdef OPUS_DYNAMIC
SDL_UnloadObject(opus.handle);
#endif
}
--opus.loaded;
}
typedef struct {
SDL_IOStream *src;
bool closeio;
int play_count;
int volume;
OggOpusFile *of;
const OpusHead *op_info;
int section;
SDL_AudioStream *stream;
char *buffer;
int buffer_size;
int loop;
ogg_int64_t loop_start;
ogg_int64_t loop_end;
ogg_int64_t loop_len;
ogg_int64_t full_length;
Mix_MusicMetaTags tags;
} OPUS_music;
static int set_op_error(const char *function, int error)
{
#define HANDLE_ERROR_CASE(X) case X: SDL_SetError("%s: %s", function, #X); break;
switch (error) {
HANDLE_ERROR_CASE(OP_FALSE)
HANDLE_ERROR_CASE(OP_EOF)
HANDLE_ERROR_CASE(OP_HOLE)
HANDLE_ERROR_CASE(OP_EREAD)
HANDLE_ERROR_CASE(OP_EFAULT)
HANDLE_ERROR_CASE(OP_EIMPL)
HANDLE_ERROR_CASE(OP_EINVAL)
HANDLE_ERROR_CASE(OP_ENOTFORMAT)
HANDLE_ERROR_CASE(OP_EBADHEADER)
HANDLE_ERROR_CASE(OP_EVERSION)
HANDLE_ERROR_CASE(OP_ENOTAUDIO)
HANDLE_ERROR_CASE(OP_EBADPACKET)
HANDLE_ERROR_CASE(OP_EBADLINK)
HANDLE_ERROR_CASE(OP_ENOSEEK)
HANDLE_ERROR_CASE(OP_EBADTIMESTAMP)
default:
SDL_SetError("%s: unknown error %d\n", function, error);
break;
}
return -1;
}
static int sdl_read_func(void *datasource, unsigned char *ptr, int size)
{
return (int) SDL_ReadIO((SDL_IOStream*)datasource, ptr, (size_t)size);
}
static int sdl_seek_func(void *datasource, opus_int64 offset, int whence)
{
return (SDL_SeekIO((SDL_IOStream*)datasource, offset, whence) < 0)? -1 : 0;
}
static opus_int64 sdl_tell_func(void *datasource)
{
return SDL_TellIO((SDL_IOStream*)datasource);
}
static int OPUS_Seek(void*, double);
static void OPUS_Delete(void*);
static int OPUS_UpdateSection(OPUS_music *music)
{
const OpusHead *op_info;
SDL_AudioSpec srcspec;
op_info = opus.op_head(music->of, -1);
if (!op_info) {
SDL_SetError("op_head returned NULL");
return -1;
}
if (music->op_info && op_info->channel_count == music->op_info->channel_count) {
return 0;
}
music->op_info = op_info;
if (music->buffer) {
SDL_free(music->buffer);
music->buffer = NULL;
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
music->stream = NULL;
}
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = op_info->channel_count;
srcspec.freq = 48000;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
return -1;
}
music->buffer_size = (int)4096/*music_spec.samples*/ * (int)sizeof(opus_int16) * op_info->channel_count;
music->buffer = (char *)SDL_malloc((size_t)music->buffer_size);
if (!music->buffer) {
return -1;
}
return 0;
}
/* Load an Opus stream from an SDL_IOStream object */
static void *OPUS_CreateFromIO(SDL_IOStream *src, bool closeio)
{
OPUS_music *music;
OpusFileCallbacks callbacks;
const OpusTags* tags;
int err = 0, ci;
bool is_loop_length = false;
ogg_int64_t full_length;
music = (OPUS_music *)SDL_calloc(1, sizeof *music);
if (!music) {
return NULL;
}
music->src = src;
music->volume = MIX_MAX_VOLUME;
music->section = -1;
SDL_zero(callbacks);
callbacks.read = sdl_read_func;
callbacks.seek = sdl_seek_func;
callbacks.tell = sdl_tell_func;
music->of = opus.op_open_callbacks(src, &callbacks, NULL, 0, &err);
if (music->of == NULL) {
/* set_op_error("op_open_callbacks", err);*/
SDL_SetError("Not an Opus audio stream");
SDL_free(music);
return NULL;
}
if (!opus.op_seekable(music->of)) {
OPUS_Delete(music);
SDL_SetError("Opus stream not seekable");
return NULL;
}
if (OPUS_UpdateSection(music) < 0) {
OPUS_Delete(music);
return NULL;
}
tags = opus.op_tags(music->of, -1);
if (tags != NULL) {
for (ci = 0; ci < tags->comments; ci++) {
char *param = SDL_strdup(tags->user_comments[ci]);
char *argument = param;
char *value = SDL_strchr(param, '=');
if (value == NULL) {
value = param + SDL_strlen(param);
} else {
*(value++) = '\0';
}
/* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from
* string if it is present at position 4. */
if (_Mix_IsLoopTag(argument) && ((argument[4] == '_') || (argument[4] == '-'))) {
SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4);
}
if (SDL_strcasecmp(argument, "LOOPSTART") == 0)
music->loop_start = _Mix_ParseTime(value, 48000);
else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) {
music->loop_len = SDL_strtoll(value, NULL, 10);
is_loop_length = true;
} else if (SDL_strcasecmp(argument, "LOOPEND") == 0) {
music->loop_end = _Mix_ParseTime(value, 48000);
is_loop_length = false;
} else if (SDL_strcasecmp(argument, "TITLE") == 0) {
meta_tags_set(&music->tags, MIX_META_TITLE, value);
} else if (SDL_strcasecmp(argument, "ARTIST") == 0) {
meta_tags_set(&music->tags, MIX_META_ARTIST, value);
} else if (SDL_strcasecmp(argument, "ALBUM") == 0) {
meta_tags_set(&music->tags, MIX_META_ALBUM, value);
} else if (SDL_strcasecmp(argument, "COPYRIGHT") == 0) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, value);
}
SDL_free(param);
}
if (is_loop_length) {
music->loop_end = music->loop_start + music->loop_len;
} else {
music->loop_len = music->loop_end - music->loop_start;
}
/* Ignore invalid loop tag */
if (music->loop_start < 0 || music->loop_len < 0 || music->loop_end < 0) {
music->loop_start = 0;
music->loop_len = 0;
music->loop_end = 0;
}
}
full_length = opus.op_pcm_total(music->of, -1);
if ((music->loop_end > 0) && (music->loop_end <= full_length) &&
(music->loop_start < music->loop_end)) {
music->loop = 1;
}
music->full_length = full_length;
music->closeio = closeio;
return music;
}
static const char* OPUS_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
OPUS_music *music = (OPUS_music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Set the volume for an Opus stream */
static void OPUS_SetVolume(void *context, int volume)
{
OPUS_music *music = (OPUS_music *)context;
music->volume = volume;
}
/* Get the volume for an Opus stream */
static int OPUS_GetVolume(void *context)
{
OPUS_music *music = (OPUS_music *)context;
return music->volume;
}
/* Start playback of a given Opus stream */
static int OPUS_Play(void *context, int play_count)
{
OPUS_music *music = (OPUS_music *)context;
music->play_count = play_count;
return OPUS_Seek(music, 0.0);
}
/* Clean-up the output buffer */
static void OPUS_Stop(void *context)
{
OPUS_music *music = (OPUS_music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Play some of a stream previously started with OPUS_Play() */
static int OPUS_GetSome(void *context, void *data, int bytes, bool *done)
{
OPUS_music *music = (OPUS_music *)context;
int filled, samples, section;
int result;
bool looped = false;
ogg_int64_t pcmPos;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
section = music->section;
samples = opus.op_read(music->of, (opus_int16 *)music->buffer, music->buffer_size / (int)sizeof(opus_int16), &section);
if (samples < 0) {
return set_op_error("op_read", samples);
}
if (section != music->section) {
music->section = section;
if (OPUS_UpdateSection(music) < 0) {
return -1;
}
}
pcmPos = opus.op_pcm_tell(music->of);
if (music->loop && (music->play_count != 1) && (pcmPos >= music->loop_end)) {
samples -= (int)((pcmPos - music->loop_end) * music->op_info->channel_count) * (int)sizeof(Sint16);
result = opus.op_pcm_seek(music->of, music->loop_start);
if (result < 0) {
return set_op_error("ov_pcm_seek", result);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
music->play_count = play_count;
}
looped = true;
}
if (samples > 0) {
filled = samples * music->op_info->channel_count * 2;
if (!SDL_PutAudioStreamData(music->stream, music->buffer, filled)) {
return -1;
}
} else if (!looped) {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (OPUS_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int OPUS_GetAudio(void *context, void *data, int bytes)
{
OPUS_music *music = (OPUS_music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, OPUS_GetSome);
}
/* Jump (seek) to a given position (time is in seconds) */
static int OPUS_Seek(void *context, double time)
{
OPUS_music *music = (OPUS_music *)context;
int result = opus.op_pcm_seek(music->of, (ogg_int64_t)(time * 48000));
if (result < 0) {
return set_op_error("op_pcm_seek", result);
}
return 0;
}
static double OPUS_Tell(void *context)
{
OPUS_music *music = (OPUS_music *)context;
return (double)(opus.op_pcm_tell(music->of)) / 48000.0;
}
/* Return music duration in seconds */
static double OPUS_Duration(void *context)
{
OPUS_music *music = (OPUS_music *)context;
return music->full_length / 48000.0;
}
static double OPUS_LoopStart(void *music_p)
{
OPUS_music *music = (OPUS_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_start / 48000.0;
}
return -1.0;
}
static double OPUS_LoopEnd(void *music_p)
{
OPUS_music *music = (OPUS_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_end / 48000.0;
}
return -1.0;
}
static double OPUS_LoopLength(void *music_p)
{
OPUS_music *music = (OPUS_music *)music_p;
if (music->loop > 0) {
return (double)music->loop_len / 48000.0;
}
return -1.0;
}
/* Close the given Opus stream */
static void OPUS_Delete(void *context)
{
OPUS_music *music = (OPUS_music *)context;
meta_tags_clear(&music->tags);
opus.op_free(music->of);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
if (music->closeio) {
SDL_CloseIO(music->src);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_Opus =
{
"OPUS",
MIX_MUSIC_OPUS,
MUS_OPUS,
false,
false,
OPUS_Load,
NULL, /* Open */
OPUS_CreateFromIO,
NULL, /* CreateFromFile */
OPUS_SetVolume,
OPUS_GetVolume,
OPUS_Play,
NULL, /* IsPlaying */
OPUS_GetAudio,
NULL, /* Jump */
OPUS_Seek,
OPUS_Tell,
OPUS_Duration,
OPUS_LoopStart,
OPUS_LoopEnd,
OPUS_LoopLength,
OPUS_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
OPUS_Stop,
OPUS_Delete,
NULL, /* Close */
OPUS_Unload
};
#endif /* MUSIC_OPUS */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports Ogg Opus music streams */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_Opus;
/* vi: set ts=4 sw=4 expandtab: */
+293
View File
@@ -0,0 +1,293 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MIDI files with timidity */
#ifdef MUSIC_MID_TIMIDITY
#include "music_timidity.h"
#include "timidity/timidity.h"
typedef struct
{
int play_count;
MidiSong *song;
SDL_AudioStream *stream;
void *buffer;
Sint32 buffer_size;
int volume;
} TIMIDITY_Music;
static int TIMIDITY_Seek(void *context, double position);
static void TIMIDITY_Delete(void *context);
/* Config file should contain any other directory that needs
* to be added to the search path. The library adds the path
* of the config file to its search path, too. */
#if defined(SDL_PLATFORM_WIN32)
# define TIMIDITY_CFG "C:\\TIMIDITY\\TIMIDITY.CFG"
#else /* unix: */
# define TIMIDITY_CFG_ETC "/etc/timidity.cfg"
# define TIMIDITY_CFG_FREEPATS "/etc/timidity/freepats.cfg"
#endif
static int TIMIDITY_Open(const SDL_AudioSpec *spec)
{
const char *cfg;
int rc = -1;
(void) spec;
cfg = SDL_getenv("TIMIDITY_CFG");
if(!cfg) cfg = Mix_GetTimidityCfg();
if (cfg) {
return Timidity_Init(cfg); /* env or user override: no other tries */
}
#if defined(TIMIDITY_CFG)
if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG);
#endif
#if defined(TIMIDITY_CFG_ETC)
if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG_ETC);
#endif
#if defined(TIMIDITY_CFG_FREEPATS)
if (rc < 0) rc = Timidity_Init(TIMIDITY_CFG_FREEPATS);
#endif
if (rc < 0) rc = Timidity_Init(NULL); /* library's default cfg. */
return rc;
}
static void TIMIDITY_Close(void)
{
Timidity_Exit();
}
void *TIMIDITY_CreateFromIO(SDL_IOStream *src, bool closeio)
{
TIMIDITY_Music *music;
SDL_AudioSpec spec;
bool need_stream = false;
music = (TIMIDITY_Music *)SDL_calloc(1, sizeof(*music));
if (!music) {
return NULL;
}
music->volume = MIX_MAX_VOLUME;
SDL_memcpy(&spec, &music_spec, sizeof(spec));
if (spec.channels > 2) {
need_stream = true;
spec.channels = 2;
}
music->song = Timidity_LoadSong(src, &spec);
if (!music->song) {
TIMIDITY_Delete(music);
return NULL;
}
if (need_stream) {
music->stream = SDL_CreateAudioStream(&spec, &music_spec);
if (!music->stream) {
TIMIDITY_Delete(music);
return NULL;
}
music->buffer_size = 4096/*spec.samples*/ * (SDL_AUDIO_BITSIZE(spec.format) / 8) * spec.channels;
music->buffer = SDL_malloc((size_t)music->buffer_size);
if (!music->buffer) {
TIMIDITY_Delete(music);
return NULL;
}
}
if (closeio) {
SDL_CloseIO(src);
}
return music;
}
static void TIMIDITY_SetVolume(void *context, int volume)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
music->volume = volume;
Timidity_SetVolume(music->song, volume);
}
static int TIMIDITY_GetVolume(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
return music->volume;
}
static int TIMIDITY_Play(void *context, int play_count)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
music->play_count = play_count;
Timidity_Start(music->song);
return TIMIDITY_Seek(music, 0.0);
}
static bool TIMIDITY_IsPlaying(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
return Timidity_IsActive(music->song);
}
static int TIMIDITY_GetSome(void *context, void *data, int bytes, bool *done)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
int filled, amount, expected;
if (music->stream) {
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
if (music->stream) {
expected = music->buffer_size;
amount = Timidity_PlaySome(music->song, music->buffer, music->buffer_size);
if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) {
return -1;
}
} else {
expected = bytes;
amount = Timidity_PlaySome(music->song, data, bytes);
}
if (amount < expected) {
if (music->play_count == 1) {
/* We didn't consume anything and we're done */
music->play_count = 0;
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (TIMIDITY_Play(music, play_count) < 0) {
return -1;
}
}
}
if (music->stream) {
/* We'll pick it up from the stream next time around */
return 0;
} else {
/* We wrote output data */
return amount;
}
}
static int TIMIDITY_GetAudio(void *context, void *data, int bytes)
{
return music_pcm_getaudio(context, data, bytes, MIX_MAX_VOLUME, TIMIDITY_GetSome);
}
static int TIMIDITY_Seek(void *context, double position)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
Timidity_Seek(music->song, (Uint32)(position * 1000));
return 0;
}
static double TIMIDITY_Tell(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
return Timidity_GetSongTime(music->song) / 1000.0;
}
static double TIMIDITY_Duration(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
return Timidity_GetSongLength(music->song) / 1000.0;
}
static void TIMIDITY_Delete(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
if (music->song) {
Timidity_FreeSong(music->song);
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
SDL_free(music);
}
static void TIMIDITY_Stop(void *context)
{
TIMIDITY_Music *music = (TIMIDITY_Music *)context;
Timidity_Stop(music->song);
}
Mix_MusicInterface Mix_MusicInterface_TIMIDITY =
{
"TIMIDITY",
MIX_MUSIC_TIMIDITY,
MUS_MID,
false,
false,
NULL, /* Load */
TIMIDITY_Open,
TIMIDITY_CreateFromIO,
NULL, /* CreateFromFile */
TIMIDITY_SetVolume,
TIMIDITY_GetVolume,
TIMIDITY_Play,
TIMIDITY_IsPlaying,
TIMIDITY_GetAudio,
NULL, /* Jump */
TIMIDITY_Seek,
TIMIDITY_Tell,
TIMIDITY_Duration,
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
NULL, /* GetMetaTag */
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
TIMIDITY_Stop,
TIMIDITY_Delete,
TIMIDITY_Close,
NULL /* Unload */
};
#endif /* MUSIC_MID_TIMIDITY */
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MIDI files with timidity */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_TIMIDITY;
/* vi: set ts=4 sw=4 expandtab: */
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports streaming WAV files */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_WAV;
/* vi: set ts=4 sw=4 expandtab: */
+771
View File
@@ -0,0 +1,771 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#if defined(MUSIC_WAVPACK)
#define WAVPACK_DBG 0
/* This file supports WavPack music streams */
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_log.h>
#include "music_wavpack.h"
#if defined(WAVPACK_HEADER)
#include WAVPACK_HEADER
#elif defined(HAVE_WAVPACK_H)
#include <wavpack.h>
#else
#include <wavpack/wavpack.h>
#endif
#include <stdio.h> /* SEEK_SET, ... */
#ifndef OPEN_DSD_NATIVE
#define OPEN_DSD_NATIVE 0x100
#define OPEN_DSD_AS_PCM 0x200
#define WAVPACK4_OR_OLDER
#endif
#ifdef WAVPACK4_OR_OLDER
typedef struct {
int32_t (*read_bytes)(void *id, void *data, int32_t bcount);
int32_t (*write_bytes)(void *id, void *data, int32_t bcount);
int64_t (*get_pos)(void *id);
int (*set_pos_abs)(void *id, int64_t pos);
int (*set_pos_rel)(void *id, int64_t delta, int mode);
int (*push_back_byte)(void *id, int c);
int64_t (*get_length)(void *id);
int (*can_seek)(void *id);
int (*truncate_here)(void *id);
int (*close)(void *id);
} WavpackStreamReader64;
#endif
typedef struct {
int loaded;
void *handle;
uint32_t libversion;
uint32_t (*WavpackGetLibraryVersion)(void);
char *(*WavpackGetErrorMessage)(WavpackContext*);
WavpackContext *(*WavpackOpenFileInputEx)(WavpackStreamReader *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset);
WavpackContext *(*WavpackCloseFile)(WavpackContext*);
int (*WavpackGetMode)(WavpackContext*);
int (*WavpackGetBytesPerSample)(WavpackContext*);
int (*WavpackGetNumChannels)(WavpackContext*);
uint32_t (*WavpackGetNumSamples)(WavpackContext*);
uint32_t (*WavpackGetSampleRate)(WavpackContext*);
uint32_t (*WavpackUnpackSamples)(WavpackContext*, int32_t *buffer, uint32_t samples);
int (*WavpackSeekSample)(WavpackContext*, uint32_t sample);
uint32_t (*WavpackGetSampleIndex)(WavpackContext*);
int (*WavpackGetTagItem)(WavpackContext*, const char *item, char *value, int size);
/* WavPack 5.x functions with 64 bit support: */
WavpackContext *(*WavpackOpenFileInputEx64)(WavpackStreamReader64 *reader, void *wv_id, void *wvc_id, char *error, int flags, int norm_offset);
int64_t (*WavpackGetNumSamples64)(WavpackContext*);
int64_t (*WavpackGetSampleIndex64)(WavpackContext*);
int (*WavpackSeekSample64)(WavpackContext*, int64_t sample);
} wavpack_loader;
static wavpack_loader wvpk;
#ifdef WAVPACK_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
wvpk.FUNC = (SIG) SDL_LoadFunction(wvpk.handle, #FUNC); \
if (wvpk.FUNC == NULL) { SDL_UnloadObject(wvpk.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
wvpk.FUNC = FUNC; \
if (wvpk.FUNC == NULL) { SDL_SetError("Missing wavpack.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int WAVPACK_Load(void)
{
if (wvpk.loaded == 0) {
#ifdef WAVPACK_DYNAMIC
wvpk.handle = SDL_LoadObject(WAVPACK_DYNAMIC);
if (wvpk.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(WavpackGetLibraryVersion, uint32_t (*)(void));
FUNCTION_LOADER(WavpackGetErrorMessage, char *(*)(WavpackContext*));
FUNCTION_LOADER(WavpackOpenFileInputEx, WavpackContext *(*)(WavpackStreamReader*, void*, void*, char*, int, int));
FUNCTION_LOADER(WavpackCloseFile, WavpackContext *(*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetMode, int (*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetBytesPerSample, int (*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetNumChannels, int (*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetNumSamples, uint32_t (*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetSampleRate, uint32_t (*)(WavpackContext*));
FUNCTION_LOADER(WavpackUnpackSamples, uint32_t (*)(WavpackContext*, int32_t*, uint32_t));
FUNCTION_LOADER(WavpackSeekSample, int (*)(WavpackContext*, uint32_t));
FUNCTION_LOADER(WavpackGetSampleIndex, uint32_t (*)(WavpackContext*));
FUNCTION_LOADER(WavpackGetTagItem, int (*)(WavpackContext*, const char*, char*, int));
/* WavPack 5.x functions with 64 bit support: */
#ifdef WAVPACK_DYNAMIC
wvpk.WavpackOpenFileInputEx64 = (WavpackContext *(*)(WavpackStreamReader64*, void*, void*, char*, int, int)) SDL_LoadFunction(wvpk.handle, "WavpackOpenFileInputEx64");
wvpk.WavpackGetNumSamples64 = (int64_t (*)(WavpackContext*)) SDL_LoadFunction(wvpk.handle, "WavpackGetNumSamples64");
wvpk.WavpackGetSampleIndex64 = (int64_t (*)(WavpackContext*)) SDL_LoadFunction(wvpk.handle, "WavpackGetSampleIndex64");
wvpk.WavpackSeekSample64 = (int (*)(WavpackContext*, int64_t)) SDL_LoadFunction(wvpk.handle, "WavpackSeekSample64");
if (!wvpk.WavpackOpenFileInputEx64 || !wvpk.WavpackGetNumSamples64 || !wvpk.WavpackGetSampleIndex64 || !wvpk.WavpackSeekSample64) {
wvpk.WavpackOpenFileInputEx64 = NULL;
wvpk.WavpackGetNumSamples64 = NULL;
wvpk.WavpackGetSampleIndex64 = NULL;
wvpk.WavpackSeekSample64 = NULL;
SDL_ClearError(); /* WavPack 5.x functions are optional. */
}
#if WAVPACK_DBG
else {
SDL_Log("WavPack 5.x functions available");
}
#endif
#elif !defined(WAVPACK4_OR_OLDER)
wvpk.WavpackOpenFileInputEx64 = WavpackOpenFileInputEx64;
wvpk.WavpackGetNumSamples64 = WavpackGetNumSamples64;
wvpk.WavpackGetSampleIndex64 = WavpackGetSampleIndex64;
wvpk.WavpackSeekSample64 = WavpackSeekSample64;
#else
wvpk.WavpackOpenFileInputEx64 = NULL;
wvpk.WavpackGetNumSamples64 = NULL;
wvpk.WavpackGetSampleIndex64 = NULL;
wvpk.WavpackSeekSample64 = NULL;
#endif
wvpk.libversion = wvpk.WavpackGetLibraryVersion();
#if WAVPACK_DBG
SDL_Log("WavPack library version: 0x%x", wvpk.libversion);
#endif
}
++wvpk.loaded;
return 0;
}
static void WAVPACK_Unload(void)
{
if (wvpk.loaded == 0) {
return;
}
if (wvpk.loaded == 1) {
#ifdef WAVPACK_DYNAMIC
SDL_UnloadObject(wvpk.handle);
#endif
}
--wvpk.loaded;
}
typedef struct {
SDL_IOStream *src1; /* wavpack file */
SDL_IOStream *src2; /* correction file */
bool closeio;
int play_count;
int volume;
WavpackContext *ctx;
int64_t numsamples;
uint32_t samplerate;
int bps, channels, mode;
#ifdef MUSIC_WAVPACK_DSD
int decimation;
void *decimation_ctx;
#endif
SDL_AudioStream *stream;
void *buffer;
int32_t frames;
Mix_MusicMetaTags tags;
} WAVPACK_music;
static int32_t sdl_read_bytes(void *id, void *data, int32_t bcount)
{
return (int32_t) SDL_ReadIO((SDL_IOStream*)id, data, (size_t)bcount);
}
static uint32_t sdl_get_pos32(void *id)
{
return (uint32_t) SDL_TellIO((SDL_IOStream*)id);
}
static int64_t sdl_get_pos64(void *id)
{
return SDL_TellIO((SDL_IOStream*)id);
}
static int sdl_setpos_rel64(void *id, int64_t delta, int mode)
{
switch (mode) { /* just in case SDL_IO doesn't match stdio.. */
case SEEK_SET: mode = SDL_IO_SEEK_SET; break;
case SEEK_CUR: mode = SDL_IO_SEEK_CUR; break;
case SEEK_END: mode = SDL_IO_SEEK_END; break;
default: return -1;
}
return (SDL_SeekIO((SDL_IOStream*)id, delta, mode) < 0)? -1 : 0;
}
static int sdl_setpos_rel32(void *id, int32_t delta, int mode)
{
return sdl_setpos_rel64(id, delta, mode);
}
static int sdl_setpos_abs64(void *id, int64_t pos)
{
return (SDL_SeekIO((SDL_IOStream*)id, pos, SDL_IO_SEEK_SET) < 0)? -1 : 0;
}
static int sdl_setpos_abs32(void *id, uint32_t pos)
{
return (SDL_SeekIO((SDL_IOStream*)id, pos, SDL_IO_SEEK_SET) < 0)? -1 : 0;
}
static int sdl_pushback_byte(void *id, int c)
{
(void)c;
/* libwavpack calls ungetc(), but doesn't really modify buffer. */
return (SDL_SeekIO((SDL_IOStream*)id, -1, SDL_IO_SEEK_CUR) < 0)? -1 : 0;
}
static uint32_t sdl_get_length32(void *id)
{
return (uint32_t) SDL_GetIOSize((SDL_IOStream*)id);
}
static int64_t sdl_get_length64(void *id)
{
return SDL_GetIOSize((SDL_IOStream*)id);
}
static int sdl_can_seek(void *id)
{
return (SDL_SeekIO((SDL_IOStream*)id, 0, SDL_IO_SEEK_CUR) < 0)? 0 : 1;
}
static WavpackStreamReader sdl_reader32 = {
sdl_read_bytes,
sdl_get_pos32,
sdl_setpos_abs32,
sdl_setpos_rel32,
sdl_pushback_byte,
sdl_get_length32,
sdl_can_seek,
NULL /* write_bytes */
};
static WavpackStreamReader64 sdl_reader64 = {
sdl_read_bytes,
NULL, /* write_bytes */
sdl_get_pos64,
sdl_setpos_abs64,
sdl_setpos_rel64,
sdl_pushback_byte,
sdl_get_length64,
sdl_can_seek,
NULL, /* truncate_here */
NULL /* close */
};
static int WAVPACK_Seek(void *context, double time);
static void WAVPACK_Delete(void *context);
static void *WAVPACK_CreateFromIO_internal(SDL_IOStream *src1, SDL_IOStream *src2, bool closeio, bool *closeio2);
#ifdef MUSIC_WAVPACK_DSD
static void *decimation_init(int num_channels, int ratio);
static int decimation_run(void *context, int32_t *samples, int num_samples);
static void decimation_reset(void *context);
#define FLAGS_DSD OPEN_DSD_AS_PCM
#define DECIMATION(x) (x)->decimation
#else
#define FLAGS_DSD 0
#define DECIMATION(x) 1
#endif
static void *WAVPACK_CreateFromIO(SDL_IOStream *src, bool closeio)
{
return WAVPACK_CreateFromIO_internal(src, NULL, closeio, NULL);
}
static void *WAVPACK_CreateFromFile(const char *file)
{
SDL_IOStream *src1, *src2;
WAVPACK_music *music;
bool closeio2 = true;
size_t len;
char *file2;
src1 = SDL_IOFromFile(file, "rb");
if (!src1) {
SDL_SetError("Couldn't open '%s'", file);
return NULL;
}
len = SDL_strlen(file);
file2 = SDL_stack_alloc(char, len + 2);
if (!file2) src2 = NULL;
else {
/* this assumes 'file' is a good boy and has 'wv' as an extension.
* official wavpack command line tools do the same thing so I'm not
* doing anything extra. besides, the wavpack library validates the
* correction file, therefore, no harm done. */
SDL_memcpy(file2, file, len);
file2[len] = 'c';
file2[len + 1] = '\0';
src2 = SDL_IOFromFile(file2, "rb");
#if WAVPACK_DBG
if (src2) {
SDL_Log("Loaded WavPack correction file %s", file2);
}
#endif
SDL_stack_free(file2);
}
music = WAVPACK_CreateFromIO_internal(src1, src2, true, &closeio2);
if (!music) {
SDL_CloseIO(src1);
if (closeio2 && src2) {
SDL_CloseIO(src2);
}
}
return music;
}
/* Load a WavPack stream from an SDL_IOStream object */
static void *WAVPACK_CreateFromIO_internal(SDL_IOStream *src1, SDL_IOStream *src2, bool closeio, bool *closeio2)
{
SDL_AudioSpec srcspec;
WAVPACK_music *music;
SDL_AudioFormat format;
char *tag;
char err[80];
int n;
music = (WAVPACK_music *)SDL_calloc(1, sizeof *music);
if (!music) {
return NULL;
}
music->src1 = src1;
music->src2 = src2;
music->volume = MIX_MAX_VOLUME;
music->ctx = (wvpk.WavpackOpenFileInputEx64 != NULL) ?
wvpk.WavpackOpenFileInputEx64(&sdl_reader64, src1, src2, err, OPEN_NORMALIZE|OPEN_TAGS|FLAGS_DSD, 0) :
wvpk.WavpackOpenFileInputEx(&sdl_reader32, src1, src2, err, OPEN_NORMALIZE|OPEN_TAGS, 0);
if (!music->ctx) {
SDL_SetError("%s", err);
SDL_free(music);
if (src2) {
SDL_CloseIO(src2);
}
return NULL;
}
music->numsamples = (wvpk.WavpackGetNumSamples64 != NULL) ?
wvpk.WavpackGetNumSamples64(music->ctx) :
wvpk.WavpackGetNumSamples(music->ctx);
music->samplerate = wvpk.WavpackGetSampleRate(music->ctx);
music->bps = wvpk.WavpackGetBytesPerSample(music->ctx) << 3;
music->channels = wvpk.WavpackGetNumChannels(music->ctx);
music->mode = wvpk.WavpackGetMode(music->ctx);
if (closeio2) {
*closeio2 = false; /* WAVPACK_Delete() will free it */
}
#ifdef MUSIC_WAVPACK_DSD
music->decimation = 1;
/* for very high sample rates (including DSD, which will normally be 352,800 Hz)
* decimate 4x here before sending on */
if (music->samplerate >= 256000) {
music->decimation = 4;
music->decimation_ctx = decimation_init(music->channels, music->decimation);
if (!music->decimation_ctx) {
WAVPACK_Delete(music);
return NULL;
}
}
#endif
#if WAVPACK_DBG
SDL_Log("WavPack loader:\n numsamples: %" SDL_PRIs64 "\n samplerate: %d\n bitspersample: %d\n channels: %d\n mode: 0x%x\n lossy: %d\n duration: %f\n",
(Sint64)music->numsamples, music->samplerate, music->bps, music->channels, music->mode, !(music->mode & MODE_LOSSLESS), music->numsamples / (double)music->samplerate);
#endif
/* library returns the samples in 8, 16, 24, or 32 bit depth, but
* always in an int32_t[] buffer, in signed host-endian format. */
switch (music->bps) {
case 8:
format = SDL_AUDIO_U8;
break;
case 16:
format = SDL_AUDIO_S16;
break;
default:
format = (music->mode & MODE_FLOAT) ? SDL_AUDIO_F32 : SDL_AUDIO_S32;
break;
}
SDL_zero(srcspec);
srcspec.format = format;
srcspec.channels = music->channels;
srcspec.freq = (int)music->samplerate / DECIMATION(music);
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
WAVPACK_Delete(music);
return NULL;
}
music->frames = 4096/*music_spec.samples*/;
music->buffer = SDL_malloc(music->frames * music->channels * sizeof(int32_t) * DECIMATION(music));
if (!music->buffer) {
WAVPACK_Delete(music);
return NULL;
}
tag = NULL;
n = wvpk.WavpackGetTagItem(music->ctx, "TITLE", NULL, 0);
if (n > 0) {
tag = SDL_realloc(tag, (size_t)(++n));
wvpk.WavpackGetTagItem(music->ctx, "TITLE", tag, n);
meta_tags_set(&music->tags, MIX_META_TITLE, tag);
}
n = wvpk.WavpackGetTagItem(music->ctx, "ARTIST", NULL, 0);
if (n > 0) {
tag = SDL_realloc(tag, (size_t)(++n));
wvpk.WavpackGetTagItem(music->ctx, "ARTIST", tag, n);
meta_tags_set(&music->tags, MIX_META_ARTIST, tag);
}
n = wvpk.WavpackGetTagItem(music->ctx, "ALBUM", NULL, 0);
if (n > 0) {
tag = SDL_realloc(tag, (size_t)(++n));
wvpk.WavpackGetTagItem(music->ctx, "ALBUM", tag, n);
meta_tags_set(&music->tags, MIX_META_ALBUM, tag);
}
n = wvpk.WavpackGetTagItem(music->ctx, "COPYRIGHT", NULL, 0);
if (n > 0) {
tag = SDL_realloc(tag, (size_t)(++n));
wvpk.WavpackGetTagItem(music->ctx, "COPYRIGHT", tag, n);
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, tag);
}
SDL_free(tag);
music->closeio = closeio;
return music;
}
static const char* WAVPACK_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
WAVPACK_music *music = (WAVPACK_music *)context;
return meta_tags_get(&music->tags, tag_type);
}
static void WAVPACK_SetVolume(void *context, int volume)
{
WAVPACK_music *music = (WAVPACK_music *)context;
music->volume = volume;
}
static int WAVPACK_GetVolume(void *context)
{
WAVPACK_music *music = (WAVPACK_music *)context;
return music->volume;
}
/* Start playback of a given WavPack stream */
static int WAVPACK_Play(void *context, int play_count)
{
WAVPACK_music *music = (WAVPACK_music *)context;
music->play_count = play_count;
return WAVPACK_Seek(music, 0.0);
}
static void WAVPACK_Stop(void *context)
{
WAVPACK_music *music = (WAVPACK_music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Play some of a stream previously started with WAVPACK_play() */
static int WAVPACK_GetSome(void *context, void *data, int bytes, bool *done)
{
WAVPACK_music *music = (WAVPACK_music *)context;
int amount;
amount = SDL_GetAudioStreamData(music->stream, data, bytes);
if (amount != 0) {
return amount;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
amount = (int) wvpk.WavpackUnpackSamples(music->ctx, music->buffer, music->frames * DECIMATION(music));
#ifdef MUSIC_WAVPACK_DSD
if (amount && music->decimation_ctx) {
amount = decimation_run(music->decimation_ctx, music->buffer, amount);
}
#endif
if (amount) {
int32_t *src = (int32_t *)music->buffer;
int c = 0;
amount *= music->channels;
switch (music->bps) {
case 8: {
Uint8 *dst = (Uint8 *)music->buffer;
for (; c < amount; ++c) {
*dst++ = 0x80 ^ (Uint8)*src++;
} }
break;
case 16: {
Sint16 *dst = (Sint16 *)music->buffer;
for (; c < amount; ++c) {
*dst++ = *src++;
} }
amount *= sizeof(Sint16);
break;
case 24:
for (; c < amount; ++c) {
src[c] <<= 8;
}
/* FALLTHRU */
default:
amount *= sizeof(Sint32);
break;
}
if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) {
return -1;
}
} else {
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (WAVPACK_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int WAVPACK_GetAudio(void *context, void *data, int bytes)
{
WAVPACK_music *music = (WAVPACK_music *)context;
return music_pcm_getaudio(music, data, bytes, music->volume, WAVPACK_GetSome);
}
/* Jump (seek) to a given position (time is in seconds) */
static int WAVPACK_Seek(void *context, double time)
{
WAVPACK_music *music = (WAVPACK_music *)context;
int64_t sample = (int64_t)(time * music->samplerate);
int success = (wvpk.WavpackSeekSample64 != NULL) ?
wvpk.WavpackSeekSample64(music->ctx, sample) :
wvpk.WavpackSeekSample(music->ctx, (uint32_t)sample);
if (!success) {
SDL_SetError("%s", wvpk.WavpackGetErrorMessage(music->ctx));
return -1;
}
#ifdef MUSIC_WAVPACK_DSD
if (music->decimation_ctx) {
decimation_reset(music->decimation_ctx);
}
#endif
return 0;
}
static double WAVPACK_Tell(void *context)
{
WAVPACK_music *music = (WAVPACK_music *)context;
if (wvpk.WavpackGetSampleIndex64 != NULL) {
return wvpk.WavpackGetSampleIndex64(music->ctx) / (double)music->samplerate;
}
return wvpk.WavpackGetSampleIndex(music->ctx) / (double)music->samplerate;
}
/* Return music duration in seconds */
static double WAVPACK_Duration(void *context)
{
WAVPACK_music *music = (WAVPACK_music *)context;
return music->numsamples / (double)music->samplerate;
}
/* Close the given WavPack stream */
static void WAVPACK_Delete(void *context)
{
WAVPACK_music *music = (WAVPACK_music *)context;
meta_tags_clear(&music->tags);
wvpk.WavpackCloseFile(music->ctx);
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
SDL_free(music->buffer);
#ifdef MUSIC_WAVPACK_DSD
SDL_free(music->decimation_ctx);
#endif
if (music->src2) {
SDL_CloseIO(music->src2);
}
if (music->closeio) {
SDL_CloseIO(music->src1);
}
SDL_free(music);
}
#ifdef MUSIC_WAVPACK_DSD
/* Decimation code for playing DSD (which comes from the library already decimated 8x) */
/* Code provided by David Bryant. */
/* sinc low-pass filter, cutoff = fs/12, 80 terms */
#define NUM_TERMS 80
static const int32_t filter[NUM_TERMS] = {
50, 464, 968, 711, -1203, -5028, -9818, -13376,
-12870, -6021, 7526, 25238, 41688, 49778, 43050, 18447,
-21428, -67553, -105876, -120890, -100640, -41752, 47201, 145510,
224022, 252377, 208224, 86014, -97312, -301919, -470919, -541796,
-461126, -199113, 239795, 813326, 1446343, 2043793, 2509064, 2763659,
2763659, 2509064, 2043793, 1446343, 813326, 239795, -199113, -461126,
-541796, -470919, -301919, -97312, 86014, 208224, 252377, 224022,
145510, 47201, -41752, -100640, -120890, -105876, -67553, -21428,
18447, 43050, 49778, 41688, 25238, 7526, -6021, -12870,
-13376, -9818, -5028, -1203, 711, 968, 464, 50
};
typedef struct chan_state {
int32_t delay[NUM_TERMS];
int index, num_channels, ratio;
} ChanState;
static void *decimation_init(int num_channels, int ratio)
{
ChanState *sp = (ChanState *)SDL_calloc(num_channels, sizeof(ChanState));
if (sp) {
int i = 0;
for (; i < num_channels; ++i) {
sp[i].num_channels = num_channels;
sp[i].index = NUM_TERMS - ratio;
sp[i].ratio = ratio;
}
}
return sp;
}
/** FIXME: This isn't particularly easy on the CPU ! **/
static int decimation_run(void *context, int32_t *samples, int num_samples)
{
ChanState *sp = (ChanState *)context;
int32_t *in_samples = samples;
int32_t *out_samples = samples;
const int num_channels = sp->num_channels;
const int ratio = sp->ratio;
int chan = 0;
while (num_samples) {
sp = (ChanState *)context + chan;
sp->delay[sp->index++] = *in_samples++;
if (sp->index == NUM_TERMS) {
int64_t sum = 0;
int i = 0;
for (; i < NUM_TERMS; ++i) {
sum += (int64_t)filter[i] * sp->delay[i];
}
*out_samples++ = (int32_t)(sum >> 24);
SDL_memmove(sp->delay, sp->delay + ratio, sizeof(sp->delay[0]) * (NUM_TERMS - ratio));
sp->index = NUM_TERMS - ratio;
}
if (++chan == num_channels) {
num_samples--;
chan = 0;
}
}
return (int)(out_samples - samples) / num_channels;
}
static void decimation_reset(void *context)
{
ChanState *sp = (ChanState *)context;
const int num_channels = sp->num_channels;
const int ratio = sp->ratio;
int i = 0;
SDL_memset(sp, 0, sizeof(ChanState) * num_channels);
for (; i < num_channels; ++i) {
sp[i].num_channels = num_channels;
sp[i].index = NUM_TERMS - ratio;
sp[i].ratio = ratio;
}
}
#endif /* MUSIC_WAVPACK_DSD */
Mix_MusicInterface Mix_MusicInterface_WAVPACK =
{
"WAVPACK",
MIX_MUSIC_WAVPACK,
MUS_WAVPACK,
false,
false,
WAVPACK_Load,
NULL, /* Open */
WAVPACK_CreateFromIO,
WAVPACK_CreateFromFile,
WAVPACK_SetVolume,
WAVPACK_GetVolume,
WAVPACK_Play,
NULL, /* IsPlaying */
WAVPACK_GetAudio,
NULL, /* Jump */
WAVPACK_Seek,
WAVPACK_Tell,
WAVPACK_Duration,
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
WAVPACK_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
WAVPACK_Stop,
WAVPACK_Delete,
NULL, /* Close */
WAVPACK_Unload
};
#endif /* MUSIC_WAVPACK */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports streaming WavPack files */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_WAVPACK;
/* vi: set ts=4 sw=4 expandtab: */
+468
View File
@@ -0,0 +1,468 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef MUSIC_MOD_XMP
#include <SDL3/SDL_loadso.h>
#include "music_xmp.h"
#ifdef LIBXMP_HEADER
#include LIBXMP_HEADER
#else
#include <xmp.h>
#endif
/* libxmp >= 4.5.0 constified several funcs */
/* and also added load using file callbacks */
#if (XMP_VERCODE < 0x040500)
struct xmp_callbacks {
unsigned long (*read_func)(void *, unsigned long, unsigned long, void *);
int (*seek_func)(void *, long, int);
long (*tell_func)(void *);
int (*close_func)(void*);
};
#define LIBXMP_CONST
#else
#define LIBXMP_CONST const
#endif
typedef struct {
int loaded;
void *handle;
xmp_context (*xmp_create_context)(void);
int (*xmp_load_module_from_memory)(xmp_context, LIBXMP_CONST void *, long);
int (*xmp_load_module_from_callbacks)(xmp_context, void *, struct xmp_callbacks);
int (*xmp_start_player)(xmp_context, int, int);
void (*xmp_end_player)(xmp_context);
void (*xmp_get_module_info)(xmp_context, struct xmp_module_info *);
int (*xmp_play_buffer)(xmp_context, void *, int, int);
int (*xmp_set_position)(xmp_context, int);
int (*xmp_seek_time)(xmp_context, int);
void (*xmp_get_frame_info)(xmp_context, struct xmp_frame_info *);
void (*xmp_stop_module)(xmp_context);
void (*xmp_release_module)(xmp_context);
void (*xmp_free_context)(xmp_context);
} xmp_loader;
static xmp_loader libxmp;
#ifdef XMP_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
libxmp.FUNC = (SIG) SDL_LoadFunction(libxmp.handle, #FUNC); \
if (libxmp.FUNC == NULL) { SDL_UnloadObject(libxmp.handle); return -1; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
libxmp.FUNC = FUNC; \
if (libxmp.FUNC == NULL) { SDL_SetError("Missing xmp.framework"); return -1; }
#endif
#ifdef __APPLE__
/* Need to turn off optimizations so weak framework load check works */
__attribute__ ((optnone))
#endif
static int XMP_Load(void)
{
if (libxmp.loaded == 0) {
#ifdef XMP_DYNAMIC
libxmp.handle = SDL_LoadObject(XMP_DYNAMIC);
if (libxmp.handle == NULL) {
return -1;
}
#endif
FUNCTION_LOADER(xmp_create_context, xmp_context(*)(void))
FUNCTION_LOADER(xmp_load_module_from_memory, int(*)(xmp_context,LIBXMP_CONST void *,long))
FUNCTION_LOADER(xmp_start_player, int(*)(xmp_context,int,int))
FUNCTION_LOADER(xmp_end_player, void(*)(xmp_context))
FUNCTION_LOADER(xmp_get_module_info, void(*)(xmp_context,struct xmp_module_info*))
FUNCTION_LOADER(xmp_play_buffer, int(*)(xmp_context,void*,int,int))
FUNCTION_LOADER(xmp_set_position, int(*)(xmp_context,int))
FUNCTION_LOADER(xmp_seek_time, int(*)(xmp_context,int))
FUNCTION_LOADER(xmp_get_frame_info, void(*)(xmp_context,struct xmp_frame_info*))
FUNCTION_LOADER(xmp_stop_module, void(*)(xmp_context))
FUNCTION_LOADER(xmp_release_module, void(*)(xmp_context))
FUNCTION_LOADER(xmp_free_context, void(*)(xmp_context))
#if defined(XMP_DYNAMIC)
libxmp.xmp_load_module_from_callbacks = (int (*)(xmp_context,void*,struct xmp_callbacks)) SDL_LoadFunction(libxmp.handle, "xmp_load_module_from_callbacks");
if (libxmp.xmp_load_module_from_callbacks == NULL) {
SDL_ClearError(); /* xmp_load_module_from_callbacks is optional. */
}
#elif (XMP_VERCODE >= 0x040500)
libxmp.xmp_load_module_from_callbacks = xmp_load_module_from_callbacks;
#else
libxmp.xmp_load_module_from_callbacks = NULL;
#endif
}
++libxmp.loaded;
return 0;
}
static void XMP_Unload(void)
{
if (libxmp.loaded == 0) {
return;
}
if (libxmp.loaded == 1) {
#ifdef XMP_DYNAMIC
SDL_UnloadObject(libxmp.handle);
#endif
}
--libxmp.loaded;
}
typedef struct
{
SDL_IOStream *src;
Sint64 src_offset;
int volume;
int play_count;
struct xmp_module_info mi;
struct xmp_frame_info fi;
xmp_context ctx;
SDL_AudioStream *stream;
void *buffer;
int buffer_size;
Mix_MusicMetaTags tags;
} XMP_Music;
static int XMP_Seek(void *ctx, double pos);
static void XMP_Delete(void *ctx);
static void libxmp_set_error(int e)
{
const char *msg;
switch (e) {
case -XMP_ERROR_INTERNAL:
msg = "Internal error in libxmp";
break;
case -XMP_ERROR_FORMAT:
msg = "Unrecognized file format";
break;
case -XMP_ERROR_LOAD:
msg = "Error loading file";
break;
case -XMP_ERROR_DEPACK:
msg = "Error depacking file";
break;
case -XMP_ERROR_SYSTEM:
msg = "System error in libxmp";
break;
case -XMP_ERROR_INVALID:
msg = "Invalid parameter";
break;
case -XMP_ERROR_STATE:
msg = "Invalid player state";
break;
default:
msg = "Unknown error";
break;
}
SDL_SetError("XMP: %s", msg);
}
static unsigned long xmp_fread(void *dst, unsigned long len, unsigned long nmemb, void *src)
{
XMP_Music *music = (XMP_Music *)src;
if (len > 0 && nmemb > 0) {
return SDL_ReadIO(music->src, dst, len * nmemb) / len;
}
return 0;
}
static int xmp_fseek(void *src, long offset, int whence)
{
XMP_Music *music = (XMP_Music *)src;
Sint64 offset64 = (Sint64)offset;
if (whence == SDL_IO_SEEK_SET) {
offset64 += music->src_offset;
}
return (SDL_SeekIO(music->src, offset64, whence) < 0) ? -1 : 0;
}
static long xmp_ftell(void *src)
{
XMP_Music *music = (XMP_Music *)src;
return (long)(SDL_TellIO(music->src) - music->src_offset);
}
/* Load a libxmp stream from an SDL_IOStream object */
void *XMP_CreateFromIO(SDL_IOStream *src, bool closeio)
{
SDL_AudioSpec srcspec;
XMP_Music *music;
struct xmp_callbacks file_callbacks = {
xmp_fread, xmp_fseek, xmp_ftell, NULL
};
int err;
music = (XMP_Music *)SDL_calloc(1, sizeof(*music));
if (!music) {
return NULL;
}
music->ctx = libxmp.xmp_create_context();
if (!music->ctx) {
SDL_OutOfMemory();
goto e0;
}
music->buffer_size = 4096/*music_spec.samples*/ * 2 * 2;
music->buffer = SDL_malloc((size_t)music->buffer_size);
if (!music->buffer) {
goto e1;
}
if (libxmp.xmp_load_module_from_callbacks) {
music->src = src;
music->src_offset = SDL_TellIO(src);
err = libxmp.xmp_load_module_from_callbacks(music->ctx, music, file_callbacks);
} else {
size_t size;
void *mem = SDL_LoadFile_IO(src, &size, false);
if (!mem) {
goto e1;
}
err = libxmp.xmp_load_module_from_memory(music->ctx, mem, (long)size);
SDL_free(mem);
}
if (err < 0) {
libxmp_set_error(err);
goto e1;
}
err = libxmp.xmp_start_player(music->ctx, music_spec.freq, 0);
if (err < 0) {
libxmp_set_error(err);
goto e2;
}
music->volume = MIX_MAX_VOLUME;
SDL_zero(srcspec);
srcspec.format = SDL_AUDIO_S16;
srcspec.channels = 2;
srcspec.freq = music_spec.freq;
music->stream = SDL_CreateAudioStream(&srcspec, &music_spec);
if (!music->stream) {
goto e3;
}
meta_tags_init(&music->tags);
libxmp.xmp_get_module_info(music->ctx, &music->mi);
if (music->mi.mod->name[0]) {
meta_tags_set(&music->tags, MIX_META_TITLE, music->mi.mod->name);
}
if (music->mi.comment) {
meta_tags_set(&music->tags, MIX_META_COPYRIGHT, music->mi.comment);
}
if (closeio) {
SDL_CloseIO(src);
}
return music;
e3: libxmp.xmp_end_player(music->ctx);
e2: libxmp.xmp_release_module(music->ctx);
e1: libxmp.xmp_free_context(music->ctx);
e0: SDL_free(music->buffer); SDL_free(music);
return NULL;
}
/* Set the volume for a libxmp stream */
static void XMP_SetVolume(void *context, int volume)
{
XMP_Music *music = (XMP_Music *)context;
music->volume = volume;
}
/* Get the volume for a libxmp stream */
static int XMP_GetVolume(void *context)
{
XMP_Music *music = (XMP_Music *)context;
return music->volume;
}
/* Start playback of a given libxmp stream */
static int XMP_Play(void *context, int play_count)
{
XMP_Music *music = (XMP_Music *)context;
music->play_count = play_count;
return XMP_Seek(music, 0);
}
/* Clean-up the output buffer */
static void XMP_Stop(void *context)
{
XMP_Music *music = (XMP_Music *)context;
SDL_ClearAudioStream(music->stream);
}
/* Play some of a stream previously started with xmp_play() */
static int XMP_GetSome(void *context, void *data, int bytes, bool *done)
{
XMP_Music *music = (XMP_Music *)context;
int filled, amount, ret;
filled = SDL_GetAudioStreamData(music->stream, data, bytes);
if (filled != 0) {
return filled;
}
if (!music->play_count) {
/* All done */
*done = true;
return 0;
}
/* if the data write is partial, rest of the buffer will be zero-filled.
* the loop param is the max number that the current sequence of song
* will be looped, or 0 to disable loop checking: 0 for play_count < 0
* for an endless loop, or 1 for our own loop checks to do their job. */
ret = libxmp.xmp_play_buffer(music->ctx, music->buffer, music->buffer_size, (music->play_count > 0));
amount = music->buffer_size;
if (ret == 0) {
if (!SDL_PutAudioStreamData(music->stream, music->buffer, amount)) {
return -1;
}
} else {
if (ret != -XMP_END) {
return -1;
}
if (music->play_count == 1) {
music->play_count = 0;
SDL_FlushAudioStream(music->stream);
} else {
int play_count = -1;
if (music->play_count > 0) {
play_count = (music->play_count - 1);
}
if (XMP_Play(music, play_count) < 0) {
return -1;
}
}
}
return 0;
}
static int XMP_GetAudio(void *context, void *data, int bytes)
{
XMP_Music *music = (XMP_Music *)context;
return music_pcm_getaudio(context, data, bytes, music->volume, XMP_GetSome);
}
/* Jump to a given order */
static int XMP_Jump(void *context, int order)
{
XMP_Music *music = (XMP_Music *)context;
return libxmp.xmp_set_position(music->ctx, order);
}
/* Jump (seek) to a given position */
static int XMP_Seek(void *context, double pos)
{
XMP_Music *music = (XMP_Music *)context;
libxmp.xmp_seek_time(music->ctx, (int)(pos * 1000));
libxmp.xmp_play_buffer(music->ctx, NULL, 0, 0); /* reset internal state. */
return 0;
}
static double XMP_Tell(void *context)
{
XMP_Music *music = (XMP_Music *)context;
libxmp.xmp_get_frame_info(music->ctx, &music->fi);
return music->fi.time / 1000.0;
}
static double XMP_Duration(void *context)
{
XMP_Music *music = (XMP_Music *)context;
libxmp.xmp_get_frame_info(music->ctx, &music->fi);
return music->fi.total_time / 1000.0;
}
static const char* XMP_GetMetaTag(void *context, Mix_MusicMetaTag tag_type)
{
XMP_Music *music = (XMP_Music *)context;
return meta_tags_get(&music->tags, tag_type);
}
/* Close the given libxmp stream */
static void XMP_Delete(void *context)
{
XMP_Music *music = (XMP_Music *)context;
meta_tags_clear(&music->tags);
if (music->ctx) {
libxmp.xmp_stop_module(music->ctx);
libxmp.xmp_end_player(music->ctx);
libxmp.xmp_release_module(music->ctx);
libxmp.xmp_free_context(music->ctx);
}
if (music->stream) {
SDL_DestroyAudioStream(music->stream);
}
if (music->buffer) {
SDL_free(music->buffer);
}
SDL_free(music);
}
Mix_MusicInterface Mix_MusicInterface_XMP =
{
"XMP",
MIX_MUSIC_LIBXMP,
MUS_MOD,
false,
false,
XMP_Load,
NULL, /* Open */
XMP_CreateFromIO,
NULL, /* CreateFromFile */
XMP_SetVolume,
XMP_GetVolume,
XMP_Play,
NULL, /* IsPlaying */
XMP_GetAudio,
XMP_Jump,
XMP_Seek,
XMP_Tell,
XMP_Duration,
NULL, /* LoopStart */
NULL, /* LoopEnd */
NULL, /* LoopLength */
XMP_GetMetaTag,
NULL, /* GetNumTracks */
NULL, /* StartTrack */
NULL, /* Pause */
NULL, /* Resume */
XMP_Stop,
XMP_Delete,
NULL, /* Close */
XMP_Unload
};
#endif /* MUSIC_MOD_XMP */
/* vi: set ts=4 sw=4 expandtab: */
+28
View File
@@ -0,0 +1,28 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* This file supports playing MOD files with libxmp */
#include "music.h"
extern Mix_MusicInterface Mix_MusicInterface_XMP;
/* vi: set ts=4 sw=4 expandtab: */
@@ -0,0 +1,40 @@
/*
native_midi: Hardware Midi support for the SDL_mixer library
Copyright (C) 2000 Florian 'Proff' Schulze <florian.proff.schulze@gmx.net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef NATIVE_MIDI_H_
#define NATIVE_MIDI_H_
#include <SDL3/SDL_iostream.h>
typedef struct _NativeMidiSong NativeMidiSong;
bool native_midi_detect(void);
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio);
void native_midi_freesong(NativeMidiSong *song);
void native_midi_start(NativeMidiSong *song, int loops);
void native_midi_pause(void);
void native_midi_resume(void);
void native_midi_stop(void);
bool native_midi_active(void);
void native_midi_setvolume(int volume);
const char *native_midi_error(void);
#endif /* NATIVE_MIDI_H_ */
@@ -0,0 +1,414 @@
/*
native_midi: Hardware Midi support for the SDL_mixer library
Copyright (C) 2000,2001 Florian 'Proff' Schulze <florian.proff.schulze@gmx.net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "native_midi_common.h"
#include <SDL3_mixer/SDL_mixer.h>
/* The constant 'MThd' */
#define MIDI_MAGIC 0x4d546864
/* The constant 'RIFF' */
#define RIFF_MAGIC 0x52494646
/* A single midi track as read from the midi file */
typedef struct
{
Uint8 *data; /* MIDI message stream */
int len; /* length of the track data */
} MIDITrack;
/* A midi file, stripped down to the absolute minimum - divison & track data */
typedef struct
{
int division; /* number of pulses per quarter note (ppqn) */
int nTracks; /* number of tracks */
MIDITrack *track; /* tracks */
} MIDIFile;
/* Some macros that help us stay endianess-independant */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define BE_SHORT(x) (x)
#define BE_LONG(x) (x)
#else
#define BE_SHORT(x) ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
#define BE_LONG(x) ((((x)&0x0000FF)<<24) | \
(((x)&0x00FF00)<<8) | \
(((x)&0xFF0000)>>8) | \
(((x)>>24)&0xFF))
#endif
/* Get Variable Length Quantity */
static int GetVLQ(MIDITrack *track, int *currentPos)
{
int l = 0;
Uint8 c;
while (1) {
c = track->data[*currentPos];
(*currentPos)++;
l += (c & 0x7f);
if (!(c & 0x80)) {
return l;
}
l <<= 7;
}
}
/* Create a single MIDIEvent */
static MIDIEvent *CreateEvent(Uint32 time, Uint8 event, Uint8 a, Uint8 b)
{
MIDIEvent *newEvent;
newEvent = SDL_calloc(1, sizeof(MIDIEvent));
if (newEvent) {
newEvent->time = time;
newEvent->status = event;
newEvent->data[0] = a;
newEvent->data[1] = b;
}
return newEvent;
}
/* Convert a single midi track to a list of MIDIEvents */
static MIDIEvent *MIDITracktoStream(MIDITrack *track)
{
Uint32 atime = 0;
Uint32 len = 0;
Uint8 event,type,a,b;
Uint8 laststatus = 0;
Uint8 lastchan = 0;
int currentPos = 0;
int end = 0;
MIDIEvent *head = CreateEvent(0,0,0,0); /* dummy event to make handling the list easier */
MIDIEvent *currentEvent = head;
while (!end) {
if (currentPos >= track->len) {
break; /* End of data stream reached */
}
atime += GetVLQ(track, &currentPos);
event = track->data[currentPos++];
/* Handle SysEx seperatly */
if (((event>>4) & 0x0F) == MIDI_STATUS_SYSEX) {
if (event == 0xFF) {
type = track->data[currentPos];
currentPos++;
switch(type) {
case 0x2f: /* End of data marker */
end = 1;
case 0x51: /* Tempo change */
/*
a=track->data[currentPos];
b=track->data[currentPos+1];
c=track->data[currentPos+2];
AddEvent(song, atime, MEVT_TEMPO, c, b, a);
*/
break;
}
} else {
type = 0;
}
len = GetVLQ(track, &currentPos);
/* Create an event and attach the extra data, if any */
currentEvent->next = CreateEvent(atime, event, type, 0);
currentEvent = currentEvent->next;
if (NULL == currentEvent) {
FreeMIDIEventList(head);
return NULL;
}
if (len) {
currentEvent->extraLen = len;
currentEvent->extraData = SDL_malloc(len);
SDL_memcpy(currentEvent->extraData, &(track->data[currentPos]), len);
currentPos += len;
}
} else {
a = event;
if (a & 0x80) { /* It's a status byte */
/* Extract channel and status information */
lastchan = a & 0x0F;
laststatus = (a>>4) & 0x0F;
/* Read the next byte which should always be a data byte */
a = track->data[currentPos++] & 0x7F;
}
switch(laststatus) {
case MIDI_STATUS_NOTE_OFF:
case MIDI_STATUS_NOTE_ON: /* Note on */
case MIDI_STATUS_AFTERTOUCH: /* Key Pressure */
case MIDI_STATUS_CONTROLLER: /* Control change */
case MIDI_STATUS_PITCH_WHEEL: /* Pitch wheel */
b = track->data[currentPos++] & 0x7F;
currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, b);
currentEvent = currentEvent->next;
if (NULL == currentEvent) {
FreeMIDIEventList(head);
return NULL;
}
break;
case MIDI_STATUS_PROG_CHANGE: /* Program change */
case MIDI_STATUS_PRESSURE: /* Channel pressure */
a &= 0x7f;
currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, 0);
currentEvent = currentEvent->next;
if (NULL == currentEvent) {
FreeMIDIEventList(head);
return NULL;
}
break;
default: /* Sysex already handled above */
break;
}
}
}
currentEvent = head->next;
SDL_free(head); /* release the dummy head event */
return currentEvent;
}
/*
* Convert a midi song, consisting of up to 32 tracks, to a list of MIDIEvents.
* To do so, first convert the tracks seperatly, then interweave the resulting
* MIDIEvent-Lists to one big list.
*/
static MIDIEvent *MIDItoStream(MIDIFile *mididata)
{
MIDIEvent **track;
MIDIEvent *head = CreateEvent(0,0,0,0); /* dummy event to make handling the list easier */
MIDIEvent *currentEvent = head;
int trackID;
if (NULL == head) {
return NULL;
}
track = (MIDIEvent**) SDL_calloc(1, sizeof(MIDIEvent*) * mididata->nTracks);
if (NULL == track) {
SDL_free(head);
return NULL;
}
/* First, convert all tracks to MIDIEvent lists */
for (trackID = 0; trackID < mididata->nTracks; trackID++) {
track[trackID] = MIDITracktoStream(&mididata->track[trackID]);
}
/* Now, merge the lists. */
/* TODO */
while (1) {
Uint32 lowestTime = 0x7FFFFFFF; /* INT_MAX */
int currentTrackID = -1;
/* Find the next event */
for (trackID = 0; trackID < mididata->nTracks; trackID++) {
if (track[trackID] && (track[trackID]->time < lowestTime)) {
currentTrackID = trackID;
lowestTime = track[currentTrackID]->time;
}
}
/* Check if we processes all events */
if (currentTrackID == -1) {
break;
}
currentEvent->next = track[currentTrackID];
track[currentTrackID] = track[currentTrackID]->next;
currentEvent = currentEvent->next;
lowestTime = 0;
}
/* Make sure the list is properly terminated */
currentEvent->next = 0;
currentEvent = head->next;
SDL_free(track);
SDL_free(head); /* release the dummy head event */
return currentEvent;
}
static int ReadMIDIFile(MIDIFile *mididata, SDL_IOStream *src)
{
int i = -1;
Uint32 ID = 0;
Uint32 size = 0;
Uint16 format = 0;
Uint16 tracks = 0;
Uint16 division = 0;
if (!mididata) {
return 0;
}
if (!src) {
return 0;
}
/* Make sure this is really a MIDI file */
if (SDL_ReadIO(src, &ID, 4) != 4) {
return 0;
}
if (BE_LONG(ID) == RIFF_MAGIC) {
SDL_SeekIO(src, 16, SDL_IO_SEEK_CUR);
if (SDL_ReadIO(src, &ID, 4) != 4) {
return 0;
}
}
if (BE_LONG(ID) != MIDI_MAGIC) {
return 0;
}
/* Header size must be 6 */
if (SDL_ReadIO(src, &size, 4) != 4) {
return 0;
}
size = BE_LONG(size);
if (size != 6) {
return 0;
}
/* We only support format 0 and 1, but not 2 */
if (SDL_ReadIO(src, &format, 2) != 2) {
return 0;
}
format = BE_SHORT(format);
if (format != 0 && format != 1) {
return 0;
}
if (SDL_ReadIO(src, &tracks, 2) != 2) {
return 0;
}
tracks = BE_SHORT(tracks);
mididata->nTracks = tracks;
/* Allocate tracks */
mididata->track = (MIDITrack*) SDL_calloc(1, sizeof(MIDITrack) * mididata->nTracks);
if (NULL == mididata->track) {
goto bail;
}
/* Retrieve the PPQN value, needed for playback */
if (SDL_ReadIO(src, &division, 2) != 2) {
goto bail;
}
mididata->division = BE_SHORT(division);
for (i = 0; i < tracks; i++) {
if (SDL_ReadIO(src, &ID, 4) != 4) { /* We might want to verify this is MTrk... */
goto bail;
}
if (SDL_ReadIO(src, &size, 4) != 4) {
goto bail;
}
size = BE_LONG(size);
mididata->track[i].len = size;
mididata->track[i].data = SDL_malloc(size);
if (NULL == mididata->track[i].data) {
goto bail;
}
if (SDL_ReadIO(src, mididata->track[i].data, size) != size) {
goto bail;
}
}
return 1;
bail:
for (; i >= 0; i--) {
if (mididata->track[i].data) {
SDL_free(mididata->track[i].data);
}
}
SDL_free(mididata->track);
return 0;
}
MIDIEvent *CreateMIDIEventList(SDL_IOStream *src, Uint16 *division)
{
MIDIFile *mididata = NULL;
MIDIEvent *eventList;
int trackID;
mididata = SDL_calloc(1, sizeof(MIDIFile));
if (!mididata) {
return NULL;
}
/* Open the file */
if (src != NULL) {
/* Read in the data */
if (!ReadMIDIFile(mididata, src)) {
SDL_free(mididata);
return NULL;
}
} else {
SDL_free(mididata);
return NULL;
}
if (division) {
*division = mididata->division;
}
eventList = MIDItoStream(mididata);
if (eventList == NULL) {
SDL_free(mididata);
return NULL;
}
for (trackID = 0; trackID < mididata->nTracks; trackID++) {
if (mididata->track[trackID].data) {
SDL_free(mididata->track[trackID].data);
}
}
SDL_free(mididata->track);
SDL_free(mididata);
return eventList;
}
void FreeMIDIEventList(MIDIEvent *head)
{
MIDIEvent *cur, *next;
cur = head;
while (cur) {
next = cur->next;
if (cur->extraData) {
SDL_free(cur->extraData);
}
SDL_free(cur);
cur = next;
}
}
@@ -0,0 +1,63 @@
/*
native_midi: Hardware Midi support for the SDL_mixer library
Copyright (C) 2000,2001 Florian 'Proff' Schulze <florian.proff.schulze@gmx.net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef _NATIVE_MIDI_COMMON_H_
#define _NATIVE_MIDI_COMMON_H_
#include <SDL3/SDL.h>
/* Midi Status Bytes */
#define MIDI_STATUS_NOTE_OFF 0x8
#define MIDI_STATUS_NOTE_ON 0x9
#define MIDI_STATUS_AFTERTOUCH 0xA
#define MIDI_STATUS_CONTROLLER 0xB
#define MIDI_STATUS_PROG_CHANGE 0xC
#define MIDI_STATUS_PRESSURE 0xD
#define MIDI_STATUS_PITCH_WHEEL 0xE
#define MIDI_STATUS_SYSEX 0xF
/* We store the midi events in a linked list; this way it is
easy to shuffle the tracks together later on; and we are
flexible in the size of each elemnt.
*/
typedef struct MIDIEvent
{
Uint32 time; /* Time at which this midi events occurs */
Uint8 status; /* Status byte */
Uint8 data[2]; /* 1 or 2 bytes additional data for most events */
Uint32 extraLen; /* For some SysEx events, we need additional storage */
Uint8 *extraData;
struct MIDIEvent *next;
} MIDIEvent;
/* Load a midifile to memory, converting it to a list of MIDIEvents.
This function returns a linked lists of MIDIEvents, 0 if an error occured.
*/
MIDIEvent *CreateMIDIEventList(SDL_IOStream *src, Uint16 *division);
/* Release a MIDIEvent list after usage. */
void FreeMIDIEventList(MIDIEvent *head);
#endif /* _NATIVE_MIDI_COMMON_H_ */
@@ -0,0 +1,295 @@
/*
native_midi_haiku: Native Midi support on Haiku for the SDL_mixer library
Copyright (C) 2010 Egor Suvorov <egor_suvorov@mail.ru>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <SDL3/SDL_platform.h>
#ifdef SDL_PLATFORM_HAIKU
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <MidiStore.h>
#include <MidiDefs.h>
#include <MidiSynthFile.h>
#include <algorithm>
#include <assert.h>
extern "C" {
#include "native_midi.h"
#include "native_midi_common.h"
}
bool compareMIDIEvent(const MIDIEvent &a, const MIDIEvent &b)
{
return a.time < b.time;
}
class MidiEventsStore : public BMidi
{
public:
MidiEventsStore()
{
fPlaying = false;
fLoops = 0;
}
virtual status_t Import(SDL_IOStream *src)
{
fEvs = CreateMIDIEventList(src, &fDivision);
if (!fEvs) {
return B_BAD_MIDI_DATA;
}
fTotal = 0;
for (MIDIEvent *x = fEvs; x; x = x->next) fTotal++;
fPos = fTotal;
sort_events();
return B_OK;
}
virtual void Run()
{
fPlaying = true;
fPos = 0;
MIDIEvent *ev = fEvs;
uint32 startTime = B_NOW;
while (KeepRunning())
{
if (!ev) {
if (fLoops && fEvs) {
if (fLoops > 0) --fLoops;
fPos = 0;
ev = fEvs;
} else
break;
}
SprayEvent(ev, ev->time + startTime);
ev = ev->next;
fPos++;
}
fPos = fTotal;
fPlaying = false;
}
virtual ~MidiEventsStore()
{
if (!fEvs) return;
FreeMIDIEventList(fEvs);
fEvs = 0;
}
bool IsPlaying()
{
return fPlaying;
}
void SetLoops(int loops)
{
fLoops = loops;
}
protected:
MIDIEvent *fEvs;
Uint16 fDivision;
int fPos, fTotal;
int fLoops;
bool fPlaying;
void SprayEvent(MIDIEvent *ev, uint32 time)
{
switch (ev->status & 0xF0)
{
case B_NOTE_OFF:
SprayNoteOff((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time);
break;
case B_NOTE_ON:
SprayNoteOn((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time);
break;
case B_KEY_PRESSURE:
SprayKeyPressure((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time);
break;
case B_CONTROL_CHANGE:
SprayControlChange((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time);
break;
case B_PROGRAM_CHANGE:
SprayProgramChange((ev->status & 0x0F) + 1, ev->data[0], time);
break;
case B_CHANNEL_PRESSURE:
SprayChannelPressure((ev->status & 0x0F) + 1, ev->data[0], time);
break;
case B_PITCH_BEND:
SprayPitchBend((ev->status & 0x0F) + 1, ev->data[0], ev->data[1], time);
break;
case 0xF:
switch (ev->status)
{
case B_SYS_EX_START:
SpraySystemExclusive(ev->extraData, ev->extraLen, time);
break;
case B_MIDI_TIME_CODE:
case B_SONG_POSITION:
case B_SONG_SELECT:
case B_CABLE_MESSAGE:
case B_TUNE_REQUEST:
case B_SYS_EX_END:
SpraySystemCommon(ev->status, ev->data[0], ev->data[1], time);
break;
case B_TIMING_CLOCK:
case B_START:
case B_STOP:
case B_CONTINUE:
case B_ACTIVE_SENSING:
SpraySystemRealTime(ev->status, time);
break;
case B_SYSTEM_RESET:
if (ev->data[0] == 0x51 && ev->data[1] == 0x03)
{
assert(ev->extraLen == 3);
int val = (ev->extraData[0] << 16) | (ev->extraData[1] << 8) | ev->extraData[2];
int tempo = 60000000 / val;
SprayTempoChange(tempo, time);
}
else
{
SpraySystemRealTime(ev->status, time);
}
}
break;
}
}
void sort_events()
{
MIDIEvent *items = new MIDIEvent[fTotal];
MIDIEvent *x = fEvs;
for (int i = 0; i < fTotal; i++)
{
memcpy(items + i, x, sizeof(MIDIEvent));
x = x->next;
}
std::sort(items, items + fTotal, compareMIDIEvent);
x = fEvs;
for (int i = 0; i < fTotal; i++)
{
MIDIEvent *ne = x->next;
memcpy(x, items + i, sizeof(MIDIEvent));
x->next = ne;
x = ne;
}
for (x = fEvs; x && x->next; x = x->next)
assert(x->time <= x->next->time);
delete[] items;
}
};
BMidiSynth synth;
struct _NativeMidiSong {
MidiEventsStore *store;
} *currentSong = NULL;
char lasterr[1024];
bool native_midi_detect(void)
{
status_t res = synth.EnableInput(true, false);
return res == B_OK;
}
void native_midi_setvolume(int volume)
{
if (volume < 0) volume = 0;
if (volume > 128) volume = 128;
synth.SetVolume(volume / 128.0);
}
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *song = new NativeMidiSong;
song->store = new MidiEventsStore;
status_t res = song->store->Import(src);
if (res != B_OK)
{
snprintf(lasterr, sizeof lasterr, "Cannot Import() midi file: status_t=%d", res);
delete song->store;
delete song;
return NULL;
}
else
{
if (closeio) {
SDL_CloseIO(src);
}
}
return song;
}
void native_midi_freesong(NativeMidiSong *song)
{
if (song == NULL) return;
song->store->Stop();
song->store->Disconnect(&synth);
if (currentSong == song)
{
currentSong = NULL;
}
delete song->store;
delete song; song = 0;
}
void native_midi_start(NativeMidiSong *song, int loops)
{
native_midi_stop();
song->store->Connect(&synth);
song->store->SetLoops(loops);
song->store->Start();
currentSong = song;
}
void native_midi_pause(void)
{
}
void native_midi_resume(void)
{
}
void native_midi_stop(void)
{
if (currentSong == NULL) return;
currentSong->store->Stop();
currentSong->store->Disconnect(&synth);
while (currentSong->store->IsPlaying())
usleep(1000);
currentSong = NULL;
}
bool native_midi_active(void)
{
if (currentSong == NULL) return false;
return currentSong->store->IsPlaying();
}
const char* native_midi_error(void)
{
return lasterr;
}
#endif /* SDL_PLATFORM_HAIKU */
@@ -0,0 +1,902 @@
/*
native_midi_linux_alsa: Native Midi support on Linux for the SDL_mixer library
Copyright (C) 2024 Tasos Sahanidis <code@tasossah.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <SDL3/SDL_platform.h>
#ifdef SDL_PLATFORM_LINUX
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <SDL3/SDL_hints.h>
#include <SDL3/SDL_atomic.h>
#include "native_midi.h"
#include "native_midi_common.h"
#include <alsa/asoundef.h>
#include <alsa/asoundlib.h>
#include <errno.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <assert.h>
//#define SDL_NATIVE_MIDI_ALSA_DYNAMIC "libasound.so.2"
static int load_alsa_syms(void);
#ifdef SDL_NATIVE_MIDI_ALSA_DYNAMIC
#define snd_seq_client_info_sizeof ALSA_snd_seq_client_info_sizeof
#define snd_seq_port_info_sizeof ALSA_snd_seq_port_info_sizeof
#define snd_seq_queue_tempo_sizeof ALSA_snd_seq_queue_tempo_sizeof
#define snd_seq_control_queue ALSA_snd_seq_control_queue
static void *alsa_handle = NULL;
static int load_alsa_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(alsa_handle, fn);
if (!*addr) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return 0;
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_ALSA_SYM(x) \
if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
return -1
static void unload_alsa_library(void)
{
if (alsa_handle) {
SDL_UnloadObject(alsa_handle);
alsa_handle = NULL;
}
}
static int load_alsa_library(void)
{
int retval = 0;
if (!alsa_handle) {
alsa_handle = SDL_LoadObject(SDL_NATIVE_MIDI_ALSA_DYNAMIC);
if (!alsa_handle) {
retval = -1;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_alsa_syms();
if (retval < 0) {
unload_alsa_library();
}
}
}
return retval;
}
#else
#define SDL_ALSA_SYM(x) ALSA_##x = x
static void unload_alsa_library(void)
{
}
static int load_alsa_library(void)
{
return load_alsa_syms();
}
#endif // SDL_NATIVE_MIDI_ALSA_DYNAMIC
static int (*ALSA_snd_seq_alloc_named_queue)(snd_seq_t *seq, const char *name);
static int (*ALSA_snd_seq_client_id)(snd_seq_t *handle);
static int (*ALSA_snd_seq_client_info_get_client)(const snd_seq_client_info_t *info);
static size_t (*ALSA_snd_seq_client_info_sizeof)(void);
static int (*ALSA_snd_seq_close)(snd_seq_t *handle);
static int (*ALSA_snd_seq_connect_to)(snd_seq_t *seq, int my_port, int dest_client, int dest_port);
static int (*ALSA_snd_seq_control_queue)(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev);
static int (*ALSA_snd_seq_create_simple_port)(snd_seq_t *seq, const char *name, unsigned int caps, unsigned int type);
static int (*ALSA_snd_seq_delete_simple_port)(snd_seq_t *seq, int port);
static int (*ALSA_snd_seq_drain_output)(snd_seq_t *handle);
static int (*ALSA_snd_seq_drop_output)(snd_seq_t *handle);
static int (*ALSA_snd_seq_event_input)(snd_seq_t *handle, snd_seq_event_t **ev);
static int (*ALSA_snd_seq_event_output)(snd_seq_t *handle, snd_seq_event_t *ev);
static int (*ALSA_snd_seq_event_output_direct)(snd_seq_t *handle, snd_seq_event_t *ev);
static int (*ALSA_snd_seq_free_queue)(snd_seq_t *handle, int q);
static int (*ALSA_snd_seq_get_any_client_info)(snd_seq_t *handle, int client, snd_seq_client_info_t *info);
static int (*ALSA_snd_seq_get_any_port_info)(snd_seq_t *handle, int client, int port, snd_seq_port_info_t *info);
static int (*ALSA_snd_seq_nonblock)(snd_seq_t *handle, int nonblock);
static int (*ALSA_snd_seq_open)(snd_seq_t **handle, const char *name, int streams, int mode);
static int (*ALSA_snd_seq_parse_address)(snd_seq_t *seq, snd_seq_addr_t *addr, const char *str);
static int (*ALSA_snd_seq_poll_descriptors)(snd_seq_t *handle, struct pollfd *pfds, unsigned int space, short events);
static unsigned int (*ALSA_snd_seq_port_info_get_capability)(const snd_seq_port_info_t *info);
static int (*ALSA_snd_seq_port_info_get_port)(const snd_seq_port_info_t *info);
static unsigned int (*ALSA_snd_seq_port_info_get_type)(const snd_seq_port_info_t *info);
static size_t (*ALSA_snd_seq_port_info_sizeof)(void);
static int (*ALSA_snd_seq_query_next_client)(snd_seq_t *handle, snd_seq_client_info_t *info);
static int (*ALSA_snd_seq_query_next_port)(snd_seq_t *handle, snd_seq_port_info_t *info);
static void (*ALSA_snd_seq_queue_tempo_set_ppq)(snd_seq_queue_tempo_t *info, int ppq);
static void (*ALSA_snd_seq_queue_tempo_set_tempo)(snd_seq_queue_tempo_t *info, unsigned int tempo);
static size_t (*ALSA_snd_seq_queue_tempo_sizeof)(void);
static int (*ALSA_snd_seq_set_client_event_filter)(snd_seq_t *seq, int event_type);
static int (*ALSA_snd_seq_set_client_name)(snd_seq_t *seq, const char *name);
static int (*ALSA_snd_seq_set_queue_tempo)(snd_seq_t *handle, int q, snd_seq_queue_tempo_t *tempo);
static int load_alsa_syms(void)
{
SDL_ALSA_SYM(snd_seq_alloc_named_queue);
SDL_ALSA_SYM(snd_seq_client_id);
SDL_ALSA_SYM(snd_seq_client_info_get_client);
SDL_ALSA_SYM(snd_seq_client_info_sizeof);
SDL_ALSA_SYM(snd_seq_close);
SDL_ALSA_SYM(snd_seq_connect_to);
SDL_ALSA_SYM(snd_seq_control_queue);
SDL_ALSA_SYM(snd_seq_create_simple_port);
SDL_ALSA_SYM(snd_seq_delete_simple_port);
SDL_ALSA_SYM(snd_seq_drain_output);
SDL_ALSA_SYM(snd_seq_drop_output);
SDL_ALSA_SYM(snd_seq_event_input);
SDL_ALSA_SYM(snd_seq_event_output);
SDL_ALSA_SYM(snd_seq_event_output_direct);
SDL_ALSA_SYM(snd_seq_free_queue);
SDL_ALSA_SYM(snd_seq_get_any_client_info);
SDL_ALSA_SYM(snd_seq_get_any_port_info);
SDL_ALSA_SYM(snd_seq_nonblock);
SDL_ALSA_SYM(snd_seq_open);
SDL_ALSA_SYM(snd_seq_parse_address);
SDL_ALSA_SYM(snd_seq_poll_descriptors);
SDL_ALSA_SYM(snd_seq_port_info_get_capability);
SDL_ALSA_SYM(snd_seq_port_info_get_port);
SDL_ALSA_SYM(snd_seq_port_info_get_type);
SDL_ALSA_SYM(snd_seq_port_info_sizeof);
SDL_ALSA_SYM(snd_seq_query_next_client);
SDL_ALSA_SYM(snd_seq_query_next_port);
SDL_ALSA_SYM(snd_seq_queue_tempo_set_ppq);
SDL_ALSA_SYM(snd_seq_queue_tempo_set_tempo);
SDL_ALSA_SYM(snd_seq_queue_tempo_sizeof);
SDL_ALSA_SYM(snd_seq_set_client_event_filter);
SDL_ALSA_SYM(snd_seq_set_client_name);
SDL_ALSA_SYM(snd_seq_set_queue_tempo);
return 0;
}
#ifndef NDEBUG
#define MIDIDbgLog(...) SDL_LogDebug(SDL_LOG_CATEGORY_AUDIO, __VA_ARGS__)
#else
#define MIDIDbgLog(...) \
do { \
} while (0)
#endif
#define MIDI_Mix_OutOfMemory() errmsg_final = oom
static const char *const oom = "Out of memory";
static char errmsg[256] = "";
static const char *errmsg_final = errmsg;
#define MIDI_SET_ERROR(...) SDL_snprintf(errmsg, sizeof(errmsg), __VA_ARGS__)
#define MIDI_SMF_META_EVENT 0xFF
#define MIDI_SMF_META_TEMPO 0x51
typedef enum
{
NATIVE_MIDI_STOPPED,
NATIVE_MIDI_STARTING,
NATIVE_MIDI_PLAYING,
NATIVE_MIDI_PAUSED
} native_midi_state;
typedef enum
{
THREAD_CMD_QUIT = 1,
THREAD_CMD_PAUSE,
THREAD_CMD_RESUME,
THREAD_CMD_SETVOL,
} native_midi_thread_cmd;
struct _NativeMidiSong
{
SDL_Thread *playerthread;
int mainsock, threadsock;
Uint16 ppqn;
MIDIEvent *evtlist;
snd_seq_t *seq;
int srcport;
snd_seq_addr_t dstaddr;
int loopcount;
Uint32 endtime;
SDL_AtomicInt playerstate; /* Stores a native_midi_state */
bool allow_pause;
};
/* Fixed length command packets */
#define CMD_PKT_LEN 2
static const unsigned char pkt_thread_cmd_quit[CMD_PKT_LEN] = { THREAD_CMD_QUIT };
static const unsigned char pkt_thread_cmd_pause[CMD_PKT_LEN] = { THREAD_CMD_PAUSE };
static const unsigned char pkt_thread_cmd_resume[CMD_PKT_LEN] = { THREAD_CMD_RESUME };
static SDL_INLINE const char *get_app_name_hint(void)
{
const char *ret = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_ICON_NAME);
if (ret && *ret)
return ret;
ret = SDL_GetHint(SDL_HINT_APP_NAME);
if (ret && *ret)
return ret;
return NULL;
}
/* Make sure to SDL_free this */
static char *get_app_name(void)
{
/* Try the SDL hints first */
const char *hint = get_app_name_hint();
if (hint) {
char *ret = SDL_strdup(hint);
if (!ret)
MIDI_Mix_OutOfMemory();
return ret;
}
/* Build the path to access the application's cmdline */
char procfs_path[64];
SDL_snprintf(procfs_path, sizeof(procfs_path), "/proc/%ld/cmdline", (long)getpid());
long pathmax = pathconf("/", _PC_PATH_MAX);
if (pathmax == -1)
pathmax = 4096;
char *cmdline = SDL_calloc(1, pathmax + 1);
if (!cmdline) {
MIDI_Mix_OutOfMemory();
return NULL;
}
size_t len = 0;
int fd = open(procfs_path, O_RDONLY);
if (fd >= 0) {
if (read(fd, cmdline, pathmax) > 0) {
char *base = SDL_strrchr(cmdline, '/') + 1; /* Absolute worst case we get to a '\0' */
if (base) {
len = strlen(base);
if (len)
SDL_memmove(cmdline, base, len + 1);
}
}
close(fd);
}
/* len is used both for checking if we read any data and if a path separator was found */
if (!len)
SDL_snprintf(cmdline, pathmax + 1, "SDL_Mixer Application");
return cmdline;
}
static snd_seq_t *open_seq(int *srcport_out)
{
snd_seq_t *seq;
int ret;
if (load_alsa_library()) {
MIDI_SET_ERROR("Failed to load libasound");
return NULL;
}
if ((ret = ALSA_snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0)) < 0) {
MIDI_SET_ERROR("snd_seq_open returned %d", ret);
return NULL;
}
char *seq_name = get_app_name();
if (!seq_name) {
ALSA_snd_seq_close(seq);
return NULL;
}
ALSA_snd_seq_set_client_name(seq, seq_name);
if ((ret = ALSA_snd_seq_create_simple_port(seq, seq_name,
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SYNC_READ,
SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC)) < 0) {
MIDI_SET_ERROR("snd_seq_create_simple_port failed with %d", ret);
ALSA_snd_seq_close(seq);
SDL_free(seq_name);
return NULL;
}
SDL_free(seq_name);
*srcport_out = ret;
return seq;
}
static void close_seq(snd_seq_t *seq, const int port)
{
ALSA_snd_seq_delete_simple_port(seq, port);
ALSA_snd_seq_close(seq);
unload_alsa_library();
}
bool native_midi_detect(void)
{
int port;
snd_seq_t *seq_temp = open_seq(&port);
if (!seq_temp)
return 0;
close_seq(seq_temp, port);
return 1;
}
static void close_sockpair(NativeMidiSong *song)
{
shutdown(song->mainsock, SHUT_RDWR);
shutdown(song->threadsock, SHUT_RDWR);
close(song->mainsock);
close(song->threadsock);
}
static SDL_INLINE int subscribe_to_first_available_port(snd_seq_t *seq, const int srcport, const unsigned int required_type)
{
snd_seq_client_info_t *clientinfo;
snd_seq_client_info_alloca(&clientinfo);
/* Query System to fill the struct initially */
if (ALSA_snd_seq_get_any_client_info(seq, 0, clientinfo))
return -1;
while (ALSA_snd_seq_query_next_client(seq, clientinfo) == 0) {
int client = ALSA_snd_seq_client_info_get_client(clientinfo);
/* Not necessary, as we don't allow subscription to our ports, but let's ignore ourselves anyway */
if (client == ALSA_snd_seq_client_id(seq))
continue;
snd_seq_port_info_t *portinfo;
snd_seq_port_info_alloca(&portinfo);
/* Start with port 0 */
if (ALSA_snd_seq_get_any_port_info(seq, client, 0, portinfo))
continue;
do {
int port = ALSA_snd_seq_port_info_get_port(portinfo);
unsigned int cap = ALSA_snd_seq_port_info_get_capability(portinfo);
unsigned int type = ALSA_snd_seq_port_info_get_type(portinfo);
if ((type & required_type) == required_type &&
cap & SND_SEQ_PORT_CAP_WRITE &&
cap & SND_SEQ_PORT_CAP_SUBS_WRITE &&
!(cap & SND_SEQ_PORT_CAP_NO_EXPORT)) {
MIDIDbgLog("Client %d Cap %x Type %x", client, cap, type);
/* Could we connect to it? */
if (ALSA_snd_seq_connect_to(seq, srcport, client, port) == 0)
return 0;
}
} while (ALSA_snd_seq_query_next_port(seq, portinfo) == 0);
}
return 1;
}
static SDL_INLINE void pick_seq_dest_addr(NativeMidiSong *song)
{
/* Send events to all subscribers */
song->dstaddr.client = SND_SEQ_ADDRESS_SUBSCRIBERS;
song->dstaddr.port = SND_SEQ_ADDRESS_UNKNOWN;
/* Connect us somewhere, unless it's not desired */
if (SDL_GetHintBoolean("SDL_NATIVE_MUSIC_NO_CONNECT_PORTS", false))
return;
/* If ALSA_OUTPUT_PORTS is specified, try to parse it and connect to it */
snd_seq_addr_t conn_addr;
const char *ports_env = SDL_getenv("ALSA_OUTPUT_PORTS");
if (ports_env && ALSA_snd_seq_parse_address(song->seq, &conn_addr, ports_env) == 0)
if (ALSA_snd_seq_connect_to(song->seq, song->srcport, conn_addr.client, conn_addr.port) == 0)
return;
/* If we're not connecting to a specific client, pick the first one available after System (0) */
/* Prefer connecting to synthesizers, as that is the primary use case */
if (!subscribe_to_first_available_port(song->seq, song->srcport, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_SYNTHESIZER))
return;
/* If we can't find a synth, then pick the first available port */
if (!subscribe_to_first_available_port(song->seq, song->srcport, SND_SEQ_PORT_TYPE_MIDI_GENERIC))
return;
}
static NativeMidiSong *currentsong = NULL;
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *song;
MIDIEvent *event;
int sv[2];
if (!(song = SDL_calloc(1, sizeof(NativeMidiSong)))) {
MIDI_Mix_OutOfMemory();
return NULL;
}
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) {
MIDI_SET_ERROR("Failed to create socketpair with errno %d", errno);
SDL_free(song);
return NULL;
}
song->mainsock = sv[0];
song->threadsock = sv[1];
event = song->evtlist = CreateMIDIEventList(src, &song->ppqn);
if (!song->evtlist) {
close_sockpair(song);
SDL_free(song);
MIDI_SET_ERROR("Failed to create MIDIEventList");
return NULL;
}
/* Since ALSA requires the starting F0 for SysEx, but MIDIEvent.extraData doesn't contain it, we must preprocess the list */
/* In addition, since we're going through the list, store the last event's time for looping purposes */
do {
/* Is this a SysEx? */
if (event->status == MIDI_CMD_COMMON_SYSEX && event->extraLen) {
/* Sanity check in case something changes in the future */
/* This is safe to do since we can't have an F0 manufacturer ID */
assert(event->extraData[0] != MIDI_CMD_COMMON_SYSEX);
/* Resize by + 1 */
Uint8 *newData = SDL_realloc(event->extraData, event->extraLen + 1);
if (newData == NULL) {
close_sockpair(song);
/* Original allocation is still valid on failure */
FreeMIDIEventList(song->evtlist);
SDL_free(song);
MIDI_SET_ERROR("Failed to preprocess MIDIEventList SysEx");
return NULL;
}
/* Prepend the F0 */
event->extraData = newData;
SDL_memmove(event->extraData + 1, event->extraData, event->extraLen);
event->extraData[0] = MIDI_CMD_COMMON_SYSEX;
event->extraLen++;
}
/* Store the end time */
song->endtime = event->time;
} while ((event = event->next));
if (!(song->seq = open_seq(&song->srcport))) {
FreeMIDIEventList(song->evtlist);
close_sockpair(song);
SDL_free(song);
return NULL;
}
/* Only allow echo events to be sent */
ALSA_snd_seq_set_client_event_filter(song->seq, SND_SEQ_EVENT_ECHO);
pick_seq_dest_addr(song);
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STOPPED);
/* Since there's no reliable volume control solution it's better to leave the music playing instead of having hanging notes */
song->allow_pause = SDL_GetHintBoolean("SDL_NATIVE_MUSIC_ALLOW_PAUSE", false);
if (closeio)
SDL_CloseIO(src);
currentsong = song;
return song;
}
void native_midi_freesong(NativeMidiSong *song)
{
if (!song)
return;
close_seq(song->seq, song->srcport);
FreeMIDIEventList(song->evtlist);
close_sockpair(song);
SDL_free(song);
}
/* Schedule an echo event right after the last event to know when playback is finished */
static SDL_INLINE void enqueue_echo_event(const NativeMidiSong *song, const int queue)
{
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
evt.type = SND_SEQ_EVENT_ECHO;
snd_seq_ev_set_source(&evt, song->srcport);
snd_seq_ev_set_dest(&evt, ALSA_snd_seq_client_id(song->seq), song->srcport);
snd_seq_ev_schedule_tick(&evt, queue, 0, song->endtime + 1);
while (ALSA_snd_seq_event_output(song->seq, &evt) == -EAGAIN)
;
}
/* Reset the queue position to 0 */
static SDL_INLINE void enqueue_queue_reset_event(const NativeMidiSong *song, const int queue)
{
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
snd_seq_ev_set_source(&evt, song->srcport);
snd_seq_ev_set_queue_pos_tick(&evt, queue, 0);
/* Schedule it to some point in the past, so that it is guaranteed */
/* to run immediately and before the echo */
snd_seq_ev_schedule_tick(&evt, queue, 0, 0);
while (ALSA_snd_seq_event_output(song->seq, &evt) == -EAGAIN)
;
}
/* Sysex to set the volume */
static SDL_INLINE void send_volume_sysex(const NativeMidiSong *song, const unsigned char vol)
{
unsigned char vol_sysex[] = { MIDI_CMD_COMMON_SYSEX, 0x7F, 0x7F, 0x04, 0x01, 0x00, vol, MIDI_CMD_COMMON_SYSEX_END };
/* Event used to set the volume */
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
snd_seq_ev_set_source(&evt, song->srcport);
snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port);
snd_seq_ev_set_direct(&evt);
snd_seq_ev_set_sysex(&evt, sizeof(vol_sysex), vol_sysex);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
}
/* Sequencer queue control */
static SDL_INLINE void stop_queue(const NativeMidiSong *song, const int queue)
{
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
snd_seq_ev_set_queue_control(&evt, SND_SEQ_EVENT_STOP, queue, 0);
snd_seq_ev_set_direct(&evt);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
}
static SDL_INLINE void continue_queue(const NativeMidiSong *song, const int queue)
{
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
snd_seq_ev_set_queue_control(&evt, SND_SEQ_EVENT_CONTINUE, queue, 0);
snd_seq_ev_set_direct(&evt);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
}
/* Playback thread */
static int native_midi_player_thread(void *d)
{
unsigned char current_volume = 0x7F;
bool playback_finished = false;
NativeMidiSong *song = d;
MIDIEvent *event = song->evtlist;
int i;
int queue = ALSA_snd_seq_alloc_named_queue(song->seq, "SDL_Mixer Playback");
snd_seq_start_queue(song->seq, queue, NULL);
/* Prepare main sequencer event */
snd_seq_event_t evt;
snd_seq_ev_clear(&evt);
snd_seq_ev_set_source(&evt, song->srcport);
snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port);
/* Set up nonblock functionality */
struct pollfd pfds[2] = { {
.fd = song->threadsock,
.events = POLLIN,
} };
ALSA_snd_seq_poll_descriptors(song->seq, pfds + 1, 1, POLLIN | POLLOUT);
ALSA_snd_seq_nonblock(song->seq, 1);
/* Set initial queue tempo and ppqn */
snd_seq_queue_tempo_t *tempo;
snd_seq_queue_tempo_alloca(&tempo);
ALSA_snd_seq_queue_tempo_set_tempo(tempo, 500000);
ALSA_snd_seq_queue_tempo_set_ppq(tempo, song->ppqn);
ALSA_snd_seq_set_queue_tempo(song->seq, queue, tempo);
/* We use this to know when the track has finished playing */
enqueue_echo_event(song, queue);
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PLAYING);
while (1) {
unsigned char readbuf[CMD_PKT_LEN];
MIDIDbgLog("Poll...");
if (poll(pfds, 2, -1) <= 0)
break;
MIDIDbgLog("revents: cmdsock %hd, ALSA %hd", pfds[0].revents, pfds[1].revents);
/* Do we have a command from the main thread? */
if (pfds[0].revents & POLLIN) {
/* This will process exactly one command by design because all packets are fixed size (CMD_PKT_LEN) */
if (read(song->threadsock, readbuf, sizeof(readbuf)) == sizeof(readbuf)) {
MIDIDbgLog("Got control %hhx", readbuf[0]);
switch ((native_midi_thread_cmd)readbuf[0]) {
case THREAD_CMD_QUIT:
event = NULL;
song->loopcount = 0;
playback_finished = true;
break;
case THREAD_CMD_SETVOL:
current_volume = readbuf[1];
send_volume_sysex(song, current_volume);
break;
case THREAD_CMD_PAUSE:
send_volume_sysex(song, 0);
stop_queue(song, queue);
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PAUSED);
break;
case THREAD_CMD_RESUME:
continue_queue(song, queue);
send_volume_sysex(song, current_volume);
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_PLAYING);
break;
}
}
}
/* Can we read from the sequencer? */
if (pfds[1].revents & POLLIN) {
snd_seq_event_t *revt;
/* Make sure we read an echo event, and that it came from us */
if (ALSA_snd_seq_event_input(song->seq, &revt) >= 0 && revt->type == SND_SEQ_EVENT_ECHO && revt->source.client == ALSA_snd_seq_client_id(song->seq) && revt->source.port == song->srcport)
playback_finished = true;
}
/* Have we reached the end of the event list? */
if (!event) {
/* If we have, are we done playing? */
if (playback_finished) {
if (song->loopcount == 0)
break;
MIDIDbgLog("Playback is looping");
/* If we need to loop, roll back the list head and keep going */
event = song->evtlist;
/* We need to reset the queue, otherwise the ticks will be wrong */
enqueue_queue_reset_event(song, queue);
enqueue_echo_event(song, queue);
if (song->loopcount > 0)
song->loopcount--;
playback_finished = false;
/* Allow ready to write events again */
pfds[1].events |= POLLOUT;
} else {
/* If not, keep draining, otherwise we'll never reach the echo event */
/* When we finish though, prevent any "ready to write to alsa" polls */
MIDIDbgLog("Draining output!");
if (ALSA_snd_seq_drain_output(song->seq) == 0)
pfds[1].events &= ~POLLOUT;
continue;
}
}
/* Don't proceed if we can't write to the sequencer */
if (!(pfds[1].revents & POLLOUT))
continue;
/* Finally, if we get here, we process MIDI events and send them to the sequencer */
const unsigned char cmd = event->status & 0xF0;
const unsigned char channel = event->status & 0x0F;
snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port);
snd_seq_ev_set_fixed(&evt);
snd_seq_ev_schedule_tick(&evt, queue, 0, event->time);
bool unhandled = false;
switch (cmd) {
case MIDI_CMD_NOTE_ON:
snd_seq_ev_set_noteon(&evt, channel, event->data[0], event->data[1]);
break;
case MIDI_CMD_NOTE_OFF:
snd_seq_ev_set_noteoff(&evt, channel, event->data[0], event->data[1]);
break;
case MIDI_CMD_CONTROL:
snd_seq_ev_set_controller(&evt, channel, event->data[0], event->data[1]);
break;
case MIDI_CMD_NOTE_PRESSURE:
snd_seq_ev_set_keypress(&evt, channel, event->data[0], event->data[1]);
break;
case MIDI_CMD_PGM_CHANGE:
snd_seq_ev_set_pgmchange(&evt, channel, event->data[0]);
break;
case MIDI_CMD_BENDER:
snd_seq_ev_set_pitchbend(&evt, channel, ((((int)event->data[1]) << 7) | (event->data[0] & 0x7F)) - 8192);
break;
default:
if (event->status == MIDI_SMF_META_EVENT) {
if (event->data[0] == MIDI_SMF_META_TEMPO && event->extraLen == 3) {
unsigned int t = ((unsigned)event->extraData[0] << 16) |
((unsigned)event->extraData[1] << 8) |
event->extraData[2];
/* This changes the event destination, so we have to restore it in the next iteration */
snd_seq_ev_set_queue_tempo(&evt, queue, t);
break;
}
} else if (event->status == MIDI_CMD_COMMON_SYSEX) {
snd_seq_ev_set_sysex(&evt, event->extraLen, event->extraData);
break;
}
unhandled = true;
}
if (unhandled || ALSA_snd_seq_event_output(song->seq, &evt) != -EAGAIN) {
MIDIDbgLog("%s %" SDL_PRIu32 ": %hhx %hhx %hhx (extraLen %" SDL_PRIu32 ")", (unhandled ? "Unhandled" : "Event"), event->time, event->status, event->data[0], event->data[1], event->extraLen);
event = event->next;
}
}
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STOPPED);
/* Switch back to blocking mode and drop everything */
ALSA_snd_seq_nonblock(song->seq, 0);
ALSA_snd_seq_drop_output(song->seq);
snd_seq_stop_queue(song->seq, queue, NULL);
ALSA_snd_seq_drain_output(song->seq);
ALSA_snd_seq_free_queue(song->seq, queue);
/* Stop all audio */
/* Some of these are bound to work */
snd_seq_ev_set_direct(&evt);
for (i = 0; i < MIDI_CHANNELS; i++) {
snd_seq_ev_set_controller(&evt, i, MIDI_CTL_SUSTAIN, 0);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
snd_seq_ev_set_controller(&evt, i, MIDI_CTL_ALL_NOTES_OFF, 0);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
snd_seq_ev_set_controller(&evt, i, MIDI_CTL_RESET_CONTROLLERS, 0);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
snd_seq_ev_set_controller(&evt, i, MIDI_CTL_ALL_SOUNDS_OFF, 0);
ALSA_snd_seq_event_output_direct(song->seq, &evt);
}
MIDIDbgLog("Playback thread returns");
return 0;
}
void native_midi_start(NativeMidiSong *song, int loops)
{
if (!song)
return;
if (song->playerthread) {
if (SDL_GetAtomicInt(&currentsong->playerstate) > NATIVE_MIDI_STOPPED)
if (write(song->mainsock, pkt_thread_cmd_quit, CMD_PKT_LEN) != sizeof(pkt_thread_cmd_quit))
return;
SDL_WaitThread(song->playerthread, NULL);
}
song->loopcount = loops;
/* If this isn't set here, then the application might think we finished before playback even started */
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STARTING);
song->playerthread = SDL_CreateThread(native_midi_player_thread, "SDL_Mixer Midi", song);
}
/* The following functions require song to be global (thus currentsong is used) */
void native_midi_pause(void)
{
NativeMidiSong *song = currentsong;
if (!song || SDL_GetAtomicInt(&song->playerstate) == NATIVE_MIDI_STOPPED || !song->allow_pause)
return;
(void)!write(song->mainsock, pkt_thread_cmd_pause, CMD_PKT_LEN);
}
void native_midi_resume(void)
{
NativeMidiSong *song = currentsong;
if (!song || SDL_GetAtomicInt(&song->playerstate) != NATIVE_MIDI_PAUSED || !song->allow_pause)
return;
(void)!write(song->mainsock, pkt_thread_cmd_resume, CMD_PKT_LEN);
}
void native_midi_stop(void)
{
NativeMidiSong *song = currentsong;
if (!song || !song->playerthread)
return;
/* Don't send any messages to the main thread if it's out of the main loop */
if (SDL_GetAtomicInt(&song->playerstate) > NATIVE_MIDI_STOPPED)
if (write(song->mainsock, pkt_thread_cmd_quit, CMD_PKT_LEN) != sizeof(pkt_thread_cmd_quit))
return;
SDL_WaitThread(song->playerthread, NULL);
song->playerthread = NULL;
}
bool native_midi_active(void)
{
NativeMidiSong *song = currentsong;
if (!song)
return 0;
return SDL_GetAtomicInt(&song->playerstate) > NATIVE_MIDI_STOPPED;
}
void native_midi_setvolume(int volume)
{
NativeMidiSong *song = currentsong;
if (!song || SDL_GetAtomicInt(&song->playerstate) != NATIVE_MIDI_PLAYING)
return;
if (volume < 0)
volume = 0;
else if (volume > 0x7F)
volume = 0x7F;
unsigned char pkt_thread_cmd_setvol[CMD_PKT_LEN] = { THREAD_CMD_SETVOL, volume };
(void)!write(song->mainsock, pkt_thread_cmd_setvol, CMD_PKT_LEN);
}
const char *native_midi_error(void)
{
return errmsg_final;
}
#endif
@@ -0,0 +1,409 @@
/*
native_midi_macosx: Native Midi support on Mac OS X for the SDL_mixer library
Copyright (C) 2009 Ryan C. Gordon <icculus@icculus.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <SDL3/SDL_platform.h>
#ifdef SDL_PLATFORM_MACOS
/* Mac OS X 10.6+, using Core MIDI. */
#include <SDL3/SDL_stdinc.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AvailabilityMacros.h>
#include <SDL3/SDL_endian.h>
#include <SDL3_mixer/SDL_mixer.h>
#include "../../mixer.h"
#include "native_midi.h"
/* Native Midi song */
struct _NativeMidiSong
{
MusicPlayer player;
MusicSequence sequence;
MusicTimeStamp endTime;
AudioUnit audiounit;
int loops;
};
static NativeMidiSong *currentsong = NULL;
static int latched_volume = MIX_MAX_VOLUME;
static OSStatus
GetSequenceLength(MusicSequence sequence, MusicTimeStamp *_sequenceLength)
{
/* http://lists.apple.com/archives/Coreaudio-api/2003/Jul/msg00370.html
* figure out sequence length */
UInt32 ntracks, i;
MusicTimeStamp sequenceLength = 0;
OSStatus err;
err = MusicSequenceGetTrackCount(sequence, &ntracks);
if (err != noErr)
return err;
for (i = 0; i < ntracks; ++i)
{
MusicTrack track;
MusicTimeStamp tracklen = 0;
UInt32 tracklenlen = sizeof (tracklen);
err = MusicSequenceGetIndTrack(sequence, i, &track);
if (err != noErr)
return err;
err = MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength,
&tracklen, &tracklenlen);
if (err != noErr)
return err;
if (sequenceLength < tracklen)
sequenceLength = tracklen;
}
*_sequenceLength = sequenceLength;
return noErr;
}
static OSStatus
GetSequenceAudioUnitMatching(MusicSequence sequence, AudioUnit *aunit,
OSType type, OSType subtype)
{
AUGraph graph;
UInt32 nodecount, i;
OSStatus err;
err = MusicSequenceGetAUGraph(sequence, &graph);
if (err != noErr)
return err;
err = AUGraphGetNodeCount(graph, &nodecount);
if (err != noErr)
return err;
for (i = 0; i < nodecount; i++) {
AUNode node;
AudioComponentDescription desc;
if (AUGraphGetIndNode(graph, i, &node) != noErr)
continue; /* better luck next time. */
if (AUGraphNodeInfo(graph, node, &desc, aunit) != noErr)
continue;
else if (desc.componentType != type)
continue;
else if (desc.componentSubType != subtype)
continue;
return noErr; /* found it! */
}
*aunit = NULL;
return kAUGraphErr_NodeNotFound;
}
typedef struct {
AudioUnit aunit;
bool soundfont_set;
CFURLRef default_url;
} macosx_load_soundfont_ctx;
static bool SDLCALL
macosx_load_soundfont(const char *path, void *data)
{
CFURLRef url;
OSStatus err;
macosx_load_soundfont_ctx *ctx = data;
if (ctx->soundfont_set)
return false;
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
(const UInt8*)path,
strlen(path), false);
if (!url)
return false;
err = AudioUnitSetProperty(ctx->aunit, kMusicDeviceProperty_SoundBankURL,
kAudioUnitScope_Global, 0, &url, sizeof(url));
CFRelease(url);
if (err != noErr) {
if (ctx->default_url)
err = AudioUnitSetProperty(ctx->aunit,
kMusicDeviceProperty_SoundBankURL,
kAudioUnitScope_Global, 0,
&ctx->default_url, sizeof(CFURLRef));
if (err != noErr) {
/* uh-oh, this might leave the audio unit in an unusable state
(e.g. if the soundfont was an incompatible file type) */
}
return false;
}
ctx->soundfont_set = true;
return true;
}
static void
SetSequenceSoundFont(MusicSequence sequence)
{
OSStatus err;
macosx_load_soundfont_ctx ctx;
ctx.soundfont_set = false;
ctx.default_url = NULL;
CFBundleRef bundle = CFBundleGetBundleWithIdentifier(
CFSTR("com.apple.audio.units.Components"));
if (bundle)
ctx.default_url = CFBundleCopyResourceURL(bundle,
CFSTR("gs_instruments"),
CFSTR("dls"), NULL);
err = GetSequenceAudioUnitMatching(sequence, &ctx.aunit,
kAudioUnitType_MusicDevice,
kAudioUnitSubType_DLSSynth);
if (err != noErr)
return;
Mix_EachSoundFont(macosx_load_soundfont, &ctx);
if (ctx.default_url)
CFRelease(ctx.default_url);
return;
}
bool native_midi_detect(void)
{
return true; /* always available. */
}
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *retval = NULL;
void *buf = NULL;
size_t len = 0;
CFDataRef data = NULL;
buf = SDL_LoadFile_IO(src, &len, false);
if (buf == NULL)
goto fail;
retval = SDL_malloc(sizeof(NativeMidiSong));
if (retval == NULL)
goto fail;
SDL_memset(retval, '\0', sizeof (*retval));
if (NewMusicPlayer(&retval->player) != noErr)
goto fail;
if (NewMusicSequence(&retval->sequence) != noErr)
goto fail;
data = CFDataCreate(NULL, (const UInt8 *) buf, len);
if (data == NULL)
goto fail;
SDL_free(buf);
buf = NULL;
if (MusicSequenceFileLoadData(retval->sequence, data, 0, 0) != noErr)
goto fail;
CFRelease(data);
data = NULL;
if (GetSequenceLength(retval->sequence, &retval->endTime) != noErr)
goto fail;
if (MusicPlayerSetSequence(retval->player, retval->sequence) != noErr)
goto fail;
if (closeio)
SDL_CloseIO(src);
return retval;
fail:
if (retval) {
if (retval->sequence)
DisposeMusicSequence(retval->sequence);
if (retval->player)
DisposeMusicPlayer(retval->player);
SDL_free(retval);
}
if (data)
CFRelease(data);
if (buf)
SDL_free(buf);
return NULL;
}
void native_midi_freesong(NativeMidiSong *song)
{
if (song != NULL) {
if (currentsong == song)
currentsong = NULL;
MusicPlayerStop(song->player);
/* needed to prevent error and memory leak when disposing sequence */
MusicPlayerSetSequence(song->player, NULL);
DisposeMusicSequence(song->sequence);
DisposeMusicPlayer(song->player);
SDL_free(song);
}
}
void native_midi_start(NativeMidiSong *song, int loops)
{
int vol;
if (song == NULL)
return;
if (currentsong)
MusicPlayerStop(currentsong->player);
currentsong = song;
currentsong->loops = loops;
MusicPlayerPreroll(song->player);
GetSequenceAudioUnitMatching(song->sequence, &song->audiounit,
kAudioUnitType_Output,
kAudioUnitSubType_DefaultOutput);
SetSequenceSoundFont(song->sequence);
vol = latched_volume;
latched_volume++; /* just make this not match. */
native_midi_setvolume(vol);
MusicPlayerSetTime(song->player, 0);
MusicPlayerStart(song->player);
}
void native_midi_pause(void)
{
}
void native_midi_resume(void)
{
}
void native_midi_stop(void)
{
if (currentsong) {
MusicPlayerStop(currentsong->player);
currentsong = NULL;
}
}
bool native_midi_active(void)
{
MusicTimeStamp currentTime = 0;
if (currentsong == NULL)
return false;
MusicPlayerGetTime(currentsong->player, &currentTime);
if ((currentTime < currentsong->endTime) ||
(currentTime >= kMusicTimeStamp_EndOfTrack)) {
return true;
} else if (currentsong->loops) {
--currentsong->loops;
MusicPlayerSetTime(currentsong->player, 0);
return true;
}
return false;
}
void native_midi_setvolume(int volume)
{
if (latched_volume == volume)
return;
latched_volume = volume;
if ((currentsong) && (currentsong->audiounit)) {
const float floatvol = ((float) volume) / ((float) MIX_MAX_VOLUME);
AudioUnitSetParameter(currentsong->audiounit, kHALOutputParam_Volume,
kAudioUnitScope_Global, 0, floatvol, 0);
}
}
const char *native_midi_error(void)
{
return ""; /* !!! FIXME */
}
#else
#include "native_midi.h"
bool native_midi_detect(void)
{
return false;
}
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
if (closeio) {
SDL_CloseIO(src);
}
SDL_Unsupported();
return NULL;
}
void native_midi_freesong(NativeMidiSong *song)
{
}
void native_midi_start(NativeMidiSong *song, int loops)
{
}
void native_midi_pause(void)
{
}
void native_midi_resume(void)
{
}
void native_midi_stop(void)
{
}
bool native_midi_active(void)
{
}
void native_midi_setvolume(int volume)
{
}
const char *native_midi_error(void)
{
return "";
}
#endif /* Mac OS X native MIDI support */
@@ -0,0 +1,340 @@
/*
native_midi: Hardware Midi support for the SDL_mixer library
Copyright (C) 2000,2001 Florian 'Proff' Schulze <florian.proff.schulze@gmx.net>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <SDL3/SDL_platform.h>
/* everything below is currently one very big bad hack ;) Proff */
#ifdef SDL_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#include "native_midi.h"
#include "native_midi_common.h"
struct _NativeMidiSong {
bool MusicLoaded;
bool MusicPlaying;
int Loops;
int CurrentHdr;
MIDIHDR MidiStreamHdr[2];
MIDIEVENT *NewEvents;
Uint16 ppqn;
int Size;
int NewPos;
SDL_Mutex *mutex;
};
static UINT MidiDevice=MIDI_MAPPER;
static HMIDISTRM hMidiStream;
static NativeMidiSong *currentsong;
static int BlockOut(NativeMidiSong *song)
{
MMRESULT err;
int BlockSize;
MIDIHDR *hdr;
if ((song->MusicLoaded) && (song->NewEvents))
{
/* proff 12/8/98: Added for safety*/
song->CurrentHdr = !song->CurrentHdr;
hdr = &song->MidiStreamHdr[song->CurrentHdr];
midiOutUnprepareHeader((HMIDIOUT)hMidiStream,hdr,sizeof(MIDIHDR));
if (song->NewPos>=song->Size)
return 0;
BlockSize=(song->Size-song->NewPos);
if (BlockSize<=0)
return 0;
if (BlockSize>36000)
BlockSize=36000;
hdr->lpData=(void *)((unsigned char *)song->NewEvents+song->NewPos);
song->NewPos+=BlockSize;
hdr->dwBufferLength=BlockSize;
hdr->dwBytesRecorded=BlockSize;
hdr->dwFlags=0;
hdr->dwOffset=0;
err=midiOutPrepareHeader((HMIDIOUT)hMidiStream,hdr,sizeof(MIDIHDR));
if (err!=MMSYSERR_NOERROR)
return 0;
err=midiStreamOut(hMidiStream,hdr,sizeof(MIDIHDR));
return 0;
}
return 1;
}
static void MIDItoStream(NativeMidiSong *song, MIDIEvent *evntlist)
{
int eventcount;
MIDIEvent *event;
MIDIEVENT *newevent;
eventcount=0;
event=evntlist;
while (event)
{
eventcount++;
event=event->next;
}
song->NewEvents = SDL_malloc(eventcount*3*sizeof(DWORD));
if (!song->NewEvents)
return;
SDL_memset(song->NewEvents,0,(eventcount*3*sizeof(DWORD)));
eventcount=0;
event=evntlist;
newevent=song->NewEvents;
while (event)
{
int status = (event->status&0xF0)>>4;
switch (status)
{
case MIDI_STATUS_NOTE_OFF:
case MIDI_STATUS_NOTE_ON:
case MIDI_STATUS_AFTERTOUCH:
case MIDI_STATUS_CONTROLLER:
case MIDI_STATUS_PROG_CHANGE:
case MIDI_STATUS_PRESSURE:
case MIDI_STATUS_PITCH_WHEEL:
newevent->dwDeltaTime=event->time;
newevent->dwEvent=(event->status|0x80)|(event->data[0]<<8)|(event->data[1]<<16)|(MEVT_SHORTMSG<<24);
newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
eventcount++;
break;
case MIDI_STATUS_SYSEX:
if (event->status == 0xFF && event->data[0] == 0x51) /* Tempo change */
{
int tempo = (event->extraData[0] << 16) |
(event->extraData[1] << 8) |
event->extraData[2];
newevent->dwDeltaTime=event->time;
newevent->dwEvent=(MEVT_TEMPO<<24) | tempo;
newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
eventcount++;
}
break;
}
event=event->next;
}
song->Size=eventcount*3*sizeof(DWORD);
{
int time;
int temptime;
song->NewPos=0;
time=0;
newevent=song->NewEvents;
while (song->NewPos<song->Size)
{
temptime=newevent->dwDeltaTime;
newevent->dwDeltaTime-=time;
time=temptime;
if ((song->NewPos+12)>=song->Size)
newevent->dwEvent |= MEVT_F_CALLBACK;
newevent=(MIDIEVENT*)((char*)newevent+(3*sizeof(DWORD)));
song->NewPos+=12;
}
}
song->NewPos=0;
song->MusicLoaded = true;
}
void CALLBACK MidiProc( HMIDIIN hMidi, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
{
NativeMidiSong *song = (NativeMidiSong *)dwInstance;
(void)hMidi;
(void)dwParam2;
if (!song) {
return;
}
SDL_LockMutex(song->mutex);
switch( uMsg )
{
case MOM_DONE:
if (song->MusicPlaying && song->MusicLoaded && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr]))
BlockOut(song);
break;
case MOM_POSITIONCB:
if (song->MusicPlaying && song->MusicLoaded && (dwParam1 == (DWORD_PTR)&song->MidiStreamHdr[song->CurrentHdr])) {
if (song->Loops) {
if (song->Loops > 0)
--song->Loops;
song->NewPos=0;
BlockOut(song);
} else {
song->MusicPlaying = false;
}
}
break;
case MOM_CLOSE:
song->MusicPlaying = false;
break;
default:
break;
}
SDL_UnlockMutex(song->mutex);
}
bool native_midi_detect(void)
{
MMRESULT merr;
HMIDISTRM MidiStream;
merr=midiStreamOpen(&MidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)0,CALLBACK_FUNCTION);
if (merr != MMSYSERR_NOERROR)
return false;
midiStreamClose(MidiStream);
return true;
}
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *newsong;
MIDIEvent *evntlist = NULL;
newsong = SDL_malloc(sizeof(NativeMidiSong));
if (!newsong) {
return NULL;
}
SDL_memset(newsong,0,sizeof(NativeMidiSong));
/* Attempt to load the midi file */
evntlist = CreateMIDIEventList(src, &newsong->ppqn);
if (!evntlist)
{
SDL_free(newsong);
return NULL;
}
MIDItoStream(newsong, evntlist);
FreeMIDIEventList(evntlist);
newsong->mutex = SDL_CreateMutex();
if (closeio) {
SDL_CloseIO(src);
}
return newsong;
}
void native_midi_freesong(NativeMidiSong *song)
{
if (song)
{
if (song->NewEvents)
SDL_free(song->NewEvents);
SDL_DestroyMutex(song->mutex);
SDL_free(song);
}
}
void native_midi_start(NativeMidiSong *song, int loops)
{
MMRESULT merr;
MIDIPROPTIMEDIV mptd;
native_midi_stop();
if (!hMidiStream)
{
merr=midiStreamOpen(&hMidiStream,&MidiDevice,(DWORD)1,(DWORD_PTR)MidiProc,(DWORD_PTR)song,CALLBACK_FUNCTION);
if (merr!=MMSYSERR_NOERROR)
{
hMidiStream = NULL; /* should I do midiStreamClose(hMidiStream) before? */
return;
}
/* midiStreamStop(hMidiStream); */
currentsong=song;
currentsong->NewPos=0;
currentsong->MusicPlaying = true;
currentsong->Loops=loops;
mptd.cbStruct=sizeof(MIDIPROPTIMEDIV);
mptd.dwTimeDiv=currentsong->ppqn;
merr=midiStreamProperty(hMidiStream,(LPBYTE)&mptd,MIDIPROP_SET | MIDIPROP_TIMEDIV);
BlockOut(song);
merr=midiStreamRestart(hMidiStream);
}
}
void native_midi_pause(void)
{
if (!hMidiStream)
return;
midiStreamPause(hMidiStream);
}
void native_midi_resume(void)
{
if (!hMidiStream)
return;
midiStreamRestart(hMidiStream);
}
void native_midi_stop(void)
{
NativeMidiSong *song = currentsong;
if (!hMidiStream)
return;
SDL_LockMutex(song->mutex);
midiStreamStop(hMidiStream);
midiStreamClose(hMidiStream);
currentsong=NULL;
hMidiStream = NULL;
SDL_UnlockMutex(song->mutex);
}
bool native_midi_active(void)
{
if (!hMidiStream)
return false;
if (!currentsong)
return false;
return currentsong->MusicPlaying;
}
void native_midi_setvolume(int volume)
{
int calcVolume;
if (volume > 128)
volume = 128;
if (volume < 0)
volume = 0;
calcVolume = (65535 * volume / 128);
midiOutSetVolume((HMIDIOUT)hMidiStream, MAKELONG(calcVolume , calcVolume));
}
const char *native_midi_error(void)
{
return "";
}
#endif /* Windows native MIDI support */
@@ -0,0 +1,9 @@
stb
-------------------
single-file public domain (or MIT licensed) libraries for C/C++
-------------------
https://github.com/nothings/stb
-------------------
stv_vorbis.c (renamed into h) version 1.22
decode ogg vorbis files from file/memory to float/16-bit signed output
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,24 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := timidity
LOCAL_C_INCLUDES :=
LOCAL_CFLAGS :=
LOCAL_SRC_FILES += \
common.c \
instrum.c \
mix.c \
output.c \
playmidi.c \
readmidi.c \
resample.c \
tables.c \
timidity.c
LOCAL_SHARED_LIBRARIES := SDL3
include $(BUILD_STATIC_LIBRARY)
@@ -0,0 +1,77 @@
This version of TiMidity should contain all the fixes from the
September 25 2003 SDL_mixer CVS snapshot. In addition, I've made some
changes of my own, e.g.:
* All file access is done through SDL_IOStream. This means the MIDI
stream no longer has to be a file. (The config file and instruments
still have to be though.)
* Replacing of TiMidity's endian-handling with SDL's.
* Removal of much unused or unnecessary code, such as
+ The "hooks" for putting a user interface onto TiMidity.
+ The antialias filter. It wasn't active, and even at 4 kHz I
couldn't hear any difference when activating it.
+ Removed all traces of LOOKUP_HACK and LOOKUP_INTERPOLATION.
According to the code comments they weren't very good anyway.
("degrades sound quality noticeably"). I also removed the
disclaimer about the "8-bit uLaw to 16-bit PCM and the 13-bit-PCM
to 8-bit uLaw tables" disclaimer, since I believe those were the
tables I removed.
+ Removed LOOKUP_SINE since it was already commented out. I think we
can count on our target audience having math co-processors
nowadays.
+ Removed USE_LDEXP since it wasn't being used and "it doesn't make
much of a difference either way".
+ Removed decompress hack from open_file() since it didn't look very
portable.
+ Removed heaps of unnecessary constants.
+ Removed unused functions.
+ Assume that LINEAR_INTERPOLATION is always used, so remove all
code dealing with it not being so. It's not that I think the
difference in audio quality is that great, but since it wouldn't
compile without code changes I assume no one's used it for quite
some time...
+ Assume PRECALC_LOOPS is always defined. Judging by the comments it
may not make much of a difference either way, so why maintain two
versions of the same code?
* Moving several static globals into the MidiSong struct. This
includes sample rate, formate, etc. which are now all per-song.
* Moved some typedefs (e.g. MidiSong) to timidity.h for easy inclusion
into the MIDI decoder.
* Added free_pathlist().
* Replaced TiMidity's own 8, 16 and 32-bit types with SDL's.
* Made TiMidity look for its configuration file in both /etc and
/usr/local/lib/timidity. (Windows version remains unchanged.)
* Timidity_PlaySome() now takes three arguments. A MidiSong, a decode
buffer and decode buffer size in bytes. (MidiSong is a new argument,
and buffer size used to be in samples.)
In addition, it will return the number of bytes decoded.
* Added Timidity_Exit().
* Removed Timidity_Stop() and Timidity_Active(). Stopping playback
should be handled by SDL_sound, and Timidity_PlaySome() will return
0 when the MIDI stream is finished.
* Modified the ToneBank stuff to allow some data to be shared between
MidiSongs.
* The following files have been removed: controls.c, controls.h,
filter.c, filter.h, sdl_a.c, sdl_c.c
* config.h has been renamed as options.h to avoid confusion with the
automatically generated config.h for SDL_sound.
* Added support for loading DLS format instruments:
Timidity_LoadDLS(), Timidity_FreeDLS(), Timidity_LoadDLSSong()
* Added Timidity_Init_NoConfig()
+127
View File
@@ -0,0 +1,127 @@
The "Artistic License"
Preamble
The intent of this document is to state the conditions under which a
Package may be copied, such that the Copyright Holder maintains some
semblance of artistic control over the development of the package,
while giving the users of the package the right to use and distribute
the Package in a more-or-less customary fashion, plus the right to make
reasonable modifications.
Definitions:
"Package" refers to the collection of files distributed by the
Copyright Holder, and derivatives of that collection of files
created through textual modification.
"Standard Version" refers to such a Package if it has not been
modified, or has been modified in accordance with the wishes
of the Copyright Holder as specified below.
"Copyright Holder" is whoever is named in the copyright or
copyrights for the package.
"You" is you, if you're thinking about copying or distributing
this Package.
"Reasonable copying fee" is whatever you can justify on the
basis of media cost, duplication charges, time of people involved,
and so on. (You will not be required to justify it to the
Copyright Holder, but only to the computing community at large
as a market that must bear the fee.)
"Freely Available" means that no fee is charged for the item
itself, though there may be fees involved in handling the item.
It also means that recipients of the item may redistribute it
under the same conditions they received it.
1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that you
duplicate all of the original copyright notices and associated disclaimers.
2. You may apply bug fixes, portability fixes and other modifications
derived from the Public Domain or from the Copyright Holder. A Package
modified in such a way shall still be considered the Standard Version.
3. You may otherwise modify your copy of this Package in any way, provided
that you insert a prominent notice in each changed file stating how and
when you changed that file, and provided that you do at least ONE of the
following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or placing the modifications on a major archive
site such as uunet.uu.net, or by allowing the Copyright Holder to include
your modifications in the Standard Version of the Package.
b) use the modified Package only within your corporation or organization.
c) rename any non-standard executables so the names do not conflict
with standard executables, which must also be provided, and provide
a separate manual page for each non-standard executable that clearly
documents how it differs from the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
4. You may distribute the programs of this Package in object code or
executable form, provided that you do at least ONE of the following:
a) distribute a Standard Version of the executables and library files,
together with instructions (in the manual page or equivalent) on where
to get the Standard Version.
b) accompany the distribution with the machine-readable source of
the Package with your modifications.
c) give non-standard executables non-standard names, and clearly
document the differences in manual pages (or equivalent), together
with instructions on where to get the Standard Version.
d) make other distribution arrangements with the Copyright Holder.
5. You may charge a reasonable copying fee for any distribution of this
Package. You may charge any fee you choose for support of this
Package. You may not charge a fee for this Package itself. However,
you may distribute this Package in aggregate with other (possibly
commercial) programs as part of a larger (possibly commercial) software
distribution provided that you do not advertise this Package as a
product of your own. You may embed this Package's interpreter within
an executable of yours (by linking); this shall be construed as a mere
form of aggregation, provided that the complete Standard Version of the
interpreter is so embedded.
6. The scripts and library files supplied as input to or produced as
output from the programs of this Package do not automatically fall
under the copyright of this Package, but belong to whoever generated
them, and may be sold commercially, and may be aggregated with this
Package. If such scripts or library files are aggregated with this
Package via the so-called "undump" or "unexec" methods of producing a
binary executable image, then distribution of such an image shall
neither be construed as a distribution of this Package nor shall it
fall under the restrictions of Paragraphs 3 and 4, provided that you do
not represent such an executable image as a Standard Version of this
Package.
7. C subroutines (or comparably compiled subroutines in other
languages) supplied by you and linked into this Package in order to
emulate subroutines and variables of the language defined by this
Package shall not be considered part of this Package, but are the
equivalent of input as in Paragraph 6, provided these subroutines do
not change the language in any way that would cause it to fail the
regression tests for the language.
8. Aggregation of this Package with a commercial distribution is always
permitted provided that the use of this Package is embedded; that is,
when no overt attempt is made to make this Package's interfaces visible
to the end user of the commercial distribution. Such use shall not be
construed as a distribution of this Package.
9. The name of the Copyright Holder may not be used to endorse or promote
products derived from this software without specific prior written permission.
10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
The End
+100
View File
@@ -0,0 +1,100 @@
---------------------------*-indented-text-*------------------------------
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
--------------------------------------------------------------------------
Frequently Asked Questions with answers:
--------------------------------------------------------------------------
Q: What is it?
A: Where? Well Chris, TiMidity is a software-only synthesizer, MIDI
renderer, MIDI to WAVE converter, realtime MIDI player for UNIX machines,
even (I've heard) a Netscape helper application. It takes a MIDI file
and writes a WAVE or raw PCM data or plays it on your digital audio
device. It sounds much more realistic than FM synthesis, but you need a
~100Mhz processor to listen to 32kHz stereo music in the background while
you work. 11kHz mono can be played on a low-end 486, and, to some, it
still sounds better than FM.
--------------------------------------------------------------------------
Q: I don't have a GUS, can I use TiMidity?
A: Yes. That's the point. You don't need a Gravis Ultrasound to use
TiMidity, you just need GUS-compatible patches, which are freely
available on the Internet. See below for pointers.
--------------------------------------------------------------------------
Q: I have a GUS, can I use TiMidity?
A: The DOS port doesn't have GUS support, and TiMidity won't be taking
advantage of the board's internal synthesizer under other operating
systems either. So it kind of defeats the purpose. But you can use it.
--------------------------------------------------------------------------
Q: I tried playing a MIDI file I got off the Net but all I got was a
dozen warnings saying "No instrument mapped to tone bank 0, program
xx - this instrument will not be heard". What's wrong?
A: The General MIDI standard specifies 128 melodic instruments and
some sixty percussion sounds. If you wish to play arbitrary General
MIDI files, you'll need to get more patch files.
There's a program called Midia for SGI's, which also plays MIDI
files and has a lot more bells and whistles than TiMidity. It uses
GUS-compatible patches, too -- so you can get the 8 MB set at
ftp://archive.cs.umbc.edu/pub/midia for pretty good GM compatibility.
There are also many excellent patches on the Ultrasound FTP sites.
I can recommend Dustin McCartney's collections gsdrum*.zip and
wow*.zip in the "[.../]sound/patches/files" directory. The huge
ProPats series (pp3-*.zip) contains good patches as well. General
MIDI files can also be found on these sites.
This site list is from the GUS FAQ:
> FTP Sites Archive Directories
> --------- -------------------
> Main N.American Site: archive.orst.edu pub/packages/gravis
> wuarchive.wustl.edu systems/ibmpc/ultrasound
> Main Asian Site: nctuccca.edu.tw PC/ultrasound
> Main European Site: src.doc.ic.ac.uk packages/ultrasound
> Main Australian Site: ftp.mpx.com.au /ultrasound/general
> /ultrasound/submit
> South African Site: ftp.sun.ac.za /pub/packages/ultrasound
> Submissions: archive.epas.utoronto.ca pub/pc/ultrasound/submit
> Newly Validated Files: archive.epas.utoronto.ca pub/pc/ultrasound
>
> Mirrors: garbo.uwasa.fi mirror/ultrasound
> ftp.st.nepean.uws.edu.au pc/ultrasound
> ftp.luth.se pub/msdos/ultrasound
--------------------------------------------------------------------------
Q: Some files have awful clicks and pops.
A: Find out which patch is responsible for the clicking (try "timidity
-P<patch> <midi/test-decay|midi/test-panning>". Add "strip=tail" in
the config file after its name. If this doesn't fix it, mail me the
patch.
--------------------------------------------------------------------------
Q: I'm playing Fantasie Impromptu in the background. When I run Netscape,
the sound gets choppy and it takes ten minutes to load. What can I do?
A: Here are some things to try:
- Use a lower sampling rate.
- Use mono output. This can improve performance by 10-30%.
(Using 8-bit instead of 16-bit output makes no difference.)
- Use a smaller number of simultaneous voices.
- Make sure you compiled with FAST_DECAY enabled in options.h
- Recompile with an Intel-optimized gcc for a 5-15%
performance increase.
--------------------------------------------------------------------------
+58
View File
@@ -0,0 +1,58 @@
[This version of timidity has been stripped for simplicity in porting to SDL,
and then even further for SDL_sound]
---------------------------------*-text-*---------------------------------
From http://www.cgs.fi/~tt/discontinued.html :
If you'd like to continue hacking on TiMidity, feel free. I'm
hereby extending the TiMidity license agreement: you can now
select the most convenient license for your needs from (1) the
GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License.
--------------------------------------------------------------------------
This is the README file for TiMidity v0.2i
TiMidity is a MIDI to WAVE converter that uses Gravis
Ultrasound(*)-compatible patch files to generate digital audio data
from General MIDI files. The audio data can be played through any
sound device or stored on disk. On a fast machine, music can be
played in real time. TiMidity runs under Linux, FreeBSD, HP-UX, SunOS, and
Win32, and porting to other systems with gcc should be easy.
TiMidity Features:
* 32 or more dynamically allocated fully independent voices
* Compatibility with GUS patch files
* Output to 16- or 8-bit PCM or uLaw audio device, file, or
stdout at any sampling rate
* Optional interactive mode with real-time status display
under ncurses and SLang terminal control libraries. Also
a user friendly motif interface since version 0.2h
* Support for transparent loading of compressed MIDI files and
patch files
* Support for the following MIDI events:
- Program change
- Key pressure
- Channel main volume
- Tempo
- Panning
- Damper pedal (Sustain)
- Pitch wheel
- Pitch wheel sensitivity
- Change drum set
* TiMidity requires sampled instruments (patches) to play MIDI files. You
should get the file "timidity-lib-0.1.tar.gz" and unpack it in the same
directory where you unpacked the source code archive. You'll want more
patches later -- read the file "FAQ" for pointers.
* Timidity is no longer supported, but can be found by searching the web.
Tuukka Toivonen <toivonen@clinet.fi>
[(*) Any Registered Trademarks used anywhere in the documentation or
source code for TiMidity are acknowledged as belonging to their
respective owners.]
+35
View File
@@ -0,0 +1,35 @@
* I don't like the indentation style at all, but for the most part
I've left it alone.
* Much of the code looks ugly to me.
* Group the members of MidiSong into logical units, i.e. structs?
* The debug messages are probably a bit too noisy. I've removed one
particularly annoying one, but...
Some of them should be turned into error messages instead.
* Can the instrument handling be made more efficient? At the moment
different MidiSongs may separately load the same instrument.
Note that the MidiSong's audio format affects how the instrument is
loaded, so it's not as easy as just letting all MidiSongs share tone
and drum banks.
At the moment they do share the data that is simply read from the
config file, but that's just a quick hack to avoid having to read
the config file every time a MIDI song is loaded.
* Check if any of MidiStruct's members can safely be made into static
globals again.
* TiMidity++ adds a number of undocumented (?) extensions to the
configuration syntax. These are not implemented here. In particular,
the "map" keyword used by the "eawpats".
* The other decoders generally only read as much of the file as is
necessary. Could we do that in this decoder as well? (Currently it
seems to convert the entire file into MIDI events first.)
* Can it be optimized?
+112
View File
@@ -0,0 +1,112 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
common.c
*/
#include <SDL3/SDL.h>
#include "options.h"
#include "common.h"
#if defined(_WIN32)
#define CHAR_DIRSEP '\\'
#define is_dirsep(c) ((c) == '/' || (c) == '\\')
#define is_abspath(p) ((p)[0] == '/' || (p)[0] == '\\' || ((p)[0] && (p)[1] == ':'))
#else /* unix: */
#define CHAR_DIRSEP '/'
#define is_dirsep(c) ((c) == '/')
#define is_abspath(p) ((p)[0] == '/')
#endif
/* The paths in this list will be tried whenever we're reading a file */
typedef struct _PathList {
char *path;
struct _PathList *next;
} PathList;
static PathList *pathlist = NULL;
/* This is meant to find and open files for reading */
SDL_IOStream *timi_openfile(const char *name)
{
SDL_IOStream *io;
if (!name || !(*name)) {
SNDDBG(("Attempted to open nameless file.\n"));
return NULL;
}
/* First try the given name */
SNDDBG(("Trying to open %s\n", name));
if ((io = SDL_IOFromFile(name, "rb")) != NULL)
return io;
if (!is_abspath(name))
{
char current_filename[1024];
PathList *plp = pathlist;
char *p;
size_t l;
while (plp) { /* Try along the path then */
*current_filename = 0;
p = current_filename;
l = SDL_strlen(plp->path);
if(l >= sizeof(current_filename) - 3) l = 0;
if(l) {
SDL_memcpy(current_filename, plp->path, l);
p += l;
if(!is_dirsep(p[-1])) {
*p++ = CHAR_DIRSEP;
l++;
}
}
SDL_strlcpy(p, name, sizeof(current_filename) - l);
SNDDBG(("Trying to open %s\n", current_filename));
if ((io = SDL_IOFromFile(current_filename, "rb")))
return io;
plp = plp->next;
}
}
/* Nothing could be opened. */
SNDDBG(("Could not open %s\n", name));
return NULL;
}
/* This adds a directory to the path list */
int timi_add_pathlist(const char *s, size_t l)
{
PathList *plp = SDL_malloc(sizeof(PathList));
if (plp == NULL) return -2;
plp->path = SDL_malloc(l + 1);
if (plp->path == NULL) {
SDL_free (plp);
return -2;
}
SDL_memcpy(plp->path, s, l);
plp->path[l] = 0;
plp->next = pathlist;
pathlist = plp;
return 0;
}
void timi_free_pathlist(void)
{
PathList *plp = pathlist;
PathList *next;
while (plp) {
next = plp->next;
SDL_free(plp->path);
SDL_free(plp);
plp = next;
}
pathlist = NULL;
}
@@ -0,0 +1,31 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
common.h
*/
#ifndef TIMIDITY_COMMON_H
#define TIMIDITY_COMMON_H
extern SDL_IOStream *timi_openfile(const char *name);
/* pathlist funcs only to be used during Timidity_Init/Timidity_Exit */
extern int timi_add_pathlist(const char *s, size_t len);
extern void timi_free_pathlist(void);
/* hide private symbols by prefixing with "_timi_" */
#undef TIMI_NAMESPACE
#define TIMI_NAMESPACE(x) _timi_ ## x
/* debug output */
#ifdef DEBUG_CHATTER
#define SNDDBG(X) SDL_Log X
#else
#define SNDDBG(X)
#endif
#endif /* TIMIDITY_COMMON_H */
@@ -0,0 +1,606 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
instrum.c
Code to load and unload GUS-compatible instrument patches.
*/
#include <SDL3/SDL.h>
#include "timidity.h"
#include "options.h"
#include "common.h"
#include "instrum.h"
#include "resample.h"
#include "tables.h"
static void free_instrument(Instrument *ip)
{
Sample *sp;
int i;
if (!ip) return;
if (ip->sample) {
for (i=0; i<ip->samples; i++) {
sp=&(ip->sample[i]);
SDL_free(sp->data);
}
SDL_free(ip->sample);
}
SDL_free(ip);
}
static void free_bank(MidiSong *song, int dr, int b)
{
int i;
ToneBank *bank=((dr) ? song->drumset[b] : song->tonebank[b]);
for (i=0; i<MAXBANK; i++)
if (bank->instrument[i])
{
if (bank->instrument[i] != MAGIC_LOAD_INSTRUMENT)
free_instrument(bank->instrument[i]);
bank->instrument[i] = NULL;
}
}
static Sint32 convert_envelope_rate(MidiSong *song, Uint8 rate)
{
Sint32 r;
r = 3 - ((rate >> 6) & 0x3);
r *= 3;
r = (Sint32) (rate & 0x3f) << r; /* 6.9 fixed point */
/* 15.15 fixed point. */
r = ((r * 44100) / song->rate) * song->control_ratio;
#ifdef FAST_DECAY
return r << 10;
#else
return r << 9;
#endif
}
static Sint32 convert_envelope_offset(Uint8 offset)
{
/* This is not too good... Can anyone tell me what these values mean?
Are they GUS-style "exponential" volumes? And what does that mean? */
/* 15.15 fixed point */
return offset << (7+15);
}
static Sint32 convert_tremolo_sweep(MidiSong *song, Uint8 sweep)
{
if (!sweep)
return 0;
return
((song->control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) /
(song->rate * sweep);
}
static Sint32 convert_vibrato_sweep(MidiSong *song, Uint8 sweep,
Sint32 vib_control_ratio)
{
if (!sweep)
return 0;
return
(Sint32) (TIM_FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT)
/ (double)(song->rate * sweep));
/* this was overflowing with seashore.pat
((vib_control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) /
(song->rate * sweep); */
}
static Sint32 convert_tremolo_rate(MidiSong *song, Uint8 rate)
{
return
((SINE_CYCLE_LENGTH * song->control_ratio * rate) << RATE_SHIFT) /
(TREMOLO_RATE_TUNING * song->rate);
}
static Sint32 convert_vibrato_rate(MidiSong *song, Uint8 rate)
{
/* Return a suitable vibrato_control_ratio value */
return
(VIBRATO_RATE_TUNING * song->rate) /
(rate * 2 * VIBRATO_SAMPLE_INCREMENTS);
}
static void reverse_data(Sint16 *sp, Sint32 ls, Sint32 le)
{
Sint16 s, *ep=sp+le;
sp+=ls;
le-=ls;
le/=2;
while (le--)
{
s=*sp;
*sp++=*ep;
*ep--=s;
}
}
/*
If panning or note_to_use != -1, it will be used for all samples,
instead of the sample-specific values in the instrument file.
For note_to_use, any value <0 or >127 will be forced to 0.
For other parameters, 1 means yes, 0 means no, other values are
undefined.
TODO: do reverse loops right */
static void load_instrument(MidiSong *song, const char *name,
Instrument **out,
int percussion, int panning,
int amp, int note_to_use,
int strip_loop, int strip_envelope,
int strip_tail)
{
Instrument *ip;
Sample *sp;
SDL_IOStream *io;
char tmp[1024];
int i,j;
static char *patch_ext[] = PATCH_EXT_LIST;
(void)percussion; /* unused */
*out = NULL;
if (!name) return;
/* Open patch file */
i = -1;
if ((io=timi_openfile(name)) == NULL)
{
/* Try with various extensions */
for (i=0; patch_ext[i]; i++)
{
SDL_snprintf(tmp, sizeof(tmp), "%s%s", name, patch_ext[i]);
if ((io=timi_openfile(tmp)) != NULL)
break;
}
}
if (io == NULL)
{
SNDDBG(("Instrument `%s' can't be found.\n", name));
return;
}
SNDDBG(("Loading instrument %s\n", (i < 0)? name : tmp));
/* Read some headers and do cursory sanity checks. There are loads
of magic offsets. This could be rewritten... */
if ((239 != SDL_ReadIO(io, tmp, 239)) ||
(SDL_memcmp(tmp, "GF1PATCH110\0ID#000002", 22) &&
SDL_memcmp(tmp, "GF1PATCH100\0ID#000002", 22))) /* don't know what the
differences are */
{
SNDDBG(("%s: not an instrument\n", name));
goto badpat;
}
if (tmp[82] != 1 && tmp[82] != 0) /* instruments. To some patch makers,
0 means 1 */
{
SNDDBG(("Can't handle patches with %d instruments\n", tmp[82]));
goto badpat;
}
if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */
{
SNDDBG(("Can't handle instruments with %d layers\n", tmp[151]));
goto badpat;
}
*out=SDL_malloc(sizeof(Instrument));
ip = *out;
if (!ip) goto nomem;
ip->samples = tmp[198];
ip->sample = SDL_malloc(sizeof(Sample) * ip->samples);
if (!ip->sample) goto nomem;
for (i=0; i<ip->samples; i++)
{
Uint8 fractions;
Sint32 tmplong;
Uint16 tmpshort;
Uint8 tmpchar;
#define READ_CHAR(thing) \
if (1 != SDL_ReadIO(io, &tmpchar, 1)) goto badread; \
thing = tmpchar;
#define READ_SHORT(thing) \
if (2 != SDL_ReadIO(io, &tmpshort, 2)) goto badread; \
thing = SDL_Swap16LE(tmpshort);
#define READ_LONG(thing) \
if (4 != SDL_ReadIO(io, &tmplong, 4)) goto badread; \
thing = (Sint32)SDL_Swap32LE((Uint32)tmplong);
SDL_SeekIO(io, 7, SDL_IO_SEEK_CUR); /* Skip the wave name */
if (1 != SDL_ReadIO(io, &fractions, 1))
goto badread;
sp=&(ip->sample[i]);
READ_LONG(sp->data_length);
READ_LONG(sp->loop_start);
READ_LONG(sp->loop_end);
READ_SHORT(sp->sample_rate);
READ_LONG(sp->low_freq);
READ_LONG(sp->high_freq);
READ_LONG(sp->root_freq);
SDL_SeekIO(io, 2, SDL_IO_SEEK_CUR); /* Why have a "root frequency" and then
* "tuning"?? */
READ_CHAR(tmp[0]);
if (panning==-1)
sp->panning = (tmp[0] * 8 + 4) & 0x7f;
else
sp->panning=(Uint8)(panning & 0x7F);
/* envelope, tremolo, and vibrato */
if (18 != SDL_ReadIO(io, tmp, 18))
goto badread;
if (!tmp[13] || !tmp[14])
{
sp->tremolo_sweep_increment=
sp->tremolo_phase_increment=sp->tremolo_depth=0;
SNDDBG((" * no tremolo\n"));
}
else
{
sp->tremolo_sweep_increment=convert_tremolo_sweep(song, tmp[12]);
sp->tremolo_phase_increment=convert_tremolo_rate(song, tmp[13]);
sp->tremolo_depth=tmp[14];
SNDDBG((" * tremolo: sweep %d, phase %d, depth %d\n",
sp->tremolo_sweep_increment, sp->tremolo_phase_increment,
sp->tremolo_depth));
}
if (!tmp[16] || !tmp[17])
{
sp->vibrato_sweep_increment=
sp->vibrato_control_ratio=sp->vibrato_depth=0;
SNDDBG((" * no vibrato\n"));
}
else
{
sp->vibrato_control_ratio=convert_vibrato_rate(song, tmp[16]);
sp->vibrato_sweep_increment=
convert_vibrato_sweep(song, tmp[15], sp->vibrato_control_ratio);
sp->vibrato_depth=tmp[17];
SNDDBG((" * vibrato: sweep %d, ctl %d, depth %d\n",
sp->vibrato_sweep_increment, sp->vibrato_control_ratio,
sp->vibrato_depth));
}
READ_CHAR(sp->modes);
SDL_SeekIO(io, 40, SDL_IO_SEEK_CUR); /* skip the useless scale frequency, scale
factor (what's it mean?), and reserved
space */
/* Mark this as a fixed-pitch instrument if such a deed is desired. */
if (note_to_use!=-1)
sp->note_to_use=(Uint8)(note_to_use);
else
sp->note_to_use=0;
/* seashore.pat in the Midia patch set has no Sustain. I don't
understand why, and fixing it by adding the Sustain flag to
all looped patches probably breaks something else. We do it
anyway. */
if (sp->modes & MODES_LOOPING)
sp->modes |= MODES_SUSTAIN;
/* Strip any loops and envelopes we're permitted to */
if ((strip_loop==1) &&
(sp->modes & (MODES_SUSTAIN | MODES_LOOPING |
MODES_PINGPONG | MODES_REVERSE)))
{
SNDDBG((" - Removing loop and/or sustain\n"));
sp->modes &=~(MODES_SUSTAIN | MODES_LOOPING |
MODES_PINGPONG | MODES_REVERSE);
}
if (strip_envelope==1)
{
if (sp->modes & MODES_ENVELOPE) {
SNDDBG((" - Removing envelope\n"));
}
sp->modes &= ~MODES_ENVELOPE;
}
else if (strip_envelope != 0)
{
/* Have to make a guess. */
if (!(sp->modes & (MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE)))
{
/* No loop? Then what's there to sustain? No envelope needed
either... */
sp->modes &= ~(MODES_SUSTAIN|MODES_ENVELOPE);
SNDDBG((" - No loop, removing sustain and envelope\n"));
}
else if (!SDL_memcmp(tmp, "??????", 6) || tmp[11] >= 100)
{
/* Envelope rates all maxed out? Envelope end at a high "offset"?
That's a weird envelope. Take it out. */
sp->modes &= ~MODES_ENVELOPE;
SNDDBG((" - Weirdness, removing envelope\n"));
}
else if (!(sp->modes & MODES_SUSTAIN))
{
/* No sustain? Then no envelope. I don't know if this is
justified, but patches without sustain usually don't need the
envelope either... at least the Gravis ones. They're mostly
drums. I think. */
sp->modes &= ~MODES_ENVELOPE;
SNDDBG((" - No sustain, removing envelope\n"));
}
}
for (j=0; j<6; j++)
{
sp->envelope_rate[j]=
convert_envelope_rate(song, tmp[j]);
sp->envelope_offset[j]=
convert_envelope_offset(tmp[6+j]);
}
/* Then read the sample data */
sp->data = (sample_t *) SDL_malloc(sp->data_length+4);
if (!sp->data) goto nomem;
if (SDL_ReadIO(io, sp->data, sp->data_length) != (size_t)sp->data_length)
goto badread;
if (!(sp->modes & MODES_16BIT)) /* convert to 16-bit data */
{
Sint32 k=sp->data_length;
Uint8 *cp=(Uint8 *)(sp->data);
Uint16 *tmp16,*new16;
sp->data_length *= 2;
sp->loop_start *= 2;
sp->loop_end *= 2;
tmp16 = new16 = (Uint16 *) SDL_malloc(sp->data_length+4);
if (!new16) goto nomem;
while (k--)
*tmp16++ = (Uint16)(*cp++) << 8;
SDL_free(sp->data);
sp->data = (sample_t *)new16;
}
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
else
/* convert to machine byte order */
{
Sint32 k=sp->data_length/2;
Sint16 *tmp16=(Sint16 *)sp->data,s;
while (k--)
{
s=SDL_Swap16LE(*tmp16);
*tmp16++=s;
}
}
#endif
if (sp->modes & MODES_UNSIGNED) /* convert to signed data */
{
Sint32 k=sp->data_length/2;
Sint16 *tmp16=(Sint16 *)sp->data;
while (k--)
*tmp16++ ^= 0x8000;
}
/* Reverse reverse loops and pass them off as normal loops */
if (sp->modes & MODES_REVERSE)
{
Sint32 t;
/* The GUS apparently plays reverse loops by reversing the
whole sample. We do the same because the GUS does not SUCK. */
SNDDBG(("Reverse loop in %s\n", name));
reverse_data((Sint16 *)sp->data, 0, sp->data_length/2);
t=sp->loop_start;
sp->loop_start=sp->data_length - sp->loop_end;
sp->loop_end=sp->data_length - t;
sp->modes &= ~MODES_REVERSE;
sp->modes |= MODES_LOOPING; /* just in case */
}
#ifdef ADJUST_SAMPLE_VOLUMES
if (amp!=-1)
sp->volume=(float)((amp) / 100.0);
else
{
/* Try to determine a volume scaling factor for the sample.
This is a very crude adjustment, but things sound more
balanced with it. Still, this should be a runtime option. */
Sint32 k=sp->data_length/2;
Sint16 maxamp=0,a;
Sint16 *tmp16=(Sint16 *)sp->data;
while (k--)
{
a=*tmp16++;
if (a<0) a=-a;
if (a>maxamp)
maxamp=a;
}
sp->volume=(float)(32768.0 / maxamp);
SNDDBG((" * volume comp: %f\n", sp->volume));
}
#else
if (amp!=-1)
sp->volume=(double)(amp) / 100.0;
else
sp->volume=1.0;
#endif
sp->data_length /= 2; /* These are in bytes. Convert into samples. */
sp->loop_start /= 2;
sp->loop_end /= 2;
/* initialize the added extra sample space (see the +4 bytes) */
sp->data[sp->data_length] = sp->data[sp->data_length+1] = 0;
/* Then fractional samples */
sp->data_length <<= FRACTION_BITS;
sp->loop_start <<= FRACTION_BITS;
sp->loop_end <<= FRACTION_BITS;
/* Adjust for fractional loop points. This is a guess. Does anyone
know what "fractions" really stands for? */
sp->loop_start |=
(fractions & 0x0F) << (FRACTION_BITS-4);
sp->loop_end |=
((fractions>>4) & 0x0F) << (FRACTION_BITS-4);
/* If this instrument will always be played on the same note,
and it's not looped, we can resample it now. */
if (sp->note_to_use && !(sp->modes & MODES_LOOPING)) {
pre_resample(song, sp);
if (song->oom) goto fail;
}
if (strip_tail==1)
{
/* Let's not really, just say we did. */
SNDDBG((" - Stripping tail\n"));
sp->data_length = sp->loop_end;
}
}
SDL_CloseIO(io);
return;
nomem:
song->oom=1;
goto fail;
badread:
SNDDBG(("Error reading sample %d\n", i));
fail:
free_instrument (ip);
badpat:
SDL_CloseIO(io);
*out = NULL;
}
static int fill_bank(MidiSong *song, int dr, int b)
{
int i, errors=0;
ToneBank *bank=((dr) ? song->drumset[b] : song->tonebank[b]);
if (!bank)
{
SNDDBG(("Huh. Tried to load instruments in non-existent %s %d\n",
(dr) ? "drumset" : "tone bank", b));
return 0;
}
for (i=0; i<MAXBANK; i++)
{
if (bank->instrument[i]==MAGIC_LOAD_INSTRUMENT)
{
if (!(bank->tone[i].name))
{
SNDDBG(("No instrument mapped to %s %d, program %d%s\n",
(dr)? "drum set" : "tone bank", b, i,
(b!=0) ? "" : " - this instrument will not be heard"));
if (b!=0)
{
/* Mark the corresponding instrument in the default
bank / drumset for loading (if it isn't already) */
if (!dr)
{
if (!(song->tonebank[0]->instrument[i]))
song->tonebank[0]->instrument[i] =
MAGIC_LOAD_INSTRUMENT;
}
else
{
if (!(song->drumset[0]->instrument[i]))
song->drumset[0]->instrument[i] =
MAGIC_LOAD_INSTRUMENT;
}
}
bank->instrument[i] = NULL;
errors++;
}
else
{
load_instrument(song,
bank->tone[i].name,
&bank->instrument[i],
(dr) ? 1 : 0,
bank->tone[i].pan,
bank->tone[i].amp,
(bank->tone[i].note!=-1) ?
bank->tone[i].note :
((dr) ? i : -1),
(bank->tone[i].strip_loop!=-1) ?
bank->tone[i].strip_loop :
((dr) ? 1 : -1),
(bank->tone[i].strip_envelope != -1) ?
bank->tone[i].strip_envelope :
((dr) ? 1 : -1),
bank->tone[i].strip_tail);
if (!bank->instrument[i]) {
SNDDBG(("Couldn't load instrument %s (%s %d, program %d)\n",
bank->tone[i].name,
(dr)? "drum set" : "tone bank", b, i));
errors++;
}
}
}
}
return errors;
}
int load_missing_instruments(MidiSong *song)
{
int i=MAXBANK,errors=0;
while (i--)
{
if (song->tonebank[i])
errors+=fill_bank(song,0,i);
if (song->drumset[i])
errors+=fill_bank(song,1,i);
}
return errors;
}
void free_instruments(MidiSong *song)
{
int i=MAXBANK;
while(i--)
{
if (song->tonebank[i])
free_bank(song, 0, i);
if (song->drumset[i])
free_bank(song, 1, i);
}
}
int set_default_instrument(MidiSong *song, const char *name)
{
load_instrument(song, name, &song->default_instrument, 0, -1, -1, -1, 0, 0, 0);
if (!song->default_instrument)
return -1;
song->default_program = SPECIAL_PROGRAM;
return 0;
}
@@ -0,0 +1,37 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
instrum.h
*/
#ifndef TIMIDITY_INSTRUM_H
#define TIMIDITY_INSTRUM_H
/* Bits in modes: */
#define MODES_16BIT (1<<0)
#define MODES_UNSIGNED (1<<1)
#define MODES_LOOPING (1<<2)
#define MODES_PINGPONG (1<<3)
#define MODES_REVERSE (1<<4)
#define MODES_SUSTAIN (1<<5)
#define MODES_ENVELOPE (1<<6)
/* A hack to delay instrument loading until after reading the
entire MIDI file. */
#define MAGIC_LOAD_INSTRUMENT ((Instrument *) (-1))
#define SPECIAL_PROGRAM -1
#define load_missing_instruments TIMI_NAMESPACE(load_missing_instruments)
#define free_instruments TIMI_NAMESPACE(free_instruments)
#define set_default_instrument TIMI_NAMESPACE(set_default_instrument)
extern int load_missing_instruments(MidiSong *song);
extern void free_instruments(MidiSong *song);
extern int set_default_instrument(MidiSong *song, const char *name);
#endif /* TIMIDITY_INSTRUM_H */
+549
View File
@@ -0,0 +1,549 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
mix.c */
#include <SDL3/SDL.h>
#include "timidity.h"
#include "options.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "output.h"
#include "tables.h"
#include "resample.h"
#include "mix.h"
/* Returns 1 if envelope runs out */
int recompute_envelope(MidiSong *song, int v)
{
int stage;
stage = song->voice[v].envelope_stage;
if (stage>5)
{
/* Envelope ran out. */
song->voice[v].status = VOICE_FREE;
return 1;
}
if (song->voice[v].sample->modes & MODES_ENVELOPE)
{
if (song->voice[v].status==VOICE_ON || song->voice[v].status==VOICE_SUSTAINED)
{
if (stage>2)
{
/* Freeze envelope until note turns off. Trumpets want this. */
song->voice[v].envelope_increment=0;
return 0;
}
}
}
song->voice[v].envelope_stage=stage+1;
if (song->voice[v].envelope_volume==song->voice[v].sample->envelope_offset[stage] ||
(stage > 2 && song->voice[v].envelope_volume <
song->voice[v].sample->envelope_offset[stage]))
return recompute_envelope(song, v);
song->voice[v].envelope_target = song->voice[v].sample->envelope_offset[stage];
song->voice[v].envelope_increment = song->voice[v].sample->envelope_rate[stage];
if (song->voice[v].envelope_target < song->voice[v].envelope_volume)
song->voice[v].envelope_increment = -song->voice[v].envelope_increment;
return 0;
}
void apply_envelope_to_amp(MidiSong *song, int v)
{
float lamp = song->voice[v].left_amp, ramp;
Sint32 la,ra;
if (song->voice[v].panned == PANNED_MYSTERY)
{
ramp = song->voice[v].right_amp;
if (song->voice[v].tremolo_phase_increment)
{
lamp *= song->voice[v].tremolo_volume;
ramp *= song->voice[v].tremolo_volume;
}
if (song->voice[v].sample->modes & MODES_ENVELOPE)
{
lamp *= (float)vol_table[song->voice[v].envelope_volume>>23];
ramp *= (float)vol_table[song->voice[v].envelope_volume>>23];
}
la = (Sint32)TIM_FSCALE(lamp,AMP_BITS);
if (la>MAX_AMP_VALUE)
la=MAX_AMP_VALUE;
ra = (Sint32)TIM_FSCALE(ramp,AMP_BITS);
if (ra>MAX_AMP_VALUE)
ra=MAX_AMP_VALUE;
song->voice[v].left_mix = la;
song->voice[v].right_mix = ra;
}
else
{
if (song->voice[v].tremolo_phase_increment)
lamp *= song->voice[v].tremolo_volume;
if (song->voice[v].sample->modes & MODES_ENVELOPE)
lamp *= (float)vol_table[song->voice[v].envelope_volume>>23];
la = (Sint32)TIM_FSCALE(lamp,AMP_BITS);
if (la>MAX_AMP_VALUE)
la=MAX_AMP_VALUE;
song->voice[v].left_mix = la;
}
}
static int update_envelope(MidiSong *song, int v)
{
song->voice[v].envelope_volume += song->voice[v].envelope_increment;
/* Why is there no ^^ operator?? */
if (((song->voice[v].envelope_increment < 0) &&
(song->voice[v].envelope_volume <= song->voice[v].envelope_target)) ||
((song->voice[v].envelope_increment > 0) &&
(song->voice[v].envelope_volume >= song->voice[v].envelope_target)))
{
song->voice[v].envelope_volume = song->voice[v].envelope_target;
if (recompute_envelope(song, v))
return 1;
}
return 0;
}
static void update_tremolo(MidiSong *song, int v)
{
Sint32 depth = song->voice[v].sample->tremolo_depth << 7;
if (song->voice[v].tremolo_sweep)
{
/* Update sweep position */
song->voice[v].tremolo_sweep_position += song->voice[v].tremolo_sweep;
if (song->voice[v].tremolo_sweep_position >= (1 << SWEEP_SHIFT))
song->voice[v].tremolo_sweep=0; /* Swept to max amplitude */
else
{
/* Need to adjust depth */
depth *= song->voice[v].tremolo_sweep_position;
depth >>= SWEEP_SHIFT;
}
}
song->voice[v].tremolo_phase += song->voice[v].tremolo_phase_increment;
/* if (song->voice[v].tremolo_phase >= (SINE_CYCLE_LENGTH<<RATE_SHIFT))
song->voice[v].tremolo_phase -= SINE_CYCLE_LENGTH<<RATE_SHIFT; */
song->voice[v].tremolo_volume = (float)
(1.0 - TIM_FSCALENEG((timi_sine(song->voice[v].tremolo_phase >> RATE_SHIFT) + 1.0)
* depth * TREMOLO_AMPLITUDE_TUNING,
17));
/* I'm not sure about the +1.0 there -- it makes tremoloed voices'
volumes on average the lower the higher the tremolo amplitude. */
}
/* Returns 1 if the note died */
static int update_signal(MidiSong *song, int v)
{
if (song->voice[v].envelope_increment && update_envelope(song, v))
return 1;
if (song->voice[v].tremolo_phase_increment)
update_tremolo(song, v);
apply_envelope_to_amp(song, v);
return 0;
}
#define MIXATION(a) *lp++ += (a)*s;
static void mix_mystery_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v,
int count)
{
Voice *vp = song->voice + v;
final_volume_t
left=vp->left_mix,
right=vp->right_mix;
int cc;
sample_t s;
if (!(cc = vp->control_counter))
{
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
right = vp->right_mix;
}
while (count)
if (cc < count)
{
count -= cc;
while (cc--)
{
s = *sp++;
MIXATION(left);
MIXATION(right);
}
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
right = vp->right_mix;
}
else
{
vp->control_counter = cc - count;
while (count--)
{
s = *sp++;
MIXATION(left);
MIXATION(right);
}
return;
}
}
static void mix_center_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v,
int count)
{
Voice *vp = song->voice + v;
final_volume_t
left=vp->left_mix;
int cc;
sample_t s;
if (!(cc = vp->control_counter))
{
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
while (count)
if (cc < count)
{
count -= cc;
while (cc--)
{
s = *sp++;
MIXATION(left);
MIXATION(left);
}
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
else
{
vp->control_counter = cc - count;
while (count--)
{
s = *sp++;
MIXATION(left);
MIXATION(left);
}
return;
}
}
static void mix_single_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v,
int count)
{
Voice *vp = song->voice + v;
final_volume_t
left=vp->left_mix;
int cc;
sample_t s;
if (!(cc = vp->control_counter))
{
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
while (count)
if (cc < count)
{
count -= cc;
while (cc--)
{
s = *sp++;
MIXATION(left);
lp++;
}
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
else
{
vp->control_counter = cc - count;
while (count--)
{
s = *sp++;
MIXATION(left);
lp++;
}
return;
}
}
static void mix_mono_signal(MidiSong *song, sample_t *sp, Sint32 *lp, int v,
int count)
{
Voice *vp = song->voice + v;
final_volume_t
left=vp->left_mix;
int cc;
sample_t s;
if (!(cc = vp->control_counter))
{
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
while (count)
if (cc < count)
{
count -= cc;
while (cc--)
{
s = *sp++;
MIXATION(left);
}
cc = song->control_ratio;
if (update_signal(song, v))
return; /* Envelope ran out */
left = vp->left_mix;
}
else
{
vp->control_counter = cc - count;
while (count--)
{
s = *sp++;
MIXATION(left);
}
return;
}
}
static void mix_mystery(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count)
{
final_volume_t
left = song->voice[v].left_mix,
right = song->voice[v].right_mix;
sample_t s;
while (count--)
{
s = *sp++;
MIXATION(left);
MIXATION(right);
}
}
static void mix_center(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count)
{
final_volume_t
left = song->voice[v].left_mix;
sample_t s;
while (count--)
{
s = *sp++;
MIXATION(left);
MIXATION(left);
}
}
static void mix_single(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count)
{
final_volume_t
left = song->voice[v].left_mix;
sample_t s;
while (count--)
{
s = *sp++;
MIXATION(left);
lp++;
}
}
static void mix_mono(MidiSong *song, sample_t *sp, Sint32 *lp, int v, int count)
{
final_volume_t
left = song->voice[v].left_mix;
sample_t s;
while (count--)
{
s = *sp++;
MIXATION(left);
}
}
/* Ramp a note out in c samples */
static void ramp_out(MidiSong *song, sample_t *sp, Sint32 *lp, int v, Sint32 c)
{
/* should be final_volume_t, but Uint8 gives trouble. */
Sint32 left, right, li, ri;
sample_t s=0; /* silly warning about uninitialized s */
left=song->voice[v].left_mix;
li=-(left/c);
if (!li) li=-1;
/* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */
if (!(song->encoding & PE_MONO))
{
if (song->voice[v].panned==PANNED_MYSTERY)
{
right=song->voice[v].right_mix;
ri=-(right/c);
while (c--)
{
left += li;
if (left<0)
left=0;
right += ri;
if (right<0)
right=0;
s=*sp++;
MIXATION(left);
MIXATION(right);
}
}
else if (song->voice[v].panned==PANNED_CENTER)
{
while (c--)
{
left += li;
if (left<0)
return;
s=*sp++;
MIXATION(left);
MIXATION(left);
}
}
else if (song->voice[v].panned==PANNED_LEFT)
{
while (c--)
{
left += li;
if (left<0)
return;
s=*sp++;
MIXATION(left);
lp++;
}
}
else if (song->voice[v].panned==PANNED_RIGHT)
{
while (c--)
{
left += li;
if (left<0)
return;
s=*sp++;
lp++;
MIXATION(left);
}
}
}
else
{
/* Mono output. */
while (c--)
{
left += li;
if (left<0)
return;
s=*sp++;
MIXATION(left);
}
}
}
/**************** interface function ******************/
void mix_voice(MidiSong *song, Sint32 *buf, int v, Sint32 c)
{
Voice *vp = song->voice + v;
sample_t *sp;
if (vp->status==VOICE_DIE)
{
if (c>=MAX_DIE_TIME)
c=MAX_DIE_TIME;
sp=resample_voice(song, v, &c);
if(c > 0)
ramp_out(song, sp, buf, v, c);
vp->status=VOICE_FREE;
}
else
{
sp=resample_voice(song, v, &c);
if (song->encoding & PE_MONO)
{
/* Mono output. */
if (vp->envelope_increment || vp->tremolo_phase_increment)
mix_mono_signal(song, sp, buf, v, c);
else
mix_mono(song, sp, buf, v, c);
}
else
{
if (vp->panned == PANNED_MYSTERY)
{
if (vp->envelope_increment || vp->tremolo_phase_increment)
mix_mystery_signal(song, sp, buf, v, c);
else
mix_mystery(song, sp, buf, v, c);
}
else if (vp->panned == PANNED_CENTER)
{
if (vp->envelope_increment || vp->tremolo_phase_increment)
mix_center_signal(song, sp, buf, v, c);
else
mix_center(song, sp, buf, v, c);
}
else
{
/* It's either full left or full right. In either case,
every other sample is 0. Just get the offset right: */
if (vp->panned == PANNED_RIGHT) buf++;
if (vp->envelope_increment || vp->tremolo_phase_increment)
mix_single_signal(song, sp, buf, v, c);
else
mix_single(song, sp, buf, v, c);
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
mix.h
*/
#ifndef TIMIDITY_MIX_H
#define TIMIDITY_MIX_H
#define mix_voice TIMI_NAMESPACE(mix_voice)
#define recompute_envelope TIMI_NAMESPACE(recompute_envelope)
#define apply_envelope_to_amp TIMI_NAMESPACE(apply_envelope_to_amp)
extern void mix_voice(MidiSong *song, Sint32 *buf, int v, Sint32 c);
extern int recompute_envelope(MidiSong *song, int v);
extern void apply_envelope_to_amp(MidiSong *song, int v);
#endif /* TIMIDITY_MIX_H */
@@ -0,0 +1,107 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
*/
#ifndef TIMIDITY_OPTIONS_H
#define TIMIDITY_OPTIONS_H
/* When a patch file can't be opened, one of these extensions is
appended to the filename and the open is tried again.
*/
#define PATCH_EXT_LIST { ".pat", 0 }
/* Acoustic Grand Piano seems to be the usual default instrument. */
#define DEFAULT_PROGRAM 0
/* 9 here is MIDI channel 10, which is the standard percussion channel.
Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too.
On the other hand, some files know that 16 is not a drum channel and
try to play music on it. This is now a runtime option, so this isn't
a critical choice anymore. */
#define DEFAULT_DRUMCHANNELS (1<<9)
/* In percent. */
#define DEFAULT_AMPLIFICATION 70
/* Default polyphony */
/* #define DEFAULT_VOICES 32 */
#define DEFAULT_VOICES 256
/* 1000 here will give a control ratio of 22:1 with 22 kHz output.
Higher CONTROLS_PER_SECOND values allow more accurate rendering
of envelopes and tremolo. The cost is CPU time. */
#define CONTROLS_PER_SECOND 1000
/* Make envelopes twice as fast. Saves ~20% CPU time (notes decay
faster) and sounds more like a GUS. */
#define FAST_DECAY
/* A somewhat arbitrary output frequency range. */
#define MIN_OUTPUT_RATE 4000
#define MAX_OUTPUT_RATE 256000
/* How many bits to use for the fractional part of sample positions.
This affects tonal accuracy. The entire position counter must fit
in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of
a sample is 1048576 samples (2 megabytes in memory). The GUS gets
by with just 9 bits and a little help from its friends...
"The GUS does not SUCK!!!" -- a happy user :) */
#define FRACTION_BITS 12
/* For some reason the sample volume is always set to maximum in all
patch files. Define this for a crude adjustment that may help
equalize instrument volumes. */
#define ADJUST_SAMPLE_VOLUMES
/* The number of samples to use for ramping out a dying note. Affects
click removal. */
#define MAX_DIE_TIME 20
/**************************************************************************/
/* Anything below this shouldn't need to be changed unless you're porting
to a new machine with other than 32-bit, big-endian words. */
/**************************************************************************/
/* change FRACTION_BITS above, not these */
#define INTEGER_BITS (32 - FRACTION_BITS)
#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS)
#define FRACTION_MASK (~ INTEGER_MASK)
/* This is enforced by some computations that must fit in an int */
#define MAX_CONTROL_RATIO 255
#define MAX_AMPLIFICATION 800
/* The TiMidity configuration file */
#ifndef TIMIDITY_CFG
#define TIMIDITY_CFG "timidity.cfg"
#endif
/* These affect general volume */
#define GUARD_BITS 3
#define AMP_BITS (15-GUARD_BITS)
#define MAX_AMP_VALUE ((1<<(AMP_BITS+1))-1)
#define TIM_FSCALE(a,b) (float)((a) * (double)(1<<(b)))
#define TIM_FSCALENEG(a,b) (float)((a) * (1.0L / (double)(1<<(b))))
/* Vibrato and tremolo Choices of the Day */
#define SWEEP_TUNING 38
#define VIBRATO_AMPLITUDE_TUNING 1.0L
#define VIBRATO_RATE_TUNING 38
#define TREMOLO_AMPLITUDE_TUNING 1.0L
#define TREMOLO_RATE_TUNING 38
#define SWEEP_SHIFT 16
#define RATE_SHIFT 5
#ifndef PI
#define PI 3.14159265358979323846
#endif
#endif /* TIMIDITY_OPTIONS_H */
@@ -0,0 +1,98 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
output.c
Audio output (to file / device) functions.
*/
#include <SDL3/SDL.h>
#include "options.h"
#include "output.h"
/*****************************************************************/
/* Some functions to convert signed 32-bit data to other formats */
void timi_s32tos8(void *dp, Sint32 *lp, Sint32 c)
{
Sint8 *cp=(Sint8 *)(dp);
Sint32 l;
while (c--)
{
l=(*lp++)>>(32-8-GUARD_BITS);
if (l>127) l=127;
else if (l<-128) l=-128;
*cp++ = (Sint8) (l);
}
}
void timi_s32tou8(void *dp, Sint32 *lp, Sint32 c)
{
Uint8 *cp=(Uint8 *)(dp);
Sint32 l;
while (c--)
{
l=(*lp++)>>(32-8-GUARD_BITS);
if (l>127) l=127;
else if (l<-128) l=-128;
*cp++ = 0x80 ^ ((Uint8) l);
}
}
void timi_s32tos16(void *dp, Sint32 *lp, Sint32 c)
{
Sint16 *sp=(Sint16 *)(dp);
Sint32 l;
while (c--)
{
l=(*lp++)>>(32-16-GUARD_BITS);
if (l > 32767) l=32767;
else if (l<-32768) l=-32768;
*sp++ = (Sint16)(l);
}
}
void timi_s32tos16x(void *dp, Sint32 *lp, Sint32 c)
{
Sint16 *sp=(Sint16 *)(dp);
Sint32 l;
while (c--)
{
l=(*lp++)>>(32-16-GUARD_BITS);
if (l > 32767) l=32767;
else if (l<-32768) l=-32768;
*sp++ = SDL_Swap16((Sint16)(l));
}
}
void timi_s32tof32(void *dp, Sint32 *lp, Sint32 c)
{
float *sp=(float *)(dp);
while (c--)
{
*sp++ = (float)(*lp++) / 2147483647.0f;
}
}
void timi_s32tos32(void *dp, Sint32 *lp, Sint32 c)
{
Sint32 *sp=(Sint32 *)(dp);
while (c--)
{
*sp++ = (*lp++);
}
}
void timi_s32tos32x(void *dp, Sint32 *lp, Sint32 c)
{
Sint32 *sp=(Sint32 *)(dp);
while (c--)
{
*sp++ = SDL_Swap32(*lp++);
}
}
@@ -0,0 +1,54 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
output.h
*/
#ifndef TIMIDITY_OUTPUT_H
#define TIMIDITY_OUTPUT_H
/* Data format encoding bits */
#define PE_MONO 0x01 /* versus stereo */
#define PE_SIGNED 0x02 /* versus unsigned */
#define PE_16BIT 0x04 /* versus 8-bit */
#define PE_32BIT 0x08 /* versus 8-bit or 16-bit */
/* Conversion functions -- These overwrite the Sint32 data in *lp with
data in another format */
/* 8-bit signed and unsigned*/
extern void timi_s32tos8(void *dp, Sint32 *lp, Sint32 c);
extern void timi_s32tou8(void *dp, Sint32 *lp, Sint32 c);
/* 16-bit */
extern void timi_s32tos16(void *dp, Sint32 *lp, Sint32 c);
/* byte-exchanged 16-bit */
extern void timi_s32tos16x(void *dp, Sint32 *lp, Sint32 c);
/* 32-bit */
extern void timi_s32tof32(void *dp, Sint32 *lp, Sint32 c);
extern void timi_s32tos32(void *dp, Sint32 *lp, Sint32 c);
/* byte-exchanged 32-bit */
extern void timi_s32tos32x(void *dp, Sint32 *lp, Sint32 c);
/* little-endian and big-endian specific */
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define timi_s32tos16l timi_s32tos16
#define timi_s32tos16b timi_s32tos16x
#define timi_s32tos32l timi_s32tos32
#define timi_s32tos32b timi_s32tos32x
#else
#define timi_s32tos16l timi_s32tos16x
#define timi_s32tos16b timi_s32tos16
#define timi_s32tos32l timi_s32tos32x
#define timi_s32tos32b timi_s32tos32
#endif
#endif /* TIMIDITY_OUTPUT_H */
@@ -0,0 +1,802 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
playmidi.c -- random stuff in need of rearrangement
*/
#include <SDL3/SDL.h>
#include "timidity.h"
#include "options.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "output.h"
#include "mix.h"
#include "tables.h"
static void adjust_amplification(MidiSong *song)
{
song->master_volume = (float)(song->amplification) / 100.0f;
}
static void reset_voices(MidiSong *song)
{
int i;
for (i=0; i<MAX_VOICES; i++)
song->voice[i].status=VOICE_FREE;
}
/* Process the Reset All Controllers event */
static void reset_controllers(MidiSong *song, int c)
{
song->channel[c].volume=90; /* Some standard says, although the SCC docs say 0. */
song->channel[c].expression=127; /* SCC-1 does this. */
song->channel[c].sustain=0;
song->channel[c].pitchbend=0x2000;
song->channel[c].pitchfactor=0; /* to be computed */
}
static void reset_midi(MidiSong *song)
{
int i;
for (i=0; i<MAXCHAN; i++)
{
reset_controllers(song, i);
/* The rest of these are unaffected by the Reset All Controllers event */
song->channel[i].program=song->default_program;
song->channel[i].panning=NO_PANNING;
song->channel[i].pitchsens=2;
song->channel[i].bank=0; /* tone bank or drum set */
}
reset_voices(song);
}
static void select_sample(MidiSong *song, int v, Instrument *ip)
{
Sint32 f, cdiff, diff;
int s,i;
Sample *sp, *closest;
s=ip->samples;
sp=ip->sample;
if (s==1)
{
song->voice[v].sample=sp;
return;
}
f=song->voice[v].orig_frequency;
for (i=0; i<s; i++, sp++)
{
if (sp->low_freq <= f && sp->high_freq >= f)
{
song->voice[v].sample=sp;
return;
}
}
/*
No suitable sample found! We'll select the sample whose root
frequency is closest to the one we want. (Actually we should
probably convert the low, high, and root frequencies to MIDI
note values and compare those.)
*/
cdiff=0x7FFFFFFF;
closest=sp=ip->sample;
for(i=0; i<s; i++, sp++)
{
diff=sp->root_freq - f;
if (diff<0) diff=-diff;
if (diff<cdiff)
{
cdiff=diff;
closest=sp;
}
}
song->voice[v].sample=closest;
}
static void recompute_freq(MidiSong *song, int v)
{
int
sign=(song->voice[v].sample_increment < 0), /* for bidirectional loops */
pb=song->channel[song->voice[v].channel].pitchbend;
double a;
if (!song->voice[v].sample->sample_rate)
return;
if (song->voice[v].vibrato_control_ratio)
{
/* This instrument has vibrato. Invalidate any precomputed
sample_increments. */
int i=VIBRATO_SAMPLE_INCREMENTS;
while (i--)
song->voice[v].vibrato_sample_increment[i]=0;
}
if (pb==0x2000 || pb<0 || pb>0x3FFF)
song->voice[v].frequency = song->voice[v].orig_frequency;
else
{
pb-=0x2000;
if (!(song->channel[song->voice[v].channel].pitchfactor))
{
/* Damn. Somebody bent the pitch. */
Sint32 i=pb*song->channel[song->voice[v].channel].pitchsens;
if (pb<0)
i=-i;
song->channel[song->voice[v].channel].pitchfactor=
(float)(bend_fine[(i>>5) & 0xFF] * bend_coarse[i>>13]);
}
if (pb>0)
song->voice[v].frequency=
(Sint32)(song->channel[song->voice[v].channel].pitchfactor *
(double)(song->voice[v].orig_frequency));
else
song->voice[v].frequency=
(Sint32)((double)(song->voice[v].orig_frequency) /
song->channel[song->voice[v].channel].pitchfactor);
}
a = TIM_FSCALE(((double)(song->voice[v].sample->sample_rate) *
(double)(song->voice[v].frequency)) /
((double)(song->voice[v].sample->root_freq) *
(double)(song->rate)),
FRACTION_BITS);
if (sign)
a = -a; /* need to preserve the loop direction */
song->voice[v].sample_increment = (Sint32)(a);
}
static void recompute_amp(MidiSong *song, int v)
{
Sint32 tempamp;
/* TODO: use fscale */
tempamp= (song->voice[v].velocity *
song->channel[song->voice[v].channel].volume *
song->channel[song->voice[v].channel].expression); /* 21 bits */
if (!(song->encoding & PE_MONO))
{
if (song->voice[v].panning > 60 && song->voice[v].panning < 68)
{
song->voice[v].panned=PANNED_CENTER;
song->voice[v].left_amp=
TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
21);
}
else if (song->voice[v].panning<5)
{
song->voice[v].panned = PANNED_LEFT;
song->voice[v].left_amp=
TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
20);
}
else if (song->voice[v].panning>123)
{
song->voice[v].panned = PANNED_RIGHT;
song->voice[v].left_amp= /* left_amp will be used */
TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
20);
}
else
{
song->voice[v].panned = PANNED_MYSTERY;
song->voice[v].left_amp=
TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
27);
song->voice[v].right_amp = song->voice[v].left_amp * (song->voice[v].panning);
song->voice[v].left_amp *= (float)(127 - song->voice[v].panning);
}
}
else
{
song->voice[v].panned = PANNED_CENTER;
song->voice[v].left_amp=
TIM_FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
21);
}
}
static void start_note(MidiSong *song, MidiEvent *e, int i)
{
Instrument *ip;
int j;
if (ISDRUMCHANNEL(song, e->channel))
{
if (!(ip=song->drumset[song->channel[e->channel].bank]->instrument[e->a]))
{
if (!(ip=song->drumset[0]->instrument[e->a]))
return; /* No instrument? Then we can't play. */
}
if (ip->samples != 1)
{
SNDDBG(("Strange: percussion instrument with %d samples!",
ip->samples));
}
if (ip->sample->note_to_use) /* Do we have a fixed pitch? */
song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
else
song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
/* drums are supposed to have only one sample */
song->voice[i].sample = ip->sample;
}
else
{
if (song->channel[e->channel].program == SPECIAL_PROGRAM)
ip=song->default_instrument;
else if (!(ip=song->tonebank[song->channel[e->channel].bank]->
instrument[song->channel[e->channel].program]))
{
if (!(ip=song->tonebank[0]->instrument[song->channel[e->channel].program]))
return; /* No instrument? Then we can't play. */
}
if (ip->sample->note_to_use) /* Fixed-pitch instrument? */
song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
else
song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
select_sample(song, i, ip);
}
song->voice[i].status = VOICE_ON;
song->voice[i].channel = e->channel;
song->voice[i].note = e->a;
song->voice[i].velocity = e->b;
song->voice[i].sample_offset = 0;
song->voice[i].sample_increment = 0; /* make sure it isn't negative */
song->voice[i].tremolo_phase = 0;
song->voice[i].tremolo_phase_increment = song->voice[i].sample->tremolo_phase_increment;
song->voice[i].tremolo_sweep = song->voice[i].sample->tremolo_sweep_increment;
song->voice[i].tremolo_sweep_position = 0;
song->voice[i].vibrato_sweep = song->voice[i].sample->vibrato_sweep_increment;
song->voice[i].vibrato_sweep_position = 0;
song->voice[i].vibrato_control_ratio = song->voice[i].sample->vibrato_control_ratio;
song->voice[i].vibrato_control_counter = song->voice[i].vibrato_phase = 0;
for (j=0; j<VIBRATO_SAMPLE_INCREMENTS; j++)
song->voice[i].vibrato_sample_increment[j] = 0;
if (song->channel[e->channel].panning != NO_PANNING)
song->voice[i].panning = song->channel[e->channel].panning;
else
song->voice[i].panning = song->voice[i].sample->panning;
recompute_freq(song, i);
recompute_amp(song, i);
if (song->voice[i].sample->modes & MODES_ENVELOPE)
{
/* Ramp up from 0 */
song->voice[i].envelope_stage = 0;
song->voice[i].envelope_volume = 0;
song->voice[i].control_counter = 0;
recompute_envelope(song, i);
apply_envelope_to_amp(song, i);
}
else
{
song->voice[i].envelope_increment = 0;
apply_envelope_to_amp(song, i);
}
}
static void kill_note(MidiSong *song, int i)
{
song->voice[i].status = VOICE_DIE;
}
/* Only one instance of a note can be playing on a single channel. */
static void note_on(MidiSong *song)
{
int i = song->voices, lowest=-1;
Sint32 lv=0x7FFFFFFF, v;
MidiEvent *e = song->current_event;
while (i--)
{
if (song->voice[i].status == VOICE_FREE)
lowest=i; /* Can't get a lower volume than silence */
else if (song->voice[i].channel==e->channel &&
(song->voice[i].note==e->a || song->channel[song->voice[i].channel].mono))
kill_note(song, i);
}
if (lowest != -1)
{
/* Found a free voice. */
start_note(song,e,lowest);
return;
}
/* Look for the decaying note with the lowest volume */
i = song->voices;
while (i--)
{
if ((song->voice[i].status != VOICE_ON) &&
(song->voice[i].status != VOICE_DIE))
{
v = song->voice[i].left_mix;
if ((song->voice[i].panned == PANNED_MYSTERY)
&& (song->voice[i].right_mix > v))
v = song->voice[i].right_mix;
if (v<lv)
{
lv=v;
lowest=i;
}
}
}
if (lowest != -1)
{
/* This can still cause a click, but if we had a free voice to
spare for ramping down this note, we wouldn't need to kill it
in the first place... Still, this needs to be fixed. Perhaps
we could use a reserve of voices to play dying notes only. */
song->cut_notes++;
song->voice[lowest].status=VOICE_FREE;
start_note(song,e,lowest);
}
else
song->lost_notes++;
}
static void finish_note(MidiSong *song, int i)
{
if (song->voice[i].sample->modes & MODES_ENVELOPE)
{
/* We need to get the envelope out of Sustain stage */
song->voice[i].envelope_stage = 3;
song->voice[i].status = VOICE_OFF;
recompute_envelope(song, i);
apply_envelope_to_amp(song, i);
}
else
{
/* Set status to OFF so resample_voice() will let this voice out
of its loop, if any. In any case, this voice dies when it
hits the end of its data (ofs>=data_length). */
song->voice[i].status = VOICE_OFF;
}
}
static void note_off(MidiSong *song)
{
int i = song->voices;
MidiEvent *e = song->current_event;
while (i--)
if (song->voice[i].status == VOICE_ON &&
song->voice[i].channel == e->channel &&
song->voice[i].note == e->a)
{
if (song->channel[e->channel].sustain)
{
song->voice[i].status = VOICE_SUSTAINED;
}
else
finish_note(song, i);
return;
}
}
/* Process the All Notes Off event */
static void all_notes_off(MidiSong *song)
{
int i = song->voices;
int c = song->current_event->channel;
SNDDBG(("All notes off on channel %d", c));
while (i--)
if (song->voice[i].status == VOICE_ON &&
song->voice[i].channel == c)
{
if (song->channel[c].sustain)
song->voice[i].status = VOICE_SUSTAINED;
else
finish_note(song, i);
}
}
/* Process the All Sounds Off event */
static void all_sounds_off(MidiSong *song)
{
int i = song->voices;
int c = song->current_event->channel;
while (i--)
if (song->voice[i].channel == c &&
song->voice[i].status != VOICE_FREE &&
song->voice[i].status != VOICE_DIE)
{
kill_note(song, i);
}
}
static void adjust_pressure(MidiSong *song)
{
MidiEvent *e = song->current_event;
int i = song->voices;
while (i--)
if (song->voice[i].status == VOICE_ON &&
song->voice[i].channel == e->channel &&
song->voice[i].note == e->a)
{
song->voice[i].velocity = e->b;
recompute_amp(song, i);
apply_envelope_to_amp(song, i);
return;
}
}
static void drop_sustain(MidiSong *song)
{
int i = song->voices;
int c = song->current_event->channel;
while (i--)
if (song->voice[i].status == VOICE_SUSTAINED && song->voice[i].channel == c)
finish_note(song, i);
}
static void adjust_pitchbend(MidiSong *song)
{
int c = song->current_event->channel;
int i = song->voices;
while (i--)
if (song->voice[i].status != VOICE_FREE && song->voice[i].channel == c)
{
recompute_freq(song, i);
}
}
static void adjust_volume(MidiSong *song)
{
int c = song->current_event->channel;
int i = song->voices;
while (i--)
if (song->voice[i].channel == c &&
(song->voice[i].status==VOICE_ON || song->voice[i].status==VOICE_SUSTAINED))
{
recompute_amp(song, i);
apply_envelope_to_amp(song, i);
}
}
static void seek_forward(MidiSong *song, Sint32 until_time)
{
reset_voices(song);
while (song->current_event->time < until_time)
{
switch(song->current_event->type)
{
/* All notes stay off. Just handle the parameter changes. */
case ME_PITCH_SENS:
song->channel[song->current_event->channel].pitchsens =
song->current_event->a;
song->channel[song->current_event->channel].pitchfactor = 0;
break;
case ME_PITCHWHEEL:
song->channel[song->current_event->channel].pitchbend =
song->current_event->a + song->current_event->b * 128;
song->channel[song->current_event->channel].pitchfactor = 0;
break;
case ME_MAINVOLUME:
song->channel[song->current_event->channel].volume =
song->current_event->a;
break;
case ME_PAN:
song->channel[song->current_event->channel].panning =
song->current_event->a;
break;
case ME_EXPRESSION:
song->channel[song->current_event->channel].expression =
song->current_event->a;
break;
case ME_PROGRAM:
if (ISDRUMCHANNEL(song, song->current_event->channel))
/* Change drum set */
song->channel[song->current_event->channel].bank =
song->current_event->a;
else
song->channel[song->current_event->channel].program =
song->current_event->a;
break;
case ME_SUSTAIN:
song->channel[song->current_event->channel].sustain =
song->current_event->a;
break;
case ME_RESET_CONTROLLERS:
reset_controllers(song, song->current_event->channel);
break;
case ME_TONE_BANK:
song->channel[song->current_event->channel].bank =
song->current_event->a;
break;
case ME_EOT:
song->current_sample = song->current_event->time;
return;
}
song->current_event++;
}
/*song->current_sample=song->current_event->time;*/
if (song->current_event != song->events)
song->current_event--;
song->current_sample=until_time;
}
static void skip_to(MidiSong *song, Sint32 until_time)
{
if (song->current_sample > until_time)
song->current_sample = 0;
reset_midi(song);
song->buffered_count = 0;
song->buffer_pointer = song->common_buffer;
song->current_event = song->events;
if (until_time)
seek_forward(song, until_time);
}
static void do_compute_data(MidiSong *song, Sint32 count)
{
int i;
SDL_memset(song->buffer_pointer, 0,
(song->encoding & PE_MONO) ? (count * 4) : (count * 8));
for (i = 0; i < song->voices; i++)
{
if(song->voice[i].status != VOICE_FREE)
mix_voice(song, song->buffer_pointer, i, count);
}
song->current_sample += count;
}
/* count=0 means flush remaining buffered data to output device, then
flush the device itself */
static void compute_data(MidiSong *song, void *stream, Sint32 count)
{
int channels;
if ( song->encoding & PE_MONO )
channels = 1;
else
channels = 2;
if (!count)
{
if (song->buffered_count)
song->write(stream, song->common_buffer, channels * song->buffered_count);
song->buffer_pointer = song->common_buffer;
song->buffered_count = 0;
return;
}
while ((count + song->buffered_count) >= song->buffer_size)
{
do_compute_data(song, song->buffer_size - song->buffered_count);
count -= song->buffer_size - song->buffered_count;
song->write(stream, song->common_buffer, channels * song->buffer_size);
song->buffer_pointer = song->common_buffer;
song->buffered_count = 0;
}
if (count>0)
{
do_compute_data(song, count);
song->buffered_count += count;
song->buffer_pointer += (song->encoding & PE_MONO) ? count : count*2;
}
}
void Timidity_Start(MidiSong *song)
{
song->playing = 1;
adjust_amplification(song);
skip_to(song, 0);
}
void Timidity_Stop(MidiSong *song)
{
song->playing = 0;
}
int Timidity_IsActive(MidiSong *song)
{
return song->playing;
}
void Timidity_Seek(MidiSong *song, Uint32 ms)
{
skip_to(song, (ms * (song->rate / 100)) / 10);
}
Uint32 Timidity_GetSongLength(MidiSong *song)
{
MidiEvent *last_event = &song->events[song->groomed_event_count - 1];
/* We want last_event->time * 1000 / song->rate */
Uint32 retvalue = (last_event->time / song->rate) * 1000;
retvalue += (last_event->time % song->rate) * 1000 / song->rate;
return retvalue;
}
Uint32 Timidity_GetSongTime(MidiSong *song)
{
Uint32 retvalue = (song->current_sample / song->rate) * 1000;
retvalue += (song->current_sample % song->rate) * 1000 / song->rate;
return retvalue;
}
int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len)
{
Sint32 start_sample, end_sample, samples;
int bytes_per_sample;
if (!song->playing)
return 0;
bytes_per_sample = 1;
bytes_per_sample *= ((song->encoding & PE_32BIT) ? 4 : ((song->encoding & PE_16BIT) ? 2 : 1));
bytes_per_sample *= ((song->encoding & PE_MONO) ? 1 : 2);
samples = len / bytes_per_sample;
start_sample = song->current_sample;
end_sample = song->current_sample+samples;
while ( song->current_sample < end_sample ) {
/* Handle all events that should happen at this time */
while (song->current_event->time <= song->current_sample) {
switch(song->current_event->type) {
/* Effects affecting a single note */
case ME_NOTEON:
if (!(song->current_event->b)) /* Velocity 0? */
note_off(song);
else
note_on(song);
break;
case ME_NOTEOFF:
note_off(song);
break;
case ME_KEYPRESSURE:
adjust_pressure(song);
break;
/* Effects affecting a single channel */
case ME_PITCH_SENS:
song->channel[song->current_event->channel].pitchsens =
song->current_event->a;
song->channel[song->current_event->channel].pitchfactor = 0;
break;
case ME_PITCHWHEEL:
song->channel[song->current_event->channel].pitchbend =
song->current_event->a + song->current_event->b * 128;
song->channel[song->current_event->channel].pitchfactor = 0;
/* Adjust pitch for notes already playing */
adjust_pitchbend(song);
break;
case ME_MAINVOLUME:
song->channel[song->current_event->channel].volume =
song->current_event->a;
adjust_volume(song);
break;
case ME_PAN:
song->channel[song->current_event->channel].panning =
song->current_event->a;
break;
case ME_EXPRESSION:
song->channel[song->current_event->channel].expression =
song->current_event->a;
adjust_volume(song);
break;
case ME_PROGRAM:
if (ISDRUMCHANNEL(song, song->current_event->channel)) {
/* Change drum set */
song->channel[song->current_event->channel].bank =
song->current_event->a;
}
else
song->channel[song->current_event->channel].program =
song->current_event->a;
break;
case ME_SUSTAIN:
song->channel[song->current_event->channel].sustain =
song->current_event->a;
if (!song->current_event->a)
drop_sustain(song);
break;
case ME_RESET_CONTROLLERS:
reset_controllers(song, song->current_event->channel);
break;
case ME_ALL_NOTES_OFF:
all_notes_off(song);
break;
case ME_ALL_SOUNDS_OFF:
all_sounds_off(song);
break;
case ME_TONE_BANK:
song->channel[song->current_event->channel].bank =
song->current_event->a;
break;
case ME_EOT:
/* Give the last notes a couple of seconds to decay */
SNDDBG(("Playing time: ~%d seconds\n",
song->current_sample/song->rate+2));
SNDDBG(("Notes cut: %d\n", song->cut_notes));
SNDDBG(("Notes lost totally: %d\n", song->lost_notes));
song->playing = 0;
return (song->current_sample - start_sample) * bytes_per_sample;
}
song->current_event++;
}
if (song->current_event->time > end_sample)
compute_data(song, stream, end_sample-song->current_sample);
else
compute_data(song, stream, song->current_event->time-song->current_sample);
}
return samples * bytes_per_sample;
}
void Timidity_SetVolume(MidiSong *song, int volume)
{
int i;
if (volume > MAX_AMPLIFICATION)
song->amplification = MAX_AMPLIFICATION;
else
if (volume < 0)
song->amplification = 0;
else
song->amplification = volume;
adjust_amplification(song);
for (i = 0; i < song->voices; i++)
if (song->voice[i].status != VOICE_FREE)
{
recompute_amp(song, i);
apply_envelope_to_amp(song, i);
}
}
@@ -0,0 +1,56 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
playmidi.h
*/
#ifndef TIMIDITY_PLAYMIDI_H
#define TIMIDITY_PLAYMIDI_H
/* Midi events */
#define ME_NONE 0
#define ME_NOTEON 1
#define ME_NOTEOFF 2
#define ME_KEYPRESSURE 3
#define ME_MAINVOLUME 4
#define ME_PAN 5
#define ME_SUSTAIN 6
#define ME_EXPRESSION 7
#define ME_PITCHWHEEL 8
#define ME_PROGRAM 9
#define ME_TEMPO 10
#define ME_PITCH_SENS 11
#define ME_ALL_SOUNDS_OFF 12
#define ME_RESET_CONTROLLERS 13
#define ME_ALL_NOTES_OFF 14
#define ME_TONE_BANK 15
#define ME_LYRIC 16
#define ME_EOT 99
/* Causes the instrument's default panning to be used. */
#define NO_PANNING -1
/* Voice status options: */
#define VOICE_FREE 0
#define VOICE_ON 1
#define VOICE_SUSTAINED 2
#define VOICE_OFF 3
#define VOICE_DIE 4
/* Voice panned options: */
#define PANNED_MYSTERY 0
#define PANNED_LEFT 1
#define PANNED_RIGHT 2
#define PANNED_CENTER 3
/* Anything but PANNED_MYSTERY only uses the left volume */
#define ISDRUMCHANNEL(s, c) (((s)->drumchannels & (1<<(c))))
#endif /* TIMIDITY_PLAYMIDI_H */
@@ -0,0 +1,670 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
*/
#include <SDL3/SDL.h>
#include "options.h"
#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"
/* Computes how many (fractional) samples one MIDI delta-time unit contains */
static void compute_sample_increment(MidiSong *song, Sint32 tempo,
Sint32 divisions)
{
double a;
a = (double) (tempo) * (double) (song->rate) * (65536.0/1000000.0) /
(double)(divisions);
song->sample_correction = (Sint32)(a) & 0xFFFF;
song->sample_increment = (Sint32)(a) >> 16;
SNDDBG(("Samples per delta-t: %d (correction %d)",
song->sample_increment, song->sample_correction));
}
/* Read variable-length number (7 bits per byte, MSB first) */
static Sint32 getvl(SDL_IOStream *io)
{
Sint32 l=0;
Uint8 c;
for (;;)
{
if (!SDL_ReadU8(io, &c)) return l;
l += (c & 0x7f);
if (!(c & 0x80)) return l;
l<<=7;
}
}
#if (defined DEBUG_CHATTER)
/* Print a string from the file, followed by a newline. Any non-ASCII
or unprintable characters will be converted to periods. */
static int dumpstring(SDL_IOStream *io, Sint32 len, Uint8 type)
{
static const char *label[] = {
"Text event: ", "Text: ", "Copyright: ", "Track name: ",
"Instrument: ", "Lyric: ", "Marker: ", "Cue point: " };
signed char *s = SDL_malloc(len+1);
if (!s)
{
SDL_SeekIO(io, len, SDL_IO_SEEK_CUR);/* should I ? */
return -1;
}
if (SDL_ReadIO(io, s, len) != (size_t)len)
{
SDL_free(s);
return -1;
}
s[len]='\0';
while (len--)
{
if (s[len]<32)
s[len]='.';
}
SNDDBG(("%s%s", label[(type>7) ? 0 : type], s));
SDL_free(s);
return 0;
}
#endif
#define MIDIEVENT(at,t,ch,pa,pb) \
newlist = (MidiEventList *) SDL_malloc(sizeof(MidiEventList));\
if (!newlist) {song->oom = 1; return NULL;} \
newlist->event.time = at; \
newlist->event.type = t; \
newlist->event.channel = ch; \
newlist->event.a = pa; \
newlist->event.b = pb; \
newlist->next = NULL; \
return newlist;
#define MAGIC_EOT ((MidiEventList *)(-1))
/* Read a MIDI event, returning a freshly allocated element that can
be linked to the event list */
static MidiEventList *read_midi_event(MidiSong *song)
{
static Uint8 laststatus, lastchan;
static Uint8 nrpn=0, rpn_msb[16], rpn_lsb[16]; /* one per channel */
Uint8 me, type, a = 0, b = 0, c = 0;
Sint32 len;
MidiEventList *newlist;
for (;;)
{
song->at += getvl(song->io);
if (!SDL_ReadU8(song->io, &me))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
if(me==0xF0 || me == 0xF7) /* SysEx event */
{
len=getvl(song->io);
SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR);
}
else if(me==0xFF) /* Meta event */
{
if (!SDL_ReadU8(song->io, &type))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
len=getvl(song->io);
if (type>0 && type<16)
{
#if (defined DEBUG_CHATTER)
dumpstring(song->io, len, type);
#else
SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR);
#endif
}
else
switch(type)
{
case 0x2F: /* End of Track */
return MAGIC_EOT;
case 0x51: /* Tempo */
if (!SDL_ReadU8(song->io, &a) ||
!SDL_ReadU8(song->io, &b) ||
!SDL_ReadU8(song->io, &c))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
MIDIEVENT(song->at, ME_TEMPO, c, a, b);
default:
SNDDBG(("(Meta event type 0x%02x, length %d)\n", type, len));
if (SDL_SeekIO(song->io, len, SDL_IO_SEEK_CUR) < 0)
{
SNDDBG(("read_midi_event: SDL_IOseek() failure\n"));
return NULL;
}
break;
}
}
else
{
a=me;
if (a & 0x80) /* status byte */
{
lastchan=a & 0x0F;
laststatus=(a>>4) & 0x07;
if (!SDL_ReadU8(song->io, &a))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
a &= 0x7F;
}
switch(laststatus)
{
case 0: /* Note off */
if (!SDL_ReadU8(song->io, &b))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
b &= 0x7F;
MIDIEVENT(song->at, ME_NOTEOFF, lastchan, a,b);
case 1: /* Note on */
if (!SDL_ReadU8(song->io, &b))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
b &= 0x7F;
MIDIEVENT(song->at, ME_NOTEON, lastchan, a,b);
case 2: /* Key Pressure */
if (!SDL_ReadU8(song->io, &b))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
b &= 0x7F;
MIDIEVENT(song->at, ME_KEYPRESSURE, lastchan, a, b);
case 3: /* Control change */
if (!SDL_ReadU8(song->io, &b))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
b &= 0x7F;
{
int control=255;
switch(a)
{
case 7: control=ME_MAINVOLUME; break;
case 10: control=ME_PAN; break;
case 11: control=ME_EXPRESSION; break;
case 64: control=ME_SUSTAIN; b = (b >= 64); break;
case 120: control=ME_ALL_SOUNDS_OFF; break;
case 121: control=ME_RESET_CONTROLLERS; break;
case 123: control=ME_ALL_NOTES_OFF; break;
/* These should be the SCC-1 tone bank switch
commands. I don't know why there are two, or
why the latter only allows switching to bank 0.
Also, some MIDI files use 0 as some sort of
continuous controller. This will cause lots of
warnings about undefined tone banks. */
case 0: control=ME_TONE_BANK; break;
case 32:
if (b!=0) {
SNDDBG(("(Strange: tone bank change 0x%02x)\n", b));
}
#if 0 /* `Bank Select LSB' is not worked at GS. Please ignore it. */
else
control=ME_TONE_BANK;
#endif
break;
case 100: nrpn=0; rpn_msb[lastchan]=b; break;
case 101: nrpn=0; rpn_lsb[lastchan]=b; break;
case 99: nrpn=1; rpn_msb[lastchan]=b; break;
case 98: nrpn=1; rpn_lsb[lastchan]=b; break;
case 6:
if (nrpn)
{
SNDDBG(("(Data entry (MSB) for NRPN %02x,%02x: %d)\n",
rpn_msb[lastchan], rpn_lsb[lastchan], b));
break;
}
switch((rpn_msb[lastchan]<<8) | rpn_lsb[lastchan])
{
case 0x0000: /* Pitch bend sensitivity */
control=ME_PITCH_SENS;
break;
case 0x7F7F: /* RPN reset */
/* reset pitch bend sensitivity to 2 */
MIDIEVENT(song->at, ME_PITCH_SENS, lastchan, 2, 0);
default:
SNDDBG(("(Data entry (MSB) for RPN %02x,%02x: %d)\n",
rpn_msb[lastchan], rpn_lsb[lastchan], b));
break;
}
break;
default:
SNDDBG(("(Control %d: %d)\n", a, b));
break;
}
if (control != 255)
{
MIDIEVENT(song->at, control, lastchan, b, 0);
}
}
break;
case 4: /* Program change */
a &= 0x7f;
MIDIEVENT(song->at, ME_PROGRAM, lastchan, a, 0);
case 5: /* Channel pressure - NOT IMPLEMENTED */
break;
case 6: /* Pitch wheel */
if (!SDL_ReadU8(song->io, &b))
{
SNDDBG(("read_midi_event: SDL_IOread() failure\n"));
return NULL;
}
b &= 0x7F;
MIDIEVENT(song->at, ME_PITCHWHEEL, lastchan, a, b);
default:
SNDDBG(("*** Can't happen: status 0x%02X, channel 0x%02X\n",
laststatus, lastchan));
break;
}
}
}
return newlist;
}
#undef MIDIEVENT
/* Read a midi track into the linked list, either merging with any previous
tracks or appending to them. */
static int read_track(MidiSong *song, int append)
{
MidiEventList *meep;
MidiEventList *next, *newlist;
Sint32 len;
Sint64 next_pos, pos;
char tmp[4];
meep = song->evlist;
if (append && meep)
{
/* find the last event in the list */
for (; meep->next; meep=meep->next)
;
song->at = meep->event.time;
}
else
song->at=0;
/* Check the formalities */
if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4)
{
SNDDBG(("Can't read track header.\n"));
return -1;
}
len=(Sint32)SDL_Swap32BE((Uint32)len);
next_pos = SDL_TellIO(song->io) + len;
if (SDL_memcmp(tmp, "MTrk", 4))
{
SNDDBG(("Corrupt MIDI file.\n"));
return -2;
}
for (;;)
{
if (!(newlist=read_midi_event(song))) /* Some kind of error */
return -2;
if (newlist==MAGIC_EOT) /* End-of-track Hack. */
{
/* If the track ends before the size of the
* track data, skip any junk at the end. */
pos = SDL_TellIO(song->io);
if (pos < next_pos)
SDL_SeekIO(song->io, next_pos - pos, SDL_IO_SEEK_CUR);
return 0;
}
next=meep->next;
while (next && (next->event.time < newlist->event.time))
{
meep=next;
next=meep->next;
}
newlist->next=next;
meep->next=newlist;
song->event_count++; /* Count the event. (About one?) */
meep=newlist;
}
}
/* Free the linked event list from memory. */
static void free_midi_list(MidiSong *song)
{
MidiEventList *meep, *next;
meep = song->evlist;
while (meep)
{
next=meep->next;
SDL_free(meep);
meep=next;
}
song->evlist = NULL;
}
/* Allocate an array of MidiEvents and fill it from the linked list of
events, marking used instruments for loading. Convert event times to
samples: handle tempo changes. Strip unnecessary events from the list.
Free the linked list. */
static MidiEvent *groom_list(MidiSong *song, Sint32 divisions,Sint32 *eventsp,
Sint32 *samplesp)
{
MidiEvent *groomed_list, *lp;
MidiEventList *meep;
Sint32 i, our_event_count, tempo, skip_this_event, new_value;
Sint32 sample_cum, samples_to_do, at, st, dt, counting_time;
int current_bank[MAXCHAN], current_set[MAXCHAN], current_program[MAXCHAN];
/* Or should each bank have its own current program? */
for (i=0; i<MAXCHAN; i++)
{
current_bank[i]=0;
current_set[i]=0;
current_program[i]=song->default_program;
}
tempo=500000;
compute_sample_increment(song, tempo, divisions);
/* This may allocate a bit more than we need */
groomed_list=lp=SDL_malloc(sizeof(MidiEvent) * (song->event_count+1));
if (!groomed_list) {
song->oom=1;
free_midi_list(song);
return NULL;
}
meep=song->evlist;
our_event_count=0;
st=at=sample_cum=0;
counting_time=2; /* We strip any silence before the first NOTE ON. */
for (i = 0; i < song->event_count; i++)
{
skip_this_event=0;
if (meep->event.type==ME_TEMPO)
{
skip_this_event=1;
}
else if (meep->event.channel >= MAXCHAN)
skip_this_event=1;
else switch (meep->event.type)
{
case ME_PROGRAM:
if (ISDRUMCHANNEL(song, meep->event.channel))
{
if (song->drumset[meep->event.a]) /* Is this a defined drumset? */
new_value=meep->event.a;
else
{
SNDDBG(("Drum set %d is undefined\n", meep->event.a));
new_value=meep->event.a=0;
}
if (current_set[meep->event.channel] != new_value)
current_set[meep->event.channel]=new_value;
else
skip_this_event=1;
}
else
{
new_value=meep->event.a;
if ((current_program[meep->event.channel] != SPECIAL_PROGRAM)
&& (current_program[meep->event.channel] != new_value))
current_program[meep->event.channel] = new_value;
else
skip_this_event=1;
}
break;
case ME_NOTEON:
if (counting_time)
counting_time=1;
if (ISDRUMCHANNEL(song, meep->event.channel))
{
/* Mark this instrument to be loaded */
if (!(song->drumset[current_set[meep->event.channel]]
->instrument[meep->event.a]))
song->drumset[current_set[meep->event.channel]]
->instrument[meep->event.a] = MAGIC_LOAD_INSTRUMENT;
}
else
{
if (current_program[meep->event.channel]==SPECIAL_PROGRAM)
break;
/* Mark this instrument to be loaded */
if (!(song->tonebank[current_bank[meep->event.channel]]
->instrument[current_program[meep->event.channel]]))
song->tonebank[current_bank[meep->event.channel]]
->instrument[current_program[meep->event.channel]] =
MAGIC_LOAD_INSTRUMENT;
}
break;
case ME_TONE_BANK:
if (ISDRUMCHANNEL(song, meep->event.channel))
{
skip_this_event=1;
break;
}
if (song->tonebank[meep->event.a]) /* Is this a defined tone bank? */
new_value=meep->event.a;
else
{
SNDDBG(("Tone bank %d is undefined\n", meep->event.a));
new_value=meep->event.a=0;
}
if (current_bank[meep->event.channel]!=new_value)
current_bank[meep->event.channel]=new_value;
else
skip_this_event=1;
break;
}
/* Recompute time in samples*/
if ((dt=meep->event.time - at) && !counting_time)
{
if (song->sample_increment > 2147483647/dt ||
song->sample_correction > 2147483647/dt) {
goto _overflow;
}
samples_to_do = song->sample_increment * dt;
sample_cum += song->sample_correction * dt;
if (sample_cum & 0xFFFF0000)
{
samples_to_do += ((sample_cum >> 16) & 0xFFFF);
sample_cum &= 0x0000FFFF;
}
if (st >= 2147483647 - samples_to_do) {
_overflow:
SNDDBG(("Overflow in sample counter\n"));
free_midi_list(song);
SDL_free(groomed_list);
return NULL;
}
st += samples_to_do;
}
else if (counting_time==1) counting_time=0;
if (meep->event.type==ME_TEMPO)
{
tempo=
meep->event.channel + meep->event.b * 256 + meep->event.a * 65536;
compute_sample_increment(song, tempo, divisions);
}
if (!skip_this_event)
{
/* Add the event to the list */
*lp=meep->event;
lp->time=st;
lp++;
our_event_count++;
}
at=meep->event.time;
meep=meep->next;
}
/* Add an End-of-Track event */
lp->time=st;
lp->type=ME_EOT;
our_event_count++;
free_midi_list(song);
*eventsp=our_event_count;
*samplesp=st;
return groomed_list;
}
MidiEvent *read_midi_file(MidiSong *song, Sint32 *count, Sint32 *sp)
{
Sint32 len, divisions;
Sint16 format = 0, tracks = 0, divisions_tmp = 0;
int i;
char tmp[4];
song->event_count=0;
song->at=0;
song->evlist = NULL;
if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4)
{
SNDDBG(("Not a MIDI file!\n"));
return NULL;
}
if (SDL_memcmp(tmp, "RIFF", 4) == 0) { /* RMID ?? */
if (SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_memcmp(tmp, "RMID", 4) != 0 ||
SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_memcmp(tmp, "data", 4) != 0 ||
SDL_ReadIO(song->io, tmp, 4) != 4 ||
/* SMF must begin from here onwards: */
SDL_ReadIO(song->io, tmp, 4) != 4 || SDL_ReadIO(song->io, &len, 4) != 4)
{
SNDDBG(("Not an RMID file!\n"));
return NULL;
}
}
len=(Sint32)SDL_Swap32BE((Uint32)len);
if (SDL_memcmp(tmp, "MThd", 4) || len < 6)
{
SNDDBG(("Not a MIDI file!\n"));
return NULL;
}
format=tracks=divisions_tmp = -1;
if (!SDL_ReadS16BE(song->io, &format) ||
!SDL_ReadS16BE(song->io, &tracks) ||
!SDL_ReadS16BE(song->io, &divisions_tmp))
{
SNDDBG(("Not a MIDI file!\n"));
return NULL;
}
if (divisions_tmp<0)
{
/* SMPTE time -- totally untested. Got a MIDI file that uses this? */
divisions=
(Sint32)(-(divisions_tmp/256)) * (Sint32)(divisions_tmp & 0xFF);
}
else divisions=(Sint32)(divisions_tmp);
if (len > 6)
{
SNDDBG(("MIDI file header size %u bytes", len));
SDL_SeekIO(song->io, len-6, SDL_IO_SEEK_CUR); /* skip the excess */
}
if (format<0 || format >2)
{
SNDDBG(("Unknown MIDI file format %d\n", format));
return NULL;
}
if (tracks<1)
{
SNDDBG(("Bad number of tracks %d\n", tracks));
return NULL;
}
if (format==0 && tracks!=1)
{
SNDDBG(("%d tracks with Type-0 MIDI (must be 1.)\n", tracks));
return NULL;
}
SNDDBG(("Format: %d Tracks: %d Divisions: %d\n",
format, tracks, divisions));
/* Put a do-nothing event first in the list for easier processing */
song->evlist=SDL_calloc(1, sizeof(MidiEventList));
if (!song->evlist) {
song->oom=1;
return NULL;
}
song->event_count++;
switch(format)
{
case 0:
if (read_track(song, 0))
{
free_midi_list(song);
return NULL;
}
break;
case 1:
for (i=0; i<tracks; i++)
if (read_track(song, 0))
{
free_midi_list(song);
return NULL;
}
break;
case 2: /* We simply play the tracks sequentially */
for (i=0; i<tracks; i++)
if (read_track(song, 1))
{
free_midi_list(song);
return NULL;
}
break;
}
return groom_list(song, divisions, count, sp);
}
@@ -0,0 +1,18 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
readmidi.h
*/
#ifndef TIMIDITY_READMIDI_H
#define TIMIDITY_READMIDI_H
#define read_midi_file TIMI_NAMESPACE(read_midi_file)
extern MidiEvent *read_midi_file(MidiSong *song, Sint32 *count, Sint32 *sp);
#endif /* TIMIDITY_READMIDI_H */
@@ -0,0 +1,600 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
resample.c
*/
#include <SDL3/SDL.h>
#include "timidity.h"
#include "options.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "tables.h"
#include "resample.h"
#define PRECALC_LOOP_COUNT(start, end, incr) (((end) - (start) + (incr) - 1) / (incr))
/*************** resampling with fixed increment *****************/
static sample_t *rs_plain(MidiSong *song, int v, Sint32 *countptr)
{
/* Play sample until end, then free the voice. */
sample_t v1, v2;
Voice
*vp=&(song->voice[v]);
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
Sint32
ofs=vp->sample_offset,
incr=vp->sample_increment,
le=vp->sample->data_length,
count=*countptr;
Sint32 i, j;
if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */
/* Precalc how many times we should go through the loop.
NOTE: Assumes that incr > 0 and that ofs <= le */
i = PRECALC_LOOP_COUNT(ofs, le, incr);
if (i > count)
{
i = count;
count = 0;
}
else count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
if (ofs >= le)
{
if (ofs == le)
*dest++ = src[(ofs>>FRACTION_BITS)-1]/2;
vp->status=VOICE_FREE;
*countptr-=count+1;
}
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
static sample_t *rs_loop(MidiSong *song, Voice *vp, Sint32 count)
{
/* Play sample until end-of-loop, skip back and continue. */
sample_t v1, v2;
Sint32
ofs=vp->sample_offset,
incr=vp->sample_increment,
le=vp->sample->loop_end,
ll=le - vp->sample->loop_start;
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
Sint32 i, j;
while (count)
{
while (ofs >= le)
ofs -= ll;
/* Precalc how many times we should go through the loop */
i = PRECALC_LOOP_COUNT(ofs, le, incr);
if (i > count)
{
i = count;
count = 0;
}
else count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
}
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
static sample_t *rs_bidir(MidiSong *song, Voice *vp, Sint32 count)
{
sample_t v1, v2;
Sint32
ofs=vp->sample_offset,
incr=vp->sample_increment,
le=vp->sample->loop_end,
ls=vp->sample->loop_start;
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
Sint32
le2 = le<<1,
ls2 = ls<<1,
i, j;
/* Play normally until inside the loop region */
if (incr > 0 && ofs < ls)
{
/* NOTE: Assumes that incr > 0, which is NOT always the case
when doing bidirectional looping. I have yet to see a case
where both ofs <= ls AND incr < 0, however. */
i = PRECALC_LOOP_COUNT(ofs, ls, incr);
if (i > count)
{
i = count;
count = 0;
}
else count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
}
/* Then do the bidirectional looping */
while(count)
{
/* Precalc how many times we should go through the loop */
i = PRECALC_LOOP_COUNT(ofs, incr > 0 ? le : ls, incr);
if (i > count)
{
i = count;
count = 0;
}
else count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
if (ofs>=le)
{
/* fold the overshoot back in */
ofs = le2 - ofs;
incr *= -1;
}
else if (ofs <= ls)
{
ofs = ls2 - ofs;
incr *= -1;
}
}
vp->sample_increment=incr;
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
/*********************** vibrato versions ***************************/
/* We only need to compute one half of the vibrato sine cycle */
static int vib_phase_to_inc_ptr(int phase)
{
if (phase < VIBRATO_SAMPLE_INCREMENTS/2)
return VIBRATO_SAMPLE_INCREMENTS/2-1-phase;
else if (phase >= 3*VIBRATO_SAMPLE_INCREMENTS/2)
return 5*VIBRATO_SAMPLE_INCREMENTS/2-1-phase;
else
return phase-VIBRATO_SAMPLE_INCREMENTS/2;
}
static Sint32 update_vibrato(MidiSong *song, Voice *vp, int sign)
{
Sint32 depth;
int phase, pb;
double a;
if (vp->vibrato_phase++ >= 2*VIBRATO_SAMPLE_INCREMENTS-1)
vp->vibrato_phase=0;
phase=vib_phase_to_inc_ptr(vp->vibrato_phase);
if (vp->vibrato_sample_increment[phase])
{
if (sign)
return -vp->vibrato_sample_increment[phase];
else
return vp->vibrato_sample_increment[phase];
}
/* Need to compute this sample increment. */
depth=vp->sample->vibrato_depth<<7;
if (vp->vibrato_sweep)
{
/* Need to update sweep */
vp->vibrato_sweep_position += vp->vibrato_sweep;
if (vp->vibrato_sweep_position >= (1<<SWEEP_SHIFT))
vp->vibrato_sweep=0;
else
{
/* Adjust depth */
depth *= vp->vibrato_sweep_position;
depth >>= SWEEP_SHIFT;
}
}
a = TIM_FSCALE(((double)(vp->sample->sample_rate) *
(double)(vp->frequency)) /
((double)(vp->sample->root_freq) *
(double)(song->rate)),
FRACTION_BITS);
pb=(int)((timi_sine(vp->vibrato_phase *
(SINE_CYCLE_LENGTH/(2*VIBRATO_SAMPLE_INCREMENTS)))
* (double)(depth) * VIBRATO_AMPLITUDE_TUNING));
if (pb<0)
{
pb=-pb;
a /= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13];
}
else
a *= bend_fine[(pb>>5) & 0xFF] * bend_coarse[pb>>13];
/* If the sweep's over, we can store the newly computed sample_increment */
if (!vp->vibrato_sweep)
vp->vibrato_sample_increment[phase]=(Sint32) a;
if (sign)
a = -a; /* need to preserve the loop direction */
return (Sint32) a;
}
static sample_t *rs_vib_plain(MidiSong *song, int v, Sint32 *countptr)
{
/* Play sample until end, then free the voice. */
sample_t v1, v2;
Voice *vp=&(song->voice[v]);
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
Sint32
le=vp->sample->data_length,
ofs=vp->sample_offset,
incr=vp->sample_increment,
count=*countptr;
int
cc=vp->vibrato_control_counter;
/* This has never been tested */
if (incr<0) incr = -incr; /* In case we're coming out of a bidir loop */
while (count--)
{
if (!cc--)
{
cc=vp->vibrato_control_ratio;
incr=update_vibrato(song, vp, 0);
}
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
if (ofs >= le)
{
if (ofs == le)
*dest++ = src[(ofs>>FRACTION_BITS)-1]/2;
vp->status=VOICE_FREE;
*countptr-=count+1;
break;
}
}
vp->vibrato_control_counter=cc;
vp->sample_increment=incr;
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
static sample_t *rs_vib_loop(MidiSong *song, Voice *vp, Sint32 count)
{
/* Play sample until end-of-loop, skip back and continue. */
sample_t v1, v2;
Sint32
ofs=vp->sample_offset,
incr=vp->sample_increment,
le=vp->sample->loop_end,
ll=le - vp->sample->loop_start;
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
int
cc=vp->vibrato_control_counter;
Sint32 i, j;
int
vibflag=0;
while (count)
{
/* Hopefully the loop is longer than an increment */
while(ofs >= le)
ofs -= ll;
/* Precalc how many times to go through the loop, taking
the vibrato control ratio into account this time. */
i = PRECALC_LOOP_COUNT(ofs, le, incr);
if(i > count) i = count;
if(i > cc)
{
i = cc;
vibflag = 1;
}
else cc -= i;
count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
if(vibflag)
{
cc = vp->vibrato_control_ratio;
incr = update_vibrato(song, vp, 0);
vibflag = 0;
}
}
vp->vibrato_control_counter=cc;
vp->sample_increment=incr;
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
static sample_t *rs_vib_bidir(MidiSong *song, Voice *vp, Sint32 count)
{
sample_t v1, v2;
Sint32
ofs=vp->sample_offset,
incr=vp->sample_increment,
le=vp->sample->loop_end,
ls=vp->sample->loop_start;
sample_t
*dest=song->resample_buffer,
*src=vp->sample->data;
int
cc=vp->vibrato_control_counter;
Sint32
le2=le<<1,
ls2=ls<<1,
i, j;
int
vibflag = 0;
/* Play normally until inside the loop region */
while (count && incr > 0 && ofs < ls)
{
i = PRECALC_LOOP_COUNT(ofs, ls, incr);
if (i > count) i = count;
if (i > cc)
{
i = cc;
vibflag = 1;
}
else cc -= i;
count -= i;
for (j = 0; j < i; j++)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
if (vibflag)
{
cc = vp->vibrato_control_ratio;
incr = update_vibrato(song, vp, 0);
vibflag = 0;
}
}
/* Then do the bidirectional looping */
while (count)
{
/* Precalc how many times we should go through the loop */
i = PRECALC_LOOP_COUNT(ofs, incr > 0 ? le : ls, incr);
if(i > count) i = count;
if(i > cc)
{
i = cc;
vibflag = 1;
}
else cc -= i;
count -= i;
while (i--)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS)+1];
*dest++ = v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS);
ofs += incr;
}
if (vibflag)
{
cc = vp->vibrato_control_ratio;
incr = update_vibrato(song, vp, (incr < 0));
vibflag = 0;
}
if (ofs >= le)
{
/* fold the overshoot back in */
ofs = le2 - ofs;
incr *= -1;
}
else if (ofs <= ls)
{
ofs = ls2 - ofs;
incr *= -1;
}
}
vp->vibrato_control_counter=cc;
vp->sample_increment=incr;
vp->sample_offset=ofs; /* Update offset */
return song->resample_buffer;
}
sample_t *resample_voice(MidiSong *song, int v, Sint32 *countptr)
{
Sint32 ofs;
Uint8 modes;
Voice *vp=&(song->voice[v]);
if (!(vp->sample->sample_rate))
{
/* Pre-resampled data -- just update the offset and check if
we're out of data. */
ofs=vp->sample_offset >> FRACTION_BITS; /* Kind of silly to use
FRACTION_BITS here... */
if (*countptr >= (vp->sample->data_length>>FRACTION_BITS) - ofs)
{
/* Note finished. Free the voice. */
vp->status = VOICE_FREE;
/* Let the caller know how much data we had left */
*countptr = (vp->sample->data_length>>FRACTION_BITS) - ofs;
}
else
vp->sample_offset += *countptr << FRACTION_BITS;
return vp->sample->data+ofs;
}
/* Need to resample. Use the proper function. */
modes=vp->sample->modes;
if (vp->vibrato_control_ratio)
{
if ((modes & MODES_LOOPING) &&
((modes & MODES_ENVELOPE) ||
(vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED)))
{
if (modes & MODES_PINGPONG)
return rs_vib_bidir(song, vp, *countptr);
else
return rs_vib_loop(song, vp, *countptr);
}
else
return rs_vib_plain(song, v, countptr);
}
else
{
if ((modes & MODES_LOOPING) &&
((modes & MODES_ENVELOPE) ||
(vp->status==VOICE_ON || vp->status==VOICE_SUSTAINED)))
{
if (modes & MODES_PINGPONG)
return rs_bidir(song, vp, *countptr);
else
return rs_loop(song, vp, *countptr);
}
else
return rs_plain(song, v, countptr);
}
}
void pre_resample(MidiSong *song, Sample *sp)
{
double a, xdiff;
Sint32 incr, ofs, newlen, count;
Sint16 *newdata, *dest, *src = (Sint16 *) sp->data, *vptr;
Sint32 v, v1, v2, v3, v4, v5, i;
#ifdef DEBUG_CHATTER
static const char note_name[12][3] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};
SNDDBG((" * pre-resampling for note %d (%s%d)\n",
sp->note_to_use,
note_name[sp->note_to_use % 12], (sp->note_to_use & 0x7F) / 12));
#endif
a = ((double) (sp->root_freq) * song->rate) /
((double) (sp->sample_rate) * freq_table[(int) (sp->note_to_use)]);
if(sp->data_length * a >= 0x7fffffffL) { /* Too large to compute */
SNDDBG((" *** Can't pre-resampling for note %d\n", sp->note_to_use));
return;
}
newlen = (Sint32)(sp->data_length * a);
count = (newlen >> FRACTION_BITS) - 1;
ofs = incr = (sp->data_length - (1 << FRACTION_BITS)) / count;
if((double)newlen + incr >= 0x7fffffffL) { /* Too large to compute */
SNDDBG((" *** Can't pre-resampling for note %d\n", sp->note_to_use));
return;
}
dest = newdata = (Sint16 *) SDL_malloc((newlen >> (FRACTION_BITS - 1)) + 2);
if (!dest) {
song->oom = 1;
return;
}
if (--count)
*dest++ = src[0];
/* Since we're pre-processing and this doesn't have to be done in
real-time, we go ahead and do the full sliding cubic interpolation. */
count--;
for(i = 0; i < count; i++)
{
vptr = src + (ofs >> FRACTION_BITS);
v1 = ((vptr>=src+1)? *(vptr - 1):0);
v2 = *vptr;
v3 = *(vptr + 1);
v4 = *(vptr + 2);
v5 = v2 - v3;
xdiff = TIM_FSCALENEG(ofs & FRACTION_MASK, FRACTION_BITS);
v = (Sint32)(v2 + xdiff * (1.0/6.0) * (3 * (v3 - v5) - 2 * v1 - v4 +
xdiff * (3 * (v1 - v2 - v5) + xdiff * (3 * v5 + v4 - v1))));
*dest++ = (Sint16)((v > 32767) ? 32767 : ((v < -32768) ? -32768 : v));
ofs += incr;
}
if (ofs & FRACTION_MASK)
{
v1 = src[ofs >> FRACTION_BITS];
v2 = src[(ofs >> FRACTION_BITS) + 1];
*dest++ = (Sint16)(v1 + (((v2 - v1) * (ofs & FRACTION_MASK)) >> FRACTION_BITS));
}
else
*dest++ = src[ofs >> FRACTION_BITS];
*dest = *(dest - 1) / 2;
++dest;
*dest = *(dest - 1) / 2;
sp->data_length = newlen;
sp->loop_start = (Sint32)(sp->loop_start * a);
sp->loop_end = (Sint32)(sp->loop_end * a);
SDL_free(sp->data);
sp->data = (sample_t *) newdata;
sp->sample_rate = 0;
}
@@ -0,0 +1,20 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
resample.h
*/
#ifndef TIMIDITY_RESAMPLE_H
#define TIMIDITY_RESAMPLE_H
#define resample_voice TIMI_NAMESPACE(resample_voice)
#define pre_resample TIMI_NAMESPACE(pre_resample)
extern sample_t *resample_voice(MidiSong *song, int v, Sint32 *countptr);
extern void pre_resample(MidiSong *song, Sample *sp);
#endif /* TIMIDITY_RESAMPLE_H */
+198
View File
@@ -0,0 +1,198 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
*/
#include <SDL3/SDL.h>
#include "common.h"
#include "tables.h"
const Sint32 freq_table[128] =
{
8176, 8662, 9177, 9723,
10301, 10913, 11562, 12250,
12978, 13750, 14568, 15434,
16352, 17324, 18354, 19445,
20602, 21827, 23125, 24500,
25957, 27500, 29135, 30868,
32703, 34648, 36708, 38891,
41203, 43654, 46249, 48999,
51913, 55000, 58270, 61735,
65406, 69296, 73416, 77782,
82407, 87307, 92499, 97999,
103826, 110000, 116541, 123471,
130813, 138591, 146832, 155563,
164814, 174614, 184997, 195998,
207652, 220000, 233082, 246942,
261626, 277183, 293665, 311127,
329628, 349228, 369994, 391995,
415305, 440000, 466164, 493883,
523251, 554365, 587330, 622254,
659255, 698456, 739989, 783991,
830609, 880000, 932328, 987767,
1046502, 1108731, 1174659, 1244508,
1318510, 1396913, 1479978, 1567982,
1661219, 1760000, 1864655, 1975533,
2093005, 2217461, 2349318, 2489016,
2637020, 2793826, 2959955, 3135963,
3322438, 3520000, 3729310, 3951066,
4186009, 4434922, 4698636, 4978032,
5274041, 5587652, 5919911, 6271927,
6644875, 7040000, 7458620, 7902133,
8372018, 8869844, 9397273, 9956063,
10548082, 11175303, 11839822, 12543854
};
/* v=2.^((x/127-1) * 6) */
const double vol_table[128] =
{
0.015625, 0.016145143728351113, 0.016682602624583379, 0.017237953096759438,
0.017811790741104401, 0.01840473098076444, 0.019017409725829021, 0.019650484055324921,
0.020304632921913132, 0.020980557880044631, 0.021678983838355849, 0.02240065983711079,
0.023146359851523596, 0.023916883621822989, 0.024713057510949051, 0.025535735390801884,
0.026385799557992876, 0.027264161680080529, 0.028171763773305786, 0.029109579212875332,
0.030078613776876421, 0.031079906724942836, 0.032114531912828696, 0.033183598944085631,
0.034288254360078256, 0.035429682869614412, 0.036609108619508737, 0.037827796507442342,
0.039087053538526394, 0.040388230227024875, 0.041732722044739302, 0.043121970917609151,
0.044557466772132896, 0.046040749133268132, 0.047573408775524545, 0.049157089429020417,
0.050793489542332405, 0.05248436410402918, 0.054231526524842463, 0.056036850582493913,
0.057902272431264008, 0.059829792678457581, 0.061821478529993396, 0.063879466007418645,
0.066005962238725971, 0.068203247825430205, 0.070473679288442961, 0.072819691595368496,
0.075243800771931268, 0.077748606600335793, 0.080336795407452768, 0.083011142945821612,
0.085774517370559328, 0.088629882315368294, 0.091580300070941839, 0.094628934869176312,
0.097779056276712184, 0.10103404270144323, 0.1043973850157546, 0.1078726903003755,
0.11146368571286204, 0.11517422248485852, 0.11900828005242428, 0.12296997032385605,
0.12706354208958254, 0.13129338557886089, 0.13566403716816194, 0.14018018424629392,
0.14484667024148207, 0.14966849981579558, 0.15465084423249356, 0.15979904690204472,
0.16511862911277009, 0.17061529595225433, 0.17629494242587571, 0.18216365977901747,
0.18822774202974024, 0.19449369271892172, 0.20096823188510385, 0.20765830327152621,
0.21457108177307616, 0.22171398113114205, 0.2290946618846218, 0.23672103958561411,
0.2446012932886038, 0.25274387432224471, 0.26115751535314891, 0.26985123975140174,
0.27883437126784744, 0.28811654403352405, 0.29770771289197112, 0.30761816407549192,
0.31785852623682015, 0.32843978184802081, 0.33937327897885317, 0.3506707434672246,
0.36234429149478936, 0.37440644258117928, 0.38687013301080181, 0.39974872970660535,
0.41305604456569134, 0.42680634927214656, 0.44101439060298442, 0.45569540624360722,
0.47086514112975281, 0.48653986433345225, 0.50273638651110641, 0.51947207793239625,
0.53676488710936021, 0.55463336004561792, 0.57309666012638816, 0.59217458867062556,
0.61188760616732485, 0.63225685421876243, 0.65330417821421161, 0.67505215075844849,
0.69752409588017272, 0.72074411404630734, 0.74473710800900605, 0.76952880951308478,
0.79514580689252357, 0.82161557358563286, 0.84896649759946774, 0.87722791195508854,
0.90643012614631979, 0.93660445864574493, 0.96778327049280244, 1
};
const double bend_fine[256] = {
1, 1.0002256593050698, 1.0004513695322617, 1.0006771306930664,
1.0009029427989777, 1.0011288058614922, 1.0013547198921082, 1.0015806849023274,
1.0018067009036538, 1.002032767907594, 1.0022588859256572, 1.0024850549693551,
1.0027112750502025, 1.0029375461797159, 1.0031638683694153, 1.0033902416308227,
1.0036166659754628, 1.0038431414148634, 1.0040696679605541, 1.0042962456240678,
1.0045228744169397, 1.0047495543507072, 1.0049762854369111, 1.0052030676870944,
1.0054299011128027, 1.0056567857255843, 1.00588372153699, 1.006110708558573,
1.0063377468018897, 1.0065648362784985, 1.0067919769999607, 1.0070191689778405,
1.0072464122237039, 1.0074737067491204, 1.0077010525656616, 1.0079284496849015,
1.0081558981184175, 1.008383397877789, 1.008610948974598, 1.0088385514204294,
1.0090662052268706, 1.0092939104055114, 1.0095216669679448, 1.0097494749257656,
1.009977334290572, 1.0102052450739643, 1.0104332072875455, 1.0106612209429215,
1.0108892860517005, 1.0111174026254934, 1.0113455706759138, 1.0115737902145781,
1.0118020612531047, 1.0120303838031153, 1.0122587578762337, 1.012487183484087,
1.0127156606383041, 1.0129441893505169, 1.0131727696323602, 1.0134014014954713,
1.0136300849514894, 1.0138588200120575, 1.0140876066888203, 1.0143164449934257,
1.0145453349375237, 1.0147742765327674, 1.0150032697908125, 1.0152323147233171,
1.015461411341942, 1.0156905596583505, 1.0159197596842091, 1.0161490114311862,
1.0163783149109531, 1.0166076701351838, 1.0168370771155553, 1.0170665358637463,
1.0172960463914391, 1.0175256087103179, 1.0177552228320703, 1.0179848887683858,
1.0182146065309567, 1.0184443761314785, 1.0186741975816487, 1.0189040708931674,
1.0191339960777379, 1.0193639731470658, 1.0195940021128593, 1.0198240829868295,
1.0200542157806898, 1.0202844005061564, 1.0205146371749483, 1.0207449257987866,
1.0209752663893958, 1.0212056589585028, 1.0214361035178368, 1.0216666000791297,
1.0218971486541166, 1.0221277492545349, 1.0223584018921241, 1.0225891065786274,
1.0228198633257899, 1.0230506721453596, 1.023281533049087, 1.0235124460487257,
1.0237434111560313, 1.0239744283827625, 1.0242054977406807, 1.0244366192415495,
1.0246677928971357, 1.0248990187192082, 1.025130296719539, 1.0253616269099028,
1.0255930093020766, 1.0258244439078401, 1.0260559307389761, 1.0262874698072693,
1.0265190611245079, 1.0267507047024822, 1.0269824005529853, 1.027214148687813,
1.0274459491187637, 1.0276778018576387, 1.0279097069162415, 1.0281416643063788,
1.0283736740398595, 1.0286057361284953, 1.0288378505841009, 1.0290700174184932,
1.0293022366434921, 1.0295345082709197, 1.0297668323126017, 1.0299992087803651,
1.030231637686041, 1.0304641190414621, 1.0306966528584645, 1.0309292391488862,
1.0311618779245688, 1.0313945691973556, 1.0316273129790936, 1.0318601092816313,
1.0320929581168212, 1.0323258594965172, 1.0325588134325767, 1.0327918199368598,
1.0330248790212284, 1.0332579906975481, 1.0334911549776868, 1.033724371873515,
1.0339576413969056, 1.0341909635597348, 1.0344243383738811, 1.0346577658512259,
1.034891246003653, 1.0351247788430489, 1.0353583643813031, 1.0355920026303078,
1.0358256936019572, 1.0360594373081489, 1.0362932337607829, 1.0365270829717617,
1.0367609849529913, 1.0369949397163791, 1.0372289472738365, 1.0374630076372766,
1.0376971208186156, 1.0379312868297725, 1.0381655056826686, 1.0383997773892284,
1.0386341019613787, 1.0388684794110492, 1.0391029097501721, 1.0393373929906822,
1.0395719291445176, 1.0398065182236185, 1.0400411602399278, 1.0402758552053915,
1.0405106031319582, 1.0407454040315787, 1.0409802579162071, 1.0412151647977996,
1.0414501246883161, 1.0416851375997183, 1.0419202035439705, 1.0421553225330404,
1.042390494578898, 1.042625719693516, 1.0428609978888699, 1.043096329176938,
1.0433317135697009, 1.0435671510791424, 1.0438026417172486, 1.0440381854960086,
1.0442737824274138, 1.044509432523459, 1.044745135796141, 1.0449808922574599,
1.0452167019194181, 1.0454525647940205, 1.0456884808932754, 1.0459244502291931,
1.0461604728137874, 1.0463965486590741, 1.046632677777072, 1.0468688601798024,
1.0471050958792898, 1.047341384887561, 1.0475777272166455, 1.047814122878576,
1.048050571885387, 1.0482870742491166, 1.0485236299818055, 1.0487602390954964,
1.0489969016022356, 1.0492336175140715, 1.0494703868430555, 1.0497072096012419,
1.0499440858006872, 1.0501810154534512, 1.050417998571596, 1.0506550351671864,
1.0508921252522903, 1.0511292688389782, 1.0513664659393229, 1.0516037165654004,
1.0518410207292894, 1.0520783784430709, 1.0523157897188296, 1.0525532545686513,
1.0527907730046264, 1.0530283450388465, 1.0532659706834067, 1.0535036499504049,
1.0537413828519411, 1.0539791694001188, 1.0542170096070436, 1.0544549034848243,
1.0546928510455722, 1.0549308523014012, 1.0551689072644284, 1.0554070159467728,
1.0556451783605572, 1.0558833945179062, 1.0561216644309479, 1.0563599881118126,
1.0565983655726334, 1.0568367968255465, 1.0570752818826903, 1.0573138207562065,
1.057552413458239, 1.0577910600009348, 1.0580297603964437, 1.058268514656918,
1.0585073227945128, 1.0587461848213857, 1.058985100749698, 1.0592240705916123
};
const double bend_coarse[128] = {
1, 1.0594630943592953, 1.122462048309373, 1.189207115002721,
1.2599210498948732, 1.3348398541700344, 1.4142135623730951, 1.4983070768766815,
1.5874010519681994, 1.681792830507429, 1.7817974362806785, 1.8877486253633868,
2, 2.1189261887185906, 2.244924096618746, 2.3784142300054421,
2.5198420997897464, 2.6696797083400687, 2.8284271247461903, 2.996614153753363,
3.1748021039363992, 3.363585661014858, 3.5635948725613571, 3.7754972507267741,
4, 4.2378523774371812, 4.4898481932374912, 4.7568284600108841,
5.0396841995794928, 5.3393594166801366, 5.6568542494923806, 5.993228307506727,
6.3496042078727974, 6.727171322029716, 7.1271897451227151, 7.5509945014535473,
8, 8.4757047548743625, 8.9796963864749824, 9.5136569200217682,
10.079368399158986, 10.678718833360273, 11.313708498984761, 11.986456615013454,
12.699208415745595, 13.454342644059432, 14.25437949024543, 15.101989002907095,
16, 16.951409509748721, 17.959392772949972, 19.027313840043536,
20.158736798317967, 21.357437666720553, 22.627416997969522, 23.972913230026901,
25.398416831491197, 26.908685288118864, 28.508758980490853, 30.203978005814196,
32, 33.902819019497443, 35.918785545899944, 38.054627680087073,
40.317473596635935, 42.714875333441107, 45.254833995939045, 47.945826460053802,
50.796833662982394, 53.817370576237728, 57.017517960981706, 60.407956011628393,
64, 67.805638038994886, 71.837571091799887, 76.109255360174146,
80.63494719327187, 85.429750666882214, 90.509667991878089, 95.891652920107603,
101.59366732596479, 107.63474115247546, 114.03503592196341, 120.81591202325679,
128, 135.61127607798977, 143.67514218359977, 152.21851072034829,
161.26989438654374, 170.85950133376443, 181.01933598375618, 191.78330584021521,
203.18733465192958, 215.26948230495091, 228.07007184392683, 241.63182404651357,
256, 271.22255215597971, 287.35028436719938, 304.43702144069658,
322.53978877308765, 341.71900266752868, 362.03867196751236, 383.56661168043064,
406.37466930385892, 430.53896460990183, 456.14014368785394, 483.26364809302686,
512, 542.44510431195943, 574.70056873439876, 608.87404288139317,
645.0795775461753, 683.43800533505737, 724.07734393502471, 767.13322336086128,
812.74933860771785, 861.07792921980365, 912.28028737570787, 966.52729618605372,
1024, 1084.8902086239189, 1149.4011374687975, 1217.7480857627863,
1290.1591550923506, 1366.8760106701147, 1448.1546878700494, 1534.2664467217226
};
@@ -0,0 +1,28 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
tables.h
*/
#ifndef TIMIDITY_TABLES_H
#define TIMIDITY_TABLES_H
#define timi_sine(x) (SDL_sin((2*PI/1024.0) * (x)))
#define SINE_CYCLE_LENGTH 1024
#define freq_table TIMI_NAMESPACE(freq_table)
#define vol_table TIMI_NAMESPACE(vol_table)
#define bend_fine TIMI_NAMESPACE(bend_fine)
#define bend_coarse TIMI_NAMESPACE(bend_coarse)
extern const Sint32 freq_table[];
extern const double vol_table[];
extern const double bend_fine[];
extern const double bend_coarse[];
#endif /* TIMIDITY_TABLES_H */
@@ -0,0 +1,681 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
*/
#include <SDL3/SDL.h>
#include "timidity.h"
#include "options.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "readmidi.h"
#include "output.h"
#include "tables.h"
static ToneBank *master_tonebank[MAXBANK], *master_drumset[MAXBANK];
static char def_instr_name[256] = "";
#define MAXWORDS 10
#define MAX_RCFCOUNT 50
/* Quick-and-dirty fgets() replacement. */
static char *IOgets(SDL_IOStream *io, char *s, int size)
{
int num_read = 0;
char *p = s;
--size;/* so that we nul terminate properly */
for (; num_read < size; ++p)
{
if (SDL_ReadIO(io, p, 1) != 1)
break;
num_read++;
/* Unlike fgets(), don't store newline. Under Windows/DOS we'll
* probably get an extra blank line for every line that's being
* read, but that should be ok.
*/
if (*p == '\n' || *p == '\r')
{
*p = '\0';
return s;
}
}
*p = '\0';
return (num_read != 0) ? s : NULL;
}
static int read_config_file(const char *name, int rcf_count)
{
SDL_IOStream *io;
char tmp[1024];
char *w[MAXWORDS], *cp;
char *endp;
ToneBank *bank;
int i, j, k, r, words;
#ifdef DEBUG_CHATTER
int line;
#endif
if (rcf_count >= MAX_RCFCOUNT) {
SNDDBG(("Probable source loop in configuration files\n"));
return -1;
}
if (!(io=timi_openfile(name)))
return -1;
bank = NULL;
#ifdef DEBUG_CHATTER
line = 0;
#endif
r = -1; /* start by assuming failure, */
while (IOgets(io, tmp, sizeof(tmp)))
{
#ifdef DEBUG_CHATTER
line++;
#endif
words=0;
w[0]=SDL_strtok_r(tmp, " \t\240", &endp);
if (!w[0]) continue;
/* Originally the TiMidity++ extensions were prefixed like this */
if (SDL_strcmp(w[0], "#extension") == 0)
{
w[0]=SDL_strtok_r(0, " \t\240", &endp);
if (!w[0]) continue;
}
if (*w[0] == '#')
continue;
while (words < MAXWORDS - 1) /* -1 : next arg */
{
while (*endp == ' ' || *endp == '\t' || *endp == '\240')
endp++;
if (*endp == '\0' || *endp == '#')
break;
if (*endp == '"' || *endp == '\'') { /* quoted string */
char *terminator = SDL_strchr(endp + 1, *endp);
if (terminator != NULL) { /* terminated */
if (terminator[1] == ' ' || terminator[1] == '\t' || terminator[1] == '\240' || terminator[1] == '\0') {
char *extraQuote = SDL_strchr(endp + 1, *endp == '"' ? '\'' : '"');
if (extraQuote != NULL && extraQuote < terminator) {
SNDDBG(("%s: line %d: Quote characters are not allowed inside a quoted string", name, line));
goto fail;
}
w[++words] = endp + 1;
endp = terminator + 1;
*terminator = '\0';
}
else { /* no space after quoted string */
SNDDBG(("%s: line %d: There must be at least one whitespace between string terminator (%c) and the next parameter", name, line, *endp));
goto fail;
}
}
else { /* not terminated */
SNDDBG(("%s: line %d: The quoted string is not terminated", name, line));
goto fail;
}
}
else { /* not quoted string */
w[++words] = endp;
while (!(*endp == ' ' || *endp == '\t' || *endp == '\240' || *endp == '\0')) {
if (*endp == '"' || *endp == '\'') { /* no space before quoted string */
SNDDBG(("%s: line %d: There must be at least one whitespace between previous parameter and a beginning of the quoted string (%c)", name, line, *endp));
goto fail;
}
endp++;
}
if (*endp != '\0') { /* unless at the end-of-string (i.e. EOF) */
*endp = '\0'; /* terminate the token */
endp++;
}
}
}
w[++words] = NULL;
/* TiMidity++ adds a number of extensions to the config file format.
* Many of them are completely irrelevant to SDL_sound, but at least
* we shouldn't choke on them.
*
* Unfortunately the documentation for these extensions is often quite
* vague, gramatically strange or completely absent.
*/
if (!SDL_strcmp(w[0], "comm") /* "comm" program second */ ||
!SDL_strcmp(w[0], "HTTPproxy") /* "HTTPproxy" hostname:port */ ||
!SDL_strcmp(w[0], "FTPproxy") /* "FTPproxy" hostname:port */ ||
!SDL_strcmp(w[0], "mailaddr") /* "mailaddr" your-mail-address */ ||
!SDL_strcmp(w[0], "opt") /* "opt" timidity-options */ )
{
/* + "comm" sets some kind of comment -- the documentation is too
* vague for me to understand at this time.
* + "HTTPproxy", "FTPproxy" and "mailaddr" are for reading data
* over a network, rather than from the file system.
* + "opt" specifies default options for TiMidity++.
*
* Quite useless for us, so they can safely remain no-ops.
*/
}
else if (!SDL_strcmp(w[0], "timeout")) /* "timeout" program second */
{
/* Specifies a timeout value of the program. A number of seconds
* before TiMidity kills the note. No urgent need for it.
*/
SNDDBG(("FIXME: Implement \"timeout\" in TiMidity config.\n"));
}
else if (!SDL_strcmp(w[0], "copydrumset") /* "copydrumset" drumset */ ||
!SDL_strcmp(w[0], "copybank")) /* "copybank" bank */
{
/* Copies all the settings of the specified drumset or bank to
* the current drumset or bank. May be useful later, but not a
* high priority.
*/
SNDDBG(("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]));
}
else if (!SDL_strcmp(w[0], "undef")) /* "undef" progno */
{
/* Undefines the tone "progno" of the current tone bank (or
* drum set?). Not a high priority.
*/
SNDDBG(("FIXME: Implement \"undef\" in TiMidity config.\n"));
}
else if (!SDL_strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */
{
/* Sets the alternate assign for drum set. Whatever that's
* supposed to mean.
*/
SNDDBG(("FIXME: Implement \"altassign\" in TiMidity config.\n"));
}
else if (!SDL_strcmp(w[0], "soundfont") ||
!SDL_strcmp(w[0], "font"))
{
/* "soundfont" sf_file "remove"
* "soundfont" sf_file ["order=" order] ["cutoff=" cutoff]
* ["reso=" reso] ["amp=" amp]
* "font" "exclude" bank preset keynote
* "font" "order" order bank preset keynote
*/
SNDDBG(("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0]));
}
else if (!SDL_strcmp(w[0], "progbase"))
{
/* The documentation for this makes absolutely no sense to me, but
* apparently it sets some sort of base offset for tone numbers.
*/
SNDDBG(("FIXME: Implement \"progbase\" in TiMidity config.\n"));
}
else if (!SDL_strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */
{
/* This one is used by the "eawpats". Looks like it's used
* for remapping one instrument to another somehow.
*/
SNDDBG(("FIXME: Implement \"map\" in TiMidity config.\n"));
}
/* Standard TiMidity config */
else if (!SDL_strcmp(w[0], "dir"))
{
if (words < 2)
{
SNDDBG(("%s: line %d: No directory given\n", name, line));
goto fail;
}
for (i=1; i<words; i++) {
if (timi_add_pathlist(w[i], SDL_strlen(w[i])) < 0)
goto fail;
}
}
else if (!SDL_strcmp(w[0], "source"))
{
if (words < 2)
{
SNDDBG(("%s: line %d: No file name given\n", name, line));
goto fail;
}
for (i=1; i<words; i++)
{
r = read_config_file(w[i], rcf_count + 1);
if (r != 0)
goto fail;
}
r = -1; /* not finished yet, */
}
else if (!SDL_strcmp(w[0], "default"))
{
if (words != 2)
{
SNDDBG(("%s: line %d: Must specify exactly one patch name\n",
name, line));
goto fail;
}
SDL_strlcpy(def_instr_name, w[1], 256);
}
else if (!SDL_strcmp(w[0], "drumset"))
{
if (words < 2)
{
SNDDBG(("%s: line %d: No drum set number given\n", name, line));
goto fail;
}
i=SDL_atoi(w[1]);
if (i<0 || i>(MAXBANK-1))
{
SNDDBG(("%s: line %d: Drum set must be between 0 and %d\n",
name, line, MAXBANK-1));
goto fail;
}
if (!master_drumset[i])
{
master_drumset[i] = SDL_calloc(1, sizeof(ToneBank));
if (!master_drumset[i]) goto fail;
master_drumset[i]->tone = SDL_calloc(128, sizeof(ToneBankElement));
if (!master_drumset[i]->tone) goto fail;
}
bank=master_drumset[i];
}
else if (!SDL_strcmp(w[0], "bank"))
{
if (words < 2)
{
SNDDBG(("%s: line %d: No bank number given\n", name, line));
goto fail;
}
i=SDL_atoi(w[1]);
if (i<0 || i>(MAXBANK-1))
{
SNDDBG(("%s: line %d: Tone bank must be between 0 and %d\n",
name, line, MAXBANK-1));
goto fail;
}
if (!master_tonebank[i])
{
master_tonebank[i] = SDL_calloc(1, sizeof(ToneBank));
if (!master_tonebank[i]) goto fail;
master_tonebank[i]->tone = SDL_calloc(128, sizeof(ToneBankElement));
if (!master_tonebank[i]->tone) goto fail;
}
bank=master_tonebank[i];
}
else
{
size_t sz;
if ((words < 2) || (*w[0] < '0' || *w[0] > '9'))
{
SNDDBG(("%s: line %d: syntax error\n", name, line));
goto fail;
}
i=SDL_atoi(w[0]);
if (i<0 || i>127)
{
SNDDBG(("%s: line %d: Program must be between 0 and 127\n",
name, line));
goto fail;
}
if (!bank)
{
SNDDBG(("%s: line %d: Must specify tone bank or drum set before assignment\n",
name, line));
goto fail;
}
SDL_free(bank->tone[i].name);
sz = SDL_strlen(w[1])+1;
bank->tone[i].name = SDL_malloc(sz);
if (!bank->tone[i].name) goto fail;
SDL_memcpy(bank->tone[i].name,w[1],sz);
bank->tone[i].note=bank->tone[i].amp=bank->tone[i].pan=
bank->tone[i].strip_loop=bank->tone[i].strip_envelope=
bank->tone[i].strip_tail=-1;
for (j=2; j<words; j++)
{
if (!(cp=SDL_strchr(w[j], '=')))
{
SNDDBG(("%s: line %d: bad patch option %s\n", name, line, w[j]));
goto fail;
}
*cp++=0;
if (!SDL_strcmp(w[j], "amp"))
{
k=SDL_atoi(cp);
if ((k<0 || k>MAX_AMPLIFICATION) || (*cp < '0' || *cp > '9'))
{
SNDDBG(("%s: line %d: amplification must be between 0 and %d\n",
name, line, MAX_AMPLIFICATION));
goto fail;
}
bank->tone[i].amp=k;
}
else if (!SDL_strcmp(w[j], "note"))
{
k=SDL_atoi(cp);
if ((k<0 || k>127) || (*cp < '0' || *cp > '9'))
{
SNDDBG(("%s: line %d: note must be between 0 and 127\n",
name, line));
goto fail;
}
bank->tone[i].note=k;
}
else if (!SDL_strcmp(w[j], "pan"))
{
if (!SDL_strcmp(cp, "center"))
k=64;
else if (!SDL_strcmp(cp, "left"))
k=0;
else if (!SDL_strcmp(cp, "right"))
k=127;
else
k=((SDL_atoi(cp)+100) * 100) / 157;
if ((k<0 || k>127) || (k==0 && *cp!='-' && (*cp < '0' || *cp > '9')))
{
SNDDBG(("%s: line %d: panning must be left, right, center, or between -100 and 100\n",
name, line));
goto fail;
}
bank->tone[i].pan=k;
}
else if (!SDL_strcmp(w[j], "keep"))
{
if (!SDL_strcmp(cp, "env"))
bank->tone[i].strip_envelope=0;
else if (!SDL_strcmp(cp, "loop"))
bank->tone[i].strip_loop=0;
else
{
SNDDBG(("%s: line %d: keep must be env or loop\n", name, line));
goto fail;
}
}
else if (!SDL_strcmp(w[j], "strip"))
{
if (!SDL_strcmp(cp, "env"))
bank->tone[i].strip_envelope=1;
else if (!SDL_strcmp(cp, "loop"))
bank->tone[i].strip_loop=1;
else if (!SDL_strcmp(cp, "tail"))
bank->tone[i].strip_tail=1;
else
{
SNDDBG(("%s: line %d: strip must be env, loop, or tail\n",
name, line));
goto fail;
}
}
else
{
SNDDBG(("%s: line %d: bad patch option %s\n", name, line, w[j]));
goto fail;
}
}
}
}
r = 0; /* we're good. */
fail:
SDL_CloseIO(io);
return r;
}
#if defined(_WIN32) || defined(__CYGWIN__)
/* FIXME: What about C:FOO ? */
static SDL_INLINE char *get_last_dirsep (const char *p) {
char *p1 = SDL_strrchr(p, '/');
char *p2 = SDL_strrchr(p, '\\');
if (!p1) return p2;
if (!p2) return p1;
return (p1 > p2)? p1 : p2;
}
#else /* assumed UNIX-ish : */
static SDL_INLINE char *get_last_dirsep (const char *p) {
return SDL_strrchr(p, '/');
}
#endif
static int init_alloc_banks(void)
{
/* Allocate memory for the standard tonebank and drumset */
master_tonebank[0] = SDL_calloc(1, sizeof(ToneBank));
if (!master_tonebank[0]) goto _nomem;
master_tonebank[0]->tone = SDL_calloc(128, sizeof(ToneBankElement));
if (!master_tonebank[0]->tone) goto _nomem;
master_drumset[0] = SDL_calloc(1, sizeof(ToneBank));
if (!master_drumset[0]) goto _nomem;
master_drumset[0]->tone = SDL_calloc(128, sizeof(ToneBankElement));
if (!master_drumset[0]->tone) goto _nomem;
return 0;
_nomem:
SNDDBG(("Out of memory\n"));
Timidity_Exit ();
return -2;
}
static int init_begin_config(const char *cf)
{
const char *p = get_last_dirsep(cf);
if (p != NULL)
return timi_add_pathlist(cf, p - cf + 1); /* including DIRSEP */
return 0;
}
static int init_with_config(const char *cf)
{
int rc = init_begin_config(cf);
if (rc != 0) {
Timidity_Exit ();
return rc;
}
rc = read_config_file(cf, 0);
if (rc != 0) {
Timidity_Exit ();
}
return rc;
}
int Timidity_Init_NoConfig(void)
{
master_tonebank[0] = NULL;
master_drumset[0] = NULL;
return init_alloc_banks();
}
int Timidity_Init(const char *config_file)
{
int rc = Timidity_Init_NoConfig();
if (rc != 0) {
return rc;
}
if (config_file == NULL || *config_file == '\0') {
return init_with_config(TIMIDITY_CFG);
}
return init_with_config(config_file);
}
static void do_song_load(SDL_IOStream *io, SDL_AudioSpec *audio, MidiSong **out)
{
MidiSong *song;
int i;
*out = NULL;
if (io == NULL)
return;
/* Allocate memory for the song */
song = (MidiSong *)SDL_calloc(1, sizeof(*song));
if (song == NULL)
return;
for (i = 0; i < MAXBANK; i++)
{
if (master_tonebank[i]) {
song->tonebank[i] = SDL_calloc(1, sizeof(ToneBank));
if (!song->tonebank[i]) goto fail;
song->tonebank[i]->tone = master_tonebank[i]->tone;
}
if (master_drumset[i]) {
song->drumset[i] = SDL_calloc(1, sizeof(ToneBank));
if (!song->drumset[i]) goto fail;
song->drumset[i]->tone = master_drumset[i]->tone;
}
}
song->amplification = DEFAULT_AMPLIFICATION;
song->voices = DEFAULT_VOICES;
song->drumchannels = DEFAULT_DRUMCHANNELS;
song->io = io;
song->rate = audio->freq;
song->encoding = 0;
if ((audio->format & 0xFF) == 16)
song->encoding |= PE_16BIT;
else if ((audio->format & 0xFF) == 32)
song->encoding |= PE_32BIT;
if (audio->format & 0x8000)
song->encoding |= PE_SIGNED;
if (audio->channels == 1)
song->encoding |= PE_MONO;
else if (audio->channels > 2) {
SDL_SetError("Surround sound not supported");
goto fail;
}
switch (audio->format) {
case SDL_AUDIO_S8:
song->write = timi_s32tos8;
break;
case SDL_AUDIO_U8:
song->write = timi_s32tou8;
break;
case SDL_AUDIO_S16LE:
song->write = timi_s32tos16l;
break;
case SDL_AUDIO_S16BE:
song->write = timi_s32tos16b;
break;
case SDL_AUDIO_S32LE:
song->write = timi_s32tos32l;
break;
case SDL_AUDIO_S32BE:
song->write = timi_s32tos32b;
break;
case SDL_AUDIO_F32:
song->write = timi_s32tof32;
break;
default:
SDL_SetError("Unsupported audio format");
goto fail;
}
song->buffer_size = 4096/*audio->samples*/;
song->resample_buffer = SDL_malloc(4096/*audio->samples*/ * sizeof(sample_t));
if (!song->resample_buffer) goto fail;
song->common_buffer = SDL_malloc(4096/*audio->samples*/ * 2 * sizeof(Sint32));
if (!song->common_buffer) goto fail;
song->control_ratio = audio->freq / CONTROLS_PER_SECOND;
if (song->control_ratio < 1)
song->control_ratio = 1;
else if (song->control_ratio > MAX_CONTROL_RATIO)
song->control_ratio = MAX_CONTROL_RATIO;
song->lost_notes = 0;
song->cut_notes = 0;
song->events = read_midi_file(song, &(song->groomed_event_count),
&song->samples);
/* Make sure everything is okay */
if (!song->events)
goto fail;
song->default_instrument = NULL;
song->default_program = DEFAULT_PROGRAM;
if (*def_instr_name)
set_default_instrument(song, def_instr_name);
load_missing_instruments(song);
if (! song->oom)
*out = song;
else {
fail: Timidity_FreeSong(song);
}
}
MidiSong *Timidity_LoadSong(SDL_IOStream *io, SDL_AudioSpec *audio)
{
MidiSong *song;
do_song_load(io, audio, &song);
return song;
}
void Timidity_FreeSong(MidiSong *song)
{
int i;
if (!song) return;
free_instruments(song);
for (i = 0; i < 128; i++) {
SDL_free(song->tonebank[i]);
SDL_free(song->drumset[i]);
}
SDL_free(song->common_buffer);
SDL_free(song->resample_buffer);
SDL_free(song->events);
SDL_free(song);
}
void Timidity_Exit(void)
{
int i, j;
for (i = 0; i < MAXBANK; i++) {
if (master_tonebank[i]) {
ToneBankElement *e = master_tonebank[i]->tone;
if (e != NULL) {
for (j = 0; j < 128; j++) {
SDL_free(e[j].name);
}
SDL_free(e);
}
SDL_free(master_tonebank[i]);
master_tonebank[i] = NULL;
}
if (master_drumset[i]) {
ToneBankElement *e = master_drumset[i]->tone;
if (e != NULL) {
for (j = 0; j < 128; j++) {
SDL_free(e[j].name);
}
SDL_free(e);
}
SDL_free(master_drumset[i]);
master_drumset[i] = NULL;
}
}
timi_free_pathlist();
}
@@ -0,0 +1,167 @@
/*
TiMidity -- Experimental MIDI to WAVE converter
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
This program is free software; you can redistribute it and/or modify
it under the terms of the Perl Artistic License, available in COPYING.
*/
#ifndef TIMIDITY_H
#define TIMIDITY_H
#ifdef __cplusplus
extern "C" {
#endif
typedef Sint16 sample_t;
typedef Sint32 final_volume_t;
#define VIBRATO_SAMPLE_INCREMENTS 32
/* Maximum polyphony. */
/* #define MAX_VOICES 48 */
#define MAX_VOICES 256
#define MAXCHAN 16
/* #define MAXCHAN 64 */
#define MAXBANK 128
typedef struct {
Sint32
loop_start, loop_end, data_length,
sample_rate, low_freq, high_freq, root_freq;
Sint32
envelope_rate[6], envelope_offset[6];
float
volume;
sample_t *data;
Sint32
tremolo_sweep_increment, tremolo_phase_increment,
vibrato_sweep_increment, vibrato_control_ratio;
Uint8
tremolo_depth, vibrato_depth,
modes;
Sint8
panning, note_to_use;
} Sample;
typedef struct {
int
bank, program, volume, sustain, panning, pitchbend, expression,
mono, /* one note only on this channel -- not implemented yet */
pitchsens;
/* chorus, reverb... Coming soon to a 300-MHz, eight-way superscalar
processor near you */
float
pitchfactor; /* precomputed pitch bend factor to save some fdiv's */
} Channel;
typedef struct {
Uint8
status, channel, note, velocity;
Sample *sample;
Sint32
orig_frequency, frequency,
sample_offset, sample_increment,
envelope_volume, envelope_target, envelope_increment,
tremolo_sweep, tremolo_sweep_position,
tremolo_phase, tremolo_phase_increment,
vibrato_sweep, vibrato_sweep_position;
final_volume_t left_mix, right_mix;
float
left_amp, right_amp, tremolo_volume;
Sint32
vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS];
int
vibrato_phase, vibrato_control_ratio, vibrato_control_counter,
envelope_stage, control_counter, panning, panned;
} Voice;
typedef struct {
int samples;
Sample *sample;
} Instrument;
/* Shared data */
typedef struct {
char *name;
int note, amp, pan, strip_loop, strip_envelope, strip_tail;
} ToneBankElement;
typedef struct {
ToneBankElement *tone;
Instrument *instrument[128];
} ToneBank;
typedef struct {
Sint32 time;
Uint8 channel, type, a, b;
} MidiEvent;
typedef struct _MidiEventList {
MidiEvent event;
struct _MidiEventList *next;
} MidiEventList;
typedef struct {
int oom; /* malloc() failed */
int playing;
SDL_IOStream *io;
Sint32 rate;
Sint32 encoding;
float master_volume;
Sint32 amplification;
ToneBank *tonebank[MAXBANK];
ToneBank *drumset[MAXBANK];
Instrument *default_instrument;
int default_program;
void (*write)(void *dp, Sint32 *lp, Sint32 c);
int buffer_size;
sample_t *resample_buffer;
Sint32 *common_buffer;
Sint32 *buffer_pointer;
/* These would both fit into 32 bits, but they are often added in
large multiples, so it's simpler to have two roomy ints */
/* samples per MIDI delta-t */
Sint32 sample_increment;
Sint32 sample_correction;
Channel channel[MAXCHAN];
Voice voice[MAX_VOICES];
int voices;
Sint32 drumchannels;
Sint32 buffered_count;
Sint32 control_ratio;
Sint32 lost_notes;
Sint32 cut_notes;
Sint32 samples;
MidiEvent *events;
MidiEvent *current_event;
MidiEventList *evlist;
Sint32 current_sample;
Sint32 event_count;
Sint32 at;
Sint32 groomed_event_count;
} MidiSong;
/* Some of these are not defined in timidity.c but are here for convenience */
extern int Timidity_Init(const char *config_file);
extern int Timidity_Init_NoConfig(void);
extern void Timidity_SetVolume(MidiSong *song, int volume);
extern int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len);
extern MidiSong *Timidity_LoadSong(SDL_IOStream *io, SDL_AudioSpec *audio);
extern void Timidity_Start(MidiSong *song);
extern void Timidity_Seek(MidiSong *song, Uint32 ms);
extern Uint32 Timidity_GetSongLength(MidiSong *song); /* returns millseconds */
extern Uint32 Timidity_GetSongTime(MidiSong *song); /* returns millseconds */
extern void Timidity_Stop(MidiSong *song);
extern int Timidity_IsActive(MidiSong *song);
extern void Timidity_FreeSong(MidiSong *song);
extern void Timidity_Exit(void);
#ifdef __cplusplus
}
#endif
#endif /* TIMIDITY_H */
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This file by Ryan C. Gordon (icculus@icculus.org)
These are some internally supported special effects that use SDL_mixer's
effect callback API. They are meant for speed over quality. :)
*/
#include <SDL3_mixer/SDL_mixer.h>
#define MIX_INTERNAL_EFFECT__
#include "effects_internal.h"
/* profile code:
#include <sys/time.h>
#include <unistd.h>
struct timeval tv1;
struct timeval tv2;
gettimeofday(&tv1, NULL);
... do your thing here ...
gettimeofday(&tv2, NULL);
printf("%ld\n", tv2.tv_usec - tv1.tv_usec);
*/
/*
* Stereo reversal effect...this one's pretty straightforward...
*/
static void SDLCALL _Eff_reversestereo32(int chan, void *stream, int len, void *udata)
{
/* 16 bits * 2 channels. */
Uint32 *ptr = (Uint32 *) stream;
Uint32 tmp;
int i;
(void)chan;
(void)udata;
for (i = 0; i < len; i += 2 * sizeof(Uint32), ptr += 2) {
tmp = ptr[0];
ptr[0] = ptr[1];
ptr[1] = tmp;
}
}
static void SDLCALL _Eff_reversestereo16(int chan, void *stream, int len, void *udata)
{
/* 16 bits * 2 channels. */
Uint32 *ptr = (Uint32 *) stream;
int i;
(void)chan;
(void)udata;
for (i = 0; i < len; i += sizeof(Uint32), ptr++) {
*ptr = (((*ptr) & 0xFFFF0000) >> 16) | (((*ptr) & 0x0000FFFF) << 16);
}
}
static void SDLCALL _Eff_reversestereo8(int chan, void *stream, int len, void *udata)
{
/* 8 bits * 2 channels. */
Uint32 *ptr = (Uint32 *) stream;
int i;
(void)chan;
(void)udata;
/* get the last two bytes if len is not divisible by four... */
if (len % (int)sizeof(Uint32) != 0) {
Uint16 *p = (Uint16 *) (((Uint8 *) stream) + (len - 2));
*p = (Uint16)((((*p) & 0xFF00) >> 8) | (((*ptr) & 0x00FF) << 8));
len -= 2;
}
for (i = 0; i < len; i += sizeof(Uint32), ptr++) {
*ptr = (((*ptr) & 0x0000FF00) >> 8) | (((*ptr) & 0x000000FF) << 8) |
(((*ptr) & 0xFF000000) >> 8) | (((*ptr) & 0x00FF0000) << 8);
}
}
bool Mix_SetReverseStereo(int channel, int flip)
{
Mix_EffectFunc_t f = NULL;
int channels;
SDL_AudioFormat format;
Mix_QuerySpec(NULL, &format, &channels);
if (channels == 2) {
int bits = (format & 0xFF);
switch (bits) {
case 8:
f = _Eff_reversestereo8;
break;
case 16:
f = _Eff_reversestereo16;
break;
case 32:
f = _Eff_reversestereo32;
break;
default:
return SDL_SetError("Unsupported audio format");
}
if (!flip) {
return Mix_UnregisterEffect(channel, f);
}
return Mix_RegisterEffect(channel, f, NULL, NULL);
}
return SDL_SetError("Trying to reverse stereo on a non-stereo stream");
}
+114
View File
@@ -0,0 +1,114 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
This file by Ryan C. Gordon (icculus@icculus.org)
These are some helper functions for the internal mixer special effects.
*/
/* ------ These are used internally only. Don't touch. ------ */
#include <SDL3_mixer/SDL_mixer.h>
#define MIX_INTERNAL_EFFECT__
#include "effects_internal.h"
/* Should we favor speed over memory usage and/or quality of output? */
int _Mix_effects_max_speed = 0;
void _Mix_InitEffects(void)
{
_Mix_effects_max_speed = (SDL_getenv(MIX_EFFECTSMAXSPEED) != NULL);
}
void _Mix_DeinitEffects(void)
{
_Eff_PositionDeinit();
}
void *_Eff_volume_table = NULL;
/* Build the volume table for Uint8-format samples.
*
* Each column of the table is a possible sample, while each row of the
* table is a volume. Volume is a Uint8, where 0 is silence and 255 is full
* volume. So _Eff_volume_table[128][mysample] would be the value of
* mysample, at half volume.
*/
void *_Eff_build_volume_table_u8(void)
{
int volume;
int sample;
Uint8 *rc;
if (!_Mix_effects_max_speed) {
return NULL;
}
if (!_Eff_volume_table) {
rc = SDL_malloc(256 * 256);
if (rc) {
_Eff_volume_table = (void *) rc;
for (volume = 0; volume < 256; volume++) {
for (sample = -128; sample < 128; sample ++) {
*rc = (Uint8)(((float) sample) * ((float) volume / 255.0f))
+ 128;
rc++;
}
}
}
}
return _Eff_volume_table;
}
/* Build the volume table for Sint8-format samples.
*
* Each column of the table is a possible sample, while each row of the
* table is a volume. Volume is a Uint8, where 0 is silence and 255 is full
* volume. So _Eff_volume_table[128][mysample+128] would be the value of
* mysample, at half volume.
*/
void *_Eff_build_volume_table_s8(void)
{
int volume;
int sample;
Sint8 *rc;
if (!_Eff_volume_table) {
rc = SDL_malloc(256 * 256);
if (rc) {
_Eff_volume_table = (void *) rc;
for (volume = 0; volume < 256; volume++) {
for (sample = -128; sample < 128; sample ++) {
*rc = (Sint8)(((float) sample) * ((float) volume / 255.0f));
rc++;
}
}
}
}
return _Eff_volume_table;
}
+44
View File
@@ -0,0 +1,44 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef INCLUDE_EFFECTS_INTERNAL_H_
#define INCLUDE_EFFECTS_INTERNAL_H_
#ifndef MIX_INTERNAL_EFFECT__
#error You should not include this file or use these functions.
#endif
#include <SDL3_mixer/SDL_mixer.h>
extern int _Mix_effects_max_speed;
extern void *_Eff_volume_table;
void *_Eff_build_volume_table_u8(void);
void *_Eff_build_volume_table_s8(void);
void _Mix_InitEffects(void);
void _Mix_DeinitEffects(void);
void _Eff_PositionDeinit(void);
bool _Mix_RegisterEffect_locked(int channel, Mix_EffectFunc_t f, Mix_EffectDone_t d, void *arg);
bool _Mix_UnregisterEffect_locked(int channel, Mix_EffectFunc_t f);
bool _Mix_UnregisterAllEffects_locked(int channel);
#endif /* _INCLUDE_EFFECTS_INTERNAL_H_ */
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef MIXER_H_
#define MIXER_H_
/* Locking wrapper functions */
extern void Mix_LockAudio(void);
extern void Mix_UnlockAudio(void);
extern void add_chunk_decoder(const char *decoder);
#endif /* MIXER_H_ */
File diff suppressed because it is too large Load Diff
+177
View File
@@ -0,0 +1,177 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef MUSIC_H_
#define MUSIC_H_
#include <SDL3_mixer/SDL_mixer.h>
/* Supported music APIs, in order of preference */
typedef enum
{
MIX_MUSIC_WAVE,
MIX_MUSIC_FLUIDSYNTH,
MIX_MUSIC_TIMIDITY,
MIX_MUSIC_NATIVEMIDI,
MIX_MUSIC_OGG,
MIX_MUSIC_DRMP3,
MIX_MUSIC_MPG123,
MIX_MUSIC_DRFLAC,
MIX_MUSIC_FLAC,
MIX_MUSIC_OPUS,
MIX_MUSIC_LIBXMP,
MIX_MUSIC_WAVPACK,
MIX_MUSIC_GME,
MIX_MUSIC_LAST
} Mix_MusicAPI;
/* Supported meta-tags */
typedef enum
{
MIX_META_TITLE,
MIX_META_ARTIST,
MIX_META_ALBUM,
MIX_META_COPYRIGHT,
MIX_META_LAST
} Mix_MusicMetaTag;
/* MIXER-X: Meta-tags utility structure */
typedef struct {
char *tags[4];
} Mix_MusicMetaTags;
extern void meta_tags_init(Mix_MusicMetaTags *tags);
extern void meta_tags_clear(Mix_MusicMetaTags *tags);
extern void meta_tags_set(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type, const char *value);
extern const char* meta_tags_get(Mix_MusicMetaTags *tags, Mix_MusicMetaTag type);
/* Music API implementation */
typedef struct
{
const char *tag;
Mix_MusicAPI api;
Mix_MusicType type;
bool loaded;
bool opened;
/* Load the library */
int (*Load)(void);
/* Initialize for the audio output */
int (*Open)(const SDL_AudioSpec *spec);
/* Create a music object from an SDL_IOStream stream
* If the function returns NULL, 'src' will be freed if needed by the caller.
*/
void *(*CreateFromIO)(SDL_IOStream *src, bool closeio);
/* Create a music object from a file, if SDL_IOStream are not supported */
void *(*CreateFromFile)(const char *file);
/* Set the volume */
void (*SetVolume)(void *music, int volume);
/* Get the volume */
int (*GetVolume)(void *music);
/* Start playing music from the beginning with an optional loop count */
int (*Play)(void *music, int play_count);
/* Returns true if music is still playing */
bool (*IsPlaying)(void *music);
/* Get music data, returns the number of bytes left */
int (*GetAudio)(void *music, void *data, int bytes);
/* Jump to a given order in mod music */
int (*Jump)(void *music, int order);
/* Seek to a play position (in seconds) */
int (*Seek)(void *music, double position);
/* Tell a play position (in seconds) */
double (*Tell)(void *music);
/* Get Music duration (in seconds) */
double (*Duration)(void *music);
/* Tell a loop start position (in seconds) */
double (*LoopStart)(void *music);
/* Tell a loop end position (in seconds) */
double (*LoopEnd)(void *music);
/* Tell a loop length position (in seconds) */
double (*LoopLength)(void *music);
/* Get a meta-tag string if available */
const char* (*GetMetaTag)(void *music, Mix_MusicMetaTag tag_type);
/* Get number of tracks. Returns -1 if not applicable */
int (*GetNumTracks)(void *music);
/* Start a specific track */
int (*StartTrack)(void *music, int track);
/* Pause playing music */
void (*Pause)(void *music);
/* Resume playing music */
void (*Resume)(void *music);
/* Stop playing music */
void (*Stop)(void *music);
/* Delete a music object */
void (*Delete)(void *music);
/* Close the library and clean up */
void (*Close)(void);
/* Unload the library */
void (*Unload)(void);
} Mix_MusicInterface;
extern int get_num_music_interfaces(void);
extern Mix_MusicInterface *get_music_interface(int index);
extern Mix_MusicType detect_music_type(SDL_IOStream *src);
extern bool load_music_type(Mix_MusicType type);
extern bool open_music_type(Mix_MusicType type);
extern bool has_music(Mix_MusicType type);
extern void open_music(const SDL_AudioSpec *spec);
extern int music_pcm_getaudio(void *context, void *data, int bytes, int volume,
int (*GetSome)(void *context, void *data, int bytes, bool *done));
extern void SDLCALL music_mixer(void *udata, Uint8 *stream, int len);
extern void pause_async_music(int pause_on);
extern void close_music(void);
extern void unload_music(void);
extern SDL_AudioSpec music_spec;
#endif /* MUSIC_H_ */
+71
View File
@@ -0,0 +1,71 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* misc helper routines */
#include "utils.h"
#include <stddef.h>
/* Is given tag a loop tag? */
bool _Mix_IsLoopTag(const char *tag)
{
char buf[5];
SDL_strlcpy(buf, tag, 5);
return SDL_strcasecmp(buf, "LOOP") == 0;
}
/* Parse time string of the form HH:MM:SS.mmm and return equivalent sample
* position */
Sint64 _Mix_ParseTime(char *time, long samplerate_hz)
{
char *num_start, *p;
Sint64 result;
char c;
int val;
/* Time is directly expressed as a sample position */
if (SDL_strchr(time, ':') == NULL) {
return SDL_strtoll(time, NULL, 10);
}
result = 0;
num_start = time;
for (p = time; *p != '\0'; ++p) {
if (*p == '.' || *p == ':') {
c = *p; *p = '\0';
if ((val = SDL_atoi(num_start)) < 0)
return -1;
result = result * 60 + val;
num_start = p + 1;
*p = c;
}
if (*p == '.') {
double val_f = SDL_atof(p);
if (val_f < 0) return -1;
return result * samplerate_hz + (Sint64) (val_f * samplerate_hz);
}
}
if ((val = SDL_atoi(num_start)) < 0) return -1;
return (result * 60 + val) * samplerate_hz;
}
+36
View File
@@ -0,0 +1,36 @@
/*
SDL_mixer: An audio mixer library based on the SDL library
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef UTILS_H_
#define UTILS_H_
/* misc helper routines */
#include <SDL3/SDL_stdinc.h>
/* Parse time string of the form HH:MM:SS.mmm and return equivalent sample
* position */
extern Sint64 _Mix_ParseTime(char *time, long samplerate_hz);
extern bool _Mix_IsLoopTag(const char *tag);
#endif /* UTILS_H_ */
+38
View File
@@ -0,0 +1,38 @@
#include "winresrc.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,0,0,0
PRODUCTVERSION 3,0,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS 0x40004L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "\0"
VALUE "FileDescription", "SDL_mixer\0"
VALUE "FileVersion", "3, 0, 0, 0\0"
VALUE "InternalName", "SDL_mixer\0"
VALUE "LegalCopyright", "Copyright (C) 2025 Sam Lantinga\0"
VALUE "OriginalFilename", "SDL3_mixer.dll\0"
VALUE "ProductName", "Simple DirectMedia Layer\0"
VALUE "ProductVersion", "3, 0, 0, 0\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END