diff --git a/hmm.json b/hmm.json index f4f8ddc7b..c8aa048ce 100644 --- a/hmm.json +++ b/hmm.json @@ -11,14 +11,14 @@ "name": "flixel", "type": "git", "dir": null, - "ref": "6728df7", + "ref": "8ff2aa9", "url": "https://github.com/MasterEric/flixel" }, { "name": "flixel-addons", "type": "git", "dir": null, - "ref": "a3877f0", + "ref": "157eaf3", "url": "https://github.com/MasterEric/flixel-addons" }, { diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 9a95a9863..378930f34 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -129,6 +129,7 @@ class Conductor */ public static function forceBPM(bpm:Float) { + trace('[CONDUCTOR] Forcing BPM to ' + bpm); Conductor.bpmOverride = bpm; } @@ -213,10 +214,8 @@ class Conductor } } - public static function mapTimeChanges(currentChart:SongDifficulty) + public static function mapTimeChanges(songTimeChanges:Array) { - var songTimeChanges:Array = currentChart.timeChanges; - timeChanges = []; for (currentTimeChange in songTimeChanges) diff --git a/source/funkin/VoicesGroup.hx b/source/funkin/VoicesGroup.hx index 306d82900..db31cb052 100644 --- a/source/funkin/VoicesGroup.hx +++ b/source/funkin/VoicesGroup.hx @@ -7,30 +7,38 @@ import flixel.system.FlxSound; // when needed class VoicesGroup extends FlxTypedGroup { - public var time(default, set):Float = 0; + public var time(get, set):Float; - public var volume(default, set):Float = 1; + public var volume(get, set):Float; - public var pitch(default, set):Float = 1; + public var pitch(get, set):Float; // make it a group that you add to? - public function new(song:String, ?files:Array = null) + public function new() { super(); + } + + // TODO: Remove this. + public static function build(song:String, ?files:Array = null):VoicesGroup + { + var result = new VoicesGroup(); if (files == null) { // Add an empty voice. - add(new FlxSound()); - return; + result.add(new FlxSound()); + return result; } for (sndFile in files) { var snd:FlxSound = new FlxSound().loadEmbedded(Paths.voices(song, '$sndFile')); FlxG.sound.list.add(snd); // adds it to sound group for proper volumes - add(snd); // adds it to main group for other shit + result.add(snd); // adds it to main group for other shit } + + return result; } /** @@ -83,6 +91,14 @@ class VoicesGroup extends FlxTypedGroup }); } + function get_time():Float + { + if (getFirstAlive() != null) + return getFirstAlive().time; + else + return 0; + } + function set_time(time:Float):Float { forEachAlive(function(snd) @@ -94,6 +110,14 @@ class VoicesGroup extends FlxTypedGroup return time; } + function get_volume():Float + { + if (getFirstAlive() != null) + return getFirstAlive().volume; + else + return 1; + } + // in PlayState, adjust the code so that it only mutes the player1 vocal tracks? function set_volume(volume:Float):Float { @@ -105,9 +129,20 @@ class VoicesGroup extends FlxTypedGroup return volume; } + function get_pitch():Float + { + #if FLX_PITCH + if (getFirstAlive() != null) + return getFirstAlive().pitch; + else + #end + return 1; + } + function set_pitch(val:Float):Float { - #if HAS_PITCH + #if FLX_PITCH + trace('Setting audio pitch to ' + val); forEachAlive(function(snd) { snd.pitch = val; diff --git a/source/funkin/charting/ChartingState.hx b/source/funkin/charting/ChartingState.hx index d51df7689..1b78c1d6a 100644 --- a/source/funkin/charting/ChartingState.hx +++ b/source/funkin/charting/ChartingState.hx @@ -445,7 +445,7 @@ class ChartingState extends MusicBeatState add(playheadTest); // WONT WORK FOR TUTORIAL OR TEST SONG!!! REDO LATER - vocals = new VoicesGroup(daSong, _song.voiceList); + vocals = VoicesGroup.build(daSong, _song.voiceList); // vocals = new FlxSound().loadEmbedded(Paths.voices(daSong)); // FlxG.sound.list.add(vocals); diff --git a/source/funkin/input/Cursor.hx b/source/funkin/input/Cursor.hx index 6be47c919..50cdad079 100644 --- a/source/funkin/input/Cursor.hx +++ b/source/funkin/input/Cursor.hx @@ -35,7 +35,7 @@ class Cursor static final CURSOR_GRABBING_PARAMS:CursorParams = { graphic: "assets/images/cursor/cursor-grabbing.png", scale: 1.0, - offsetX: 8, + offsetX: 32, offsetY: 0, }; static var assetCursorGrabbing:BitmapData = null; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index eb7f99fa8..ee8a9d3c3 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -354,7 +354,7 @@ class PlayState extends MusicBeatState if (currentSong_NEW != null) { - Conductor.mapTimeChanges(currentChart); + Conductor.mapTimeChanges(currentChart.timeChanges); // Conductor.bpm = currentChart.getStartingBPM(); // TODO: Support for dialog. @@ -1029,9 +1029,9 @@ class PlayState extends MusicBeatState currentSong.song = currentSong.song; if (currentSong.needsVoices) - vocals = new VoicesGroup(currentSong.song, currentSong.voiceList); + vocals = VoicesGroup.build(currentSong.song, currentSong.voiceList); else - vocals = new VoicesGroup(currentSong.song, null); + vocals = VoicesGroup.build(currentSong.song, null); vocals.members[0].onComplete = function() { diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index b8ef2d7d6..1b24261f4 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -45,6 +45,11 @@ class Song // implements IPlayStateScriptedClass populateFromMetadata(); } + public function getRawMetadata():Array + { + return _metadata; + } + /** * Populate the song data from the provided metadata, * including data from individual difficulties. Does not load chart data. @@ -246,7 +251,7 @@ class SongDifficulty public function buildVocals(charId:String = "bf"):VoicesGroup { - var result:VoicesGroup = new VoicesGroup(this.song.songId, this.buildVoiceList()); + var result:VoicesGroup = VoicesGroup.build(this.song.songId, this.buildVoiceList()); return result; } } diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index e5679cab9..05c389b9e 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -96,12 +96,12 @@ class SongDataParser if (songCache.exists(songId)) { var song:Song = songCache.get(songId); - trace('[STAGEDATA] Successfully fetch song: ${songId}'); + trace('[SONGDATA] Successfully fetch song: ${songId}'); return song; } else { - trace('[STAGEDATA] Failed to fetch song, not found in cache: ${songId}'); + trace('[SONGDATA] Failed to fetch song, not found in cache: ${songId}'); return null; } } diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 3ff247e2b..7440b47aa 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -110,6 +110,25 @@ class ChartEditorDialogHandler } // TODO: Get the list of songs and insert them as links into the "Create From Song" section. + var linkTemplateDadBattle:Link = dialog.findComponent('splashTemplateDadBattle', Link); + linkTemplateDadBattle.onClick = (_event) -> + { + dialog.hideDialog(DialogButton.CANCEL); + + // Load song from template + state.loadSongAsTemplate('dadbattle'); + } + var linkTemplateBopeebo:Link = dialog.findComponent('splashTemplateBopeebo', Link); + linkTemplateBopeebo.onClick = (_event) -> + { + dialog.hideDialog(DialogButton.CANCEL); + + // Load song from template + state.loadSongAsTemplate('bopeebo'); + } + + var splashTemplateContainer:VBox = dialog.findComponent('splashTemplateContainer', VBox); + return dialog; } @@ -237,7 +256,7 @@ class ChartEditorDialogHandler var dialogBPM:NumberStepper = dialog.findComponent('dialogBPM', NumberStepper); dialogBPM.onChange = (event:UIEvent) -> { - if (event.value == null) + if (event.value == null || event.value <= 0) return; var timeChanges = state.currentSongMetadata.timeChanges; @@ -249,6 +268,9 @@ class ChartEditorDialogHandler { timeChanges[0].bpm = event.value; } + + Conductor.forceBPM(event.value); + state.currentSongMetadata.timeChanges = timeChanges; }; @@ -388,9 +410,9 @@ class ChartEditorDialogHandler { if (selectedFile != null) { - trace('Selected file: ' + selectedFile); - vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n$selectedFile'; - // state.loadVocalsFromBytes(selectedFile.bytes); + trace('Selected file: ' + selectedFile.name + "~" + selectedFile.fullPath); + vocalsEntryLabel.text = 'Vocals for $charName (click to browse)\n${selectedFile.name}'; + state.loadVocalsFromBytes(selectedFile.bytes); removeDropHandler(onDropFile); } }); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index dd6f2d8fd..d349d9cdb 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1,5 +1,7 @@ package funkin.ui.debug.charting; +import funkin.play.song.SongData.SongDataParser; +import funkin.play.song.Song; import lime.media.AudioBuffer; import funkin.input.Cursor; import flixel.FlxSprite; @@ -433,7 +435,7 @@ class ChartEditorState extends HaxeUIState * The audio track for the vocals. * TODO: Replace with a VocalSoundGroup. */ - var audioVocalTrack:FlxSound; + var audioVocalTrackGroup:VoicesGroup; /** * CHART DATA @@ -758,7 +760,9 @@ class ChartEditorState extends HaxeUIState // TODO: We should be loading the music later when the user requests it. // loadDefaultMusic(); - ChartEditorDialogHandler.openWelcomeDialog(this, false); + // TODO: Change to false. + var canCloseInitialDialog = true; + ChartEditorDialogHandler.openWelcomeDialog(this, canCloseInitialDialog); } function buildDefaultSongData() @@ -771,6 +775,8 @@ class ChartEditorState extends HaxeUIState // Initialize the song chart data. songChartData = new Map(); + + audioVocalTrackGroup = new VoicesGroup(); } /** @@ -933,7 +939,7 @@ class ChartEditorState extends HaxeUIState playbarHeadDragging = true; // If we were dragging the playhead while the song was playing, resume playing. - if (audioVocalTrack.playing) + if (audioInstTrack != null && audioInstTrack.playing) { playbarHeadDraggingWasPlaying = true; stopAudioPlayback(); @@ -1057,16 +1063,35 @@ class ChartEditorState extends HaxeUIState }); setUISelected('menubarItemMetronomeEnabled', shouldPlayMetronome); + var instVolumeLabel:Label = findComponent('menubarLabelVolumeInstrumental', Label); addUIChangeListener('menubarItemVolumeInstrumental', (event:UIEvent) -> { var volume:Float = event.value / 100.0; - audioInstTrack.volume = volume; + if (audioInstTrack != null) + audioInstTrack.volume = volume; + instVolumeLabel.text = 'Instrumental - ${event.value}%'; }); + var vocalsVolumeLabel:Label = findComponent('menubarLabelVolumeVocals', Label); addUIChangeListener('menubarItemVolumeVocals', (event:UIEvent) -> { var volume:Float = event.value / 100.0; - audioVocalTrack.volume = volume; + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.volume = volume; + vocalsVolumeLabel.text = 'Vocals - ${event.value}%'; + }); + + var playbackSpeedLabel:Label = findComponent('menubarLabelPlaybackSpeed', Label); + addUIChangeListener('menubarItemPlaybackSpeed', (event:UIEvent) -> + { + var pitch = event.value * 2.0 / 100.0; + #if FLX_PITCH + if (audioInstTrack != null) + audioInstTrack.pitch = pitch; + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.pitch = pitch; + #end + playbackSpeedLabel.text = 'Playback Speed - ${pitch}x'; }); addUIChangeListener('menubarItemToggleToolboxTools', (event:UIEvent) -> @@ -2242,8 +2267,8 @@ class ChartEditorState extends HaxeUIState var oldStepTime = Conductor.currentStepTime; Conductor.update(audioInstTrack.time); // Resync vocals. - if (Math.abs(audioInstTrack.time - audioVocalTrack.time) > 100) - audioVocalTrack.time = audioInstTrack.time; + if (Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) + audioVocalTrackGroup.time = audioInstTrack.time; var diffStepTime = Conductor.currentStepTime - oldStepTime; // Move the playhead. @@ -2257,8 +2282,8 @@ class ChartEditorState extends HaxeUIState Conductor.update(audioInstTrack.time); // Resync vocals. - if (audioVocalTrack != null && Math.abs(audioInstTrack.time - audioVocalTrack.time) > 100) - audioVocalTrack.time = audioInstTrack.time; + if (audioVocalTrackGroup != null && Math.abs(audioInstTrack.time - audioVocalTrackGroup.time) > 100) + audioVocalTrackGroup.time = audioInstTrack.time; // We need time in fractional steps here to allow the song to actually play. // Also account for a potentially offset playhead. @@ -2281,16 +2306,20 @@ class ChartEditorState extends HaxeUIState { if (audioInstTrack != null) audioInstTrack.play(); - if (audioVocalTrack != null) - audioVocalTrack.play(); + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.play(); + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.play(); } function stopAudioPlayback() { if (audioInstTrack != null) audioInstTrack.pause(); - if (audioVocalTrack != null) - audioVocalTrack.pause(); + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.pause(); + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.pause(); } function toggleAudioPlayback() @@ -2418,8 +2447,12 @@ class ChartEditorState extends HaxeUIState */ public function loadInstrumentalFromPath(path:String):Void { + #if sys var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path); loadInstrumentalFromBytes(fileBytes); + #else + trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); + #end } /** @@ -2440,6 +2473,14 @@ class ChartEditorState extends HaxeUIState postLoadInstrumental(); } + public function loadInstrumentalFromAsset(path:String):Void + { + var vocalTrack = FlxG.sound.load(path, 1.0, false); + audioInstTrack = vocalTrack; + + postLoadInstrumental(); + } + function postLoadInstrumental() { // Prevent the time from skipping back to 0 when the song ends. @@ -2447,14 +2488,10 @@ class ChartEditorState extends HaxeUIState { if (audioInstTrack != null) audioInstTrack.pause(); - if (audioVocalTrack != null) - audioVocalTrack.pause(); + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.pause(); }; - var DAD_BATTLE_BPM = 180; - var BOPEEBO_BPM = 100; - Conductor.forceBPM(DAD_BATTLE_BPM); - songLength = Std.int(Conductor.getTimeInSteps(audioInstTrack.length) * GRID_SIZE); gridTiledSprite.height = songLength; @@ -2470,20 +2507,79 @@ class ChartEditorState extends HaxeUIState } /** - * Load a music track for playback. + * Loads a vocal track from an absolute file path. */ - function loadDefaultMusic() + public function loadVocalsFromPath(path:String):Void { - // TODO: How to load music by selecting with a file dialog? - audioInstTrack = FlxG.sound.play(Paths.inst('dadbattle'), 1.0, false); - audioInstTrack.autoDestroy = false; - audioInstTrack.pause(); + #if sys + var fileBytes:haxe.io.Bytes = sys.io.File.getBytes(path); + loadVocalsFromBytes(fileBytes); + #else + trace("[WARN] This platform can't load audio from a file path, you'll need to fetch the bytes some other way."); + #end + } - audioVocalTrack = FlxG.sound.play(Paths.voices('dadbattle'), 1.0, false); - audioVocalTrack.autoDestroy = false; - audioVocalTrack.pause(); + public function loadVocalsFromAsset(path:String):Void + { + var vocalTrack = FlxG.sound.load(path, 1.0, false); + audioVocalTrackGroup.add(vocalTrack); + } - postLoadInstrumental(); + /** + * Loads a vocal track from audio byte data. + */ + public function loadVocalsFromBytes(bytes:haxe.io.Bytes):Void + { + var openflSound = new openfl.media.Sound(); + openflSound.loadCompressedDataFromByteArray(openfl.utils.ByteArray.fromBytes(bytes), bytes.length); + var vocalTrack = FlxG.sound.load(openflSound, 1.0, false); + audioVocalTrackGroup.add(vocalTrack); + + // Tell the user the load was successful. + // TODO: Un-bork this. + // showNotification('Loaded instrumental track successfully.'); + } + + /** + * Fetch's a song's existing chart and audio and loads it, replacing the current song. + */ + public function loadSongAsTemplate(songId:String) + { + var song:Song = SongDataParser.fetchSong(songId); + + if (song == null) + { + // showNotification('Failed to load song template.'); + return; + } + + // Load the song metadata. + var rawSongMetadata:Array = song.getRawMetadata(); + + this.songMetadata = new Map(); + + for (metadata in rawSongMetadata) + { + var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation; + this.songMetadata.set(variation, metadata); + } + + this.songChartData = new Map(); + + for (metadata in rawSongMetadata) + { + var variation = (metadata.variation == null || metadata.variation == '') ? 'default' : metadata.variation; + this.songChartData.set(variation, SongDataParser.parseSongChartData(songId, metadata.variation)); + } + + Conductor.mapTimeChanges(currentSongMetadata.timeChanges); + + loadInstrumentalFromAsset(Paths.inst(songId)); + loadVocalsFromAsset(Paths.voices(songId)); + + // Apply BPM + + // showNotification('Loaded song ${songId}.'); } /** @@ -2498,8 +2594,8 @@ class ChartEditorState extends HaxeUIState // Update the songPosition in the audio tracks. if (audioInstTrack != null) audioInstTrack.time = scrollPositionInMs + playheadPositionInMs; - if (audioVocalTrack != null) - audioVocalTrack.time = scrollPositionInMs + playheadPositionInMs; + if (audioVocalTrackGroup != null) + audioVocalTrackGroup.time = scrollPositionInMs + playheadPositionInMs; // We need to update the note sprites because we changed the scroll position. noteDisplayDirty = true;