what have i done
This commit is contained in:
5
scripts/csound-parser/.gitignore
vendored
Normal file
5
scripts/csound-parser/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
dist/
|
||||
downloaded-opcodes/
|
||||
*.log
|
||||
.DS_Store
|
||||
277
scripts/csound-parser/README.md
Normal file
277
scripts/csound-parser/README.md
Normal file
@ -0,0 +1,277 @@
|
||||
# Csound Manual Parser
|
||||
|
||||
A robust parser that converts the Csound reference manual into TypeScript reference files for use in code editors with hover tooltips and searchable documentation.
|
||||
|
||||
## Features
|
||||
|
||||
- Downloads markdown files directly from GitHub
|
||||
- Parses 1000+ Csound opcodes
|
||||
- Extracts structured data (syntax, parameters, examples)
|
||||
- Generates organized TypeScript files by category
|
||||
- Creates a main aggregator file with lookup functions
|
||||
- Handles edge cases and provides detailed error reporting
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
cd scripts/csound-parser
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Option 1: Download and Parse (Recommended)
|
||||
|
||||
Download fresh markdown files from GitHub and parse them:
|
||||
|
||||
```bash
|
||||
pnpm run download
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Download all opcode markdown files from the Csound manual repository
|
||||
2. Parse them into structured data
|
||||
3. Generate TypeScript reference files in `src/lib/csound-reference/`
|
||||
|
||||
### Option 2: Parse Local Files
|
||||
|
||||
If you already have the Csound manual cloned locally:
|
||||
|
||||
```bash
|
||||
pnpm run parse -- --input=/path/to/csound-manual/docs/opcodes
|
||||
```
|
||||
|
||||
### Custom Output Directory
|
||||
|
||||
```bash
|
||||
pnpm run parse -- --output=/custom/output/path
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
The parser generates:
|
||||
|
||||
```
|
||||
src/lib/csound-reference/
|
||||
├── types.ts # Type definitions
|
||||
├── csoundReference.ts # Main aggregator
|
||||
├── signal-generators-basic-oscillators.ts
|
||||
├── signal-modifiers-standard-filters.ts
|
||||
├── mathematical-operations-trigonometric-functions.ts
|
||||
└── ... (100+ category files)
|
||||
```
|
||||
|
||||
Each category file contains an array of `CsoundReference` objects:
|
||||
|
||||
```typescript
|
||||
export const signalGeneratorsBasicOscillators: CsoundReference[] = [
|
||||
{
|
||||
name: 'oscil',
|
||||
type: 'opcode',
|
||||
category: 'Signal Generators:Basic Oscillators',
|
||||
description: 'A simple oscillator without any interpolation.',
|
||||
syntax: 'ares = oscil(xamp, xcps [, ifn, iphs])\nkres = oscil(kamp, kcps [, ifn, iphs])',
|
||||
rates: ['a-rate', 'k-rate', 'i-rate'],
|
||||
parameters: [
|
||||
{
|
||||
name: 'ifn',
|
||||
description: 'function table number. Requires a wrap-around guard point.',
|
||||
type: 'initialization'
|
||||
},
|
||||
// ...
|
||||
],
|
||||
seeAlso: ['oscili', 'poscil']
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
Import in your application:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
allCsoundReferences,
|
||||
getCsoundReference,
|
||||
getCsoundReferencesByCategory
|
||||
} from './lib/csound-reference/csoundReference'
|
||||
|
||||
// Get a specific opcode
|
||||
const oscil = getCsoundReference('oscil')
|
||||
|
||||
// Get all oscillators
|
||||
const oscillators = getCsoundReferencesByCategory('Signal Generators:Basic Oscillators')
|
||||
|
||||
// Search all references
|
||||
const filtered = allCsoundReferences.filter(ref =>
|
||||
ref.description.includes('filter')
|
||||
)
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Hover Tooltips in CodeMirror
|
||||
|
||||
Create a tooltip extension similar to the GLSL tooltips:
|
||||
|
||||
```typescript
|
||||
import { hoverTooltip } from '@codemirror/view'
|
||||
import { getCsoundReference } from './lib/csound-reference/csoundReference'
|
||||
|
||||
export const csoundTooltip = hoverTooltip((view, pos) => {
|
||||
const word = getWordAt(view, pos)
|
||||
if (!word) return null
|
||||
|
||||
const reference = getCsoundReference(word.text)
|
||||
if (!reference) return null
|
||||
|
||||
return {
|
||||
pos: word.from,
|
||||
end: word.to,
|
||||
above: true,
|
||||
create() {
|
||||
const dom = document.createElement('div')
|
||||
dom.innerHTML = `
|
||||
<div class="tooltip-header">
|
||||
<strong>${reference.name}</strong>
|
||||
<span>${reference.type}</span>
|
||||
</div>
|
||||
<div class="tooltip-description">${reference.description}</div>
|
||||
${reference.syntax ? `<pre>${reference.syntax}</pre>` : ''}
|
||||
`
|
||||
return { dom }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Searchable Help Panel
|
||||
|
||||
Create a reference panel with fuzzy search:
|
||||
|
||||
```typescript
|
||||
import { allCsoundReferences } from './lib/csound-reference/csoundReference'
|
||||
import Fuse from 'fuse.js'
|
||||
|
||||
const fuse = new Fuse(allCsoundReferences, {
|
||||
keys: ['name', 'description', 'category'],
|
||||
threshold: 0.4
|
||||
})
|
||||
|
||||
const results = fuse.search('oscillator')
|
||||
```
|
||||
|
||||
### 3. Autocomplete
|
||||
|
||||
Use the reference data for intelligent autocomplete:
|
||||
|
||||
```typescript
|
||||
import { allCsoundReferences } from './lib/csound-reference/csoundReference'
|
||||
|
||||
const completions = allCsoundReferences.map(ref => ({
|
||||
label: ref.name,
|
||||
type: ref.type,
|
||||
info: ref.description,
|
||||
detail: ref.syntax
|
||||
}))
|
||||
```
|
||||
|
||||
## Data Structure
|
||||
|
||||
### CsoundReference Interface
|
||||
|
||||
```typescript
|
||||
interface CsoundReference {
|
||||
name: string // Opcode name (e.g., "oscil")
|
||||
type: 'opcode' | 'keyword' | 'header' | 'constant'
|
||||
category: string // Full category path
|
||||
description: string // Brief description
|
||||
syntax?: string // Syntax examples
|
||||
example?: string // Code example
|
||||
rates?: string[] // ['a-rate', 'k-rate', 'i-rate']
|
||||
parameters?: {
|
||||
name: string
|
||||
description: string
|
||||
type: 'initialization' | 'performance'
|
||||
}[]
|
||||
seeAlso?: string[] // Related opcodes
|
||||
}
|
||||
```
|
||||
|
||||
## Parser Architecture
|
||||
|
||||
### 1. Downloader (`downloader.ts`)
|
||||
- Fetches markdown files from GitHub API
|
||||
- Handles rate limiting and retries
|
||||
- Downloads to `downloaded-opcodes/` directory
|
||||
|
||||
### 2. Parser (`parser.ts`)
|
||||
- Parses markdown frontmatter (id, category)
|
||||
- Extracts sections using regex
|
||||
- Extracts parameters from initialization/performance sections
|
||||
- Handles modern and classic syntax variants
|
||||
- Detects rate types (a-rate, k-rate, i-rate)
|
||||
|
||||
### 3. Generator (`generator.ts`)
|
||||
- Groups opcodes by category
|
||||
- Sanitizes category names for file names
|
||||
- Generates TypeScript files with proper escaping
|
||||
- Creates main aggregator with imports
|
||||
- Provides lookup functions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Download Fails
|
||||
|
||||
If the GitHub download fails, manually clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/csound/manual.git
|
||||
cd manual
|
||||
git checkout develop
|
||||
```
|
||||
|
||||
Then run the parser with the local path:
|
||||
|
||||
```bash
|
||||
pnpm run parse -- --input=../manual/docs/opcodes
|
||||
```
|
||||
|
||||
### Missing Categories
|
||||
|
||||
Some opcodes may not have categories defined. The parser will skip these and log warnings.
|
||||
|
||||
### Parse Errors
|
||||
|
||||
The parser is robust and will continue parsing even if individual files fail. Check the console output for warnings about skipped files.
|
||||
|
||||
## Extending the Parser
|
||||
|
||||
### Adding New Extraction
|
||||
|
||||
To extract additional information from the markdown files, modify `parser.ts`:
|
||||
|
||||
```typescript
|
||||
extractNewField(content: string): string {
|
||||
const section = this.extractSection(content, 'NewSection')
|
||||
// Parse the section content
|
||||
return parsed
|
||||
}
|
||||
```
|
||||
|
||||
Then add the field to the `CsoundReference` interface in `types.ts`.
|
||||
|
||||
### Custom Categories
|
||||
|
||||
To reorganize categories, modify the `category` field in the parser or create a mapping function in the generator.
|
||||
|
||||
## Performance
|
||||
|
||||
- Parsing 1000+ opcodes: ~2-5 seconds
|
||||
- Generating TypeScript files: ~1-2 seconds
|
||||
- Download from GitHub: ~30-60 seconds (network dependent)
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
45
scripts/csound-parser/debug-params.ts
Normal file
45
scripts/csound-parser/debug-params.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import * as fs from 'fs';
|
||||
import { CsoundManualParser } from './parser';
|
||||
|
||||
const parser = new CsoundManualParser();
|
||||
const content = fs.readFileSync('downloaded-opcodes/moogladder.md', 'utf-8');
|
||||
|
||||
console.log('=== Testing moogladder.md ===\n');
|
||||
|
||||
// Test extractSection
|
||||
const syntaxSection = parser.extractSection(content, 'Syntax');
|
||||
console.log('Syntax section length:', syntaxSection.length);
|
||||
console.log('First 200 chars:', syntaxSection.substring(0, 200));
|
||||
console.log('\n---\n');
|
||||
|
||||
// Look for ### Initialization
|
||||
const initMatch = syntaxSection.match(/###\s+Initialization\s*\n([\s\S]*?)(?=\n###|\n##|$)/i);
|
||||
console.log('Initialization match:', initMatch ? 'FOUND' : 'NOT FOUND');
|
||||
if (initMatch) {
|
||||
console.log('Init section content (first 300 chars):');
|
||||
console.log(initMatch[1].substring(0, 300));
|
||||
console.log('\n---\n');
|
||||
|
||||
// Test the parameter regex
|
||||
const paramRegex = /_([a-zA-Z0-9_,\s/]+)_\s*[-–—]+\s*([^\n]+(?:\n(?!_)[^\n]+)*)/g;
|
||||
const params = Array.from(initMatch[1].matchAll(paramRegex));
|
||||
console.log(`Found ${params.length} parameters`);
|
||||
params.forEach((p, i) => {
|
||||
console.log(` Param ${i + 1}: name="${p[1]}", desc="${p[2].substring(0, 60)}..."`);
|
||||
});
|
||||
}
|
||||
|
||||
// Test extractParameters directly
|
||||
const initParams = parser.extractParameters(content, 'Initialization');
|
||||
const perfParams = parser.extractParameters(content, 'Performance');
|
||||
|
||||
console.log('\n=== extractParameters results ===');
|
||||
console.log(`Initialization params: ${initParams.length}`);
|
||||
initParams.forEach(p => {
|
||||
console.log(` - ${p.name}: ${p.description.substring(0, 60)}...`);
|
||||
});
|
||||
|
||||
console.log(`\nPerformance params: ${perfParams.length}`);
|
||||
perfParams.forEach(p => {
|
||||
console.log(` - ${p.name}: ${p.description.substring(0, 60)}...`);
|
||||
});
|
||||
30
scripts/csound-parser/debug-section.ts
Normal file
30
scripts/csound-parser/debug-section.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import * as fs from 'fs';
|
||||
|
||||
const content = fs.readFileSync('downloaded-opcodes/moogladder.md', 'utf-8');
|
||||
|
||||
console.log('Full content length:', content.length);
|
||||
console.log('\n=== Testing extractSection regex ===\n');
|
||||
|
||||
const regex = /##\s+Syntax\s*\n([\s\S]*?)(?=\n##|$)/i;
|
||||
const match = content.match(regex);
|
||||
|
||||
if (match) {
|
||||
console.log('Match found!');
|
||||
console.log('Captured content length:', match[1].length);
|
||||
console.log('\nFull captured content:');
|
||||
console.log('---START---');
|
||||
console.log(match[1]);
|
||||
console.log('---END---');
|
||||
} else {
|
||||
console.log('No match found');
|
||||
}
|
||||
|
||||
// Also check what comes after
|
||||
const syntaxIndex = content.indexOf('## Syntax');
|
||||
const examplesIndex = content.indexOf('## Examples');
|
||||
console.log('\n=== Indices ===');
|
||||
console.log('## Syntax at:', syntaxIndex);
|
||||
console.log('## Examples at:', examplesIndex);
|
||||
console.log('Distance:', examplesIndex - syntaxIndex);
|
||||
console.log('\nContent between (first 500 chars):');
|
||||
console.log(content.substring(syntaxIndex, syntaxIndex + 500));
|
||||
123
scripts/csound-parser/downloader.ts
Normal file
123
scripts/csound-parser/downloader.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as https from 'https';
|
||||
|
||||
interface GitHubFile {
|
||||
name: string;
|
||||
download_url: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class GitHubDownloader {
|
||||
private apiBase = 'https://api.github.com/repos/csound/manual/contents';
|
||||
private branch = 'develop';
|
||||
private downloadDir: string;
|
||||
|
||||
constructor(downloadDir: string) {
|
||||
this.downloadDir = downloadDir;
|
||||
}
|
||||
|
||||
private async fetchJson<T>(url: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(
|
||||
url,
|
||||
{
|
||||
headers: {
|
||||
'User-Agent': 'Csound-Parser',
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (err) {
|
||||
reject(new Error(`Failed to parse JSON: ${err}`));
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
private async downloadFile(url: string, destPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (res) => {
|
||||
if (res.statusCode === 302 || res.statusCode === 301) {
|
||||
if (res.headers.location) {
|
||||
this.downloadFile(res.headers.location, destPath).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const fileStream = fs.createWriteStream(destPath);
|
||||
res.pipe(fileStream);
|
||||
|
||||
fileStream.on('finish', () => {
|
||||
fileStream.close();
|
||||
resolve();
|
||||
});
|
||||
|
||||
fileStream.on('error', (err) => {
|
||||
fs.unlink(destPath, () => {});
|
||||
reject(err);
|
||||
});
|
||||
}).on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async downloadOpcodes(): Promise<number> {
|
||||
console.log('Fetching opcode list from GitHub...');
|
||||
|
||||
const url = `${this.apiBase}/docs/opcodes?ref=${this.branch}`;
|
||||
const files = await this.fetchJson<GitHubFile[]>(url);
|
||||
|
||||
const markdownFiles = files.filter(f => f.type === 'file' && f.name.endsWith('.md'));
|
||||
|
||||
console.log(`Found ${markdownFiles.length} markdown files`);
|
||||
|
||||
if (!fs.existsSync(this.downloadDir)) {
|
||||
fs.mkdirSync(this.downloadDir, { recursive: true });
|
||||
}
|
||||
|
||||
let downloaded = 0;
|
||||
let failed = 0;
|
||||
|
||||
for (const file of markdownFiles) {
|
||||
const destPath = path.join(this.downloadDir, file.name);
|
||||
|
||||
try {
|
||||
await this.downloadFile(file.download_url, destPath);
|
||||
downloaded++;
|
||||
|
||||
if (downloaded % 50 === 0) {
|
||||
console.log(`Downloaded ${downloaded}/${markdownFiles.length}...`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to download ${file.name}:`, err);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDownloaded ${downloaded} files, ${failed} failed`);
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
async downloadCategories(): Promise<void> {
|
||||
console.log('Downloading categories.py...');
|
||||
const url = 'https://raw.githubusercontent.com/csound/manual/develop/categories.py';
|
||||
const destPath = path.join(this.downloadDir, 'categories.py');
|
||||
|
||||
await this.downloadFile(url, destPath);
|
||||
console.log('Categories file downloaded');
|
||||
}
|
||||
}
|
||||
207
scripts/csound-parser/generator.ts
Normal file
207
scripts/csound-parser/generator.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { CsoundReference } from './types';
|
||||
|
||||
export class TypeScriptGenerator {
|
||||
private outputDir: string;
|
||||
|
||||
constructor(outputDir: string) {
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
sanitizeCategoryName(category: string): string {
|
||||
return category
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
}
|
||||
|
||||
sanitizeVariableName(category: string): string {
|
||||
return category
|
||||
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
||||
.split(' ')
|
||||
.map((word, index) =>
|
||||
index === 0
|
||||
? word.toLowerCase()
|
||||
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
||||
)
|
||||
.join('')
|
||||
.replace(/^[0-9]/, '_$&');
|
||||
}
|
||||
|
||||
escapeString(str: string): string {
|
||||
return str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t');
|
||||
}
|
||||
|
||||
generateReferenceObject(ref: CsoundReference, indent: string = ' '): string {
|
||||
const lines: string[] = [];
|
||||
lines.push(`{`);
|
||||
lines.push(`${indent}name: '${this.escapeString(ref.name)}',`);
|
||||
lines.push(`${indent}type: '${ref.type}',`);
|
||||
lines.push(`${indent}category: '${this.escapeString(ref.category)}',`);
|
||||
lines.push(`${indent}description: '${this.escapeString(ref.description)}',`);
|
||||
|
||||
if (ref.syntax) {
|
||||
lines.push(`${indent}syntax: '${this.escapeString(ref.syntax)}',`);
|
||||
}
|
||||
|
||||
if (ref.example) {
|
||||
const escapedExample = this.escapeString(ref.example);
|
||||
lines.push(`${indent}example: '${escapedExample}',`);
|
||||
}
|
||||
|
||||
if (ref.rates && ref.rates.length > 0) {
|
||||
lines.push(`${indent}rates: [${ref.rates.map(r => `'${r}'`).join(', ')}],`);
|
||||
}
|
||||
|
||||
if (ref.parameters && ref.parameters.length > 0) {
|
||||
lines.push(`${indent}parameters: [`);
|
||||
for (const param of ref.parameters) {
|
||||
lines.push(`${indent} {`);
|
||||
lines.push(`${indent} name: '${this.escapeString(param.name)}',`);
|
||||
lines.push(`${indent} description: '${this.escapeString(param.description)}',`);
|
||||
lines.push(`${indent} type: '${param.type}'`);
|
||||
lines.push(`${indent} },`);
|
||||
}
|
||||
lines.push(`${indent}],`);
|
||||
}
|
||||
|
||||
if (ref.seeAlso && ref.seeAlso.length > 0) {
|
||||
lines.push(`${indent}seeAlso: [${ref.seeAlso.map(s => `'${this.escapeString(s)}'`).join(', ')}]`);
|
||||
}
|
||||
|
||||
lines.push(`}`);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
generateCategoryFile(
|
||||
category: string,
|
||||
references: CsoundReference[],
|
||||
fileName: string
|
||||
): string {
|
||||
const varName = this.sanitizeVariableName(category);
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(`import type { CsoundReference } from './types'`);
|
||||
lines.push(``);
|
||||
lines.push(`// ${category}`);
|
||||
lines.push(`export const ${varName}: CsoundReference[] = [`);
|
||||
|
||||
for (const ref of references) {
|
||||
const refLines = this.generateReferenceObject(ref, ' ');
|
||||
lines.push(refLines + ',');
|
||||
}
|
||||
|
||||
lines.push(`]`);
|
||||
lines.push(``);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
generateMainFile(categories: Map<string, CsoundReference[]>): string {
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push(`import type { CsoundReference } from './types'`);
|
||||
|
||||
const imports: string[] = [];
|
||||
const varNames: string[] = [];
|
||||
|
||||
for (const [category] of categories) {
|
||||
const fileName = this.sanitizeCategoryName(category);
|
||||
const varName = this.sanitizeVariableName(category);
|
||||
imports.push(`import { ${varName} } from './${fileName}'`);
|
||||
varNames.push(varName);
|
||||
}
|
||||
|
||||
lines.push(...imports);
|
||||
lines.push(``);
|
||||
lines.push(`export type { CsoundReference }`);
|
||||
lines.push(``);
|
||||
lines.push(`export const allCsoundReferences: CsoundReference[] = [`);
|
||||
for (const varName of varNames) {
|
||||
lines.push(` ...${varName},`);
|
||||
}
|
||||
lines.push(`]`);
|
||||
lines.push(``);
|
||||
|
||||
lines.push(`export const csoundReferenceMap = new Map<string, CsoundReference>()`);
|
||||
lines.push(`allCsoundReferences.forEach(ref => {`);
|
||||
lines.push(` csoundReferenceMap.set(ref.name, ref)`);
|
||||
lines.push(`})`);
|
||||
lines.push(``);
|
||||
|
||||
lines.push(`export function getCsoundReference(name: string): CsoundReference | undefined {`);
|
||||
lines.push(` return csoundReferenceMap.get(name)`);
|
||||
lines.push(`}`);
|
||||
lines.push(``);
|
||||
|
||||
lines.push(`export function getCsoundReferencesByCategory(category: string): CsoundReference[] {`);
|
||||
lines.push(` return allCsoundReferences.filter(ref => ref.category === category)`);
|
||||
lines.push(`}`);
|
||||
lines.push(``);
|
||||
|
||||
lines.push(`export {`);
|
||||
for (const varName of varNames) {
|
||||
lines.push(` ${varName},`);
|
||||
}
|
||||
lines.push(`}`);
|
||||
lines.push(``);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
writeCategoryFiles(categories: Map<string, CsoundReference[]>): void {
|
||||
if (!fs.existsSync(this.outputDir)) {
|
||||
fs.mkdirSync(this.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
let fileCount = 0;
|
||||
|
||||
for (const [category, references] of categories) {
|
||||
const fileName = this.sanitizeCategoryName(category);
|
||||
const filePath = path.join(this.outputDir, `${fileName}.ts`);
|
||||
const content = this.generateCategoryFile(category, references, fileName);
|
||||
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
fileCount++;
|
||||
console.log(`Generated: ${fileName}.ts (${references.length} opcodes)`);
|
||||
}
|
||||
|
||||
console.log(`\nGenerated ${fileCount} category files`);
|
||||
}
|
||||
|
||||
writeMainFile(categories: Map<string, CsoundReference[]>): void {
|
||||
const filePath = path.join(this.outputDir, 'csoundReference.ts');
|
||||
const content = this.generateMainFile(categories);
|
||||
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(`Generated: csoundReference.ts`);
|
||||
}
|
||||
|
||||
copyTypes(): void {
|
||||
if (!fs.existsSync(this.outputDir)) {
|
||||
fs.mkdirSync(this.outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const sourceTypes = path.join(__dirname, 'types.ts');
|
||||
const destTypes = path.join(this.outputDir, 'types.ts');
|
||||
|
||||
if (fs.existsSync(sourceTypes)) {
|
||||
fs.copyFileSync(sourceTypes, destTypes);
|
||||
console.log(`Copied: types.ts`);
|
||||
}
|
||||
}
|
||||
|
||||
generateAll(categories: Map<string, CsoundReference[]>): void {
|
||||
console.log(`\nGenerating TypeScript files...`);
|
||||
this.copyTypes();
|
||||
this.writeCategoryFiles(categories);
|
||||
this.writeMainFile(categories);
|
||||
console.log(`\nDone!`);
|
||||
}
|
||||
}
|
||||
92
scripts/csound-parser/index.ts
Normal file
92
scripts/csound-parser/index.ts
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as path from 'path';
|
||||
import { CsoundManualParser } from './parser';
|
||||
import { TypeScriptGenerator } from './generator';
|
||||
import { GitHubDownloader } from './downloader';
|
||||
|
||||
const DOWNLOAD_DIR = path.join(__dirname, 'downloaded-opcodes');
|
||||
const OUTPUT_DIR = path.join(__dirname, '../../src/lib/csound-reference');
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const shouldDownload = args.includes('--download') || args.includes('-d');
|
||||
const inputDir = args.find(arg => arg.startsWith('--input='))?.split('=')[1] || DOWNLOAD_DIR;
|
||||
const outputDir = args.find(arg => arg.startsWith('--output='))?.split('=')[1] || OUTPUT_DIR;
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('Csound Manual Parser');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
if (shouldDownload) {
|
||||
console.log('\nStep 1: Downloading markdown files from GitHub...');
|
||||
const downloader = new GitHubDownloader(DOWNLOAD_DIR);
|
||||
|
||||
try {
|
||||
await downloader.downloadOpcodes();
|
||||
await downloader.downloadCategories();
|
||||
} catch (err) {
|
||||
console.error('Download failed:', err);
|
||||
console.log('\nYou can also manually clone the repository:');
|
||||
console.log(' git clone https://github.com/csound/manual.git');
|
||||
console.log(' Then run: npm run parse -- --input=manual/docs/opcodes');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
console.log(`\nUsing existing files from: ${inputDir}`);
|
||||
console.log('(Use --download or -d to download fresh files from GitHub)');
|
||||
}
|
||||
|
||||
console.log('\nStep 2: Parsing markdown files...');
|
||||
const parser = new CsoundManualParser();
|
||||
|
||||
try {
|
||||
parser.parseDirectory(inputDir);
|
||||
} catch (err) {
|
||||
console.error('Parsing failed:', err);
|
||||
console.log('\nMake sure the input directory exists and contains .md files');
|
||||
console.log(`Input directory: ${inputDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const opcodeCount = parser.getOpcodes().length;
|
||||
console.log(`\nParsed ${opcodeCount} opcodes`);
|
||||
|
||||
if (opcodeCount === 0) {
|
||||
console.error('No opcodes found. Check the input directory.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('\nStep 3: Grouping by category...');
|
||||
const categoriesMap = parser.getReferencesByCategory();
|
||||
console.log(`Found ${categoriesMap.size} categories`);
|
||||
|
||||
const topCategories = Array.from(categoriesMap.entries())
|
||||
.sort((a, b) => b[1].length - a[1].length)
|
||||
.slice(0, 10);
|
||||
|
||||
console.log('\nTop 10 categories by opcode count:');
|
||||
for (const [category, refs] of topCategories) {
|
||||
console.log(` - ${category}: ${refs.length} opcodes`);
|
||||
}
|
||||
|
||||
console.log('\nStep 4: Generating TypeScript files...');
|
||||
const generator = new TypeScriptGenerator(outputDir);
|
||||
generator.generateAll(categoriesMap);
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('Success!');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`\nGenerated files in: ${outputDir}`);
|
||||
console.log(`Total opcodes: ${opcodeCount}`);
|
||||
console.log(`Total categories: ${categoriesMap.size}`);
|
||||
console.log('\nYou can now import the reference in your application:');
|
||||
console.log(` import { allCsoundReferences, getCsoundReference } from './lib/csound-reference/csoundReference'`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
26
scripts/csound-parser/package.json
Normal file
26
scripts/csound-parser/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "csound-manual-parser",
|
||||
"version": "1.0.0",
|
||||
"description": "Parser for Csound manual to generate TypeScript reference files",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"parse": "tsx index.ts",
|
||||
"download": "tsx index.ts --download",
|
||||
"build": "tsc"
|
||||
},
|
||||
"keywords": [
|
||||
"csound",
|
||||
"parser",
|
||||
"documentation"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fuse.js": "^7.1.0"
|
||||
}
|
||||
}
|
||||
245
scripts/csound-parser/parser.ts
Normal file
245
scripts/csound-parser/parser.ts
Normal file
@ -0,0 +1,245 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { ParsedOpcode, CsoundReference } from './types';
|
||||
|
||||
interface FrontMatter {
|
||||
id?: string;
|
||||
category?: string;
|
||||
}
|
||||
|
||||
export class CsoundManualParser {
|
||||
private opcodes: Map<string, ParsedOpcode> = new Map();
|
||||
|
||||
parseFrontMatter(content: string): FrontMatter {
|
||||
const frontMatterMatch = content.match(/^<!--\s*\n([\s\S]*?)\n-->/);
|
||||
if (!frontMatterMatch) return {};
|
||||
|
||||
const frontMatter: FrontMatter = {};
|
||||
const lines = frontMatterMatch[1].split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const [key, ...valueParts] = line.split(':');
|
||||
if (key && valueParts.length > 0) {
|
||||
const trimmedKey = key.trim();
|
||||
const value = valueParts.join(':').trim();
|
||||
if (trimmedKey === 'id') frontMatter.id = value;
|
||||
if (trimmedKey === 'category') frontMatter.category = value;
|
||||
}
|
||||
}
|
||||
|
||||
return frontMatter;
|
||||
}
|
||||
|
||||
extractFirstParagraph(content: string): string {
|
||||
const withoutFrontMatter = content.replace(/^<!--[\s\S]*?-->\n/, '');
|
||||
const withoutTitle = withoutFrontMatter.replace(/^#\s+.*\n/, '');
|
||||
|
||||
const lines = withoutTitle.split('\n');
|
||||
const paragraphLines: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed === '') {
|
||||
if (paragraphLines.length > 0) break;
|
||||
continue;
|
||||
}
|
||||
if (trimmed.startsWith('#')) break;
|
||||
paragraphLines.push(trimmed);
|
||||
}
|
||||
|
||||
return paragraphLines.join(' ').trim();
|
||||
}
|
||||
|
||||
extractSection(content: string, sectionTitle: string): string {
|
||||
const regex = new RegExp(`##\\s+${sectionTitle}\\s*\\n([\\s\\S]*?)(?=\\n##(?!#)|$)`, 'i');
|
||||
const match = content.match(regex);
|
||||
return match ? match[1].trim() : '';
|
||||
}
|
||||
|
||||
extractSyntax(content: string): { modern?: string; classic?: string } {
|
||||
const syntaxSection = this.extractSection(content, 'Syntax');
|
||||
if (!syntaxSection) return {};
|
||||
|
||||
const modernMatch = syntaxSection.match(/===\s*"Modern"[\s\S]*?```[\s\S]*?\n([\s\S]*?)```/);
|
||||
const classicMatch = syntaxSection.match(/===\s*"Classic"[\s\S]*?```[\s\S]*?\n([\s\S]*?)```/);
|
||||
|
||||
return {
|
||||
modern: modernMatch ? modernMatch[1].trim() : undefined,
|
||||
classic: classicMatch ? classicMatch[1].trim() : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
extractParameters(content: string, subsectionTitle: string): { name: string; description: string }[] {
|
||||
const syntaxSection = this.extractSection(content, 'Syntax');
|
||||
const subsectionRegex = new RegExp(`###\\s+${subsectionTitle}\\s*\\n([\\s\\S]*?)(?=\\n###|\\n##|$)`, 'i');
|
||||
const match = syntaxSection.match(subsectionRegex);
|
||||
|
||||
if (!match) return [];
|
||||
|
||||
const subsectionContent = match[1];
|
||||
const params: { name: string; description: string }[] = [];
|
||||
|
||||
const paramMatches = subsectionContent.matchAll(/_([a-zA-Z0-9_,\s/]+)_\s*[-–—]+\s*([^\n]+(?:\n(?!_)[^\n]+)*)/g);
|
||||
|
||||
for (const paramMatch of paramMatches) {
|
||||
const names = paramMatch[1]
|
||||
.split(/[,/]/)
|
||||
.map(n => n.trim().replace(/^_+|_+$/g, ''))
|
||||
.filter(n => n.length > 0);
|
||||
const description = paramMatch[2].trim().replace(/\s+/g, ' ');
|
||||
|
||||
for (const name of names) {
|
||||
params.push({ name, description });
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
extractExample(content: string): string | undefined {
|
||||
const exampleSection = this.extractSection(content, 'Examples');
|
||||
if (!exampleSection) return undefined;
|
||||
|
||||
const codeMatch = exampleSection.match(/```[\s\S]*?\n([\s\S]*?)```/);
|
||||
return codeMatch ? codeMatch[1].trim() : undefined;
|
||||
}
|
||||
|
||||
extractSeeAlso(content: string): string[] {
|
||||
const seeAlsoSection = this.extractSection(content, 'See also');
|
||||
if (!seeAlsoSection) return [];
|
||||
|
||||
const links = seeAlsoSection.matchAll(/\[([^\]]+)\]/g);
|
||||
return Array.from(links, match => match[1]);
|
||||
}
|
||||
|
||||
parseMarkdownFile(filePath: string, content: string): ParsedOpcode | null {
|
||||
try {
|
||||
const frontMatter = this.parseFrontMatter(content);
|
||||
|
||||
if (!frontMatter.id || !frontMatter.category) {
|
||||
console.warn(`Skipping ${filePath}: missing id or category`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const description = this.extractFirstParagraph(content);
|
||||
const syntax = this.extractSyntax(content);
|
||||
const initParams = this.extractParameters(content, 'Initialization');
|
||||
const perfParams = this.extractParameters(content, 'Performance');
|
||||
const example = this.extractExample(content);
|
||||
const seeAlso = this.extractSeeAlso(content);
|
||||
|
||||
return {
|
||||
id: frontMatter.id,
|
||||
category: frontMatter.category,
|
||||
title: frontMatter.id,
|
||||
description,
|
||||
syntaxModern: syntax.modern,
|
||||
syntaxClassic: syntax.classic,
|
||||
initParams,
|
||||
perfParams,
|
||||
example,
|
||||
seeAlso,
|
||||
rawContent: content,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error parsing ${filePath}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
parseDirectory(dirPath: string): void {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
throw new Error(`Directory not found: ${dirPath}`);
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dirPath);
|
||||
let parsed = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.md')) continue;
|
||||
|
||||
const filePath = path.join(dirPath, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const opcode = this.parseMarkdownFile(file, content);
|
||||
|
||||
if (opcode) {
|
||||
this.opcodes.set(opcode.id, opcode);
|
||||
parsed++;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Parsed ${parsed} opcodes, skipped ${skipped}`);
|
||||
}
|
||||
|
||||
convertToReference(opcode: ParsedOpcode): CsoundReference {
|
||||
const syntax = opcode.syntaxModern || opcode.syntaxClassic;
|
||||
|
||||
const parameters = [
|
||||
...opcode.initParams.map(p => ({ ...p, type: 'initialization' as const })),
|
||||
...opcode.perfParams.map(p => ({ ...p, type: 'performance' as const })),
|
||||
];
|
||||
|
||||
const rates = this.extractRates(syntax || '');
|
||||
|
||||
return {
|
||||
name: opcode.id,
|
||||
type: 'opcode',
|
||||
category: opcode.category,
|
||||
description: opcode.description,
|
||||
syntax,
|
||||
example: opcode.example,
|
||||
rates,
|
||||
parameters: parameters.length > 0 ? parameters : undefined,
|
||||
seeAlso: opcode.seeAlso.length > 0 ? opcode.seeAlso : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private extractRates(syntax: string): string[] {
|
||||
const rates: Set<string> = new Set();
|
||||
|
||||
if (/\bares\b|\basin\b|\basig\b/.test(syntax)) rates.add('a-rate');
|
||||
if (/\bkres\b|\bkamp\b|\bkcps\b|\bkin\b/.test(syntax)) rates.add('k-rate');
|
||||
if (/\bires\b|\bifn\b|\biphs\b/.test(syntax)) rates.add('i-rate');
|
||||
|
||||
return Array.from(rates);
|
||||
}
|
||||
|
||||
getOpcodes(): ParsedOpcode[] {
|
||||
return Array.from(this.opcodes.values());
|
||||
}
|
||||
|
||||
getOpcodesByCategory(): Map<string, ParsedOpcode[]> {
|
||||
const categories = new Map<string, ParsedOpcode[]>();
|
||||
|
||||
for (const opcode of this.opcodes.values()) {
|
||||
const category = opcode.category;
|
||||
if (!categories.has(category)) {
|
||||
categories.set(category, []);
|
||||
}
|
||||
categories.get(category)!.push(opcode);
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
getReferences(): CsoundReference[] {
|
||||
return this.getOpcodes().map(op => this.convertToReference(op));
|
||||
}
|
||||
|
||||
getReferencesByCategory(): Map<string, CsoundReference[]> {
|
||||
const categories = new Map<string, CsoundReference[]>();
|
||||
|
||||
for (const opcode of this.opcodes.values()) {
|
||||
const category = opcode.category;
|
||||
if (!categories.has(category)) {
|
||||
categories.set(category, []);
|
||||
}
|
||||
categories.get(category)!.push(this.convertToReference(opcode));
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
}
|
||||
352
scripts/csound-parser/pnpm-lock.yaml
generated
Normal file
352
scripts/csound-parser/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,352 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
fuse.js:
|
||||
specifier: ^7.1.0
|
||||
version: 7.1.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.21
|
||||
tsx:
|
||||
specifier: ^4.0.0
|
||||
version: 4.20.6
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.11':
|
||||
resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.25.11':
|
||||
resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.25.11':
|
||||
resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.25.11':
|
||||
resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.11':
|
||||
resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.25.11':
|
||||
resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.25.11':
|
||||
resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.25.11':
|
||||
resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.11':
|
||||
resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.11':
|
||||
resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.11':
|
||||
resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.25.11':
|
||||
resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.25.11':
|
||||
resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.11':
|
||||
resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.11':
|
||||
resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.11':
|
||||
resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.25.11':
|
||||
resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.25.11':
|
||||
resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.25.11':
|
||||
resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/node@20.19.21':
|
||||
resolution: {integrity: sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==}
|
||||
|
||||
esbuild@0.25.11:
|
||||
resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fuse.js@7.1.0:
|
||||
resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
get-tsconfig@4.12.0:
|
||||
resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
tsx@4.20.6:
|
||||
resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.11':
|
||||
optional: true
|
||||
|
||||
'@types/node@20.19.21':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
esbuild@0.25.11:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.11
|
||||
'@esbuild/android-arm': 0.25.11
|
||||
'@esbuild/android-arm64': 0.25.11
|
||||
'@esbuild/android-x64': 0.25.11
|
||||
'@esbuild/darwin-arm64': 0.25.11
|
||||
'@esbuild/darwin-x64': 0.25.11
|
||||
'@esbuild/freebsd-arm64': 0.25.11
|
||||
'@esbuild/freebsd-x64': 0.25.11
|
||||
'@esbuild/linux-arm': 0.25.11
|
||||
'@esbuild/linux-arm64': 0.25.11
|
||||
'@esbuild/linux-ia32': 0.25.11
|
||||
'@esbuild/linux-loong64': 0.25.11
|
||||
'@esbuild/linux-mips64el': 0.25.11
|
||||
'@esbuild/linux-ppc64': 0.25.11
|
||||
'@esbuild/linux-riscv64': 0.25.11
|
||||
'@esbuild/linux-s390x': 0.25.11
|
||||
'@esbuild/linux-x64': 0.25.11
|
||||
'@esbuild/netbsd-arm64': 0.25.11
|
||||
'@esbuild/netbsd-x64': 0.25.11
|
||||
'@esbuild/openbsd-arm64': 0.25.11
|
||||
'@esbuild/openbsd-x64': 0.25.11
|
||||
'@esbuild/openharmony-arm64': 0.25.11
|
||||
'@esbuild/sunos-x64': 0.25.11
|
||||
'@esbuild/win32-arm64': 0.25.11
|
||||
'@esbuild/win32-ia32': 0.25.11
|
||||
'@esbuild/win32-x64': 0.25.11
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
fuse.js@7.1.0: {}
|
||||
|
||||
get-tsconfig@4.12.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
tsx@4.20.6:
|
||||
dependencies:
|
||||
esbuild: 0.25.11
|
||||
get-tsconfig: 4.12.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
71
scripts/csound-parser/test-output.ts
Normal file
71
scripts/csound-parser/test-output.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import {
|
||||
allCsoundReferences,
|
||||
getCsoundReference,
|
||||
getCsoundReferencesByCategory
|
||||
} from '../../src/lib/csound-reference/csoundReference';
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('Csound Reference Output Test');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
console.log(`\nTotal opcodes: ${allCsoundReferences.length}`);
|
||||
|
||||
console.log('\n--- Testing getCsoundReference() ---');
|
||||
const oscil = getCsoundReference('oscil');
|
||||
if (oscil) {
|
||||
console.log(`\nFound oscil:`);
|
||||
console.log(` Name: ${oscil.name}`);
|
||||
console.log(` Category: ${oscil.category}`);
|
||||
console.log(` Description: ${oscil.description.substring(0, 80)}...`);
|
||||
console.log(` Syntax: ${oscil.syntax?.substring(0, 80)}...`);
|
||||
console.log(` Rates: ${oscil.rates?.join(', ')}`);
|
||||
console.log(` Parameters: ${oscil.parameters?.length || 0}`);
|
||||
}
|
||||
|
||||
const moogladder = getCsoundReference('moogladder');
|
||||
if (moogladder) {
|
||||
console.log(`\nFound moogladder:`);
|
||||
console.log(` Name: ${moogladder.name}`);
|
||||
console.log(` Category: ${moogladder.category}`);
|
||||
console.log(` Description: ${moogladder.description}`);
|
||||
console.log(` Syntax: ${moogladder.syntax?.substring(0, 100)}...`);
|
||||
console.log(` Rates: ${moogladder.rates?.join(', ')}`);
|
||||
console.log(` Parameters: ${moogladder.parameters?.length || 0}`);
|
||||
if (moogladder.parameters) {
|
||||
console.log(` Parameters detail:`);
|
||||
moogladder.parameters.forEach(p => {
|
||||
console.log(` - ${p.name} (${p.type}): ${p.description.substring(0, 60)}...`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n--- Testing getCsoundReferencesByCategory() ---');
|
||||
const oscillators = getCsoundReferencesByCategory('Signal Generators:Basic Oscillators');
|
||||
console.log(`\nBasic Oscillators (${oscillators.length} opcodes):`);
|
||||
oscillators.slice(0, 5).forEach(op => {
|
||||
console.log(` - ${op.name}: ${op.description.substring(0, 60)}...`);
|
||||
});
|
||||
|
||||
console.log('\n--- Sample categories ---');
|
||||
const categories = new Set(allCsoundReferences.map(ref => ref.category));
|
||||
const categoryArray = Array.from(categories).sort();
|
||||
console.log(`\nTotal categories: ${categories.size}`);
|
||||
console.log('First 10 categories:');
|
||||
categoryArray.slice(0, 10).forEach(cat => {
|
||||
const count = allCsoundReferences.filter(r => r.category === cat).length;
|
||||
console.log(` - ${cat} (${count} opcodes)`);
|
||||
});
|
||||
|
||||
console.log('\n--- Checking for opcodes with parameters ---');
|
||||
const opcodesWithParams = allCsoundReferences.filter(ref => ref.parameters && ref.parameters.length > 0);
|
||||
console.log(`\nOpcodes with parameters: ${opcodesWithParams.length} out of ${allCsoundReferences.length}`);
|
||||
if (opcodesWithParams.length > 0) {
|
||||
console.log('\nFirst 5 opcodes with parameters:');
|
||||
opcodesWithParams.slice(0, 5).forEach(op => {
|
||||
console.log(` - ${op.name}: ${op.parameters?.length} parameters`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('Test Complete!');
|
||||
console.log('='.repeat(60));
|
||||
18
scripts/csound-parser/tsconfig.json
Normal file
18
scripts/csound-parser/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
35
scripts/csound-parser/types.ts
Normal file
35
scripts/csound-parser/types.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export interface CsoundReference {
|
||||
name: string
|
||||
type: 'opcode' | 'keyword' | 'header' | 'constant'
|
||||
category: string
|
||||
description: string
|
||||
syntax?: string
|
||||
example?: string
|
||||
rates?: string[]
|
||||
parameters?: {
|
||||
name: string
|
||||
description: string
|
||||
type: 'initialization' | 'performance'
|
||||
}[]
|
||||
seeAlso?: string[]
|
||||
}
|
||||
|
||||
export interface ParsedOpcode {
|
||||
id: string
|
||||
category: string
|
||||
title: string
|
||||
description: string
|
||||
syntaxModern?: string
|
||||
syntaxClassic?: string
|
||||
initParams: { name: string; description: string }[]
|
||||
perfParams: { name: string; description: string }[]
|
||||
example?: string
|
||||
seeAlso: string[]
|
||||
rawContent: string
|
||||
}
|
||||
|
||||
export interface CategoryGroup {
|
||||
categoryKey: string
|
||||
categoryName: string
|
||||
opcodes: CsoundReference[]
|
||||
}
|
||||
Reference in New Issue
Block a user