To register the names associated with sound resource numbers, which are defined to allow the final story file to play sound effects.


§1. The following is called to activate the feature:

void Sounds::start(void) {
    PluginCalls::plug(MAKE_SPECIAL_MEANINGS_PLUG, Sounds::make_special_meanings);
    PluginCalls::plug(NEW_BASE_KIND_NOTIFY_PLUG, Sounds::new_base_kind_notify);
    PluginCalls::plug(NEW_INSTANCE_NOTIFY_PLUG, Sounds::new_named_instance_notify);
    PluginCalls::plug(PRODUCTION_LINE_PLUG, Sounds::production_line);
}

int Sounds::production_line(int stage, int debugging, stopwatch_timer *sequence_timer) {
    if (stage == INTER1_CSEQ) {
        BENCH(RTMultimedia::compile_sounds);
    }
    return FALSE;
}

§2. One special meaning. We add one special meaning for assertions, to catch sentences with the shape "Sound... is the file...".

int Sounds::make_special_meanings(void) {
    SpecialMeanings::declare(Sounds::new_sound_SMF, I"new-sound", 2);
    return FALSE;
}
int Sounds::new_sound_SMF(int task, parse_node *V, wording *NPs) {
    wording SW = (NPs)?(NPs[0]):EMPTY_WORDING;
    wording OW = (NPs)?(NPs[1]):EMPTY_WORDING;
    switch (task) {
        case ACCEPT_SMFT:
            if ((<nounphrase-sound>(SW)) && (<new-sound-sentence-object>(OW))) {
                parse_node *O = <<rp>>;
                <np-unparsed>(SW);
                V->next = <<rp>>;
                V->next->next = O;
                return TRUE;
            }
            break;
        case PASS_1_SMFT:
            Sounds::register_sound(Node::get_text(V->next),
                Node::get_text(V->next->next));
            break;
    }
    return FALSE;
}

§3. And this is the Preform grammar needed:

<new-sound-sentence-object> ::=
    <definite-article> <new-sound-sentence-object-unarticled> |  ==> { pass 2 }
    <new-sound-sentence-object-unarticled>                       ==> { pass 1 }

<new-sound-sentence-object-unarticled> ::=
    file <np-unparsed>                                           ==> { TRUE, RP[1] }

<nounphrase-sound> ::=
    sound ...                              ==> { 0, Diagrams::new_UNPARSED_NOUN(W) }

§4. The syntax for sound effects allows for alt-texts, exactly as for figures.

<sound-sentence-object> ::=
    <sound-source> ( <quoted-text> ) |  ==> { R[1], -, <<alttext>> = R[2] }
    <sound-source>                      ==> { pass 1 }

<sound-source> ::=
    <quoted-text> |  ==> { pass 1 }
    ...              ==> Issue PM_SoundNotTextual problem4.1;

§4.1. Issue PM_SoundNotTextual problem4.1 =

    StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundNotTextual),
        "a sound effect can only be declared as a quoted file name",
        "which should be the name of an AIFF or OGG file inside the Sounds "
        "subfolder of the project's .materials folder. For instance, 'Sound "
        "of Swordplay is the file \"Crossed Swords.aiff\".'");
    ==> { 0, - };

§5. In assertion pass 1, then, the following is called on any sentence which has been found to create a sound:

void Sounds::register_sound(wording W, wording FN) {
    <<alttext>> = -1;
    if (<sound-sentence-object>(FN)) {
        int wn = <<r>>;
        if (wn > 0) Word::dequote(wn);
        if (<<alttext>> > 0) Word::dequote(<<alttext>>);
        Make sure W is acceptable as a new sound name5.1;
        int id = Task::get_next_free_blorb_resource_ID();
        TEMPORARY_TEXT(leaf)
        WRITE_TO(leaf, "%N", wn);
        if (Str::is_whitespace(leaf)) {
            StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_SoundWhiteSpace),
                "this is not a filename I can use",
                "because it is either empty or contains only spaces.");
            return;
        }
        filename *sound_file = ResourceFinder::find_resource(Task::sounds_department(), leaf, FN);
        DISCARD_TEXT(leaf)
        if (sound_file) {
            Sounds::sounds_create(W, id, sound_file, <<alttext>>);
            LOGIF(MULTIMEDIA_CREATIONS,
                "Created sound effect <%W> = filename '%N' = resource ID %d\n", W, wn, id);
        }
    }
}

