// QWAV score encoding system (c) 2026 Erick G.G. de Paz under GNU 2.0 // Program to code music following the qwerty pattern of keyboard as piano reference // Compile with: gcc qwav.c -o qwav -lm #include #include #include #include // --- Configuration Constants --- #define maxVoices 5 // Maximum number of simultaneous polyphonic voices #define maxNotesPerLine 120 // Maximum horizontal length of a musical phrase #define maxLines 120 // Maximum vertical length of the file #define PI 3.14159265358979323846 // --- Global Audio Properties --- int SAMPLE_RATE = 8192; // 8kHz sample rate; keeps file sizes small and matches 8-bit fidelity well int DURATION_SECONDS; // Total duration of the generated audio track int num_samples; // Total number of audio samples (Sample Rate * Duration) // --- Score Data Structures --- // 2D arrays to hold the parsed tone (1-7) and octave (2-5) for each beat across all voices int tone[maxNotesPerLine*maxLines][maxVoices]; int octave[maxNotesPerLine*maxLines][maxVoices]; int ind[maxVoices]; // Tracks the current beat index for each voice during parsing // --- Musical Tuning Ratios (Just Intonation relative to Tonic) --- // These arrays calculate the frequency multiplier for scale degrees 1 through 7 int divident [] = {1,9,5,4,3,5,15}; // Numerators int divisor [] = {1,8,4,3,2,3,8}; // Denominators int bpm; // Beats per minute, parsed from the first line of the file int VOICES; // Final count of active voices in the score int BEATS; // Total number of beats in the entire score double tonicFreq = 256; // Base frequency for C4 (Scientific pitch, slightly lower than A=440 standard) /** * Maps a given character to a musical tone (scale degree 1-7). * Utilizes vertical columns on the QWERTY keyboard. */ int getTone(char c) { if(c == '.') return 0; // Silence marker // Normalize uppercase letters to lowercase if(c >= 'A' && c <= 'Z') { c -= 'A'; c += 'a'; } // Map keyboard columns to scale degrees switch(c) { case 'q': case 'a': case 'z': case '1': return 1; case 'w': case 's': case 'x': case '2': return 2; case 'e': case 'd': case 'c': case '3': return 3; case 'r': case 'f': case 'v': case '4': return 4; case 't': case 'g': case 'b': case '5': return 5; case 'y': case 'h': case 'n': case '6': return 6; case 'u': case 'j': case 'm': case '7': return 7; } return -1; // Invalid character } /** * Maps a given character to a musical octave (2-5). * Utilizes horizontal rows on the QWERTY keyboard. */ int getOctave(char c) { switch(c) { // Number row -> Octave 5 case '1': case '2': case '3': case '4': case '5': case '6': case '7': return 5; // Top letter row -> Octave 4 case 'q': case 'w': case 'e': case 'r': case 't': case 'y': case 'u': case 'Q': case 'W': case 'E': case 'R': case 'T': case 'Y': case 'U': return 4; // Home letter row -> Octave 3 case 'a': case 's': case 'd': case 'f': case 'g': case 'h': case 'j': case 'A': case 'S': case 'D': case 'F': case 'G': case 'H': case 'J': return 3; // Bottom letter row -> Octave 2 case 'z': case 'x': case 'c': case 'v': case 'b': case 'n': case 'm': case 'Z': case 'X': case 'C': case 'V': case 'B': case 'N': case 'M': return 2; } return 3; // Default for silence } /** * Parses the text score and populates the tone and octave matrices. * Validates formatting, counts voices, and handles comments. */ int loadFile(char* fNstr) { FILE* fN = fopen(fNstr,"r"); int a,b; char str; // 1. Read BPM from the first line if(fscanf(fN,"%i",&b) < 1) { printf("The first line expects a number\n"); printf("\t[Beats per minute]\n"); return 0; } bpm = b; // Clamp BPM to reasonable musical limits if(bpm < 15) bpm = 15; if(bpm > 240) bpm = 240; int line=1, col=0, voice=-1; for(a=0; a maxNotesPerLine) { printf("Error at (%i,%i) : Number of columns exceeded.", line, col); return 0; } // 3. Process valid notes a = getTone(str); if(a != -1) { b = ind[voice]; ind[voice]++; tone[b][voice] = a; octave[b][voice] = getOctave(str); } // 4. Ignore Comments (Lines starting with '%') if(str == '%') { do { a = fscanf(fN,"%c",&str); col++; } while(str != '\n' && a > 0); // Fast-forward to the end of the line voice--; // Adjust voice index since this line didn't contain music data if(a < 1) break; } // 5. Handle Line Breaks / Voice transitions if(str == '\n') { line++; voice++; // Moving to the next voice in the block // Check vertical limits if(line > maxLines) { printf("Error at (%i,%i) : Number of lines exceeded.", line, col); return 0; } if(voice > maxVoices) { printf("Error at (%i,%i) : Number of voices exceeded.", line, col); return 0; } // 6. Detect end of a voice block (when an empty line or new block starts) if(col == 1) { b = 1; // Validate that all voices in the block have the same number of notes for(a=1; a 0) voice++; else break; } VOICES = voice; BEATS = ind[0]; // Total score length based on the first voice // Final check to ensure the last block was consistent b = 1; for(a=1; ariff_header, "RIFF", 4); header->wav_size = 36 + (num_samples * num_channels * bytes_per_sample); memcpy(header->wave_header, "WAVE", 4); memcpy(header->fmt_header, "fmt ", 4); header->fmt_chunk_size = 16; header->audio_format = 1; header->num_channels = num_channels; header->sample_rate = SAMPLE_RATE; header->byte_rate = SAMPLE_RATE * num_channels * bytes_per_sample; header->sample_alignment = num_channels * bytes_per_sample; header->bit_depth = bytes_per_sample * 8; memcpy(header->data_header, "data", 4); header->data_bytes = num_samples * num_channels * bytes_per_sample; } /** * Synthesizes the audio and writes the complete .wav file. */ int createWav(char *orgF) { char strFileName[200]; sprintf(strFileName, "%s.wav", orgF); // Output file matches input name + .wav FILE *file = fopen(strFileName, "wb"); if (!file) { printf("Error: Could not write the WAV file.\n"); return 1; } // Write Header WavHeader header; createHeader(&header); fwrite(&header, sizeof(WavHeader), 1, file); int x, v, beat, t; double sumY, y, hertz; double second; double secondsPerBeat = 60.0 / bpm; // Duration of one individual beat uint8_t sample; // Generate PCM audio sample by sample for(x=0; x 2048) hertz = 2048; if(hertz < 8) hertz = 8; // Generate sine wave value (-1.0 to 1.0) y = sin(second * 2 * PI * hertz); // Amplitude correction: Softens higher frequencies dynamically y = y * exp(-hertz / (tonicFreq * 2)); // Hard clipping safety net if(y < -1) y = -1; if(y > +1) y = +1; } // Mix voices down (divide amplitude by total number of voices to prevent clipping) sumY += y * 1.0 / VOICES; } // Convert normalized double (-1.0 to 1.0) to 8-bit unsigned integer (0 to 255) sample = (uint8_t)(sumY * 127 + 128.0); // Write generated sample to file fwrite(&sample, sizeof(uint8_t), 1, file); } fclose(file); return 0; } int main(int argc, char* argv[]) { // Validate arguments if(argc != 2) { printf("Please run as\n\t%s fileName.txt\n", argv[0]); return 1; } // Parse the file if(!loadFile(argv[1])) return 1; // If successful and contains voices, generate audio if(VOICES > 0) createWav(argv[1]); return 0; }