···
+
(* WAV file format helper module *)
+
(* Simple WAV file generation for a sine wave *)
+
let generate_sine_wave ~frequency ~duration ~sample_rate ~amplitude =
+
let num_channels = 1 in (* Mono *)
+
let bits_per_sample = 16 in
+
let byte_rate = sample_rate * num_channels * bits_per_sample / 8 in
+
let block_align = num_channels * bits_per_sample / 8 in
+
let num_samples = int_of_float (float_of_int sample_rate *. duration) in
+
let data_size = num_samples * block_align in
+
(* Create buffer for the WAV data *)
+
let buffer = Buffer.create (44 + data_size) in
+
Buffer.add_string buffer "RIFF";
+
let file_size = 36 + data_size in
+
Buffer.add_char buffer (char_of_int (file_size land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((file_size lsr 24) land 0xff));
+
Buffer.add_string buffer "WAVE";
+
Buffer.add_string buffer "fmt ";
+
Buffer.add_char buffer (char_of_int 16); (* Sub-chunk size (16 for PCM) *)
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int 1); (* Audio format (1 for PCM) *)
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int num_channels); (* Number of channels *)
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int (sample_rate land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((sample_rate lsr 24) land 0xff));
+
Buffer.add_char buffer (char_of_int (byte_rate land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((byte_rate lsr 24) land 0xff));
+
Buffer.add_char buffer (char_of_int block_align);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_char buffer (char_of_int bits_per_sample);
+
Buffer.add_char buffer (char_of_int 0);
+
Buffer.add_string buffer "data";
+
Buffer.add_char buffer (char_of_int (data_size land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 8) land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 16) land 0xff));
+
Buffer.add_char buffer (char_of_int ((data_size lsr 24) land 0xff));
+
(* Generate sine wave data *)
+
let max_amplitude = float_of_int (1 lsl (bits_per_sample - 1)) -. 1.0 in
+
for i = 0 to num_samples - 1 do
+
let t = float_of_int i /. float_of_int sample_rate in
+
let value = int_of_float (amplitude *. max_amplitude *. sin (2.0 *. Float.pi *. frequency *. t)) in
+
(* Write 16-bit sample (little-endian) *)
+
Buffer.add_char buffer (char_of_int (value land 0xff));
+
Buffer.add_char buffer (char_of_int ((value lsr 8) land 0xff));
+
(* Encode binary data as base64 *)
+
let base64_encode data =
+
let buffer = Buffer.create (4 * (String.length data + 2) / 3) in
+
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" in
+
let encode_block i bytes =
+
let b1 = Char.code (String.get bytes (i * 3)) in
+
let b2 = if i * 3 + 1 < String.length bytes then Char.code (String.get bytes (i * 3 + 1)) else 0 in
+
let b3 = if i * 3 + 2 < String.length bytes then Char.code (String.get bytes (i * 3 + 2)) else 0 in
+
let n = (b1 lsl 16) lor (b2 lsl 8) lor b3 in
+
Buffer.add_char buffer (String.get alphabet ((n lsr 18) land 63));
+
Buffer.add_char buffer (String.get alphabet ((n lsr 12) land 63));
+
if i * 3 + 1 < String.length bytes then
+
Buffer.add_char buffer (String.get alphabet ((n lsr 6) land 63))
+
Buffer.add_char buffer '=';
+
if i * 3 + 2 < String.length bytes then
+
Buffer.add_char buffer (String.get alphabet (n land 63))
+
Buffer.add_char buffer '=';
+
for i = 0 to (String.length data + 2) / 3 - 1 do
(* Helper for extracting string value from JSON *)
let get_string_param json name =
···
("text", "string", "The text to describe with audio");
("frequency", "number", "The frequency in Hz for the tone (optional)");
+
("duration", "number", "The duration in seconds for the tone (optional)");
+
("amplitude", "number", "The amplitude (0.0-1.0) for the tone (optional)");
~schema_required:["text"]
let text = get_string_param args "text" in
+
(* Parse parameters with defaults *)
match List.assoc_opt "frequency" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int f) -> float_of_int f
+
| _ -> 440.0 (* Default to A440 *)
+
match List.assoc_opt "duration" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int d) -> float_of_int d
+
| _ -> 2.0 (* Default to 2 seconds *)
+
match List.assoc_opt "amplitude" (match args with `Assoc l -> l | _ -> []) with
+
| Some (`Int a) -> float_of_int a
+
| _ -> 0.8 (* Default to 80% amplitude *)
+
(* Generate WAV file for the tone *)
+
let sample_rate = 44100 in (* CD quality *)
+
let wav_data = Wav.generate_sine_wave
+
(* Encode WAV data as base64 *)
+
let base64_audio = Wav.base64_encode wav_data in
+
Log.info (Printf.sprintf "Generated %d Hz tone for %.1f seconds (%.1f KB)"
+
(int_of_float frequency) duration
+
(float_of_int (String.length wav_data) /. 1024.0));
(* Create a response with both text and audio content *)
CallToolResult.yojson_of_t CallToolResult.{
+
text = Printf.sprintf "Description: %s (with %.1f Hz tone for %.1f seconds)"
+
text frequency duration;
+
mime_type = "audio/wav";
···
("description", Some "Text description to accompany the audio", true);
("frequency", Some "Frequency in Hz for the audio tone", false);
+
("duration", Some "Duration in seconds for the audio tone", false);
try List.assoc "description" args
with Not_found -> "No description provided"
+
(* Parse frequency with default *)
+
try float_of_string (List.assoc "frequency" args)
+
with _ -> 440.0 (* Default to A440 *)
+
(* Parse duration with default *)
+
try float_of_string (List.assoc "duration" args)
+
with _ -> 3.0 (* Default to 3 seconds *)
+
(* Generate WAV data *)
+
let sample_rate = 44100 in
+
let wav_data = Wav.generate_sine_wave
+
(* Encode WAV data as base64 *)
+
let base64_audio = Wav.base64_encode wav_data in
+
Log.info (Printf.sprintf "Generated %.1f Hz tone for prompt (%.1f seconds, %.1f KB)"
+
(float_of_int (String.length wav_data) /. 1024.0));
···
+
content = make_audio_content base64_audio "audio/wav"
+
content = make_text_content (Printf.sprintf "%s (%.1f Hz tone for %.1f seconds)"
+
description frequency duration)