]> git.sesse.net Git - vlc/commitdiff
* Updated aout3 developer doc.
authorChristophe Massiot <massiot@videolan.org>
Sun, 8 Dec 2002 23:38:02 +0000 (23:38 +0000)
committerChristophe Massiot <massiot@videolan.org>
Sun, 8 Dec 2002 23:38:02 +0000 (23:38 +0000)
* Reworked the Open... menu items, and added the ability to use libdvdplay.
* Sound output menu is now under Controls.

doc/developer/audio_output.xml
extras/MacOSX/Resources/English.lproj/MainMenu.nib/classes.nib
extras/MacOSX/Resources/English.lproj/MainMenu.nib/info.nib
extras/MacOSX/Resources/English.lproj/MainMenu.nib/objects.nib
modules/gui/macosx/asystm.m
modules/gui/macosx/intf.h
modules/gui/macosx/intf.m
modules/gui/macosx/open.h
modules/gui/macosx/open.m

index 1ab691617326a7c09c7d32c1075c3aaa0f44a8e7..f4a1bda9e8701f929cd2ca404b6cc4a3ddba0fa1 100644 (file)
@@ -41,23 +41,25 @@ The whole audio output can viewed as a pipeline transforming one audio format to
     <para> The audio_sample_format_t structure is defined in include/audio_output.h. It contains the following members : </para>
 
     <itemizedlist>
-      <listitem> <para> <emphasis> i_format </emphasis> : Define the format of the coefficients. For instance AOUT_FMT_FLOAT32, AOUT_FMT_S16_NE. Undecoded sample formats include AOUT_FMT_A52, AOUT_FMT_DTS, AOUT_FMT_SPDIF. An audio filter allowing to go from one format to another is called, by definition, a "converter". Some converters play the role of a decoder (for instance a52tofloat32.c), but are in fact "audio filters". </para> </listitem>
+      <listitem> <para> <emphasis> i_format </emphasis> : Define the format of the coefficients. This is a FOURCC field. For instance 'fl32' (float32), 'fi32' (fixed32), 's16b' (signed 16-bit big endian), 's16l' (signed 16-bit little endian), AOUT_FMT_S16_NE (shortcut to either 's16b' or 's16l'), 'u16b', 'u16l','s8  ', 'u8  ', 'ac3 ', 'spdi' (S/PDIF). Undecoded sample formats include 'a52 ', 'dts ', 'spdi', 'mpga' (MPEG audio layer I and II), 'mpg3' (MPEG audio layer III). An audio filter allowing to go from one format to another is called, by definition, a "converter". Some converters play the role of a decoder (for instance a52tofloat32.c), but are in fact "audio filters". </para> </listitem>
 
       <listitem> <para> <emphasis> i_rate </emphasis> : Define the number of samples per second the audio output will have to deal with. Common values are 22050, 24000, 44100, 48000. i_rate is in Hz. </para> </listitem>
 
-      <listitem> <para> <emphasis> i_channels </emphasis> : Define the channel configuration, for instance AOUT_CHAN_MONO, AOUT_CHAN_STEREO, AOUT_CHAN_3F1R. Beware : the numeric value doesn't represent the number of coefficients per sample, see aout_FormatNbChannels() for that. The coefficients for each channel are always stored interleaved, because it is much easier for the mixer to deal with interleaved coefficients. Consequently, decoders which output planar data must implement an interleaving function. </para> </listitem>
+      <listitem> <para> <emphasis> i_physical_channels </emphasis> : Define the channels which are physically encoded in the buffer. This field is a bitmask of values defined in audio_output.h, for instance AOUT_CHAN_CENTER, AOUT_CHAN_LEFT, etc. Beware : the numeric value doesn't represent the number of coefficients per sample, see aout_FormatNbChannels() for that. The coefficients for each channel are always stored interleaved, because it is much easier for the mixer to deal with interleaved coefficients. Consequently, decoders which output planar data must implement an interleaving function. Coefficients must be output in the following order (WG-4 specification) : left, right, left surround, right surround, center, LFE.</para> </listitem>
+
+      <listitem> <para> <emphasis> i_original_channels </emphasis> : Define the channels from the original stream which have been used to constitute a buffer. For instance, imagine your output plug-ins only has mono output (AOUT_CHAN_CENTER), and your stream is stereo. You can either use both channels of the stream (i_original_channels == AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT), or select one of them. i_original_channels uses the same bitmask as i_physical_channels, and also features special bits AOUT_CHAN_DOLBYSTEREO, which indicates whether the input stream is downmixed to Dolby surround sound, and AOUT_CHAN_DUALMONO, which indicates that the stereo stream is actually constituted of two mono streams, and only one of them should be selected (for instance, two languages on one VCD).
     </itemizedlist>
 
     <note> <para>
 For 16-bit integer format types, we make a distinction between big-endian and little-endian storage types. However, floats are also stored in either big endian or little endian formats, and we didn't make a difference. The reason is, samples are hardly stored in float32 format in a file, and transferred from one machine to another ; so we assume float32 always use the native endianness.
     </para> <para>
