Fixed volume sliders, Load Instrumental button, and fixed hitsounds.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package funkin.ui.debug.charting;
|
||||
|
||||
import openfl.utils.Assets;
|
||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import flixel.system.FlxSound;
|
||||
import funkin.audio.VoicesGroup;
|
||||
import funkin.play.character.BaseCharacter.CharacterType;
|
||||
import funkin.util.FileUtil;
|
||||
import haxe.io.Bytes;
|
||||
import haxe.io.Path;
|
||||
import openfl.utils.Assets;
|
||||
|
||||
/**
|
||||
* Functions for loading audio for the chart editor.
|
||||
@@ -17,16 +20,18 @@ import haxe.io.Path;
|
||||
class ChartEditorAudioHandler
|
||||
{
|
||||
/**
|
||||
* Loads a vocal track from an absolute file path.
|
||||
* Loads and stores byte data for a vocal track from an absolute file path
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
* @param charKey The character to load the vocal track for.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromPath(state:ChartEditorState, path:Path, charKey:String = 'default'):Bool
|
||||
static function loadVocalsFromPath(state:ChartEditorState, path:Path, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadVocalsFromBytes(state, fileBytes, charKey);
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadVocalsFromBytes(state, fileBytes, charId, instId);
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
@@ -34,137 +39,235 @@ class ChartEditorAudioHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a vocal track for a given song and character and add it to the voices group.
|
||||
* Loads and stores byte data for a vocal track from an asset
|
||||
*
|
||||
* @param path ID of the asset.
|
||||
* @param charKey Character to load the vocal track for.
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadVocalsFromAsset(state:ChartEditorState, path:String, charType:CharacterType = OTHER):Bool
|
||||
static function loadVocalsFromAsset(state:ChartEditorState, path:String, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
{
|
||||
return loadVocalsFromBytes(state, trackData, charId, instId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for a vocal track
|
||||
*
|
||||
* @param bytes The audio byte data.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:Bytes, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
state.audioVocalTrackData.set(trackId, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for an instrumental track from an absolute file path
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromPath(state:ChartEditorState, path:Path, instId:String = ''):Bool
|
||||
{
|
||||
#if sys
|
||||
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadInstFromBytes(state, fileBytes, instId);
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for an instrumental track from an asset
|
||||
*
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstFromAsset(state:ChartEditorState, path:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackData:Null<Bytes> = Assets.getBytes(path);
|
||||
if (trackData != null)
|
||||
{
|
||||
return loadInstFromBytes(state, trackData, instId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and stores byte data for a vocal track
|
||||
*
|
||||
* @param bytes The audio byte data.
|
||||
* @param charId The character this vocal track will be for.
|
||||
* @param instId The instrumental this vocal track will be for.
|
||||
*/
|
||||
static function loadInstFromBytes(state:ChartEditorState, bytes:Bytes, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
state.audioInstTrackData.set(instId, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function switchToInstrumental(state:ChartEditorState, instId:String = '', playerId:String, opponentId:String):Bool
|
||||
{
|
||||
var result:Bool = playInstrumental(state, instId);
|
||||
if (!result) return false;
|
||||
|
||||
stopExistingVocals(state);
|
||||
result = playVocals(state, BF, playerId, instId);
|
||||
if (!result) return false;
|
||||
result = playVocals(state, DAD, opponentId, instId);
|
||||
if (!result) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the Chart Editor to select a specific instrumental track, that is already loaded.
|
||||
*/
|
||||
static function playInstrumental(state:ChartEditorState, instId:String = ''):Bool
|
||||
{
|
||||
if (instId == '') instId = 'default';
|
||||
var instTrackData:Null<Bytes> = state.audioInstTrackData.get(instId);
|
||||
var instTrack:Null<FlxSound> = buildFlxSoundFromBytes(instTrackData);
|
||||
if (instTrack == null) return false;
|
||||
|
||||
stopExistingInstrumental(state);
|
||||
state.audioInstTrack = instTrack;
|
||||
state.postLoadInstrumental();
|
||||
return true;
|
||||
}
|
||||
|
||||
static function stopExistingInstrumental(state:ChartEditorState):Void
|
||||
{
|
||||
if (state.audioInstTrack != null)
|
||||
{
|
||||
state.audioInstTrack.stop();
|
||||
state.audioInstTrack.destroy();
|
||||
state.audioInstTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the Chart Editor to select a specific vocal track, that is already loaded.
|
||||
*/
|
||||
static function playVocals(state:ChartEditorState, charType:CharacterType, charId:String, instId:String = ''):Bool
|
||||
{
|
||||
var trackId:String = '${charId}${instId == '' ? '' : '-${instId}'}';
|
||||
var vocalTrackData:Null<Bytes> = state.audioVocalTrackData.get(trackId);
|
||||
var vocalTrack:Null<FlxSound> = buildFlxSoundFromBytes(vocalTrackData);
|
||||
|
||||
if (state.audioVocalTrackGroup == null) state.audioVocalTrackGroup = new VoicesGroup();
|
||||
|
||||
if (vocalTrack != null)
|
||||
{
|
||||
switch (charType)
|
||||
{
|
||||
case CharacterType.BF:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
state.audioVocalTrackData.set(state.currentSongCharacterPlayer, Assets.getBytes(path));
|
||||
case CharacterType.DAD:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
state.audioVocalTrackData.set(state.currentSongCharacterOpponent, Assets.getBytes(path));
|
||||
case BF:
|
||||
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||
return true;
|
||||
case DAD:
|
||||
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||
return true;
|
||||
case OTHER:
|
||||
state.audioVocalTrackGroup.add(vocalTrack);
|
||||
return true;
|
||||
default:
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack);
|
||||
state.audioVocalTrackData.set('default', Assets.getBytes(path));
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a vocal track from audio byte data.
|
||||
*/
|
||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, charKey:String = ''):Bool
|
||||
static function stopExistingVocals(state:ChartEditorState):Void
|
||||
{
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
var vocalTrack:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack);
|
||||
state.audioVocalTrackData.set(charKey, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from an absolute file path, replacing the current instrumental.
|
||||
*
|
||||
* @param path The absolute path to the audio file.
|
||||
*
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromPath(state:ChartEditorState, path:Path):Bool
|
||||
{
|
||||
#if sys
|
||||
// Validate file extension.
|
||||
if (path.ext != null && !ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext))
|
||||
if (state.audioVocalTrackGroup != null)
|
||||
{
|
||||
return false;
|
||||
state.audioVocalTrackGroup.clear();
|
||||
}
|
||||
|
||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
||||
return loadInstrumentalFromBytes(state, fileBytes, '${path.file}.${path.ext}');
|
||||
#else
|
||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||
return false;
|
||||
#end
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from audio byte data, replacing the current instrumental.
|
||||
* @param bytes The audio byte data.
|
||||
* @param fileName The name of the file, if available. Used for notifications.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, fileName:String = null):Bool
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length);
|
||||
state.audioInstTrack = FlxG.sound.load(openflSound, 1.0, false);
|
||||
state.audioInstTrack.autoDestroy = false;
|
||||
state.audioInstTrack.pause();
|
||||
|
||||
state.audioInstTrackData = bytes;
|
||||
|
||||
state.postLoadInstrumental();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an instrumental from an OpenFL asset, replacing the current instrumental.
|
||||
* @param path The path to the asset. Use `Paths` to build this.
|
||||
* @return Success or failure.
|
||||
*/
|
||||
static function loadInstrumentalFromAsset(state:ChartEditorState, path:String):Bool
|
||||
{
|
||||
var instTrack:FlxSound = FlxG.sound.load(path, 1.0, false);
|
||||
if (instTrack != null)
|
||||
{
|
||||
state.audioInstTrack = instTrack;
|
||||
|
||||
state.audioInstTrackData = Assets.getBytes(path);
|
||||
|
||||
state.postLoadInstrumental();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a sound effect.
|
||||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
||||
* @param path The path to the sound effect. Use `Paths` to build this.
|
||||
*/
|
||||
public static function playSound(path:String):Void
|
||||
{
|
||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||
|
||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||
if (asset == null)
|
||||
{
|
||||
trace('WARN: Failed to play sound $path, asset not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
snd.loadEmbedded(asset);
|
||||
snd.autoDestroy = true;
|
||||
FlxG.sound.list.add(snd);
|
||||
snd.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert byte data into a playable sound.
|
||||
*
|
||||
* @param input The byte data.
|
||||
* @return The playable sound, or `null` if loading failed.
|
||||
*/
|
||||
public static function buildFlxSoundFromBytes(input:Null<Bytes>):Null<FlxSound>
|
||||
{
|
||||
if (input == null) return null;
|
||||
|
||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
||||
openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(input), input.length);
|
||||
var output:FlxSound = FlxG.sound.load(openflSound, 1.0, false);
|
||||
return output;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromInstrumentals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
for (key in state.audioInstTrackData.keys())
|
||||
{
|
||||
if (key == 'default')
|
||||
{
|
||||
var data:Null<Bytes> = state.audioInstTrackData.get('default');
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', data));
|
||||
}
|
||||
else
|
||||
{
|
||||
var data:Null<Bytes> = state.audioInstTrackData.get(key);
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst-${key}.ogg', data));
|
||||
}
|
||||
}
|
||||
|
||||
return zipEntries;
|
||||
}
|
||||
|
||||
static function makeZIPEntriesFromVocals(state:ChartEditorState):Array<haxe.zip.Entry>
|
||||
{
|
||||
var zipEntries = [];
|
||||
|
||||
for (key in state.audioVocalTrackData.keys())
|
||||
{
|
||||
var data:Null<Bytes> = state.audioVocalTrackData.get(key);
|
||||
if (data == null) continue;
|
||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-${key}.ogg', data));
|
||||
}
|
||||
|
||||
return zipEntries;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user