<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body bgcolor="#FFFFFF" text="#000000">
I'm very new to Haskell and am trying to write a "real" program to
motivate myself to learn it better (so far I've only gotten through
Project Euler problems after reading LYAH and most of RWH). I'm
using Taglib (<a class="moz-txt-link-freetext" href="https://github.com/taglib/taglib">https://github.com/taglib/taglib</a>) to read the metadata
from a music file and print it. I have a struct C-side (with C
linkage) serving as the bridge between Taglib's C++ and Haskell's
FFI. A small demo program (compiled with gcc and linked against the
C++ object files) gives the correct results, but Haskell is weirdly
only getting <i>some </i>of it right. Specifically, the C string
fields are working but ints are not.<br>
<br>
The output from the C demo (what Haskell should be printing):<br>
<br>
music_metadata<br>
title: It's My Life, artist: Bon Jovi, album: Bon Jovi
Greatest Hits - The Ultimate Collection<br>
comment: , genre: Rock, track: 3,<br>
length: 224, bitrate: 256, channels: 2,<br>
codec: 768<br>
<br>
The output from Haskell:<br>
<br>
MusicMetadata {codec = UNKNOWN, length = 1099511628000, bitrate =
8589934848, channels = 12884901890, track = 8589934848, title =
"It's My Life", artist = "Bon Jovi", album = "Bon Jovi Greatest Hits
- The Ultimate Collection", comment = "", genre = "Rock"}<br>
<br>
I would have expected it to work or not work at all, but did not
anticipate getting only some of it right.<br>
<br>
I was going to include snippets from my hsc file but given how new I
am to Haskell I don't trust myself to assume where the problem is,
so sorry if this is way too long:<br>
<br>
{-# LANGUAGE CPP, ForeignFunctionInterface #-}<br>
<br>
module MusicReader<br>
( Codec,<br>
MusicMetadata,<br>
readMusicMetadata<br>
) where<br>
<br>
import Control.Monad<br>
import Foreign<br>
import Foreign.C.Types<br>
import Foreign.C.String<br>
import System.IO.Unsafe as Unsafe<br>
<br>
#include "CodecDefines.h"<br>
#include "MusicReader.h"<br>
<br>
constantToCodec code<br>
| code == mp3 = MP3<br>
| code == flac = FLAC<br>
| code == ogg = OGG_VORBIS<br>
| code == mp4 = MP4<br>
| code == mpeg = MPEG<br>
| code == none = NONE<br>
| code == unknown = UNKNOWN<br>
| otherwise = UNKNOWN<br>
where mp3 = #const MP3_CODEC<br>
flac = #const FLAC_CODEC<br>
ogg = #const OGG_VORBIS_CODEC <br>
mp4 = #const MP4_CODEC<br>
mpeg = #const MPEG_CODEC<br>
none = #const NO_EXTENSION<br>
unknown = #const UNKNOWN_EXTENSION<br>
<br>
data Codec = MP3 | FLAC | OGG_VORBIS | MP4 | MPEG | NONE | UNKNOWN
deriving (Show)<br>
<br>
data MusicMetadata = MusicMetadata { codec :: Codec,<br>
length :: Int,<br>
bitrate :: Int,<br>
channels :: Int,<br>
track :: Int,<br>
title :: String,<br>
artist :: String,<br>
album :: String,<br>
comment :: String,<br>
genre :: String } deriving (Show)<br>
<br>
instance Storable MusicMetadata where<br>
sizeOf _ = (#size struct music_metadata)<br>
alignment _ = alignment (undefined::CDouble)<br>
peek a = do<br>
codec <- liftM constantToCodec $ (((#peek struct
music_metadata, codec) a) :: IO Int)<br>
length <- ((#peek struct music_metadata, length) a) :: IO
Int<br>
bitrate <- ((#peek struct music_metadata, bitrate) a) ::
IO Int<br>
channels <- ((#peek struct music_metadata, channels) a)
:: IO Int<br>
track <- ((#peek struct music_metadata, bitrate) a) :: IO
Int<br>
title <- ((#peek struct music_metadata, title) a) :: IO
CString<br>
artist <- ((#peek struct music_metadata, artist) a) :: IO
CString<br>
album <- ((#peek struct music_metadata, album) a) :: IO
CString<br>
comment <- ((#peek struct music_metadata, comment) a) ::
IO CString<br>
genre <- ((#peek struct music_metadata, genre) a) :: IO
CString<br>
--FIXME: find replacement for temporary names<br>
marshalledTitle <- peekCString title<br>
marshalledArtist <- peekCString artist<br>
marshalledAlbum <- peekCString album<br>
marshalledComment <- peekCString comment<br>
marshalledGenre <- peekCString genre<br>
return (MusicMetadata codec length bitrate channels track
marshalledTitle marshalledArtist marshalledAlbum marshalledComment
marshalledGenre)<br>
poke a = undefined<br>
<br>
--This is the "primitive" FFI call--calls the C function and gets a
pointer<br>
--in return<br>
--TODO: write a higher level function this module should export that
calls<br>
--primReadMusicMetadata and converts the C Pointer into the Haskell
data<br>
--MusicMetadata<br>
foreign import ccall unsafe "read_metadata" primReadMusicMetadata ::
CString -> IO (Ptr MusicMetadata)<br>
<br>
--convert the Haskell string to a CString, call into the FFI then<br>
--dereference the resulting pointer<br>
readMusicMetadata a = join $ withCString a $ \cs -> ((liftM peek)
$ primReadMusicMetadata cs)<br>
<br>
<br>
<br>
Here's the struct in MusicReader.h (in an extern C block):<br>
struct music_metadata<br>
{<br>
int codec;<br>
int length,<br>
bitrate,<br>
channels;<br>
int track;<br>
char *title,<br>
*artist,<br>
*album,<br>
*comment,<br>
*genre;<br>
};<br>
<br>
<br>
with the corresponding call:<br>
struct music_metadata* read_metadata(char*);<br>
<br>
<br>
I've tried playing around with the alignment but it didn't do
anything. I also tried declaring the struct's fields as int32_t
which also did nothing.<br>
<br>
The C demo in question is a very simple:<br>
<br>
#include <stdio.h><br>
#include "MusicReader.h"<br>
<br>
#define FILENAME "Its_My_Life.m4a"<br>
<br>
int main()<br>
{<br>
struct music_metadata* metadata = read_metadata(FILENAME); <br>
printf("music_metadata\ntitle: %s,\tartist: %s,\talbum: %s\n",<br>
metadata->title, metadata->artist,
metadata->album);<br>
printf("comment: %s,\tgenre: %s,\ttrack: %d,\n", <br>
metadata->comment, metadata->genre,
metadata->track);<br>
printf("length: %d,\tbitrate: %d,\tchannels: %d,\n",<br>
metadata->length, metadata->bitrate,
metadata->channels);<br>
printf("codec: %d\n");<br>
<br>
}<br>
<br>
It just reads the metadata into the struct and prints the fields.<br>
<br>
I've gotten the impression of the Haskell FFI being very
beginner-unfriendly, which isn't surprising but still disappointing
because it would be a great opportunity to replace some projects
with Haskell.<br>
<br>
Any help is appreciated, including general feedback on my code!<br>
</body>
</html>