Home

2024

Worklog

LETSGO Game

Rework InstrumentData to use single Metasound

Date
August 27, 2024
Hours
1.5
Tags
UnrealCodeC++LETSGO

I realized one of the reasons I was so confused yesterday about the SetWaveParameter was that it’s actually a function of the AudioComponent.

This is significant, because in order to dynamically set these variables for a single Metasound, I need to do it on an object that has an AudioComponent.

This is actually a good thing, because there is only really one place that has an AudioComponent now- the AudioCuePlayer.

This reinforced a lesson from 🔨Building The Core Gameplay Loop, where you should start your refactor/modification at the place that will consume the new change. I had started at the other end, deleting the 48 metasounds and changing them to USoundCue. That’s great, except I actually need USoundWave. A costly mistake if one made 48 times.

Starting with the AudioCuePlayer, I now have

	// MetaSoundPlayer.h 
	
	// This is set in editor to point to my single parameterized Metasound
	UPROPERTY(BlueprintReadWrite, EditDefaultsOnly)
	UMetaSoundSource* MetaSoundPlayer;
	
	// Iniitialize used to take a MetasoundSource*
	// It now takes data to initialize the above Metasound
	UFUNCTION()
	void Initialize(const FMetaSoundPlayerData& MetaSoundData, UQuartzClockHandle* ParentClock, const FQuartzQuantizationBoundary& ParentQuartzQuantizationBoundary);

	// I also set a specific func to map data to functionality 
	// This could just be done in Initialize
	// But I read that Metasound parameters need to be set *after* Play. 
	// In that case having a separate function makes it easier to move.
	UFUNCTION()
	void InitializeMetaSoundPlayer(const FMetaSoundPlayerData& Data) const;
	
	// MetaSoundPlayer.cpp
	
	// The actual code here is fairly straight forward for now 
	// Eventually this could become more complex as more effects are implemented
	void AAudioCuePlayer::InitializeMetaSoundPlayer(const FMetaSoundPlayerData& Data) const
	{
		AudioComponent->SetWaveParameter(Data.WaveAssetName, Data.WaveAsset);
		AudioComponent->SetFloatParameter(Data.OutputVolumeName, Data.OutputVolume);
	}
	

The MetaSoundPlayerData is a fairly straight forward struct:

	// FMetaSoundPlayerData.h
	
	// This must be exact match for the parameterized MetaSound 
	UPROPERTY()
	const FName WaveAssetName = FName("WaveAsset");

	// Same with this
	UPROPERTY()
	const FName OutputVolumeName = FName("OutputVolume");

	UPROPERTY()
	USoundWave* WaveAsset;

	UPROPERTY()
	float OutputVolume = 1.0f;

So, our MetaSoundPlayerData represents the data necessary to control our single Metasound object.

This change of replacing the MetaSoundSource to MetaSoundData will now break anything using AudioCuePlayer.

This essentially triggers a refactoring chain reaction where I have to percolate up the change to the data model throughout the codebase.

Specifically, this breaks the all important Instrument Actor that Initializes a set of AudioCuePlayers on every bar- see Building A Better Drum MachineBuilding A Better Drum Machine.

Now on every bar, its trying to initialize the AudioCuePlayer with the MetasoundSource, not my new MetaSoundData.

So, I have to update Instrument to contain a Data object, which will break both DrumData and CheeseKeyData, forcing me to refactor those to contain MetaSoundData instead of MetasoundSource.

It’s some tedious bullshit, but alas, all human endeavor is the passion and survival of tedious bullshit.

Instrument receives a FInstrumentSchedule, a struct containing a set of FNotesPerBar

FNotesPerBar is a struct containing all the data required to play a set of notes in a bar- if there’s 4 beats in a bar, you could expect 4 pieces of data expressing which note to play on which beat.

NotesPerBar contains a MetasoundSource reference, and is the place I need to rework.

Update this, and Instrument should be able to pass through the data to the AudioCuePlayer easily.

This triggers the next refactoring chain reaction: I define InstrumentSchedule in two places: One for the Drums- snare, kick, hi-hat, etc; and one for a sampled synth instrument.

Once I change the spec for NotesPerBar, I will have to update the InstrumentSchedule in these data objects.