cog/Plugins/libvgmPlayer/libvgmDecoder.mm

297 lines
6.6 KiB
Plaintext

//
// libvgmDecoder.mm
// libvgmPlayer
//
// Created by Christopher Snowhill on 1/02/22.
// Copyright 2022 __LoSnoCo__. All rights reserved.
//
#import "libvgmDecoder.h"
#import "Logging.h"
#import "PlaylistController.h"
#import <libvgm/emu/EmuCores.h>
#import <libvgm/emu/Resampler.h>
#import <libvgm/emu/SoundDevs.h>
#import <libvgm/player/droplayer.hpp>
#import <libvgm/player/gymplayer.hpp>
#import <libvgm/player/s98player.hpp>
#import <libvgm/player/vgmplayer.hpp>
#import <libvgm/utils/FileLoader.h>
#import <libvgm/utils/MemoryLoader.h>
@implementation libvgmDecoder
#ifdef _DEBUG
const int logLevel = DEVLOG_DEBUG;
#else
const int logLevel = DEVLOG_INFO;
#endif
static UINT8 FilePlayCallback(PlayerBase* player, void* userParam, UINT8 evtType, void* evtParam) {
libvgmDecoder* decoder = (__bridge libvgmDecoder*)(userParam);
switch(evtType) {
case PLREVT_START:
// printf("Playback started.\n");
break;
case PLREVT_STOP:
// printf("Playback stopped.\n");
break;
case PLREVT_LOOP: {
// UINT32* curLoop = (UINT32*)evtParam;
// if (player->GetState() & PLAYSTATE_SEEK)
// break;
} break;
case PLREVT_END:
if([decoder trackEnded])
break;
[decoder setTrackEnded:YES];
break;
}
return 0x00;
}
#include "yrw801.h"
static DATA_LOADER* RequestFileCallback(void* userParam, PlayerBase* player, const char* fileName) {
DATA_LOADER* dLoad;
if(strcmp(fileName, "yrw801.rom") == 0) {
dLoad = MemoryLoader_Init(yrw801_rom, sizeof(yrw801_rom));
} else {
dLoad = FileLoader_Init(fileName);
}
UINT8 retVal = DataLoader_Load(dLoad);
if(!retVal)
return dLoad;
DataLoader_Deinit(dLoad);
return NULL;
}
static const char* LogLevel2Str(UINT8 level) {
static const char* LVL_NAMES[6] = { " ??? ", "Error", "Warn ", "Info ", "Debug", "Trace" };
if(level >= (sizeof(LVL_NAMES) / sizeof(LVL_NAMES[0])))
level = 0;
return LVL_NAMES[level];
}
static void PlayerLogCallback(void* userParam, PlayerBase* player, UINT8 level, UINT8 srcType,
const char* srcTag, const char* message) {
if(level > logLevel)
return; // don't print messages with higher verbosity than current log level
if(srcType == PLRLOGSRC_PLR) {
ALog(@"[%s] %s: %s", LogLevel2Str(level), player->GetPlayerName(), message);
} else {
ALog(@"[%s] %s %s: %s", LogLevel2Str(level), player->GetPlayerName(), srcTag, message);
}
return;
}
const int sampleRate = 44100;
const int numChannels = 2;
const int numBitsPerSample = 24;
const int smplAlloc = 2048;
const int masterVol = 0x10000; // Fixed point 16.16
- (id)init {
self = [super init];
if(self) {
fileData = NULL;
dLoad = NULL;
mainPlr = NULL;
}
return self;
}
- (BOOL)open:(id<CogSource>)s {
[self setSource:s];
// We need file-size to use GME
if(![source seekable]) {
return NO;
}
BOOL repeatOne = IsRepeatOneSet();
uint32_t maxLoops = repeatOne ? 0 : 2;
mainPlr = new PlayerA;
mainPlr->RegisterPlayerEngine(new VGMPlayer);
mainPlr->RegisterPlayerEngine(new S98Player);
mainPlr->RegisterPlayerEngine(new DROPlayer);
mainPlr->RegisterPlayerEngine(new GYMPlayer);
mainPlr->SetEventCallback(FilePlayCallback, (__bridge void*)(self));
mainPlr->SetFileReqCallback(RequestFileCallback, NULL);
mainPlr->SetLogCallback(PlayerLogCallback, NULL);
{
PlayerA::Config pCfg = mainPlr->GetConfiguration();
pCfg.masterVol = masterVol;
pCfg.loopCount = maxLoops;
pCfg.fadeSmpls = sampleRate * 4; // fade over 4 seconds
pCfg.endSilenceSmpls = sampleRate / 2; // 0.5 seconds of silence at the end
pCfg.pbSpeed = 1.0;
mainPlr->SetConfiguration(pCfg);
}
mainPlr->SetOutputSettings(sampleRate, numChannels, numBitsPerSample, smplAlloc);
[source seek:0 whence:SEEK_END];
size_t size = [source tell];
[source seek:0 whence:SEEK_SET];
fileData = (UINT8*)malloc(size);
if(!fileData)
return NO;
size_t bytesRead = [source read:fileData amount:size];
if(bytesRead != size)
return NO;
dLoad = MemoryLoader_Init(fileData, (unsigned int)size);
if(!dLoad)
return NO;
DataLoader_SetPreloadBytes(dLoad, 0x100);
if(DataLoader_Load(dLoad))
return NO;
if(mainPlr->LoadFile(dLoad))
return NO;
PlayerBase* player = mainPlr->GetPlayer();
mainPlr->SetLoopCount(maxLoops);
if(player->GetPlayerType() == FCC_VGM) {
VGMPlayer* vgmplay = dynamic_cast<VGMPlayer*>(player);
mainPlr->SetLoopCount(vgmplay->GetModifiedLoopCount(maxLoops));
}
length = player->Tick2Second(player->GetTotalTicks()) * sampleRate;
[self setTrackEnded:NO];
mainPlr->Start();
[self willChangeValueForKey:@"properties"];
[self didChangeValueForKey:@"properties"];
return YES;
}
- (NSDictionary*)properties {
return @{ @"bitrate": @(0),
@"sampleRate": @(sampleRate),
@"totalFrames": @(length),
@"bitsPerSample": @(numBitsPerSample),
@"channels": @(numChannels),
@"seekable": @(YES),
@"endian": @"host",
@"encoding": @"synthesized" };
}
- (NSDictionary *)metadata {
return @{};
}
- (int)readAudio:(void*)buf frames:(UInt32)frames {
if([self trackEnded])
return 0;
BOOL repeatOne = IsRepeatOneSet();
uint32_t maxLoops = repeatOne ? 0 : 2;
PlayerBase* player = mainPlr->GetPlayer();
mainPlr->SetLoopCount(maxLoops);
if(player->GetPlayerType() == FCC_VGM) {
VGMPlayer* vgmplay = dynamic_cast<VGMPlayer*>(player);
mainPlr->SetLoopCount(vgmplay->GetModifiedLoopCount(maxLoops));
}
UInt32 framesDone = 0;
while(framesDone < frames) {
UInt32 framesToDo = frames - framesDone;
if(framesToDo > smplAlloc)
framesToDo = smplAlloc;
int numSamples = framesToDo * numChannels * (numBitsPerSample / 8);
mainPlr->Render(numSamples, buf);
buf = (void*)(((uint8_t*)buf) + numSamples);
framesDone += framesToDo;
}
return framesDone;
}
- (long)seek:(long)frame {
[self setTrackEnded:NO];
mainPlr->Seek(PLAYPOS_SAMPLE, (unsigned int)frame);
return frame;
}
- (void)close {
if(mainPlr) {
mainPlr->Stop();
mainPlr->UnloadFile();
delete mainPlr;
mainPlr = NULL;
}
if(dLoad) {
DataLoader_Deinit(dLoad);
dLoad = NULL;
}
if(fileData) {
free(fileData);
fileData = NULL;
}
}
- (void)dealloc {
[self close];
}
+ (NSArray*)fileTypes {
return @[@"vgm", @"vgz", @"s98", @"dro", @"gym"];
}
+ (NSArray*)mimeTypes {
return nil;
}
+ (float)priority {
return 1.25;
}
+ (NSArray*)fileTypeAssociations {
NSMutableArray* ret = [[NSMutableArray alloc] init];
[ret addObject:@"libvgm Files"];
[ret addObject:@"vg.icns"];
[ret addObjectsFromArray:[self fileTypes]];
return @[[NSArray arrayWithArray:ret]];
}
- (void)setSource:(id<CogSource>)s {
source = s;
}
- (id<CogSource>)source {
return source;
}
- (BOOL)trackEnded {
return trackEnded;
}
- (void)setTrackEnded:(BOOL)ended {
trackEnded = ended;
}
@end