3333import net .raphimc .noteblocktool .audio .renderer .SongRenderer ;
3434import net .raphimc .noteblocktool .audio .renderer .impl .ProgressSongRenderer ;
3535import net .raphimc .noteblocktool .audio .util .LameException ;
36+ import net .raphimc .noteblocktool .elements .FastScrollPane ;
3637import net .raphimc .noteblocktool .elements .VerticalFileChooser ;
3738import net .raphimc .noteblocktool .util .filefilter .SingleFileFilter ;
3839
5859
5960public class ExportFrame extends JFrame {
6061
61- private static final int MAX_SOUNDS = 16384 ;
62-
6362 private final ListFrame parent ;
6463 private final List <ListFrame .LoadedSong > loadedSongs ;
64+ private final JLabel formatLabel = new JLabel ("Format:" );
6565 private final JComboBox <OutputFormat > format = new JComboBox <>(OutputFormat .values ());
66- private final JCheckBox globalNormalization = new JCheckBox ( "Global Normalization" );
67- private final JCheckBox threaded = new JCheckBox ( "Multithreaded Rendering" );
68- private final JLabel sampleRateLabel = new JLabel ( "Sample Rate:" );
66+
67+ // Audio File settings
68+ private final JPanel audioFilePanel = new JPanel ( new GridBagLayout () );
6969 private final JSpinner sampleRate = new JSpinner (new SpinnerNumberModel (48000 , 8000 , 192000 , 8000 ));
70+ private final JComboBox <Channels > channels = new JComboBox <>(Channels .values ());
7071 private final JLabel wavBitDepthLabel = new JLabel ("WAV Bit Depth:" );
7172 private final JComboBox <WavBitDepth > wavBitDepth = new JComboBox <>(WavBitDepth .values ());
7273 private final JLabel mp3QualityLabel = new JLabel ("MP3 Quality:" );
7374 private final JSlider mp3Quality = new JSlider (0 , 100 , 60 );
74- private final JLabel channelsLabel = new JLabel ( "Channels:" );
75- private final JComboBox < Channels > channels = new JComboBox <>( Channels . values ());
76- private final JLabel volumeLabel = new JLabel ( "Volume:" );
75+
76+ // Playback settings
77+ private final JPanel playbackPanel = new JPanel ( new GridBagLayout () );
7778 private final JSlider volume = new JSlider (0 , 100 , 50 );
7879 private final JCheckBox timingJitter = new JCheckBox ("Artificial Timing Jitter" );
79- private JPanel progressPanel ;
80+
81+ // Renderer settings
82+ private final JPanel rendererPanel = new JPanel (new GridBagLayout ());
83+ private final JSpinner maxSounds = new JSpinner (new SpinnerNumberModel (16384 , 64 , 131070 , 64 ));
84+ private final JCheckBox globalNormalization = new JCheckBox ("Global Normalization" );
85+ private final JCheckBox threaded = new JCheckBox ("Multithreaded Rendering" );
86+
87+ private final JPanel progressPanel = new JPanel ();
8088 private final JProgressBar progressBar = new JProgressBar ();
81- private final JButton exportButton = new JButton ("Export" );
89+ private final JButton export = new JButton ("Export" );
8290 private Thread exportThread ;
8391
8492 public ExportFrame (final ListFrame parent , final List <ListFrame .LoadedSong > loadedSongs ) {
@@ -92,7 +100,7 @@ public ExportFrame(final ListFrame parent, final List<ListFrame.LoadedSong> load
92100 this .setLocationRelativeTo (null );
93101
94102 this .initComponents ();
95- this .updateVisibility ();
103+ this .updateVisibility (true );
96104 this .initFrameHandler ();
97105
98106 this .setMinimumSize (this .getSize ());
@@ -101,92 +109,109 @@ public ExportFrame(final ListFrame parent, final List<ListFrame.LoadedSong> load
101109
102110 private void initComponents () {
103111 JPanel root = new JPanel ();
104- root .setLayout (new GridBagLayout ());
112+ root .setLayout (new BorderLayout ());
105113 this .setContentPane (root );
106- int gridy = 0 ;
107-
108- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Format:" ));
109- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .format , () -> {
110- this .format .addActionListener (e -> this .updateVisibility ());
111- });
112-
113- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .globalNormalization );
114- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .threaded );
115-
116- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .sampleRateLabel );
117- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .sampleRate );
118114
119- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .wavBitDepthLabel );
120- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .wavBitDepth , () -> {
121- this .wavBitDepth .setSelectedItem (WavBitDepth .PCM16 );
122- });
115+ { // North panel
116+ final JPanel northPanel = new JPanel (new GridBagLayout ());
117+ root .add (northPanel , BorderLayout .NORTH );
118+ GBC .create (northPanel ).nextRow ().insets (5 , 5 , 5 , 5 ).anchor (GBC .LINE_START ).add (this .formatLabel );
119+ GBC .create (northPanel ).nextColumn ().insets (5 , 5 , 5 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .format , format -> {
120+ format .addActionListener (e -> this .updateVisibility (true ));
121+ });
122+ }
123123
124- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .mp3QualityLabel );
125- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .mp3Quality , () -> {
126- this .mp3Quality .setMajorTickSpacing (10 );
127- this .mp3Quality .setMinorTickSpacing (5 );
128- this .mp3Quality .setPaintTicks (true );
129- this .mp3Quality .setPaintLabels (true );
130- });
124+ { // Center panel
125+ final JScrollPane centerScrollPane = new FastScrollPane ();
126+ final JPanel centerPanel = new ScrollPaneSizedPanel (centerScrollPane );
127+ centerScrollPane .setViewportView (centerPanel );
128+ centerPanel .setLayout (new GridBagLayout ());
129+ root .add (centerScrollPane , BorderLayout .CENTER );
130+ GBC .create (centerPanel ).nextRow ().insets (0 , 5 , 0 , 5 ).width (2 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .audioFilePanel , audioFilePanel -> {
131+ audioFilePanel .setBorder (BorderFactory .createTitledBorder ("Audio File" ));
132+ GBC .create (audioFilePanel ).nextRow ().insets (0 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Sample Rate:" ));
133+ GBC .create (audioFilePanel ).nextColumn ().insets (0 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .sampleRate );
134+ GBC .create (audioFilePanel ).nextRow ().insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Channels:" ));
135+ GBC .create (audioFilePanel ).nextColumn ().insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .channels , channels -> {
136+ channels .setSelectedItem (Channels .STEREO );
137+ });
138+ GBC .create (audioFilePanel ).nextRow ().insets (5 , 5 , 5 , 5 ).anchor (GBC .LINE_START ).add (this .wavBitDepthLabel );
139+ GBC .create (audioFilePanel ).nextColumn ().insets (5 , 0 , 5 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .wavBitDepth , wavBitDepth -> {
140+ wavBitDepth .setSelectedItem (WavBitDepth .PCM16 );
141+ });
142+ GBC .create (audioFilePanel ).nextRow ().insets (5 , 5 , 5 , 5 ).anchor (GBC .LINE_START ).add (this .mp3QualityLabel );
143+ GBC .create (audioFilePanel ).nextColumn ().insets (5 , 0 , 5 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .mp3Quality , mp3Quality -> {
144+ mp3Quality .setMajorTickSpacing (10 );
145+ mp3Quality .setMinorTickSpacing (5 );
146+ mp3Quality .setPaintTicks (true );
147+ mp3Quality .setPaintLabels (true );
148+ });
149+ });
131150
132- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .channelsLabel );
133- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .channels , () -> {
134- this .channels .setSelectedItem (Channels .STEREO );
135- });
151+ GBC .create (centerPanel ).nextRow ().insets (5 , 5 , 0 , 5 ).width (2 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .playbackPanel , playbackPanel -> {
152+ playbackPanel .setBorder (BorderFactory .createTitledBorder ("Playback" ));
153+ GBC .create (playbackPanel ).nextRow ().insets (0 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Volume:" ));
154+ GBC .create (playbackPanel ).nextColumn ().insets (0 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .volume , volume -> {
155+ volume .setMajorTickSpacing (10 );
156+ volume .setMinorTickSpacing (5 );
157+ volume .setPaintLabels (true );
158+ volume .setPaintTicks (true );
159+ });
160+ GBC .create (playbackPanel ).nextRow ().insets (5 , 5 , 5 , 5 ).width (2 ).anchor (GBC .LINE_START ).add (this .timingJitter , timingJitter -> {
161+ timingJitter .setToolTipText ("Adds slight timing jitter (±1ms) to make the song sound more natural and less artificial.\n This emulates the behaviour of playing the song in Note Block Studio." );
162+ });
163+ });
136164
137- GBC .create (root ).grid (0 , gridy ).insets (5 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .volumeLabel );
138- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .volume , () -> {
139- this .volume .setMajorTickSpacing (10 );
140- this .volume .setMinorTickSpacing (5 );
141- this .volume .setPaintTicks (true );
142- this .volume .setPaintLabels (true );
143- });
144- GBC .create (root ).grid (1 , gridy ++).insets (5 , 0 , 0 , 5 ).anchor (GBC .LINE_START ).add (this .timingJitter , () -> {
145- this .timingJitter .setToolTipText ("Adds slight timing jitter (±1ms) to make the song sound more natural and less artificial.\n This emulates the behaviour of playing the song in Note Block Studio." );
146- });
165+ GBC .create (centerPanel ).nextRow ().insets (5 , 5 , 0 , 5 ).width (2 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .rendererPanel , rendererPanel -> {
166+ rendererPanel .setBorder (BorderFactory .createTitledBorder ("Renderer" ));
167+ GBC .create (rendererPanel ).nextRow ().insets (0 , 5 , 0 , 5 ).anchor (GBC .LINE_START ).add (new JLabel ("Max Sounds:" ));
168+ GBC .create (rendererPanel ).nextColumn ().insets (0 , 0 , 0 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .maxSounds );
169+ GBC .create (rendererPanel ).nextRow ().insets (5 , 5 , 0 , 5 ).width (2 ).anchor (GBC .LINE_START ).add (this .globalNormalization );
170+ GBC .create (rendererPanel ).nextRow ().insets (5 , 5 , 5 , 5 ).width (2 ).anchor (GBC .LINE_START ).add (this .threaded );
171+ });
147172
148- GBC .create (root ).grid (0 , gridy ++).insets (5 , 5 , 0 , 5 ).width (1 ).width (2 ).weight (1 , 1 ).fill (GBC .BOTH ).add (() -> {
149- JScrollPane scrollPane = new JScrollPane ();
150- this .progressPanel = new ScrollPaneSizedPanel (scrollPane );
151- this .progressPanel .setLayout (new VerticalLayout (5 , 5 ));
152- scrollPane .setViewportView (this .progressPanel );
153- scrollPane .setBorder (BorderFactory .createEmptyBorder ());
154- return scrollPane ;
155- });
173+ GBC .create (centerPanel ).nextRow ().insets (5 , 5 , 0 , 5 ).width (1 ).width (2 ).weight (1 , 1 ).fill (GBC .BOTH ).add (this .progressPanel , progressPanel -> {
174+ progressPanel .setLayout (new VerticalLayout (5 , 5 ));
175+ });
156176
157- JPanel bottomPanel = new JPanel ();
158- bottomPanel .setLayout (new GridBagLayout ());
159- GBC .create (root ).grid (0 , gridy ++).insets (0 , 0 , 0 , 0 ).weightx (1 ).width (2 ).fill (GBC .HORIZONTAL ).add (bottomPanel );
177+ GBC .fillVerticalSpace (centerPanel );
178+ }
160179
161- GBC .create (bottomPanel ).grid (0 , 0 ).insets (5 , 5 , 5 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .progressBar , () -> {
162- this .progressBar .setStringPainted (true );
163- });
164- GBC .create (bottomPanel ).grid (1 , 0 ).insets (5 , 0 , 5 , 5 ).anchor (GBC .LINE_END ).add (this .exportButton , () -> {
165- this .exportButton .addActionListener (e -> this .export ());
166- });
180+ { // South panel
181+ final JPanel southPanel = new JPanel (new GridBagLayout ());
182+ root .add (southPanel , BorderLayout .SOUTH );
183+ GBC .create (southPanel ).nextRow ().insets (5 , 5 , 5 , 5 ).weightx (1 ).fill (GBC .HORIZONTAL ).add (this .progressBar , progressBar -> {
184+ progressBar .setStringPainted (true );
185+ });
186+ GBC .create (southPanel ).nextColumn ().insets (5 , 0 , 5 , 5 ).anchor (GBC .LINE_END ).add (this .export , exportButton -> {
187+ exportButton .addActionListener (e -> this .export ());
188+ });
189+ }
167190 }
168191
169- private void updateVisibility () {
170- final OutputFormat outputFormat = (OutputFormat ) this .format .getSelectedItem ();
171-
172- this .globalNormalization .setVisible (outputFormat .isAudioFile ());
173- this .threaded .setVisible (outputFormat .isAudioFile ());
174-
175- this .sampleRateLabel .setVisible (outputFormat .isAudioFile ());
176- this .sampleRate .setVisible (outputFormat .isAudioFile ());
192+ private void updateVisibility (final boolean showSettings ) {
193+ if (showSettings ) {
194+ final OutputFormat outputFormat = (OutputFormat ) this .format .getSelectedItem ();
195+ this .formatLabel .setVisible (true );
196+ this .format .setVisible (true );
197+ this .audioFilePanel .setVisible (outputFormat .isAudioFile ());
198+ this .playbackPanel .setVisible (outputFormat .isAudioFile ());
199+ this .rendererPanel .setVisible (outputFormat .isAudioFile ());
200+ this .progressPanel .setVisible (false );
201+
202+ this .wavBitDepthLabel .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .WAV ));
203+ this .wavBitDepth .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .WAV ));
204+ this .mp3QualityLabel .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .MP3 ));
205+ this .mp3Quality .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .MP3 ));
177206
178- this .wavBitDepthLabel .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .WAV ));
179- this .wavBitDepth .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .WAV ));
180-
181- this .mp3QualityLabel .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .MP3 ));
182- this .mp3Quality .setVisible (outputFormat .isAudioFile () && outputFormat .equals (OutputFormat .MP3 ));
183-
184- this .channelsLabel .setVisible (outputFormat .isAudioFile ());
185- this .channels .setVisible (outputFormat .isAudioFile ());
186-
187- this .volumeLabel .setVisible (outputFormat .isAudioFile ());
188- this .volume .setVisible (outputFormat .isAudioFile ());
189- this .timingJitter .setVisible (outputFormat .isAudioFile ());
207+ } else {
208+ this .formatLabel .setVisible (false );
209+ this .format .setVisible (false );
210+ this .audioFilePanel .setVisible (false );
211+ this .playbackPanel .setVisible (false );
212+ this .rendererPanel .setVisible (false );
213+ this .progressPanel .setVisible (true );
214+ }
190215 }
191216
192217 private void initFrameHandler () {
@@ -216,37 +241,21 @@ private void export() {
216241 } catch (InterruptedException ignored ) {
217242 }
218243
219- this .format .setEnabled (true );
220- this .globalNormalization .setEnabled (true );
221- this .threaded .setEnabled (true );
222- this .sampleRate .setEnabled (true );
223- this .wavBitDepth .setEnabled (true );
224- this .mp3Quality .setEnabled (true );
225- this .channels .setEnabled (true );
226- this .volume .setEnabled (true );
227- this .timingJitter .setEnabled (true );
228244 this .progressPanel .removeAll ();
229- this .exportButton .setText ("Export" );
245+ this .export .setText ("Export" );
230246 this .progressBar .setValue (0 );
247+ this .updateVisibility (true );
231248 return ;
232249 }
233250
234251 File out = this .openFileChooser ();
235252 if (out == null ) return ;
236253
237- this .format .setEnabled (false );
238- this .globalNormalization .setEnabled (false );
239- this .threaded .setEnabled (false );
240- this .sampleRate .setEnabled (false );
241- this .wavBitDepth .setEnabled (false );
242- this .mp3Quality .setEnabled (false );
243- this .channels .setEnabled (false );
244- this .volume .setEnabled (false );
245- this .timingJitter .setEnabled (false );
246254 this .progressPanel .removeAll ();
247- this .exportButton .setText ("Cancel" );
255+ this .export .setText ("Cancel" );
248256 this .progressBar .setValue (0 );
249257 this .progressBar .setMaximum (this .loadedSongs .size ());
258+ this .updateVisibility (false );
250259
251260 this .exportThread = new Thread (() -> this .doExport (out ), "Song Export Thread" );
252261 this .exportThread .setDaemon (true );
@@ -404,19 +413,11 @@ private void doExport(final File outFile) {
404413 JOptionPane .showMessageDialog (this , "Failed to export songs:\n " + t .getMessage (), "Error" , JOptionPane .ERROR_MESSAGE );
405414 } finally {
406415 SwingUtilities .invokeLater (() -> {
407- this .format .setEnabled (true );
408- this .globalNormalization .setEnabled (true );
409- this .threaded .setEnabled (true );
410- this .sampleRate .setEnabled (true );
411- this .wavBitDepth .setEnabled (true );
412- this .mp3Quality .setEnabled (true );
413- this .channels .setEnabled (true );
414- this .volume .setEnabled (true );
415- this .timingJitter .setEnabled (true );
416- this .exportButton .setText ("Export" );
416+ this .export .setText ("Export" );
417417 this .progressBar .setValue (this .loadedSongs .size ());
418418 this .progressBar .revalidate ();
419419 this .progressBar .repaint ();
420+ this .updateVisibility (true );
420421 });
421422 }
422423 }
@@ -427,7 +428,7 @@ private void exportSong(final ListFrame.LoadedSong song, final File file, final
427428 this .writeSong (song , file , outputFormat .getSongFormat ());
428429 } else if (outputFormat .isAudioFile ()) {
429430 final PcmFloatAudioFormat renderAudioFormat = new PcmFloatAudioFormat (((Number ) this .sampleRate .getValue ()).floatValue (), ((Channels ) this .channels .getSelectedItem ()).getChannels ());
430- final SongRenderer songRenderer = new ProgressSongRenderer (song .song (), MAX_SOUNDS , !this .globalNormalization .isSelected (), this .threaded .isSelected (), renderAudioFormat , progressConsumer );
431+ final SongRenderer songRenderer = new ProgressSongRenderer (song .song (), ( int ) this . maxSounds . getValue () , !this .globalNormalization .isSelected (), this .threaded .isSelected (), renderAudioFormat , progressConsumer );
431432 songRenderer .setMasterVolume (this .volume .getValue () / 100F );
432433 songRenderer .setTimingJitter (this .timingJitter .isSelected ());
433434 final float [] samples ;
0 commit comments