153 lines
3.2 KiB
C
153 lines
3.2 KiB
C
/***************
|
|
* Generate chords
|
|
* and play them
|
|
***************/
|
|
#include<ctype.h>
|
|
#include<fcntl.h>
|
|
#include<math.h>
|
|
#include<portaudio.h>
|
|
#include<stdbool.h>
|
|
#include<stdint.h>
|
|
#include<stdio.h>
|
|
#include<stdlib.h>
|
|
#include<string.h>
|
|
#include<time.h>
|
|
#include<signal.h>
|
|
#include<unistd.h>
|
|
#include"audio.h"
|
|
|
|
#define USAGE "chord [CHORDs]\n"\
|
|
"\tCHORD\t[a-g][#b]?[0-9][m]?\n"
|
|
|
|
// Chord generator macros
|
|
#define chord(aud,f,...) makechord(aud,nametofreq(f),__VA_ARGS__)
|
|
#define M7(aud,f) chord(aud,f,4,7,11)
|
|
#define f7(aud,f) chord(aud,f,4,7,10)
|
|
#define m7(aud,f) chord(aud,f,3,7,10)
|
|
#define d7(aud,f) chord(aud,f,3,6,10)
|
|
|
|
static double nametofreq(const char*name);
|
|
static void cleanup(int sig);
|
|
|
|
static Audio aud=(Audio){0};
|
|
static PaStream*pa=NULL;
|
|
|
|
/*******
|
|
* Entry point
|
|
*******/
|
|
int main(int argc,char**argv)
|
|
{
|
|
signal(SIGINT,cleanup);
|
|
|
|
aud=aud_new(44100,22050,1);
|
|
if(!aud.data)
|
|
{
|
|
fprintf(stderr,"error: could not initialize Audio struct\n");
|
|
cleanup(0);
|
|
exit(0);
|
|
}
|
|
aud_zero(&aud);
|
|
|
|
Pa_Initialize();
|
|
Pa_OpenDefaultStream(&pa,0,aud.channels,paInt16,aud.samplerate,aud.nsamples,NULL,NULL);
|
|
Pa_StartStream(pa);
|
|
|
|
// Macro to play audio
|
|
#define play() do{Pa_WriteStream(pa,aud.data,aud.nsamples);usleep((((float)aud.nsamples)/aud.samplerate)*1000000);}while(0)
|
|
|
|
// Play pre-designed chord scale
|
|
if(argc<2)
|
|
{
|
|
// Play chord scale
|
|
chord(&aud, "a4",4,7,11,14);aud_env(&aud);play();
|
|
chord(&aud, "g#4",3,6,9,13);aud_env(&aud);play();
|
|
chord(&aud, "f#4",3,7,10,14);aud_env(&aud);play();
|
|
chord(&aud, "e4",4,7,10,14);aud_env(&aud);play();
|
|
chord(&aud, "d4",4,7,11,14);aud_env(&aud);play();
|
|
chord(&aud, "c#4",3,7,10,13);aud_env(&aud);play();
|
|
chord(&aud, "b3",3,7,10,14);aud_env(&aud);play();
|
|
chord(&aud, "a3",4,7,11,14);aud_env(&aud);play();
|
|
//Pa_Sleep((((float)aud.nsamples)/aud.samplerate)*8);
|
|
}
|
|
|
|
// Play command-line supplied chord names
|
|
else
|
|
{
|
|
for(size_t i=1;i<(size_t)argc;++i)
|
|
{
|
|
// Option
|
|
if(argv[i][0]=='-')
|
|
{
|
|
if(strcmp(argv[i],"--help")==0)
|
|
printf("%s",USAGE);
|
|
}
|
|
|
|
// Chord specification
|
|
else
|
|
{
|
|
printf("playing '%s'\n",argv[i]);
|
|
if(argv[i][strlen(argv[i])-1]=='m')
|
|
{
|
|
chord(&aud,argv[i],3,7,10,14);aud_env(&aud);play();
|
|
}
|
|
else
|
|
{
|
|
chord(&aud,argv[i],4,7,11,14);aud_env(&aud);play();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup(0);
|
|
}
|
|
|
|
/*******
|
|
* Convert note name into frequency
|
|
*******/
|
|
static double nametofreq(const char*name)
|
|
{
|
|
char letter='a';
|
|
double a0=27.5;
|
|
double freq=0.0L;
|
|
size_t letter_offsets[]={0,2,3,5,7,8,10};
|
|
size_t octave=0;
|
|
size_t semi=0;
|
|
size_t str_pos=1;
|
|
|
|
if(!name)return freq;
|
|
|
|
if(!name[0])return freq;
|
|
if(!name[1])return freq;
|
|
|
|
letter=name[0];
|
|
if(name[1]=='#')
|
|
++semi,str_pos=2;
|
|
else if(name[1]=='b')
|
|
--semi,str_pos=2;
|
|
|
|
if(!name[str_pos])return freq;
|
|
|
|
octave=atoi(name+str_pos);
|
|
if(letter!='a'&&letter!='b')
|
|
octave=(octave>0)?(octave-1):0;
|
|
semi+=letter_offsets[letter-'a']+octave*12;
|
|
|
|
freq=raisesemitone(a0,semi);
|
|
return freq;
|
|
}
|
|
|
|
/*******
|
|
* Free resources and quit
|
|
*******/
|
|
static void cleanup(int sig)
|
|
{
|
|
if(sig==SIGINT)
|
|
puts("exiting due to SIGINT");
|
|
/* else */
|
|
/* puts("bye"); */
|
|
Pa_AbortStream(pa);
|
|
Pa_CloseStream(pa);
|
|
Pa_Terminate();
|
|
aud_free(&aud);
|
|
}
|