chaos4ever / chaos

The chaos Operating System
https://chaos4ever.github.io/
15 stars 6 forks source link

[servers/soundblaster] Support 16-bit stereo #148

Open perlun opened 4 years ago

perlun commented 4 years ago

The SB16 code we have currently only supports 8-bit monaural playback, up to 44 kHz. This is quite inferior to 16-bit stereo playback. Even though the .mod files don't always have 16-bit samples, it would still make sense to try and get 16-bit stereo playback working for some potential improvements in the audio quality. Should't be more than just a matter of sending the right commands to the card, I think - the VirtualBox emulates a SB16 card anyway.

perlun commented 4 years ago

Hmm... I played around with this, but whenever I try to use the 0xB6 command, I can get no sound - the audio never starts playing and I probably don't get any interrupts (or perhaps only one).

With the 0xC6 code, for playing 8-bit audio, things are working nicely, which is rather annoying. :slightly_frowning_face: I looked at the VirtualBox SB16 emulation code and it should work with 16-bit autoinitialized transfers as well. For reference: https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Devices/Audio/DevSB16.cpp

One notable difference I can tell is that I need to read from base_port + 0x0F instead of base_port + 0x0E in the IRQ handler to acknowledge the interrupt to the card. But, even after changing this, it still doesn't work.

My next strategy: change our existing 8-bit playing code to use 0xC6 at least, i.e. use those parts of this PR. That way, we are closer to 16-bit audio but not quite there yet. (but we can probably fix 8-bit stereo output very easily then, which is always nice, still a step in the right direction... i.e. 16-bit stereo in the end.)

For reference, here is my diff so far:

diff --git a/servers/sound/soundblaster/soundblaster.c b/servers/sound/soundblaster/soundblaster.c
index bf18d82..56fea1c 100644
--- a/servers/sound/soundblaster/soundblaster.c
+++ b/servers/sound/soundblaster/soundblaster.c
@@ -54,14 +54,14 @@ static tag_type empty_tag =

 static void dsp_write(uint8_t data)
 {
-    while ((system_port_in_uint8_t (DSP_DATA_WRITE) & 0x80) != 0);
-    system_port_out_uint8_t (DSP_DATA_WRITE, (data));
+    while ((system_port_in_uint8_t(DSP_DATA_WRITE) & 0x80) != 0);
+    system_port_out_uint8_t(DSP_DATA_WRITE, (data));
 }

 static uint8_t dsp_read(void)
 {
-    while ((system_port_in_uint8_t (DSP_DATA_AVAILABLE) & 0x80) == 0);
-    return system_port_in_uint8_t (DSP_DATA_READ);
+    while ((system_port_in_uint8_t(DSP_DATA_AVAILABLE_8BIT) & 0x80) == 0);
+    return system_port_in_uint8_t(DSP_DATA_READ);
 }

 static void dsp_mixer_write(uint8_t which_register, uint8_t data)
@@ -77,15 +77,15 @@ static bool detect_sb(void)
     system_call_port_range_register(base_port, 16, "Sound Blaster");

     // Reset the DSP.
-    system_port_out_uint8_t (DSP_RESET, 0x01);
+    system_port_out_uint8_t(DSP_RESET, 0x01);
     system_sleep(4);
-    system_port_out_uint8_t (DSP_RESET, 0x00);
+    system_port_out_uint8_t(DSP_RESET, 0x00);

     // FIXME: Should only wait for a maximum of 100 us.
-    while ((system_port_in_uint8_t (DSP_DATA_AVAILABLE) & (1 << 7)) == 0);
+    while ((system_port_in_uint8_t(DSP_DATA_AVAILABLE_8BIT) & (1 << 7)) == 0);

     // Check if the DSP was reset successfully.
-    if (system_port_in_uint8_t (DSP_DATA_READ) == 0xAA)
+    if (system_port_in_uint8_t(DSP_DATA_READ) == 0xAA)
     {
         // Let's check which kind of SB this is.
         dsp_write(DSP_VERSION);
@@ -177,17 +177,16 @@ int main(void)
         }
     }

-
-    // Because we only support soundblaster 2.0-functionality, treat every card as a sb2.0 whatever
-    // card is installed
+    // TODO: Consider supporting other cards than SB16, or at least log
+    // an error when they are being used.
     soundblaster_device.irq = irq;
     soundblaster_device.base_port = base_port;
     soundblaster_device.dma_channel = dma_channel;
