first commit

This commit is contained in:
corey 2024-05-14 12:58:17 -05:00
commit 1d0b47a9e2
6 changed files with 371 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/chord
*.o

14
Makefile Normal file
View File

@ -0,0 +1,14 @@
CC ?= cc
CFLAGS= -Wfatal-errors -Wall -Wextra $(shell pkgconf --cflags portaudio-2.0)
LDFLAGS= -s -lm $(shell pkgconf --libs portaudio-2.0)
OBJS= main.o audio.o
all: chord
chord: $(OBJS)
$(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS)
%.o: %.c %.h
$(CC) -c $< -o $@ $(CFLAGS)
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS)
clean:
$(RM) *.o $(OBJS) chord

31
audio.c Normal file
View File

@ -0,0 +1,31 @@
#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<unistd.h>
#include"audio.h"
Audio aud_new(size_t samplerate,size_t nsamples,size_t channels)
{
Audio aud=(Audio){0};
aud.samplerate=samplerate;
aud.nsamples=nsamples;
aud.channels=channels;
aud.data=malloc(nsamples*sizeof(uint16_t));
return aud;
}
void aud_free(Audio*aud)
{
if(!aud)return;
if(aud->data)
free(aud->data);
aud->data=0;
*aud=(Audio){0};
}

24
audio.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#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<unistd.h>
typedef struct Audio
{
size_t samplerate;
size_t nsamples;
size_t channels;
uint16_t*data;
} Audio;
Audio aud_new(size_t samplerate,size_t nsamples,size_t channels);
void aud_free(Audio*aud);

233
main.c Normal file
View File

@ -0,0 +1,233 @@
/***************
* 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-z][#b]?[0-9][m]?\n"
// Audio data manipulation
#define SEMITONE_CONSTANT 1.0594630943592953 // pow(2,1/12.0)
#define raisesemitone(freq,nsemitones) ((freq)*pow(SEMITONE_CONSTANT,(nsemitones)))
// Chord generators
#define makechord(aud,freq,...) do{double _f=nametofreq(freq);int _l[]={__VA_ARGS__};tri(aud,_f,GEN);for(size_t i=0;i<sizeof(_l)/sizeof(int);++i)tri(aud,raisesemitone(_f,_l[i]),ADD);}while(0)
#define chord makechord
#define M7(aud,f) makechord(aud,f,4,7,11)
#define f7(aud,f) makechord(aud,f,4,7,10)
#define m7(aud,f) makechord(aud,f,3,7,10)
#define d7(aud,f) makechord(aud,f,3,6,10)
enum{GEN,ADD};
double nametofreq(const char*name);
void avg(Audio*aud,size_t blocksize);
void cleanup(int sig);
void rnd(Audio*aud,size_t mode);
void tri(Audio*aud,double freq,size_t mode);
void zero(Audio*aud);
void env(Audio*aud);
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);
}
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);env(&aud);play();
chord(&aud, "g#4",3,6,9,13);env(&aud);play();
chord(&aud, "f#4",3,7,10,14);env(&aud);play();
chord(&aud, "e4",4,7,10,14);env(&aud);play();
chord(&aud, "d4",4,7,11,14);env(&aud);play();
chord(&aud, "c#4",3,7,10,13);env(&aud);play();
chord(&aud, "b3",3,7,10,14);env(&aud);play();
chord(&aud, "a3",4,7,11,14);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);env(&aud);play();
}
else
{
chord(&aud,argv[i],4,7,11,14);env(&aud);play();
}
}
}
}
cleanup(0);
}
/*******
* Convert note name into frequency
*******/
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;
}
/*******
* Convolver (low-pass filter)
*******/
void avg(Audio*aud,size_t blocksize)
{
if(!aud->data||!aud->nsamples||!aud->channels||!aud->samplerate||!blocksize)return;
for(size_t i=blocksize-1;i<aud->nsamples;++i)
{
uint16_t sum=0;
for(size_t j=0;j<blocksize&&(i+j)<aud->nsamples;++j)
sum+=aud->data[i+j];
/* for(size_t ch=0;ch<aud->channels;++ch,++i) */
aud->data[i]=sum/blocksize;
}
}
/*******
* Randomize data
*******/
void rnd(Audio*aud,size_t mode)
{
if(!aud->data||!aud->nsamples||!aud->samplerate||!aud->channels)return;
for(size_t i=0;i<aud->nsamples;++i)
{
/* for(size_t ch=0;ch<aud->channels;++ch,++i) */
uint16_t sample=rand();
if(mode==GEN)
aud->data[i]=sample;
else if(mode==ADD)
aud->data[i]=(aud->data[i]+sample)/2;
}
}
/*******
* Triangle waveform generator
*******/
void tri(Audio*aud,double freq,size_t mode)
{
if(!aud->data||!aud->nsamples||!aud->samplerate||!aud->channels)return;
for(size_t i=0;i<aud->nsamples;++i)
{
/* for(size_t ch=0;ch<aud->channels;++ch,++i) */
//aud->data[i]=sin(i);
uint16_t sample=(i%(int)(aud->samplerate/freq))*7;
if(mode==GEN)
aud->data[i]=sample;
else if(mode==ADD)
aud->data[i]=(aud->data[i]+sample)/2;
}
}
/*******
* Zero all data
*******/
void zero(Audio*aud)
{
if(!aud->data||!aud->nsamples||!aud->channels)return;
memset(aud->data,0,aud->nsamples*sizeof(uint16_t));
}
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);
}
void env(Audio*aud)
{
if(!aud)return;
if(!aud->data)return;
size_t range=aud->nsamples/10;
for(size_t i=0;i<range;++i)
aud->data[i]*=(float)i/range;
for(size_t i=0;i<aud->nsamples/8;++i)
aud->data[aud->nsamples-1-i]*=(float)i/range;
}

67
z Normal file
View File

@ -0,0 +1,67 @@
#!/usr/bin/ruby
# Prototype for converting note names to
# frequencies
def raise_semitone(freq,nsemitones)
semitone_constant=2**(1/12.0) #1.059463
freq*semitone_constant**nsemitones
end
def getfreq(name)
a0=27.5
semi=0
semi_name={'a'=>0,'b'=>2,'c'=>3,'d'=>5,'e'=>7,'f'=>8,'g'=>10}
str_pos=1
if name[1]=='#'
semi+=1
str_pos=2
elsif name[1]=='b'
semi-=1
str_pos=2
end
oct=name[str_pos..].to_i
if name[0]!='a'&&name[0]!='b'
oct=(oct>0)?(oct-1):(0)
end
semi+=semi_name[name[0]]+oct*12
freq=raise_semitone(a0,semi)
freq
end
def test name,correct
freq=getfreq name
if freq.to_i==correct
s=""
else
s="\t[FAIL: #{correct}]"
end
print "'#{name}':\t#{freq.to_i}#{s}\n"
end
def main
test 'a0',27
test 'a1',55
test 'a2',110
test 'a3',220
test 'a4',440
test 'a#4',466
test 'bb4',466
test 'a5',880
test 'a6',1760
test 'c3',130
test 'g6',1567
test 'e1',41
test 'e2',82
test 'e3',164
test 'e4',329
test 'e5',659
test 'e6',1318
test 'f#4',369
test 'd7',2349
# test 'a10',99999
end
main