-Yet, samples are quite often stored as big-endian signed 16-bit integers, such as in DVD's LPCM format. So the LPCM decoder allocates an AOUT_FMT_S16_BE input stream, and on little-endian machines, an AOUT_FMT_S16_BE-&gt;AOUT_FMT_S16_NE is automatically invoked by the input pipeline.
+Yet, samples are quite often stored as big-endian signed 16-bit integers, such as in DVD's LPCM format. So the LPCM decoder allocates an 's16b' input stream, and on little-endian machines, an 's16b'-&gt;'s16l' converter is automatically invoked by the input pipeline.
     </para> <para>
 In most cases though, AOUT_FMT_S16_NE and AOUT_FMT_U16_NE should be used.
     </para> </note>
 
     <para>
-The aout core provides macros to compare two audio sample formats. AOUT_FMTS_IDENTICAL() tests if i_format, i_rate and i_channels are identical. AOUT_FMTS_SIMILAR tests if i_rate and i_channels are identical (useful to write a pure converter filter).
+The aout core provides macros to compare two audio sample formats. AOUT_FMTS_IDENTICAL() tests if i_format, i_rate, i_physical_channels and i_original_channels are identical. AOUT_FMTS_SIMILAR tests if i_rate and i_channels are identical (useful to write a pure converter filter).
     </para>
 
     <para>
@@ -83,9 +85,9 @@ The input spawns a new audio decoder, say for instance an A/52 decoder. The A/52
     </para>
 
     <itemizedlist>
-      <listitem> <para> i_format = AOUT_FMT_A52 </para> </listitem>
+      <listitem> <para> i_format = 'a52 ' </para> </listitem>
       <listitem> <para> i_rate = 48000 </para> </listitem>
-      <listitem> <para> i_channels = AOUT_CHAN_3F2R | AOUT_CHAN_LFE </para> </listitem>
+      <listitem> <para> i_physical_channels = i_original_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE </para> </listitem>
       <listitem> <para> i_frame_length = 1536 </para> </listitem>
       <listitem> <para> i_bytes_per_frame = 24000 </para> </listitem>
     </itemizedlist>
@@ -95,13 +97,13 @@ This input format won't be modified, and will be stored in the aout_input_t stru
     </para>
 
     <para>
-The core will probe for an output module in the usual fashion, and its behavior will depend. Either the output device has the S/PDIF capability, and then it will set p_aout-&gt;output.output.i_format to AOUT_FMT_SPDIF, or it's a PCM-only device. It will thus ask for the native sample format, such as AOUT_FMT_FLOAT32 (for Darwin CoreAudio) or AOUT_FMT_S16_NE (for OSS). The output device may also have constraints on the number of channels or the rate. For instance, the p_aout-&gt;output.output structure may look like :
+The core will probe for an output module in the usual fashion, and its behavior will depend. Either the output device has the S/PDIF capability, and then it will set p_aout-&gt;output.output.i_format to 'spdi', or it's a PCM-only device. It will thus ask for the native sample format, such as 'fl32' (for Darwin CoreAudio) or AOUT_FMT_S16_NE (for OSS). The output device may also have constraints on the number of channels or the rate. For instance, the p_aout-&gt;output.output structure may look like :
     </para>
 
     <itemizedlist>
       <listitem> <para> i_format = AOUT_FMT_S16_NE </para> </listitem>
       <listitem> <para> i_rate = 44100 </para> </listitem>