-    soundblaster_device.max_frequency_output = 22050;
+    soundblaster_device.max_frequency_output = 44100;
     soundblaster_device.supports_8bit_output = TRUE;
-    soundblaster_device.supports_16bit_output = FALSE;
+    soundblaster_device.supports_16bit_output = TRUE;
     soundblaster_device.supports_autoinit_dma = TRUE;
-    soundblaster_device.device_name = "Sound Blaster 2.0";
+    soundblaster_device.device_name = "Sound Blaster 16";

     // Register the DMA channel.
     if (system_call_dma_register(soundblaster_device.dma_channel,
@@ -254,7 +253,7 @@ void irq_handler(irq_handler_data_type *irq_handler_data)
         log_print(&log_structure, LOG_URGENCY_DEBUG, "Received hardware interrupt");

         // Acknowledge the SB interrupt.
-        system_port_in_uint8_t(DSP_DATA_AVAILABLE);
+        system_port_in_uint8_t(DSP_DATA_AVAILABLE_16BIT);

         // Send an acknowledgement message to the client program.
         message_parameter.protocol = IPC_PROTOCOL_NONE;
@@ -375,7 +374,7 @@ void handle_connection(mailbox_id_type reply_mailbox_id)
                 // transferred)

                 length = sound_message->length-1;
-                dsp_write(DSP_MODE_DMA_8BIT_DAC);
+                dsp_write(DSP_MODE_DMA_16BIT_DAC); // TODO: fix
                 dsp_write((uint8_t) length);
                 dsp_write((uint8_t)(length >> 8));

@@ -441,13 +440,13 @@ void handle_connection(mailbox_id_type reply_mailbox_id)
                         // Program Sound Blaster buffer length (triggers an interrupt after 'length'
                         // bytes transferred).
                         length = sound_message->length - 1;
-                        dsp_write(DSP_SET_DMA_BLOCK_SIZE);
-                        dsp_write((uint8_t) length);
-                        dsp_write((uint8_t)(length >> 8));

                         // Enable 8-bit auto-initializing DMA-based playing. See sblaster.doc for
-                        // more details (the 01Ch command)
-                        dsp_write(DSP_MODE_DMA_8BIT_AUTOINIT_DAC);
+                        // more details (0Bxh/0Cxh  Generic DAC/ADC DMA)
+                        dsp_write(DSP_MODE_DMA_16BIT_AUTOINIT_DAC);
+                        dsp_write(0); // Bit 5 = stereo, bit 4 = signed. 0 means "mono, unsigned"
+                        dsp_write((uint8_t) length);
+                        dsp_write((uint8_t)(length >> 8));

                         // Now the sample is hopefully being played, so set some variables.
                         soundblaster_event.is_playing = TRUE;
diff --git a/servers/sound/soundblaster/soundblaster.h b/servers/sound/soundblaster/soundblaster.h
index 0ffa067..e5169be 100644
--- a/servers/sound/soundblaster/soundblaster.h
+++ b/servers/sound/soundblaster/soundblaster.h
@@ -15,7 +15,8 @@
 #define DSP_RESET                       (base_port + 0x06)
 #define DSP_DATA_READ                   (base_port + 0x0A)
 #define DSP_DATA_WRITE                  (base_port + 0x0C)
-#define DSP_DATA_AVAILABLE              (base_port + 0x0E)
+#define DSP_DATA_AVAILABLE_8BIT         (base_port + 0x0E)
+#define DSP_DATA_AVAILABLE_16BIT        (base_port + 0x0F)

 // Commands (sent to DSP_DATA_WRITE).
 #define DSP_VERSION                     (0xE1)
@@ -29,6 +30,9 @@
 #define DSP_SET_TIME_CONSTANT           (0x40)
 #define DSP_SET_DMA_BLOCK_SIZE          (0x48)

+#define DSP_MODE_DMA_16BIT_DAC          (0xB0)
+#define DSP_MODE_DMA_16BIT_AUTOINIT_DAC (0xB6) // FIXME: should be 0xB6
+
 typedef struct
 {
     unsigned int irq;