§5.1. Make sure W is acceptable as a new sound name5.1 =

    Assertions::Creator::vet_name_for_noun(W);
    if ((<s-value>(W)) && (Rvalues::is_CONSTANT_of_kind(<<rp>>, K_sound_name))) {
        StandardProblems::sentence_problem(Task::syntax_tree(),
            _p_(PM_SoundDuplicate),
            "this is already the name of a sound effect",
            "so there must be some duplication somewhere.");
        return;
    }

§6. One significant kind.

kind *K_sound_name = NULL;

§7. This is created by an Inter kit early in Inform's run; the function below detects that this has happened, and sets K_sound_name to point to it.

int Sounds::new_base_kind_notify(kind *new_base, text_stream *name, wording W) {
    if (Str::eq_wide_string(name, U"SOUND_NAME_TY")) {
        K_sound_name = new_base; return TRUE;
    }
    return FALSE;
}

§8. Significant new instances. This structure of additional data is attached to each sound instance:

typedef struct sounds_data {
    struct wording name;  text of name
    struct filename *filename_of_sound_file;  relative to the Resources folder
    int sound_number;  resource number of this picture inside Blorb
    int alt_description;  word number of double-quoted description
    struct instance *as_instance;
    CLASS_DEFINITION
} sounds_data;

§9. We allow instances of "sound name" to be created only through the above code calling Sounds::sounds_create. If any other proposition somehow manages to make a sound, a problem message is thrown.

int allow_sound_creations = FALSE;

instance *Sounds::sounds_create(wording W, int id, filename *sound_file, int alt) {
    allow_sound_creations = TRUE;
    Assert::true(Propositions::Abstract::to_create_something(K_sound_name, W), CERTAIN_CE);
    allow_sound_creations = FALSE;
    instance *I = Instances::latest();
    sounds_data *sd = FEATURE_DATA_ON_INSTANCE(sounds, I);
    sd->filename_of_sound_file = sound_file;
    sd->name = W;
    sd->sound_number = id;
    sd->alt_description = alt;
    sd->as_instance = I;
    return I;
}

int Sounds::new_named_instance_notify(instance *I) {
    if ((K_sound_name) && (Kinds::eq(Instances::to_kind(I), K_sound_name))) {
        if (allow_sound_creations == FALSE)
            StandardProblems::sentence_problem(Task::syntax_tree(),
                _p_(PM_BackdoorSoundCreation),
                "this is not the way to create a new sound name",
                "which should be done with a special 'Sound ... is the file ...' "
                "sentence.");
        ATTACH_FEATURE_DATA_TO_SUBJECT(sounds, I->as_subject, CREATE(sounds_data));
        return TRUE;
    }
    return FALSE;
}

§10. Blurb and manifest. The sounds manifest is used by the implementation of Glulx within the Inform application to connect picture ID numbers with filenames relative to the .materials folder for its project. (It's part of the XML manifest file created from Figures.w.)

void Sounds::write_sounds_manifest(OUTPUT_STREAM) {
    if (FEATURE_INACTIVE(sounds)) return;
    sounds_data *sd;
    if (NUMBER_CREATED(sounds_data) == 0) return;
    WRITE("<key>Sounds</key>\n");
    WRITE("<dict>\n"); INDENT;
    LOOP_OVER(sd, sounds_data) {
        WRITE("<key>%d</key>\n", sd->sound_number);
        TEMPORARY_TEXT(rel)
        Filenames::to_text_relative(rel, sd->filename_of_sound_file,
            Projects::materials_path(Task::project()));
        WRITE("<string>%S</string>\n", rel);
        DISCARD_TEXT(rel)
    }
    OUTDENT; WRITE("</dict>\n");
}

§11. The following writes Blurb commands for all of the sounds.

void Sounds::write_blurb_commands(OUTPUT_STREAM) {
    if (FEATURE_INACTIVE(sounds)) return;
    sounds_data *sd;
    LOOP_OVER(sd, sounds_data) {
        inchar32_t *desc = U"";
        if (sd->alt_description >= 0)
            desc = Lexer::word_text(sd->alt_description);
        if (Wide::len(desc) > 0)
            WRITE("sound %d \"%f\" \"%N\"\n",
                sd->sound_number, sd->filename_of_sound_file, sd->alt_description);
        else
            WRITE("sound %d \"%f\"\n", sd->sound_number, sd->filename_of_sound_file);
    }
}

§12. The following is used only with the "separate figures" release option.

void Sounds::write_copy_commands(release_instructions *rel) {
    if (FEATURE_INACTIVE(sounds)) return;
    sounds_data *sd;
    LOOP_OVER(sd, sounds_data)
        ReleaseInstructions::add_aux_file(rel,
            sd->filename_of_sound_file,
            Task::released_sounds_path(),
            U"--",
            SEPARATE_SOUNDS_PAYLOAD);
}