-      <listitem> <para> i_channels = AOUT_CHAN_STEREO </para> </listitem>
+      <listitem> <para> i_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT </para> </listitem>
       <listitem> <para> i_frame_length = 1 </para> </listitem>
       <listitem> <para> i_bytes_per_frame = 4 </para> </listitem>
     </itemizedlist>
@@ -111,15 +113,15 @@ Once we have an output format, we deduce the mixer format. It is strictly forbid
     </para>
 
     <itemizedlist>
-      <listitem> <para> i_format = AOUT_FMT_FLOAT32 </para> </listitem>
+      <listitem> <para> i_format = 'fl32' </para> </listitem>
       <listitem> <para> i_rate = 44100 </para> </listitem>
-      <listitem> <para> i_channels = AOUT_CHAN_STEREO </para> </listitem>
+      <listitem> <para> i_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT </para> </listitem>
       <listitem> <para> i_frame_length = 1 </para> </listitem>
       <listitem> <para> i_bytes_per_frame = 8 </para> </listitem>
     </itemizedlist>
 
     <para>
-The aout core will thus allocate an audio filter to convert AOUT_FMT_FLOAT32 to AOUT_FMT_S16_NE. This is the only audio filter in the output pipeline. It will also allocate a float32 mixer. Since only one input stream is present, the trivial mixer will be used (only copies samples from the first input stream). Otherwise it would have used a more precise float32 mixer.
+The aout core will thus allocate an audio filter to convert 'fl32' to AOUT_FMT_S16_NE. This is the only audio filter in the output pipeline. It will also allocate a float32 mixer. Since only one input stream is present, the trivial mixer will be used (only copies samples from the first input stream). Otherwise it would have used a more precise float32 mixer.
     </para>
 
     <para>
@@ -128,7 +130,7 @@ The last step of the initialization is to build an input pipeline. When several
 
     <orderedlist>
       <listitem> <para> All parameters ; </para> </listitem>
-      <listitem> <para> i_format and i_channels ; </para> </listitem>
+      <listitem> <para> i_format and i_physical_channels/i_original_channels ; </para> </listitem>
       <listitem> <para> i_format ; </para> </listitem>
     </orderedlist>
 
@@ -137,7 +139,7 @@ If the whole transformation cannot be done by only one audio filter, it will all
     </para>
 
     <para>
-When this initialization is over, the "decoder" plug-in can run its main loop. Typically the decoder requests a buffer of length i_nb_samples, and copies the undecoded samples there (using GetChunk()). The buffer then goes along the input pipeline, which will do the decoding (to AOUT_FMT_FLOAT32), and downmixing and resampling. Additional resampling will occur if complex latency issues in the output layer impose us to go temporarily faster or slower to achieve perfect lipsync (this is decided on a per-buffer basis). At the end of the input pipeline, the buffer is placed in a FIFO, and the decoder thread runs the audio mixer.
+When this initialization is over, the "decoder" plug-in can run its main loop. Typically the decoder requests a buffer of length i_nb_samples, and copies the undecoded samples there (using GetChunk()). The buffer then goes along the input pipeline, which will do the decoding (to 'fl32'), and downmixing and resampling. Additional resampling will occur if complex latency issues in the output layer impose us to go temporarily faster or slower to achieve perfect lipsync (this is decided on a per-buffer basis). At the end of the input pipeline, the buffer is placed in a FIFO, and the decoder thread runs the audio mixer.
     </para>
 
     <para>
@@ -161,10 +163,10 @@ Consequently, we have set up a locking mechanism in five parts :
     </para>
 
     <orderedlist>
