Browse Source

audio (#3)

Add TODO

Clarify code

Fix indentation

generate_tone returns SEXP_TRUE

Remove set_ptr_to_null

Syntax fixes in tone.c

General syntax fixes

Rename 'aMusic' to 'music'

Add TODO for sampling rate

Merge branch 'master' into audio

Uniform syntax

tone.c for functions, tone.h for defs

Change load-wav to play-wav-file

Merge branch 'audio' of https://source.mntmn.com/MNT/interscheme into HEAD

add runtime error reporting

Add mixer library to build.sh

Better output

Play around with tone-generation

Add audio code

Co-authored-by: Muto <shack@muto.ca>
Co-authored-by: Lukas F. Hartmann <lukas@mntre.com>
Reviewed-on: #3
master
Ben Card 5 months ago
committed by Lukas F. Hartmann
parent
commit
40d5eaee86
7 changed files with 184 additions and 10 deletions
  1. BIN
     
  2. +28
    -0
      audio-test.scm
  3. +2
    -1
      build.sh
  4. +6
    -0
      init.scm
  5. +71
    -9
      interscheme.c
  6. +75
    -0
      tone.c
  7. +2
    -0
      tone.h

BIN
View File


+ 28
- 0
audio-test.scm View File

@@ -0,0 +1,28 @@
;;; An example file to demonstrate/test the
;;; audio functionality of Interscheme.

;; Play a wav file when the program starts.
(play-wav-file "Test.wav" 0)

;; Play the wav file AND loop forever:
;;(play-wav-file "Test.wav" -1)

;; We define a single bool that acts as a switch.
;; Once the audio plays once, turn the switch off.
;; This lets the program know we don't want to loop forever.
(define play-sound1? #t)

;; Define a function that plays 3 tones
(define (sound1)
(display "Playing sound1\n")
(generate-tone 440.0 0.4)
(generate-tone 700.0 0.5)
(generate-tone 440.0 0.4)
(set! play-sound1? #f))

(define (main)
;; (display "main\n")
;; (pixel-rect 32 32 128 128 #xffffff)
;;
(if play-sound1?
(sound1)))

+ 2
- 1
build.sh View File

@@ -1,2 +1,3 @@
gcc -g -o interscheme interscheme.c -lchibi-scheme -lSDL2 -lSDL2_gfx
gcc -g -o interscheme interscheme.c tone.c \
-lchibi-scheme -lSDL2 -lSDL2_mixer -lSDL2_gfx


+ 6
- 0
init.scm View File

@@ -32,6 +32,12 @@
(define paint-color #xffffff)
(define random-color-mode #t)

;; Play a wav file when the program starts.
(play-wav-file "Test.wav" 0)

;; Play the wav file AND loop forever:
;; (play-wav-file "Test.wav" -1)

(define (main)
;;; (display "(main)\n")
(pixel-rect 32 32 128 128 #xffffff)


+ 71
- 9
interscheme.c View File

@@ -4,6 +4,12 @@
#include <SDL2/SDL.h> // SDL2 standard functions
#include <SDL2/SDL2_gfxPrimitives.h> // SDL2 shapes and other handy abstractions.

// Include SDL's audio mixing components
#include <SDL2/SDL_mixer.h>

// Include a .h file containing the math for generating a frequency.
#include "tone.h"

#define SCREEN_W 1920/2
#define SCREEN_H 1080/2

@@ -16,6 +22,13 @@ SDL_Texture* sdlTexture;
uint32_t* pixels;
int running = 1;

// music, a pointer for all our SDL audio objects. My original idea
// was to set this pointer to NULL every time we need to load an audio
// file, but it may be cleaner to have a (make-audio name1 "file.wav")
// function from Scheme, which is callable with (play-audio name1) and
// (ause-audio name1).
Mix_Music *music = NULL;

typedef struct scheme_input {
uint32_t mouse_buttons;
int32_t mouse_x;
@@ -118,6 +131,51 @@ sexp get_key(sexp ctx, sexp self, sexp n) {
return sexp_make_integer(ctx, scheme_input.keycode);
}

////////////////////// BEGIN MAIN AUDIO CODE

// generate_tone, allows us to make a beep sound from Scheme, with:
// (generate-tone 440.0 0.5) ;;440Hz is the key of A, 0.5 seconds.
// Requires much more testing.
static sexp generate_tone(sexp ctx, sexp self, sexp n,
sexp sx_tone, sexp sx_duration) {
float tone = (float)sexp_flonum_value(sx_tone);
float duration = (float)sexp_flonum_value(sx_duration);
sdl_generate_tone(tone, duration);
return SEXP_TRUE;
}

// Load a WAV file. 'filename' is the wav file,
// loopp is a number specifying whether the audio
// should loop forever.
int sdl_play_wav_file(char* filename, int loopp) {
//INIT AUDIO
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
printf("Can't start SDL_mixer! %s\n", Mix_GetError());
return 1;
}
music = Mix_LoadMUS(filename);
Mix_PlayMusic(music, loopp);
if (music == NULL) {
printf("Can't start music! %s\n", Mix_GetError());
return 1;
}

return 0;
}

// The S-Expression version of sdl_play_wav_file,
// that we provide to Scheme.
sexp play_wav_file(sexp ctx, sexp self, sexp n,
sexp filename, sexp sx_loopp) {
uint32_t loopp = sexp_unbox_fixnum(sx_loopp);
char* filen = sexp_string_data(filename);

sdl_play_wav_file(filen, loopp);
return n;
}
////////////////////// END OF AUDIO SECTION


// 'provide', a shortcut for sexp_define_foreign.
void provide(sexp ctx, sexp env, char* name, int num_args, int return_type, void* func) {
sexp op = sexp_define_foreign(ctx, env, name, num_args, func);
@@ -144,21 +202,24 @@ void scheme_define_ops(sexp ctx, sexp env) {
provide(ctx, env, "mouse-y", 0, 1, (sexp_proc1)get_mouse_y);
provide(ctx, env, "mouse-buttons", 0, 1, (sexp_proc1)get_mouse_buttons);
provide(ctx, env, "keyboard", 0, 1, (sexp_proc1)get_key);
provide(ctx, env, "play-wav-file", 2, 0, (sexp_proc1)play_wav_file);
provide(ctx, env, "generate-tone", 2, 0, (sexp_proc1)generate_tone);
}

void scheme_loop(sexp ctx) {
// TODO: error handling
// TODO: record elapsed time and input events and pass to main function for animation stuff?
//sdl_pixel_put(20,20,0xffffff);

SDL_UpdateTexture(sdlTexture, NULL, pixels, SCREEN_W * sizeof(uint32_t));
SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);
// filledEllipseRGBA(sdlRenderer, 400, 400, 50, 80, 255, 50, 80, 255);
sexp_eval_string(ctx, "(main)", -1, NULL);
// evaluate the (main) function and print any errors
sexp res;
res = sexp_eval_string(ctx, "(main)", -1, NULL);
if (sexp_exceptionp(res)) {
sexp_print_exception(ctx, res, sexp_current_error_port(ctx));
}
SDL_RenderPresent(sdlRenderer);

// Simple framerate limiter, so Interscheme doesn't eat CPU power during tests.
// FIXME: this makes the actual processing too slow for me.
// FIXME: this makes the actual processing too slow for me (mntmn).
// SDL_Delay(1000 / FPS);
}

@@ -166,7 +227,8 @@ int main(int argc, char** argv) {
sexp ctx;
SDL_Event event;

if (SDL_Init(SDL_INIT_VIDEO) < 0) {
// Initialize the SDL video/audio components.
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
printf("Couldn't initialize SDL! %s\n", SDL_GetError());
return 1;
}
@@ -226,7 +288,7 @@ int main(int argc, char** argv) {
sexp_gc_var1(obj1);
sexp_gc_preserve1(ctx, obj1);
if (argc == 2) { // If the user specified a file
obj1 = sexp_c_string(ctx, argv[1], -1);
obj1 = sexp_c_string(ctx, argv[1], -1);
} else if (argc > 2) {
printf("Too many arguments!\n");
} else { // If 0 arguments, use the default file.
@@ -285,7 +347,7 @@ int main(int argc, char** argv) {
}
}

scheme_loop(ctx);
scheme_loop(ctx);
}

sexp_destroy_context(ctx);


+ 75
- 0
tone.c View File

@@ -0,0 +1,75 @@
// Generate a tone. Generating multiple tones in a row
// will not be extremely accurate, so it's better as just
// a 'bell' or 'notification' tone generator (not music)

// Most of the generic math was taken from an anonymous pastebin post:
// https://pastebin.com/Uq9rcsJz

#include <SDL2/SDL.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

float runUntil = 0.0; // Counts to runFor then exits

// Audio sampling rate. 48k on the Reform.
// TODO: Find a way to get SDL to detect
// current system's sampling rate.
float sampleRate = 44100.0;

float phase = 0.0;
float frequency = 523.25; // Generate a C tone.

float nextSampleSquare() {
phase += (1.0 / sampleRate) * frequency;
if (phase > 1.0) phase -= 1.0;
return phase > 0.5 ? 1.0 : -1.0;
}

void SDLAudioCallback(void *udata, Uint8 *stream, int len) {
unsigned int i;
float *str = (float *) stream;
for (i = 0; i < len / sizeof(float) / 2; i++) {
float smp = nextSampleSquare();
str[i * 2] = smp;
str[i * 2 + 1] = smp;

// Uncommenting this line gives the tone a "rising / sweeping"
// effect.
//frequency += frequency * 0.00001;

runUntil += 1.0 / sampleRate;
}
}

int sdl_generate_tone(float freq, float duration) {
// 44.1k is constant and cannot be changed.
// This is because 44.1 is a perfect sinewave and
// calculating anything higher is redundant anyway.
frequency = freq; //Set user-defined frequency.
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
printf("Can't initialize SDL: \n%s\n", SDL_GetError());
return 1;
}
SDL_AudioSpec audioSpec;
audioSpec.freq = (int)sampleRate;
audioSpec.format = AUDIO_F32SYS;
audioSpec.channels = 2;
audioSpec. samples = 8192;
audioSpec.callback = SDLAudioCallback;

if (SDL_OpenAudio(&audioSpec, 0) < 0) {
printf("Can't open SDL audio: \n%s\n", SDL_GetError());
return 1;
}

SDL_PauseAudio(0);

// TODO: This code freezes the program in a busyloop.
// Find a better way to do this.
while (runUntil < duration);
SDL_CloseAudio();
runUntil = 0.0;
return 0;
}

+ 2
- 0
tone.h View File

@@ -0,0 +1,2 @@

int sdl_generate_tone (float freq, float duration);