DirectMusic — A re-implementation.

This project aims to re-implement Microsoft's long-deprecated DirectMusic API available in early Direct3D and DirectX versions. It is currently under heavy development at this time and might be unstable for some use-cases.

Soundtracks tested and verified to work (somewhat) correctly:

Gothic

Gothic II (+ Night of the Raven)

If you tested other games, please let me know. If you own any games with .dls , .sty and .sgt files in the data folders, and you want to contribute, please contact me as well or open an issue.

Important A C# wrapper package is also available at GothicKit/dmusic-cs.

Building

To build this project, you will need a C11-capable C compiler (like gcc or clang ) and CMake 3.10 or newer. Linux, macOS and Windows are supported, but I can only assist with compilation error in a limited fashion on platforms other than Linux.

On Linux, you can build the project like this (macOS and Windows might work differently):

cmake -B build -DDM_ENABLE_ASAN=OFF -DDM_BUILD_EXAMPLES=ON cmake --build build

(set -DDM_BUILD_EXAMPLES=OFF , if the example fails to compile)

You will find the library and executable files in the build and build/examples directories. Note that the example only works on systems providing the <unistd.h> and <sys/stat.h> headers.

Example

Here's how you play back a segment. This example works on POSIX only since it uses <sys/stat.h> for the file resolver. On Windows, you simply need to replace dm_resolve_file with a Windows-compatible implementation.

#include <dmusic.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> static void * dm_resolve_file ( void * ctx , char const * name , size_t * len ); int main ( int argc , char * * argv ) { Dm_setLoggerDefault ( DmLogLevel_INFO ); // 1. Create a new DmLoader. The loader is responsible for loading and caching DirectMusic files using a // user-defined callback function called a "resolver". You really only ever need one for your application. DmLoader * loader = NULL ; DmResult rv = DmLoader_create ( & loader , DmLoader_DEFAULT | DmLoader_DOWNLOAD ); if ( rv != DmResult_SUCCESS ) { puts ( "Creating the loader failed

" ); return rv ; } // 2. Register a resolver with the loader. A resolver is simply a function which gets a context pointer, // a filename and returns a memory buffer and its length as an output parameter. The context pointer // is user-defined, here it's just a path string. You can return NULL from a resolver to indicate that // the file was not found. rv = DmLoader_addResolver ( loader , dm_resolve_file , "/path/to/your/music/folder" ); if ( rv != DmResult_SUCCESS ) { puts ( "Adding the resolver failed

" ); return rv ; } // 3. Use the loader to obtain a segment. The loader will call your resolvers in order to read in the // file, and it will then perform some internal magic to load the segment. Since we set the // DmLoader_DOWNLOAD option when constructing the loader, we don't need to call DmSegment_download // afterward. Otherwise, you do have to call it. DmSegment * segment = NULL ; rv = DmLoader_getSegment ( loader , "YourSegment.sgt" , & segment ); if ( rv != DmResult_SUCCESS ) { puts ( "Getting the segment failed

" ); return rv ; } // 4. Create a new performance. The performance represents your main playback device. It handles all // the DirectMusic magic needed to produce music from your segments. You typically only need one // performance for your application. The second parameter here is the sample rate, defaulted to // 44100 Hz. DmPerformance * performance = NULL ; rv = DmPerformance_create ( & performance , 44100 ); if ( rv != DmResult_SUCCESS ) { puts ( "Creating the performance failed

" ); return rv ; } // 5. Instruct the performance to play a segment. This will set up the performance's internals so that // the following call to DmPerformance_renderPcm will start producing music. The performance renders // music on-demand, so as long as you don't call DmPerformance_renderPcm, you can consider playback to // be paused. To stop playing music, you can pass NULL as the segment parameter. // // The third parameter here is the timing. It tells the performance at which boundary to start playing // the new segment as to not interrupt the flow of music. The options are "instant", which ignores all // that and immediately plays the segment, "grid" which plays the segment at the next possible beat // subdivision, "beat" which plays the segment at the next beat and "measure" which plays it at the next // measure boundary. // // The performance also supports transitions. To play those, use DmPerformance_playTransition and see // its inline documentation for more information. rv = DmPerformance_playSegment ( performance , segment , DmTiming_MEASURE ); if ( rv != DmResult_SUCCESS ) { puts ( "Playing the segment failed

" ); return rv ; } size_t len = 1000000 ; float * pcm = malloc ( sizeof * pcm * len ); // 6. Finally, render some PCM! This will instruct the performance to start processing the underlying // DirectMusic messages and render the resulting PCM to the output buffer. In this case it will // render 1000000 stereo samples which is 500000 samples per channel. // // This will advance the internal clock for as many ticks as required to render the requested number // of samples. No more, no less. rv = DmPerformance_renderPcm ( performance , pcm , len , DmRender_FLOAT | DmRender_STEREO ); if ( rv != DmResult_SUCCESS ) { puts ( "Playing the PCM failed

" ); return rv ; } // 6.1. Write out the PCM data to some place where we can access it later. This could also just be some // audio output device or another library. FILE * fp = fopen ( "output.pcm" , "w" ); if ( fp == NULL ) { puts ( "Opening the output file failed

" ); return -1 ; } ( void ) fwrite ( pcm , sizeof * pcm , len , fp ); ( void ) fclose ( fp ); // 7. Don't forget to clean up after yourself. free ( pcm ); DmSegment_release ( segment ); DmPerformance_release ( performance ); DmLoader_release ( loader ); return 0 ; } static void * dm_resolve_file ( void * ctx , char const * name , size_t * len ) { char const * root = ctx ; // 1. Concat `root` and `name` to produce the final path. If a slash is missing // at the end of `root`, add it. size_t root_len = strlen ( root ); size_t name_len = strlen ( name ); int miss_sep = root [ root_len - 1 ] != '/' ; char * path = malloc ( root_len + name_len + 1 + miss_sep ); memcpy ( path , root , root_len ); memcpy ( path + root_len + miss_sep , name , name_len ); if ( miss_sep ) { path [ root_len ] = '/' ; } path [ root_len + name_len + miss_sep ] = '\0' ; // 2. Check if the file we want to open actually exists. If it doesn't, return NULL. struct stat st ; if ( stat ( path , & st ) != 0 ) { free ( path ); return NULL ; } // 3. Read in data from the file. FILE * fp = fopen ( path , "re" ); if ( fp == NULL ) { free ( path ); return NULL ; } void * bytes = malloc (( size_t ) st . st_size ); * len = fread ( bytes , 1 , ( size_t ) st . st_size , fp ); ( void ) fclose ( fp ); free ( path ); return bytes ; }

More examples can be found in the examples/ folder.

Contact