-      <listitem> <para> <emphasis> p_input-&gt;lock </emphasis> : This lock is taken when a decoder calls aout_BufferPlay(), as long as the buffer is in the input pipeline. The interface thread cannot change the input pipeline without holding this lock. </para> </listitem>
-
       <listitem> <para> <emphasis> p_aout-&gt;mixer_lock </emphasis> : This lock is taken when the audio mixer is entered. The decoder thread in which the mixer runs must hold the mutex during the mixing, until the buffer comes out of the output pipeline. Without holding this mutex, the interface thread cannot change the output pipeline, and a decoder cannot add a new input stream. </para> </listitem>
 
+      <listitem> <para> <emphasis> p_input-&gt;lock </emphasis> : This lock is taken when a decoder calls aout_BufferPlay(), as long as the buffer is in the input pipeline. The interface thread cannot change the input pipeline without holding this lock. </para> </listitem>
+
       <listitem> <para> <emphasis> p_aout-&gt;output_fifo_lock </emphasis> : This lock must be taken to add or remove a packet from the output FIFO, or change its dates. </para> </listitem>
 
       <listitem> <para> <emphasis> p_aout-&gt;input_fifos_lock </emphasis> : This lock must be taken to add or remove a packet from one of the input FIFOs, or change its dates. </para> </listitem>
@@ -340,7 +342,7 @@ Audio filters can be of three types :
       <itemizedlist>
         <listitem> <para> Converters : change i_format (for instance from float32 to s16). </para> </listitem>
         <listitem> <para> Resamplers : change i_rate (for instance from 48 kHz to 44.1 kHz). </para> </listitem>
-        <listitem> <para> Channel mixers : change i_channels (for instance from 5.1 to stereo). </para> </listitem>
+        <listitem> <para> Channel mixers : change i_physical_channels/i_original_channels (for instance from 5.1 to stereo). </para> </listitem>
       </itemizedlist>
 
       <para>
index 75deea3232c57cb614c54dec5bbf96da8a64a4d7..463678d9c0ee2f6dcf0063791f531729a91b4a04 100644 (file)
@@ -48,6 +48,7 @@
                 "o_mi_copy" = id; 
                 "o_mi_cut" = id; 
                 "o_mi_deinterlace" = id; 
+                "o_mi_device" = id; 
                 "o_mi_faster" = id; 
                 "o_mi_fullscreen" = id; 
                 "o_mi_hide" = id; 
@@ -60,6 +61,7 @@
                 "o_mi_next" = id; 
                 "o_mi_open_disc" = id; 
                 "o_mi_open_file" = id; 
+                "o_mi_open_generic" = id; 
                 "o_mi_open_net" = id; 
                 "o_mi_open_recent" = id; 
                 "o_mi_open_recent_cm" = id; 
                 openDiscStepperChanged = id; 
                 openDiscTypeChanged = id; 
                 openFile = id; 
-                openFileBrowse = id; 
-                openFilePathChanged = id; 
+                openFileGeneric = id; 
                 openNet = id; 
                 openNetModeChanged = id; 
                 openNetStepperChanged = id; 
index e6be6c4d0f670d31e5d10c7821b3808f9eb17b42..7700266fb59aab1bc4d5564898fd121acaea2007 100644 (file)
@@ -7,7 +7,7 @@
        <key>IBEditorPositions</key>
        <dict>
                <key>29</key>
-               <string>281 485 308 44 0 0 1152 746 </string>
+               <string>333 594 308 44 0 0 1152 746 </string>
                <key>303</key>
                <string>93 566 72 114 0 0 1600 1178 </string>
        </dict>
index e17a2e02d256090ba463175417fb305d2fdfb02a..bf61ec01526ef8b80b31b7e0ad74eba57d737137 100644 (file)
Binary files a/extras/MacOSX/Resources/English.lproj/MainMenu.nib/objects.nib and b/extras/MacOSX/Resources/English.lproj/MainMenu.nib/objects.nib differ
index 306a2920c0a12321e2ad17d73ee1c6cdad17260d..c8728eb309d8f1edb57e7b3b4ae100166aab8284 100755 (executable)
@@ -145,18 +145,18 @@ OSStatus streamListenerProc (AudioStreamID                inStream,
 
        // Build a menu
        NSMenuItem *newItem;
-       newItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output" action:NULL keyEquivalent:@""];
-       newMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output"];
-       [newItem setSubmenu:newMenu];
-       [[NSApp mainMenu] addItem:newItem];
-       [newItem release];
+       newItem = [main getMIDevice]; //[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output" action:NULL keyEquivalent:@""];
+       newMenu = [newItem submenu]; //[[NSMenu allocWithZone:[NSMenu menuZone]] initWithTitle:@"Sound output"];
+       //[newItem setSubmenu:newMenu];
+       //[[NSApp mainMenu] addItem:newItem];
+       //[newItem release];
 
        // check which devices can do what class of audio
        //    struct mosx_AudioDeviceData deviceData;
        for(i=0; i<devicesAvailable; i++)
            [self CheckDevice:deviceList[i] isInput:false];     // only check the output part
 
-       [newMenu release];
+       //[newMenu release];
        free(deviceList);
     };
     return me;
index 98d093ec049e693b39b2364d2afeb307f0400918..5ba5b5751349c09e76ad934cb0738ba5e03bfe5e 100644 (file)
@@ -2,7 +2,7 @@
  * intf.h: MacOS X interface plugin
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: intf.h,v 1.4 2002/12/07 23:50:30 massiot Exp $
+ * $Id: intf.h,v 1.5 2002/12/08 23:38:02 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
  *          Christophe Massiot <massiot@via.ecp.fr>
@@ -106,6 +106,7 @@ struct intf_sys_t
 
     IBOutlet id o_mu_file;
     IBOutlet id o_mi_open_file;
+    IBOutlet id o_mi_open_generic;
     IBOutlet id o_mi_open_disc;
     IBOutlet id o_mi_open_net;
     IBOutlet id o_mi_open_recent;
@@ -135,6 +136,7 @@ struct intf_sys_t
     IBOutlet id o_mi_vol_down;
     IBOutlet id o_mi_mute;
     IBOutlet id o_mi_channels;
+    IBOutlet id o_mi_device;
     IBOutlet id o_mi_fullscreen;
     IBOutlet id o_mi_screen;
     IBOutlet id o_mi_deinterlace;
@@ -178,6 +180,8 @@ struct intf_sys_t
 
 - (IBAction)viewPreferences:(id)sender;
 
+- (id)getMIDevice;
+
 @end
 
 @interface VLCMain (Internal)
index 23c299d8db21dece824e74549e6ef9fd4ac8fec0..1c0df640c9fee50b162a13c62884583dc40167f3 100644 (file)
@@ -2,7 +2,7 @@
  * intf.m: MacOS X interface plugin
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: intf.m,v 1.8 2002/12/08 05:30:47 jlj Exp $
+ * $Id: intf.m,v 1.9 2002/12/08 23:38:02 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
  *          Christophe Massiot <massiot@via.ecp.fr>
@@ -195,9 +195,10 @@ static void Run( intf_thread_t *p_intf )
     [o_mi_quit setTitle: _NS("Quit vlc")];
 
     [o_mu_file setTitle: _NS("File")];
-    [o_mi_open_file setTitle: _NS("Open File")];
-    [o_mi_open_disc setTitle: _NS("Open Disc")];
-    [o_mi_open_net setTitle: _NS("Open Network")];
+    [o_mi_open_file setTitle: _NS("Open File...")];
+    [o_mi_open_generic setTitle: _NS("Open Generic...")];
+    [o_mi_open_disc setTitle: _NS("Open Disc...")];
+    [o_mi_open_net setTitle: _NS("Open Network...")];
     [o_mi_open_recent setTitle: _NS("Open Recent")];
     [o_mi_open_recent_cm setTitle: _NS("Clear Menu")];
 
@@ -913,6 +914,11 @@ static void Run( intf_thread_t *p_intf )
     [o_prefs createPrefPanel: @"main"];
 }
 
+- (id)getMIDevice
+{
+    return o_mi_device;
+}
+
 @end
 
 @implementation VLCMain (NSMenuValidation)
index 1cec9d1022461bde8cb7161b35e0beb10be27b74..d170ab7622f80f9ceb35a7328d26b4416203cd09 100644 (file)
@@ -2,7 +2,7 @@
  * open.h: MacOS X plugin for vlc
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: open.h,v 1.2 2002/10/05 00:10:17 jlj Exp $
+ * $Id: open.h,v 1.3 2002/12/08 23:38:02 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net> 
  *
@@ -73,6 +73,7 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class );
 - (void)openTarget:(int)i_type;
 - (void)tabView:(NSTabView *)o_tv didSelectTabViewItem:(NSTabViewItem *)o_tvi;
 
+- (IBAction)openFileGeneric:(id)sender;
 - (IBAction)openFile:(id)sender;
 - (void)openFilePathChanged:(NSNotification *)o_notification;
 
index 32c23ccf80e05b7639050221da6cc28222e9e60e..31d4b5038a2ecffbf3d73f966e30460d3b8230db 100644 (file)
@@ -2,7 +2,7 @@
  * open.m: MacOS X plugin for vlc
  *****************************************************************************
  * Copyright (C) 2002 VideoLAN
- * $Id: open.m,v 1.3 2002/10/05 00:10:17 jlj Exp $
+ * $Id: open.m,v 1.4 2002/12/08 23:38:02 massiot Exp $
  *
  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net> 
  *
@@ -237,7 +237,7 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class )
     }  
 }
 
-- (IBAction)openFile:(id)sender
+- (IBAction)openFileGeneric:(id)sender
 {
     [self openFilePathChanged: nil];
     [self openTarget: 0];
@@ -274,13 +274,13 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class )
     
     o_type = [[o_disc_type selectedCell] title];
 
-    if( [o_type isEqualToString: @"DVD"] )
+    if( [o_type isEqualToString: @"VCD"] )
     {
-        psz_class = kIODVDMediaClass;
+        psz_class = kIOCDMediaClass;
     }
     else
     {
-        psz_class = kIOCDMediaClass;
+        psz_class = kIODVDMediaClass;
     }
     
     o_devices = GetEjectableMediaOfClass( psz_class );
@@ -303,7 +303,7 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class )
         else
         {
             [o_disc_device setStringValue: 
-                [NSString stringWithFormat: @"No %@s found", o_type]];
+                [NSString stringWithFormat: @"No %@ found", o_type]];
         }
     }
 
@@ -337,9 +337,22 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class )
     o_device = [o_disc_device stringValue];
     i_title = [o_disc_title intValue];
     i_chapter = [o_disc_chapter intValue];
+    if( [o_type isEqualToString: @"VCD"] )
+    {
+        o_type = [NSString stringWithCString: "vcd"];
+    }
+    else if ( [o_type isEqualToString: @"DVD"] )
+    {
+        o_type = [NSString stringWithCString: "dvdold"];
+    }
+    else
+    {
+        o_type = [NSString stringWithCString: "dvdplay"];
+    }
+
     
     o_mrl_string = [NSString stringWithFormat: @"%@://%@@%i,%i",
-        [o_type lowercaseString], o_device, i_title, i_chapter]; 
+        o_type, o_device, i_title, i_chapter]; 
 
     [o_mrl setStringValue: o_mrl_string]; 
 }
@@ -473,6 +486,19 @@ NSArray *GetEjectableMediaOfClass( const char *psz_class )
     }
 }
 
+- (IBAction)openFile:(id)sender
+{
+    NSOpenPanel *o_open_panel = [NSOpenPanel openPanel];
+
+    [o_open_panel setAllowsMultipleSelection: NO];
+
+    if( [o_open_panel runModalForDirectory: nil
+            file: nil types: nil] == NSOKButton )
+    {
+        [o_playlist appendArray: [o_open_panel filenames] atPos: -1];
+    }
+}
+
 - (IBAction)panelCancel:(id)sender
 {
     [NSApp stopModalWithCode: 0];