mirror of
https://github.com/FunkinCrew/Funkin.git
synced 2024-11-27 10:05:41 -05:00
Merge remote-tracking branch 'origin/rewrite/master' into rewrite/feature/save-data-rewrite
This commit is contained in:
commit
d350ba3bdc
7 changed files with 496 additions and 210 deletions
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 8104d43e584a1f25e574438d7b21a7e671358969
|
Subproject commit f2e37de1ff308eeaf65babad2a4089096c40cedb
|
|
@ -4,6 +4,7 @@ import flixel.util.typeLimit.OneOfTwo;
|
||||||
import funkin.data.song.SongRegistry;
|
import funkin.data.song.SongRegistry;
|
||||||
import thx.semver.Version;
|
import thx.semver.Version;
|
||||||
|
|
||||||
|
@:nullSafety
|
||||||
class SongMetadata
|
class SongMetadata
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +43,7 @@ class SongMetadata
|
||||||
public var timeChanges:Array<SongTimeChange>;
|
public var timeChanges:Array<SongTimeChange>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defaults to `default` or `''`. Populated later.
|
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||||
*/
|
*/
|
||||||
@:jignored
|
@:jignored
|
||||||
public var variation:String;
|
public var variation:String;
|
||||||
|
@ -228,10 +229,10 @@ class SongMusicData
|
||||||
public var timeChanges:Array<SongTimeChange>;
|
public var timeChanges:Array<SongTimeChange>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defaults to `default` or `''`. Populated later.
|
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||||
*/
|
*/
|
||||||
@:jignored
|
@:jignored
|
||||||
public var variation:String = Constants.DEFAULT_VARIATION;
|
public var variation:String;
|
||||||
|
|
||||||
public function new(songName:String, artist:String, variation:String = 'default')
|
public function new(songName:String, artist:String, variation:String = 'default')
|
||||||
{
|
{
|
||||||
|
@ -375,6 +376,9 @@ class SongChartData
|
||||||
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
|
@:default(funkin.data.song.SongRegistry.DEFAULT_GENERATEDBY)
|
||||||
public var generatedBy:String;
|
public var generatedBy:String;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults to `Constants.DEFAULT_VARIATION`. Populated later.
|
||||||
|
*/
|
||||||
@:jignored
|
@:jignored
|
||||||
public var variation:String;
|
public var variation:String;
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
return cleanMetadata(parser.value, variation);
|
return cleanMetadata(parser.value, variation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseEntryMetadataWithMigration(id:String, ?variation:String, version:thx.semver.Version):Null<SongMetadata>
|
public function parseEntryMetadataWithMigration(id:String, variation:String, version:thx.semver.Version):Null<SongMetadata>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ class SongRegistry extends BaseRegistry<Song, SongMetadata>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEntryMetadata_v2_0_0(id:String, variation:String = ""):Null<SongMetadata>
|
function parseEntryMetadata_v2_0_0(id:String, ?variation:String):Null<SongMetadata>
|
||||||
{
|
{
|
||||||
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
variation = variation == null ? Constants.DEFAULT_VARIATION : variation;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package funkin.ui.debug.charting;
|
package funkin.ui.debug.charting;
|
||||||
|
|
||||||
import openfl.utils.Assets;
|
|
||||||
import flixel.system.FlxAssets.FlxSoundAsset;
|
import flixel.system.FlxAssets.FlxSoundAsset;
|
||||||
import flixel.system.FlxSound;
|
import flixel.system.FlxSound;
|
||||||
import funkin.play.character.BaseCharacter.CharacterType;
|
|
||||||
import flixel.system.FlxSound;
|
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 haxe.io.Path;
|
||||||
|
import openfl.utils.Assets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions for loading audio for the chart editor.
|
* Functions for loading audio for the chart editor.
|
||||||
|
@ -17,16 +20,18 @@ import haxe.io.Path;
|
||||||
class ChartEditorAudioHandler
|
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 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.
|
* @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
|
#if sys
|
||||||
var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path.toString());
|
var fileBytes:Bytes = sys.io.File.getBytes(path.toString());
|
||||||
return loadVocalsFromBytes(state, fileBytes, charKey);
|
return loadVocalsFromBytes(state, fileBytes, charId, instId);
|
||||||
#else
|
#else
|
||||||
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way.");
|
||||||
return false;
|
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 path The path to the asset. Use `Paths` to build this.
|
||||||
* @param charKey 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.
|
* @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)
|
if (vocalTrack != null)
|
||||||
{
|
{
|
||||||
switch (charType)
|
switch (charType)
|
||||||
{
|
{
|
||||||
case CharacterType.BF:
|
case BF:
|
||||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
state.audioVocalTrackGroup.addPlayerVoice(vocalTrack);
|
||||||
state.audioVocalTrackData.set(state.currentSongCharacterPlayer, Assets.getBytes(path));
|
return true;
|
||||||
case CharacterType.DAD:
|
case DAD:
|
||||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
state.audioVocalTrackGroup.addOpponentVoice(vocalTrack);
|
||||||
state.audioVocalTrackData.set(state.currentSongCharacterOpponent, Assets.getBytes(path));
|
return true;
|
||||||
|
case OTHER:
|
||||||
|
state.audioVocalTrackGroup.add(vocalTrack);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
if (state.audioVocalTrackGroup != null) state.audioVocalTrackGroup.add(vocalTrack);
|
// Do nothing.
|
||||||
state.audioVocalTrackData.set('default', Assets.getBytes(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static function stopExistingVocals(state:ChartEditorState):Void
|
||||||
* Loads a vocal track from audio byte data.
|
|
||||||
*/
|
|
||||||
static function loadVocalsFromBytes(state:ChartEditorState, bytes:haxe.io.Bytes, charKey:String = ''):Bool
|
|
||||||
{
|
{
|
||||||
var openflSound:openfl.media.Sound = new openfl.media.Sound();
|
if (state.audioVocalTrackGroup != null)
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
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.
|
* Play a sound effect.
|
||||||
* Automatically cleans up after itself and recycles previous FlxSound instances if available, for performance.
|
* 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
|
public static function playSound(path:String):Void
|
||||||
{
|
{
|
||||||
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
var snd:FlxSound = FlxG.sound.list.recycle(FlxSound) ?? new FlxSound();
|
||||||
|
|
||||||
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
var asset:Null<FlxSoundAsset> = FlxG.sound.cache(path);
|
||||||
if (asset == null)
|
if (asset == null)
|
||||||
{
|
{
|
||||||
trace('WARN: Failed to play sound $path, asset not found.');
|
trace('WARN: Failed to play sound $path, asset not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
snd.loadEmbedded(asset);
|
snd.loadEmbedded(asset);
|
||||||
snd.autoDestroy = true;
|
snd.autoDestroy = true;
|
||||||
FlxG.sound.list.add(snd);
|
FlxG.sound.list.add(snd);
|
||||||
snd.play();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ class ChartEditorDialogHandler
|
||||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_WELCOME_LAYOUT, true, closable);
|
||||||
if (dialog == null) throw 'Could not locate Welcome dialog';
|
if (dialog == null) throw 'Could not locate Welcome dialog';
|
||||||
|
|
||||||
// Add handlers to the "Create From Song" section.
|
// Create New Song "Easy/Normal/Hard"
|
||||||
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link);
|
var linkCreateBasic:Null<Link> = dialog.findComponent('splashCreateFromSongBasic', Link);
|
||||||
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog';
|
if (linkCreateBasic == null) throw 'Could not locate splashCreateFromSongBasic link in Welcome dialog';
|
||||||
linkCreateBasic.onClick = function(_event) {
|
linkCreateBasic.onClick = function(_event) {
|
||||||
|
@ -94,7 +94,20 @@ class ChartEditorDialogHandler
|
||||||
//
|
//
|
||||||
// Create Song Wizard
|
// Create Song Wizard
|
||||||
//
|
//
|
||||||
openCreateSongWizard(state, false);
|
openCreateSongWizardBasic(state, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create New Song "Erect/Nightmare"
|
||||||
|
var linkCreateErect:Null<Link> = dialog.findComponent('splashCreateFromSongErect', Link);
|
||||||
|
if (linkCreateErect == null) throw 'Could not locate splashCreateFromSongErect link in Welcome dialog';
|
||||||
|
linkCreateErect.onClick = function(_event) {
|
||||||
|
// Hide the welcome dialog
|
||||||
|
dialog.hideDialog(DialogButton.CANCEL);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Create Song Wizard
|
||||||
|
//
|
||||||
|
openCreateSongWizardErect(state, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
var linkImportChartLegacy:Null<Link> = dialog.findComponent('splashImportChartLegacy', Link);
|
var linkImportChartLegacy:Null<Link> = dialog.findComponent('splashImportChartLegacy', Link);
|
||||||
|
@ -237,34 +250,112 @@ class ChartEditorDialogHandler
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function openCreateSongWizard(state:ChartEditorState, closable:Bool):Void
|
public static function openCreateSongWizardBasic(state:ChartEditorState, closable:Bool):Void
|
||||||
{
|
{
|
||||||
// Step 1. Upload Instrumental
|
// Step 1. Song Metadata
|
||||||
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||||
uploadInstDialog.onDialogClosed = function(_event) {
|
songMetadataDialog.onDialogClosed = function(_event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (_event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 2. Song Metadata
|
// Step 2. Upload Instrumental
|
||||||
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
songMetadataDialog.onDialogClosed = function(_event) {
|
uploadInstDialog.onDialogClosed = function(_event) {
|
||||||
state.isHaxeUIDialogOpen = false;
|
state.isHaxeUIDialogOpen = false;
|
||||||
if (_event.button == DialogButton.APPLY)
|
if (_event.button == DialogButton.APPLY)
|
||||||
{
|
{
|
||||||
// Step 3. Upload Vocals
|
// Step 3. Upload Vocals
|
||||||
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
openUploadVocalsDialog(state, false); // var uploadVocalsDialog:Dialog
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
|
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
|
state.postLoadInstrumental();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// User cancelled the wizard! Back to the welcome dialog.
|
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||||
openWelcomeDialog(state);
|
openWelcomeDialog(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// User cancelled the wizard! Back to the welcome dialog.
|
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function openCreateSongWizardErect(state:ChartEditorState, closable:Bool):Void
|
||||||
|
{
|
||||||
|
// Step 1. Song Metadata
|
||||||
|
var songMetadataDialog:Dialog = openSongMetadataDialog(state);
|
||||||
|
songMetadataDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 2. Upload Instrumental
|
||||||
|
var uploadInstDialog:Dialog = openUploadInstDialog(state, closable);
|
||||||
|
uploadInstDialog.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 3. Upload Vocals
|
||||||
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
|
var uploadVocalsDialog:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
|
uploadVocalsDialog.onDialogClosed = function(_event) {
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
|
// Step 4. Song Metadata (Erect)
|
||||||
|
var songMetadataDialogErect:Dialog = openSongMetadataDialog(state, 'erect');
|
||||||
|
songMetadataDialogErect.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Switch to the Erect variation so uploading the instrumental applies properly.
|
||||||
|
state.selectedVariation = 'erect';
|
||||||
|
|
||||||
|
// Step 5. Upload Instrumental (Erect)
|
||||||
|
var uploadInstDialogErect:Dialog = openUploadInstDialog(state, closable);
|
||||||
|
uploadInstDialogErect.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
if (_event.button == DialogButton.APPLY)
|
||||||
|
{
|
||||||
|
// Step 6. Upload Vocals (Erect)
|
||||||
|
// NOTE: Uploading vocals is optional, so we don't need to check if the user cancelled the wizard.
|
||||||
|
var uploadVocalsDialogErect:Dialog = openUploadVocalsDialog(state, closable); // var uploadVocalsDialog:Dialog
|
||||||
|
uploadVocalsDialogErect.onDialogClosed = function(_event) {
|
||||||
|
state.isHaxeUIDialogOpen = false;
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
|
state.postLoadInstrumental();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard at Step 5! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard at Step 4! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard at Step 2! Back to the welcome dialog.
|
||||||
|
openWelcomeDialog(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// User cancelled the wizard at Step 1! Back to the welcome dialog.
|
||||||
openWelcomeDialog(state);
|
openWelcomeDialog(state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -302,6 +393,8 @@ class ChartEditorDialogHandler
|
||||||
Cursor.cursorMode = Default;
|
Cursor.cursorMode = Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var instId:String = state.currentInstrumentalId;
|
||||||
|
|
||||||
var onDropFile:String->Void;
|
var onDropFile:String->Void;
|
||||||
|
|
||||||
instrumentalBox.onClick = function(_event) {
|
instrumentalBox.onClick = function(_event) {
|
||||||
|
@ -309,14 +402,14 @@ class ChartEditorDialogHandler
|
||||||
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
{label: 'Audio File (.ogg)', extension: 'ogg'}], function(selectedFile:SelectedFileInfo) {
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
{
|
{
|
||||||
if (ChartEditorAudioHandler.loadInstrumentalFromBytes(state, selectedFile.bytes))
|
if (ChartEditorAudioHandler.loadInstFromBytes(state, selectedFile.bytes, instId))
|
||||||
{
|
{
|
||||||
trace('Selected file: ' + selectedFile.fullPath);
|
trace('Selected file: ' + selectedFile.fullPath);
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
body: 'Loaded instrumental track (${selectedFile.name})',
|
body: 'Loaded instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||||
type: NotificationType.Success,
|
type: NotificationType.Success,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -333,7 +426,7 @@ class ChartEditorDialogHandler
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Failure',
|
title: 'Failure',
|
||||||
body: 'Failed to load instrumental track (${selectedFile.name})',
|
body: 'Failed to load instrumental track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -346,14 +439,14 @@ class ChartEditorDialogHandler
|
||||||
onDropFile = function(pathStr:String) {
|
onDropFile = function(pathStr:String) {
|
||||||
var path:Path = new Path(pathStr);
|
var path:Path = new Path(pathStr);
|
||||||
trace('Dropped file (${path})');
|
trace('Dropped file (${path})');
|
||||||
if (ChartEditorAudioHandler.loadInstrumentalFromPath(state, path))
|
if (ChartEditorAudioHandler.loadInstFromPath(state, path, instId))
|
||||||
{
|
{
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
body: 'Loaded instrumental track (${path.file}.${path.ext})',
|
body: 'Loaded instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||||
type: NotificationType.Success,
|
type: NotificationType.Success,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -370,7 +463,7 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
'Failed to load instrumental track (${path.file}.${path.ext})';
|
'Failed to load instrumental track (${path.file}.${path.ext}) for variation (${state.selectedVariation})';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
|
@ -457,11 +550,18 @@ class ChartEditorDialogHandler
|
||||||
* @return The dialog to open.
|
* @return The dialog to open.
|
||||||
*/
|
*/
|
||||||
@:haxe.warning("-WVarInit")
|
@:haxe.warning("-WVarInit")
|
||||||
public static function openSongMetadataDialog(state:ChartEditorState):Dialog
|
public static function openSongMetadataDialog(state:ChartEditorState, ?targetVariation:String):Dialog
|
||||||
{
|
{
|
||||||
|
if (targetVariation == null) targetVariation = Constants.DEFAULT_VARIATION;
|
||||||
|
|
||||||
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
var dialog:Null<Dialog> = openDialog(state, CHART_EDITOR_DIALOG_SONG_METADATA_LAYOUT, true, false);
|
||||||
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
if (dialog == null) throw 'Could not locate Song Metadata dialog';
|
||||||
|
|
||||||
|
if (targetVariation != Constants.DEFAULT_VARIATION)
|
||||||
|
{
|
||||||
|
dialog.title = 'New Chart - Provide Song Metadata (${targetVariation.toTitleCase()})';
|
||||||
|
}
|
||||||
|
|
||||||
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
var buttonCancel:Null<Button> = dialog.findComponent('dialogCancel', Button);
|
||||||
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
if (buttonCancel == null) throw 'Could not locate dialogCancel button in Song Metadata dialog';
|
||||||
buttonCancel.onClick = function(_event) {
|
buttonCancel.onClick = function(_event) {
|
||||||
|
@ -574,7 +674,11 @@ class ChartEditorDialogHandler
|
||||||
|
|
||||||
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
var dialogContinue:Null<Button> = dialog.findComponent('dialogContinue', Button);
|
||||||
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
if (dialogContinue == null) throw 'Could not locate dialogContinue button in Song Metadata dialog';
|
||||||
dialogContinue.onClick = (_event) -> dialog.hideDialog(DialogButton.APPLY);
|
dialogContinue.onClick = (_event) -> {
|
||||||
|
state.songMetadata.set(targetVariation, newSongMetadata);
|
||||||
|
|
||||||
|
dialog.hideDialog(DialogButton.APPLY);
|
||||||
|
}
|
||||||
|
|
||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
@ -587,6 +691,7 @@ class ChartEditorDialogHandler
|
||||||
*/
|
*/
|
||||||
public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
public static function openUploadVocalsDialog(state:ChartEditorState, closable:Bool = true):Dialog
|
||||||
{
|
{
|
||||||
|
var instId:String = state.currentInstrumentalId;
|
||||||
var charIdsForVocals:Array<String> = [];
|
var charIdsForVocals:Array<String> = [];
|
||||||
|
|
||||||
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
var charData:SongCharacterData = state.currentSongMetadata.playData.characters;
|
||||||
|
@ -633,14 +738,14 @@ class ChartEditorDialogHandler
|
||||||
trace('Selected file: $pathStr');
|
trace('Selected file: $pathStr');
|
||||||
var path:Path = new Path(pathStr);
|
var path:Path = new Path(pathStr);
|
||||||
|
|
||||||
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey))
|
if (ChartEditorAudioHandler.loadVocalsFromPath(state, path, charKey, instId))
|
||||||
{
|
{
|
||||||
// Tell the user the load was successful.
|
// Tell the user the load was successful.
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Success',
|
title: 'Success',
|
||||||
body: 'Loaded vocal track for $charName (${path.file}.${path.ext})',
|
body: 'Loaded vocals for $charName (${path.file}.${path.ext}), variation ${state.selectedVariation}',
|
||||||
type: NotificationType.Success,
|
type: NotificationType.Success,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -656,21 +761,14 @@ class ChartEditorDialogHandler
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var message:String = if (!ChartEditorState.SUPPORTED_MUSIC_FORMATS.contains(path.ext ?? ''))
|
trace('Failed to load vocal track (${path.file}.${path.ext})');
|
||||||
{
|
|
||||||
'File format (${path.ext}) not supported for vocal track (${path.file}.${path.ext})';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
'Failed to load vocal track (${path.file}.${path.ext})';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vocals failed to load.
|
// Vocals failed to load.
|
||||||
#if !mac
|
#if !mac
|
||||||
NotificationManager.instance.addNotification(
|
NotificationManager.instance.addNotification(
|
||||||
{
|
{
|
||||||
title: 'Failure',
|
title: 'Failure',
|
||||||
body: message,
|
body: 'Failed to load vocal track (${path.file}.${path.ext}) for variation (${state.selectedVariation})',
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
});
|
});
|
||||||
|
@ -690,14 +788,46 @@ class ChartEditorDialogHandler
|
||||||
if (selectedFile != null && selectedFile.bytes != null)
|
if (selectedFile != null && selectedFile.bytes != null)
|
||||||
{
|
{
|
||||||
trace('Selected file: ' + selectedFile.name);
|
trace('Selected file: ' + selectedFile.name);
|
||||||
#if FILE_DROP_SUPPORTED
|
if (ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey, instId))
|
||||||
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
{
|
||||||
#else
|
// Tell the user the load was successful.
|
||||||
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
#if !mac
|
||||||
#end
|
NotificationManager.instance.addNotification(
|
||||||
ChartEditorAudioHandler.loadVocalsFromBytes(state, selectedFile.bytes, charKey);
|
{
|
||||||
dialogNoVocals.hidden = true;
|
title: 'Success',
|
||||||
removeDropHandler(onDropFile);
|
body: 'Loaded vocals for $charName (${selectedFile.name}), variation ${state.selectedVariation}',
|
||||||
|
type: NotificationType.Success,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
#if FILE_DROP_SUPPORTED
|
||||||
|
vocalsEntryLabel.text = 'Vocals for $charName (drag and drop, or click to browse)\nSelected file: ${selectedFile.name}';
|
||||||
|
#else
|
||||||
|
vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}';
|
||||||
|
#end
|
||||||
|
|
||||||
|
dialogNoVocals.hidden = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('Failed to load vocal track (${selectedFile.fullPath})');
|
||||||
|
|
||||||
|
#if !mac
|
||||||
|
NotificationManager.instance.addNotification(
|
||||||
|
{
|
||||||
|
title: 'Failure',
|
||||||
|
body: 'Failed to load vocal track (${selectedFile.name}) for variation (${state.selectedVariation})',
|
||||||
|
type: NotificationType.Error,
|
||||||
|
expiryMs: ChartEditorState.NOTIFICATION_DISMISS_TIME
|
||||||
|
});
|
||||||
|
#end
|
||||||
|
|
||||||
|
#if FILE_DROP_SUPPORTED
|
||||||
|
vocalsEntryLabel.text = 'Drag and drop vocals for $charName here, or click to browse.';
|
||||||
|
#else
|
||||||
|
vocalsEntryLabel.text = 'Click to browse for vocals for $charName.';
|
||||||
|
#end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ChartEditorImportExportHandler
|
||||||
for (metadata in rawSongMetadata)
|
for (metadata in rawSongMetadata)
|
||||||
{
|
{
|
||||||
if (metadata == null) continue;
|
if (metadata == null) continue;
|
||||||
var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation;
|
var variation = (metadata.variation == null || metadata.variation == '') ? Constants.DEFAULT_VARIATION : metadata.variation;
|
||||||
|
|
||||||
// Clone to prevent modifying the original.
|
// Clone to prevent modifying the original.
|
||||||
var metadataClone:SongMetadata = metadata.clone(variation);
|
var metadataClone:SongMetadata = metadata.clone(variation);
|
||||||
|
@ -52,23 +52,44 @@ class ChartEditorImportExportHandler
|
||||||
|
|
||||||
state.clearVocals();
|
state.clearVocals();
|
||||||
|
|
||||||
ChartEditorAudioHandler.loadInstrumentalFromAsset(state, Paths.inst(songId));
|
var variations:Array<String> = state.availableVariations;
|
||||||
|
for (variation in variations)
|
||||||
var diff:Null<SongDifficulty> = song.getDifficulty(state.selectedDifficulty);
|
|
||||||
var voiceList:Array<String> = diff != null ? diff.buildVoiceList() : [];
|
|
||||||
if (voiceList.length == 2)
|
|
||||||
{
|
{
|
||||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], BF);
|
if (variation == Constants.DEFAULT_VARIATION)
|
||||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], DAD);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (voicePath in voiceList)
|
|
||||||
{
|
{
|
||||||
ChartEditorAudioHandler.loadVocalsFromAsset(state, voicePath);
|
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChartEditorAudioHandler.loadInstFromAsset(state, Paths.inst(songId, '-$variation'), variation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (difficultyId in song.listDifficulties())
|
||||||
|
{
|
||||||
|
var diff:Null<SongDifficulty> = song.getDifficulty(difficultyId);
|
||||||
|
if (diff == null) continue;
|
||||||
|
|
||||||
|
var instId:String = diff.variation == Constants.DEFAULT_VARIATION ? '' : diff.variation;
|
||||||
|
var voiceList:Array<String> = diff.buildVoiceList(); // SongDifficulty accounts for variation already.
|
||||||
|
|
||||||
|
if (voiceList.length == 2)
|
||||||
|
{
|
||||||
|
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||||
|
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[1], diff.characters.opponent, instId);
|
||||||
|
}
|
||||||
|
else if (voiceList.length == 1)
|
||||||
|
{
|
||||||
|
ChartEditorAudioHandler.loadVocalsFromAsset(state, voiceList[0], diff.characters.player, instId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[WARN] Strange quantity of voice paths for difficulty ${difficultyId}: ${voiceList.length}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.switchToCurrentInstrumental();
|
||||||
|
|
||||||
state.refreshMetadataToolbox();
|
state.refreshMetadataToolbox();
|
||||||
|
|
||||||
#if !mac
|
#if !mac
|
||||||
|
@ -148,13 +169,8 @@ class ChartEditorImportExportHandler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.audioInstTrackData != null) zipEntries.push(FileUtil.makeZIPEntryFromBytes('Inst.ogg', state.audioInstTrackData));
|
if (state.audioInstTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromInstrumentals(state));
|
||||||
for (charId in state.audioVocalTrackData.keys())
|
if (state.audioVocalTrackData != null) zipEntries.concat(ChartEditorAudioHandler.makeZIPEntriesFromVocals(state));
|
||||||
{
|
|
||||||
var entryData = state.audioVocalTrackData.get(charId);
|
|
||||||
if (entryData == null) continue;
|
|
||||||
zipEntries.push(FileUtil.makeZIPEntryFromBytes('Vocals-$charId.ogg', entryData));
|
|
||||||
}
|
|
||||||
|
|
||||||
trace('Exporting ${zipEntries.length} files to ZIP...');
|
trace('Exporting ${zipEntries.length} files to ZIP...');
|
||||||
|
|
||||||
|
|
|
@ -461,6 +461,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
notePreviewDirty = true;
|
notePreviewDirty = true;
|
||||||
notePreviewViewportBoundsDirty = true;
|
notePreviewViewportBoundsDirty = true;
|
||||||
this.scrollPositionInPixels = this.scrollPositionInPixels;
|
this.scrollPositionInPixels = this.scrollPositionInPixels;
|
||||||
|
// Characters have probably changed too.
|
||||||
|
healthIconsDirty = true;
|
||||||
|
|
||||||
return isViewDownscroll;
|
return isViewDownscroll;
|
||||||
}
|
}
|
||||||
|
@ -519,8 +521,14 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION;
|
var selectedVariation(default, set):String = Constants.DEFAULT_VARIATION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter called when we are switching variations.
|
||||||
|
* We will likely need to switch instrumentals as well.
|
||||||
|
*/
|
||||||
function set_selectedVariation(value:String):String
|
function set_selectedVariation(value:String):String
|
||||||
{
|
{
|
||||||
|
// Don't update if we're already on the variation.
|
||||||
|
if (selectedVariation == value) return selectedVariation;
|
||||||
selectedVariation = value;
|
selectedVariation = value;
|
||||||
|
|
||||||
// Make sure view is updated when the variation changes.
|
// Make sure view is updated when the variation changes.
|
||||||
|
@ -528,6 +536,8 @@ class ChartEditorState extends HaxeUIState
|
||||||
notePreviewDirty = true;
|
notePreviewDirty = true;
|
||||||
notePreviewViewportBoundsDirty = true;
|
notePreviewViewportBoundsDirty = true;
|
||||||
|
|
||||||
|
switchToCurrentInstrumental();
|
||||||
|
|
||||||
return selectedVariation;
|
return selectedVariation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,6 +558,23 @@ class ChartEditorState extends HaxeUIState
|
||||||
return selectedDifficulty;
|
return selectedDifficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instrumental ID which is currently selected.
|
||||||
|
*/
|
||||||
|
var currentInstrumentalId(get, set):String;
|
||||||
|
|
||||||
|
function get_currentInstrumentalId():String
|
||||||
|
{
|
||||||
|
var instId:Null<String> = currentSongMetadata.playData.characters.instrumental;
|
||||||
|
if (instId == null || instId == '') instId = (selectedVariation == Constants.DEFAULT_VARIATION) ? '' : selectedVariation;
|
||||||
|
return instId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_currentInstrumentalId(value:String):String
|
||||||
|
{
|
||||||
|
return currentSongMetadata.playData.characters.instrumental = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The character ID for the character which is currently selected.
|
* The character ID for the character which is currently selected.
|
||||||
*/
|
*/
|
||||||
|
@ -592,6 +619,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
var noteDisplayDirty:Bool = true;
|
var noteDisplayDirty:Bool = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the selected charactesr have been modified and the health icons need to be updated.
|
||||||
|
*/
|
||||||
|
var healthIconsDirty:Bool = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the note preview graphic needs to be FULLY rebuilt.
|
* Whether the note preview graphic needs to be FULLY rebuilt.
|
||||||
*/
|
*/
|
||||||
|
@ -773,28 +805,29 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The audio track for the instrumental.
|
* The audio track for the instrumental.
|
||||||
|
* Replaced when switching instrumentals.
|
||||||
* `null` until an instrumental track is loaded.
|
* `null` until an instrumental track is loaded.
|
||||||
*/
|
*/
|
||||||
var audioInstTrack:Null<FlxSound> = null;
|
var audioInstTrack:Null<FlxSound> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The raw byte data for the instrumental audio track.
|
* The raw byte data for the instrumental audio tracks.
|
||||||
|
* Key is the instrumental name.
|
||||||
* `null` until an instrumental track is loaded.
|
* `null` until an instrumental track is loaded.
|
||||||
*/
|
*/
|
||||||
var audioInstTrackData:Null<Bytes> = null;
|
var audioInstTrackData:Map<String, Bytes> = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The audio track for the vocals.
|
* The audio track for the vocals.
|
||||||
* `null` until vocal track(s) are loaded.
|
* `null` until vocal track(s) are loaded.
|
||||||
|
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
||||||
*/
|
*/
|
||||||
var audioVocalTrackGroup:Null<VoicesGroup> = null;
|
var audioVocalTrackGroup:Null<VoicesGroup> = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map of the audio tracks for each character's vocals.
|
* A map of the audio tracks for each character's vocals.
|
||||||
* - Keys are the character IDs.
|
* - Keys are `characterId-variation` (with `characterId` being the default variation).
|
||||||
* - Values are the FlxSound objects to play that character's vocals.
|
* - Values are the byte data for the audio track.
|
||||||
*
|
|
||||||
* When switching characters, the elements of the VoicesGroup will be swapped to match the new character.
|
|
||||||
*/
|
*/
|
||||||
var audioVocalTrackData:Map<String, Bytes> = [];
|
var audioVocalTrackData:Map<String, Bytes> = [];
|
||||||
|
|
||||||
|
@ -1045,30 +1078,6 @@ class ChartEditorState extends HaxeUIState
|
||||||
return currentSongMetadata.artist = value;
|
return currentSongMetadata.artist = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSongCharacterPlayer(get, set):String;
|
|
||||||
|
|
||||||
function get_currentSongCharacterPlayer():String
|
|
||||||
{
|
|
||||||
return currentSongMetadata.playData.characters.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_currentSongCharacterPlayer(value:String):String
|
|
||||||
{
|
|
||||||
return currentSongMetadata.playData.characters.player = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentSongCharacterOpponent(get, set):String;
|
|
||||||
|
|
||||||
function get_currentSongCharacterOpponent():String
|
|
||||||
{
|
|
||||||
return currentSongMetadata.playData.characters.opponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_currentSongCharacterOpponent(value:String):String
|
|
||||||
{
|
|
||||||
return currentSongMetadata.playData.characters.opponent = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SIGNALS
|
* SIGNALS
|
||||||
*/
|
*/
|
||||||
|
@ -1379,7 +1388,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
gridPlayhead.add(playheadBlock);
|
gridPlayhead.add(playheadBlock);
|
||||||
|
|
||||||
// Character icons.
|
// Character icons.
|
||||||
healthIconDad = new HealthIcon(currentSongCharacterOpponent);
|
healthIconDad = new HealthIcon(currentSongMetadata.playData.characters.opponent);
|
||||||
healthIconDad.autoUpdate = false;
|
healthIconDad.autoUpdate = false;
|
||||||
healthIconDad.size.set(0.5, 0.5);
|
healthIconDad.size.set(0.5, 0.5);
|
||||||
healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5);
|
||||||
|
@ -1387,7 +1396,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
add(healthIconDad);
|
add(healthIconDad);
|
||||||
healthIconDad.zIndex = 30;
|
healthIconDad.zIndex = 30;
|
||||||
|
|
||||||
healthIconBF = new HealthIcon(currentSongCharacterPlayer);
|
healthIconBF = new HealthIcon(currentSongMetadata.playData.characters.player);
|
||||||
healthIconBF.autoUpdate = false;
|
healthIconBF.autoUpdate = false;
|
||||||
healthIconBF.size.set(0.5, 0.5);
|
healthIconBF.size.set(0.5, 0.5);
|
||||||
healthIconBF.x = gridTiledSprite.x + gridTiledSprite.width + 15;
|
healthIconBF.x = gridTiledSprite.x + gridTiledSprite.width + 15;
|
||||||
|
@ -1484,6 +1493,12 @@ class ChartEditorState extends HaxeUIState
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function switchToCurrentInstrumental():Void
|
||||||
|
{
|
||||||
|
ChartEditorAudioHandler.switchToInstrumental(this, currentInstrumentalId, currentSongMetadata.playData.characters.player,
|
||||||
|
currentSongMetadata.playData.characters.opponent);
|
||||||
|
}
|
||||||
|
|
||||||
function setNotePreviewViewportBounds(bounds:FlxRect = null):Void
|
function setNotePreviewViewportBounds(bounds:FlxRect = null):Void
|
||||||
{
|
{
|
||||||
if (notePreviewViewport == null)
|
if (notePreviewViewport == null)
|
||||||
|
@ -1691,6 +1706,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
});
|
});
|
||||||
|
|
||||||
addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this));
|
addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this));
|
||||||
|
addUIClickListener('menubarItemWelcomeDialog', _ -> ChartEditorDialogHandler.openWelcomeDialog(this, true));
|
||||||
|
|
||||||
addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this));
|
||||||
|
|
||||||
|
@ -1713,6 +1729,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
});
|
});
|
||||||
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark);
|
||||||
|
|
||||||
|
addUIClickListener('menubarItemPlayPause', _ -> toggleAudioPlayback());
|
||||||
|
|
||||||
|
addUIClickListener('menubarItemLoadInstrumental', _ -> ChartEditorDialogHandler.openUploadInstDialog(this, true));
|
||||||
|
addUIClickListener('menubarItemLoadVocals', _ -> ChartEditorDialogHandler.openUploadVocalsDialog(this, true));
|
||||||
|
|
||||||
addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value);
|
addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value);
|
||||||
setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled);
|
setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled);
|
||||||
|
|
||||||
|
@ -1726,7 +1747,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
if (instVolumeLabel != null)
|
if (instVolumeLabel != null)
|
||||||
{
|
{
|
||||||
addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) {
|
addUIChangeListener('menubarItemVolumeInstrumental', function(event:UIEvent) {
|
||||||
var volume:Float = event?.value ?? 0 / 100.0;
|
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||||
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
if (audioInstTrack != null) audioInstTrack.volume = volume;
|
||||||
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
instVolumeLabel.text = 'Instrumental - ${Std.int(event.value)}%';
|
||||||
});
|
});
|
||||||
|
@ -1736,7 +1757,7 @@ class ChartEditorState extends HaxeUIState
|
||||||
if (vocalsVolumeLabel != null)
|
if (vocalsVolumeLabel != null)
|
||||||
{
|
{
|
||||||
addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) {
|
addUIChangeListener('menubarItemVolumeVocals', function(event:UIEvent) {
|
||||||
var volume:Float = event?.value ?? 0 / 100.0;
|
var volume:Float = (event?.value ?? 0) / 100.0;
|
||||||
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
if (audioVocalTrackGroup != null) audioVocalTrackGroup.volume = volume;
|
||||||
vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%';
|
vocalsVolumeLabel.text = 'Vocals - ${Std.int(event.value)}%';
|
||||||
});
|
});
|
||||||
|
@ -2986,6 +3007,12 @@ class ChartEditorState extends HaxeUIState
|
||||||
*/
|
*/
|
||||||
function handleHealthIcons():Void
|
function handleHealthIcons():Void
|
||||||
{
|
{
|
||||||
|
if (healthIconsDirty)
|
||||||
|
{
|
||||||
|
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
|
||||||
|
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
|
||||||
|
}
|
||||||
|
|
||||||
// Right align the BF health icon.
|
// Right align the BF health icon.
|
||||||
if (healthIconBF != null)
|
if (healthIconBF != null)
|
||||||
{
|
{
|
||||||
|
@ -3413,11 +3440,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
{
|
{
|
||||||
playerPreviewDirty = false;
|
playerPreviewDirty = false;
|
||||||
|
|
||||||
if (currentSongCharacterPlayer != charPlayer.charId)
|
if (currentSongMetadata.playData.characters.player != charPlayer.charId)
|
||||||
{
|
{
|
||||||
if (healthIconBF != null) healthIconBF.characterId = currentSongCharacterPlayer;
|
if (healthIconBF != null) healthIconBF.characterId = currentSongMetadata.playData.characters.player;
|
||||||
|
|
||||||
charPlayer.loadCharacter(currentSongCharacterPlayer);
|
charPlayer.loadCharacter(currentSongMetadata.playData.characters.player);
|
||||||
charPlayer.characterType = CharacterType.BF;
|
charPlayer.characterType = CharacterType.BF;
|
||||||
charPlayer.flip = true;
|
charPlayer.flip = true;
|
||||||
charPlayer.targetScale = 0.5;
|
charPlayer.targetScale = 0.5;
|
||||||
|
@ -3449,11 +3476,11 @@ class ChartEditorState extends HaxeUIState
|
||||||
{
|
{
|
||||||
opponentPreviewDirty = false;
|
opponentPreviewDirty = false;
|
||||||
|
|
||||||
if (currentSongCharacterOpponent != charPlayer.charId)
|
if (currentSongMetadata.playData.characters.opponent != charPlayer.charId)
|
||||||
{
|
{
|
||||||
if (healthIconDad != null) healthIconDad.characterId = currentSongCharacterOpponent;
|
if (healthIconDad != null) healthIconDad.characterId = currentSongMetadata.playData.characters.opponent;
|
||||||
|
|
||||||
charPlayer.loadCharacter(currentSongCharacterOpponent);
|
charPlayer.loadCharacter(currentSongMetadata.playData.characters.opponent);
|
||||||
charPlayer.characterType = CharacterType.DAD;
|
charPlayer.characterType = CharacterType.DAD;
|
||||||
charPlayer.flip = false;
|
charPlayer.flip = false;
|
||||||
charPlayer.targetScale = 0.5;
|
charPlayer.targetScale = 0.5;
|
||||||
|
@ -3833,9 +3860,9 @@ class ChartEditorState extends HaxeUIState
|
||||||
switch (noteData.getStrumlineIndex())
|
switch (noteData.getStrumlineIndex())
|
||||||
{
|
{
|
||||||
case 0: // Player
|
case 0: // Player
|
||||||
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-09'));
|
if (hitsoundsEnabledPlayer) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/playerHitsound'));
|
||||||
case 1: // Opponent
|
case 1: // Opponent
|
||||||
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('funnyNoise/funnyNoise-010'));
|
if (hitsoundsEnabledOpponent) ChartEditorAudioHandler.playSound(Paths.sound('ui/chart-editor/opponentHitsound'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4072,12 +4099,18 @@ class ChartEditorState extends HaxeUIState
|
||||||
|
|
||||||
buildSpectrogram(audioInstTrack);
|
buildSpectrogram(audioInstTrack);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trace('[WARN] Instrumental track was null!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty much everything is going to need to be reset.
|
||||||
scrollPositionInPixels = 0;
|
scrollPositionInPixels = 0;
|
||||||
playheadPositionInPixels = 0;
|
playheadPositionInPixels = 0;
|
||||||
notePreviewDirty = true;
|
notePreviewDirty = true;
|
||||||
notePreviewViewportBoundsDirty = true;
|
notePreviewViewportBoundsDirty = true;
|
||||||
noteDisplayDirty = true;
|
noteDisplayDirty = true;
|
||||||
|
healthIconsDirty = true;
|
||||||
moveSongToScrollPosition();
|
moveSongToScrollPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue