add SDL_mixer
This commit is contained in:
@@ -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: *;
|
||||
};
|
||||
@@ -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
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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, §ion);
|
||||
#else
|
||||
amount = (int)vorbis.ov_read(&music->vf, music->buffer, music->buffer_size, SDL_BYTEORDER == SDL_BIG_ENDIAN, 2, 1, §ion);
|
||||
#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 */
|
||||
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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), §ion);
|
||||
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: */
|
||||
@@ -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: */
|
||||
@@ -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
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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: */
|
||||
@@ -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, ¤tPos);
|
||||
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, ¤tPos);
|
||||
|
||||
/* 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(¤tsong->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, ¤tTime);
|
||||
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()
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
@@ -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.]
|
||||
@@ -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?
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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_ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_ */
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user