Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to Use the Game-Music-Emu Library to Play NSF Files in Mono? #130

Closed
Thysbelon opened this issue Mar 3, 2023 · 2 comments
Closed

How to Use the Game-Music-Emu Library to Play NSF Files in Mono? #130

Thysbelon opened this issue Mar 3, 2023 · 2 comments

Comments

@Thysbelon
Copy link

I admire your project. I wanted to use your code to embed a small "play NSF chiptune" button on my blog. I have successfully compiled your fork of game_music_emu with emscripten,
emcmake cmake ../ -DUSE_GME_AY=0 -DUSE_GME_GBS=0 -DUSE_GME_GYM=0 -DUSE_GME_HES=0 -DUSE_GME_KSS=0 -DUSE_GME_SAP=0 -DUSE_GME_SPC=0 -DUSE_GME_VGM=0 -DENABLE_UBSAN=OFF -DBUILD_SHARED_LIBS=OFF (After editing your CMakeList to include ym2413, and editing gme.cpp to comment out all emu types except NSF and NSFe)
and have written a few c and javascript helper functions.
I want to be able to pass true or false into my javascript helper function to render the song in stereo or mono. I have no issue playing back NSF music in stereo, but I cannot get it to switch to mono. The gme_set_effects function described in gme.h looks like it should play the song in mono if I pass NULL into it, but it has no effect.

Here is my c code:

#include <stdlib.h>
#include <stdio.h>

#include <gme.h>
#include <emscripten/webaudio.h>

#include <math.h> //for sin

//uint8_t audioThreadStack[4096];
gme_t* emu;
void* nsfdata;
long nsfdataSize=0;
int track;
EM_BOOL stereopref;
int limitlogs=0;
int buf_size;

short* buf;

void handle_error( const char* str );

static void play_siren( long count, short* out )
	{
		static double a, a2;
		while ( count-- )
			*out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) );
	}

EMSCRIPTEN_KEEPALIVE
int setupNSF(void* inputdata, long inputdataSize, int inputBufSize, int tracknum, EM_BOOL stereoInput)
{
	printf("setupNSF\n");
	
	nsfdata = inputdata;
	
	nsfdataSize = inputdataSize;
	
	track = tracknum;
	
	stereopref = stereoInput;
	
	buf_size = inputBufSize;
	
	int sample_rate = 44100;
	
	/* Open music file in new emulator */
	handle_error( gme_open_data( nsfdata, nsfdataSize, &emu, sample_rate ) );
	if (stereopref == EM_TRUE) {
		printf("STEREOPREF IS TRUE\n");
		struct gme_effects_t effects;
		effects.enabled = 1;
		effects.stereo = 0.5;
		effects.echo = 0.0;
		gme_set_effects( emu, &effects );
	} else {
		printf("STEREOPREF IS FALSE\n");
		gme_set_effects( emu, NULL ); // this does not work
		//struct gme_effects_t effects;
		//effects.enabled = 0;
		//effects.stereo = 0.0;
		//gme_set_effects( emu, &effects ); //these also do not work
	}

	// I have tried putting open_data here, it made no difference.
	
	printf("nsfdataSize: %li \n", nsfdataSize);
	
	/* Start track */
	handle_error( gme_start_track( emu, track ) );
	
	buf = malloc(sizeof(*buf) * buf_size); // https://stackoverflow.com/questions/4240331/c-initializing-a-global-array-in-a-function
	printf("array buf entry number 3: %d\n", buf[3]);
	
	printf("setupNSF done\n");
	return 0;
}

EMSCRIPTEN_KEEPALIVE
short* playNSF() { // run in script processor node's on audio process event
	handle_error( gme_play( emu, buf_size*2, buf ) );
	//play_siren(buf_size, buf);
	
	// the c code cannot return an array to javascript, use runtime method getValue to access the buf array.
	return buf;
}

void handle_error( const char* str )
{
	if ( str )
	{
		printf( "Error: %s\n", str ); //getchar();
		//exit( EXIT_FAILURE );
	}
}

Here is my Javascript:

document.getElementById("nsfplaybut").addEventListener("click",myclick,{once:true})

function myclick(){
	nsfplay("Akumajou Densetsu (VRC6).nsfe",0,false);
}

const INT16_MAX = 65535;
async function nsfplay(url, tracknum, stereopref) {
	var response = await fetch(url)
	
	const audioCtx=new AudioContext({latencyHint:"playback",sampleRate:44100});
	
	const bufferSize = Math.max( // Make sure script node bufferSize is at least baseLatency
		Math.pow(2, Math.ceil(Math.log2((audioCtx.baseLatency || 0.001) * audioCtx.sampleRate))), 2048);
	console.log(bufferSize);
	
	var audioNode=audioCtx.createScriptProcessor(bufferSize,2,2)
	audioNode.connect(audioCtx.destination)
	
	response = await response.arrayBuffer()
	var data=new Uint8Array(response)
	Module.ccall(
		"setupNSF",
		"number",
		["array", "number", "number", "number", "boolean"],
		[data, data.length, bufferSize, tracknum, stereopref]
	)
	
	var playNSF=Module.cwrap("playNSF", "number", null)
	audioNode.onaudioprocess=function(e){
		let bufPtr=playNSF()
		let outputData=[];
		for(let channel=0; channel<e.outputBuffer.numberOfChannels; channel++){
			outputData[channel]=e.outputBuffer.getChannelData(channel); /*nested loop*/
			for(let i=0; i<bufferSize; i++){
				outputData[channel][i] = Module.getValue(bufPtr+i  * 2  * 2 + /* frame offset * bytes per sample * num channels + */ channel  * 2  /* channel offset * bytes per sample */, 'i16') / INT16_MAX /* convert int16 to float */
			}
		} 
	}
}

Thank you.

@mmontag
Copy link
Owner

mmontag commented Mar 3, 2023

Good question; before digging into your code, I'll just mention that I did a quick and dirty modification to game-music-emu to output the NES sound in stereo: 839b9c2

Simply put: the two 2A03 square channels are hard panned Left and Right.

That should be easy to undo, but if you want to make the stereo/mono switchable at runtime, you would have to do some further modifications.

You could always do a mixdown in Javascript to keep things simple. (Add both NSF channels to both output channels. Or create a mono Audio Node?)

@Thysbelon
Copy link
Author

Thank you, that helps a lot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants